我们有两个并行发展的存储库:一个用于我们的项目代码,另一个用于该项目的测试。我想将这两个存储库合并到一个存储库中,这样一来,当我回顾历史时,我仍然拥有两个目录结构。

假设我们当前的结构如下,其中projecttests是两个单独的git存储库:

project
    /src
    /include
tests
    /short
    /long


我想最后得到一个git仓库,其中有两个目录projecttests

我不能简单地使用this answerthis onethis site中描述的技术来合并这两个存储库:它们导致合并前的存储库具有两个不同的历史记录,并且当检查过去的提交时, srcincludeshortlong,但是您当时并没有这四个名称。

如果我检出4个月前在project中创建的提交,我希望看到project/srcproject/include出现在此提交中,但我也想将tests/shorttest/long设置为它们同时位于(当时分开的)test存储库中。

我知道两个存储库之间的提交顺序仅取决于时间,可能并不十分精确。但这对我来说已经足够了。当然,我知道我无法保留每个存储库中的原始git ID。很好,因为这两个存储库实际上是从另一个RCS导入的,因此没有git id记录在任何地方。

应该可以按照存储库中的时间顺序,依次检查每个存储库中的所有提交,并提交结果文件。已经有可以做到这一点的工具了吗?

最佳答案

编辑:对于基于日期的方法,此方法非常简单,但假定两个存储库之一将“控制”来自另一个存储库的提交,请参见jthill's answer。您最终得到的提交历史记录与“项目”历史记录完全匹配,可能会压缩一些“测试”历史记录。如果您需要在两组历史记录之间添加一个前缀,或者想对它们进行交织(例如,对于同一“项目”提交需要两个不同的“测试”更新),则以下答案更合适。



phd's answer很好,但是如果我自己做这个,并且想要使其真正整洁,我将使用其他方法。

如果两个存储库的树不重叠,则肯定可以做到这一点-通过绕过常规的Git机制,直接转到基础git read-tree命令,您可以使它自动化。 (这就是VonC's recent comment拒绝我关于Git和Mercurial非常相似的说法的地方:这是正确的:如果绕过顶层的Git命令,您会在Mercurial中获得几乎不那么容易得到的东西。)

就像在phd's answer中一样,您将通过git fetch组合两个存储库提交数据库来开始此过程。 (您可以在第三个存储库中执行此操作,我建议您这样做,因为如果您决定要调整一些参数,或者将存储库A添加到存储库B,或者将存储库B添加到存储库B,则可以从头开始重新启动该过程。回购A。)但此后,一切都分歧了。

现在,您有两个不连续的提交DAG:

        D--...--K
       /         \
A--B--C           M--N   <-- repoA/master
       \         /
        E--...--L

O--P--Q--...--Z   <-- repoB/master


(如果repoA和repoB都具有多个分支提示,则绘制任何简化的提交图更合适。)

下一步是使用git rev-list --topo-order --reverse以及您喜欢的其他排序选项,枚举两个不相交的DAG中的所有提交。何时以及是否需要--topo-order取决于拓扑和其他排序信息,但是通常,您会希望在其任何子项之前列出一个父提交。

给定提交哈希ID的这两个线性化列表,您现在遇到了困难的部分:构造要提交的新的组合树图。每个新的提交将通过合并来自两个旧图的每个提交中的一个提交来进行。如果其中一张图很复杂(如上面的repoA)具有分支和合并,而没有一张(如上面的repoB),这可能会特别棘手。

我为此进行了自己的设置,其中有一个非常简单的图形:

A--B   <-- A/master

O--P   <-- B/master


在简化的设置中,我想对新主控机进行的第一次提交是将CA的树结合在一起的O提交:

C   <-- master


然后,我想在master上进行第二次提交,即AP的组合(不是AO也不是BO的组合),并作为我的最后一次提交是BP的组合,所以我最终得到了:

C--D--E   <-- master

with:
    C = A+O
    D = A+P
    E = B+P


因此,这里我们处于一个新的空存储库中,除了我们已经阅读了项目A和B:

$ git log --all --graph --decorate --format='%h%d %s' --name-status | sed '/^[| ] $/d'
* 7b9921a (B/master) commit-P
| A B/another
* 51955b1 commit O
  A B/start
* 69597d3 (A/master) commit-B
| A A/new
* ff40069 commit-A
  A A/file


(在这种情况下,我不小心不给O加上连字符,而对所有其他字符都加了连字符。sed是要删除一些对阅读没有帮助的空行。)

$ git status
On branch master

No commits yet

nothing to commit (create/copy files and use "git add" to track)


现在,我们一次创建一个新的提交,使用git read-tree填充索引以进行提交。我们从一个空索引开始(我们现在有):

$ git status
On branch master

No commits yet

nothing to commit (create/copy files and use "git add" to track)


我们希望我们的第一个提交结合AO,所以现在让我们将这两个提交读入索引。如果必须在A中的树上添加前缀,则可以在此处执行以下操作:

$ git read-tree --prefix= ff40069
$ git ls-files --stage
100644 7a1c6130c652b6ea92f4d19183693727e32c9ac4 0       A/file
$ git read-tree --prefix= 51955b1
$ git ls-files --stage
100644 7a1c6130c652b6ea92f4d19183693727e32c9ac4 0       A/file
100644 f6284744575ecfc520293b33122d4a99548045e4 0       B/start


我们现在可以进行所需的提交:

$ git commit -m combine-A-and-O
[master (root-commit) 7c629d8] combine-A-and-O
 2 files changed, 2 insertions(+)
 create mode 100644 A/file
 create mode 100644 B/start


现在我们需要进行下一次提交,这意味着我们需要在索引中构建正确的树。为此,我们首先必须将其清除。否则,下一个git read-tree --prefix将失败,并抱怨文件重叠,并且Cannot bind.因此,现在我们清空索引,然后读取提交A和P:

$ git read-tree --empty
$ git read-tree --prefix= ff40069
$ git read-tree --prefix= 7b9921a


如果愿意,可以再次使用git ls-file --stage检查结果:

$ git ls-files --stage
100644 7a1c6130c652b6ea92f4d19183693727e32c9ac4 0       A/file
100644 d7941926464291df213061d48784da98f8602d6c 0       B/another
100644 f6284744575ecfc520293b33122d4a99548045e4 0       B/start


无论如何,它们现在都可以作为新的提交提交:

$ git commit -m 'combine A and P'
[master eb8fa3c] combine A and P
 1 file changed, 1 insertion(+)
 create mode 100644 B/another


(您现在可以看到我最终得到的连字号不一致:-))。最后,我们通过清空索引,读入两个所需的提交(B + P)并提交结果来重复此过程:

$ git read-tree --empty
$ git read-tree --prefix= A/master
$ git read-tree --prefix= B/master
$ git ls-files --stage
100644 7a1c6130c652b6ea92f4d19183693727e32c9ac4 0       A/file
100644 8e0c97794a6e80c2d371f9bd37174b836351f6b4 0       A/new
100644 d7941926464291df213061d48784da98f8602d6c 0       B/another
100644 f6284744575ecfc520293b33122d4a99548045e4 0       B/start
$ git commit -m 'combine B and P'
[master fad84f8] combine B and P
 1 file changed, 1 insertion(+)
 create mode 100644 A/new


(我在这里使用符号名来获取最后两个提交,但是来自git rev-list的哈希ID当然可以很好地工作。)现在,我们可以看到三个提交,都在master上:

$ git log --decorate --oneline --graph
* fad84f8 (HEAD -> master) combine B and P
* eb8fa3c combine A and P
* 7c629d8 combine-A-and-O


现在可以安全地删除A/masterB/master引用(以及两个遥控器)。这里有一个特殊之处:由于我们直接在索引中完成了所有工作,而无需理会工作树,因此工作树仍然是完全空的:

$ ls
$ git status -s
 D A/file
 D A/new
 D B/another
 D B/start


要解决此问题,我们应该只运行git checkout HEAD -- .

$ git checkout HEAD -- .
$ git status -s
$ git status
On branch master
nothing to commit, working tree clean


如何编写自己的自动化脚本

实际上,您可能想使用git write-treegit commit-tree而不是git commit进行新的提交。您将编写一个小脚本(以您喜欢的任何语言)运行git rev-list来收集要合并的提交的哈希ID。脚本必须检查这些提交(例如,通过查看作者身份和日期,文件内容或其他内容),以决定如何交错提交。然后,在决定了交织以及要提供的分支和合并结构之后,脚本可以开始重复执行以下步骤的过程:


清空索引。
从repo-A的子图中的提交中抽出一棵树,并带有适当的--prefix选项-在您的情况下为--prefix=,即空字符串,但在其他情况下为目录名称后跟斜杠)。
用另一个适当的--prefix从repo-B的子图中的提交中拖入树中,以便AB的条目之间没有冲突。
使用git write-tree编写树。它的输出是下一步的树哈希ID。
git commit-tree与适当的-p参数一起使用以设置新提交的父代。向其提供适当的(组合的或类似的)提交消息文本。使用环境变量GIT_AUTHOR_NAMEGIT_AUTHOR_EMAILGIT_AUTHOR_DATEGIT_COMMITTER_NAMEGIT_COMMITTER_EMAILGIT_COMMITTER_DATE控制作者和提交者的名称和日期。 git commit-tree的输出是哈希ID,它是某些后续提交的父级。


当整个过程完成时,对任何特定分支或一组分支所做的最后一次提交就是进入这些分支的哈希ID,因此您现在可以运行:

git branch <name> <hash>


对于每个这样的哈希ID。

关于git - 通过隔行扫描 merge 两个不同的git存储库,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/55879108/

10-14 17:21
查看更多