通过隔行扫描合并两个不同的git存储库

时间:2019-04-27 09:32:30

标签: git

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

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

project
    /src
    /include
tests
    /short
    /long

我想最终得到一个git存储库,该存储库具有两个目录projecttests

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

如果我签出4个月前在project中创建的提交,我希望看到project/srcproject/include出现在此提交中,但我也想在(当时是分开的)tests/short存储库中同时具有test/longtest

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

应该可行的是,按照存储库中的时间顺序,逐个签出每个存储库中的所有提交,并提交结果文件。已经有可以执行此操作的工具吗?

3 个答案:

答案 0 :(得分:3)

编辑:对于一种基于日期的方法,该方法很容易实现,但是假设两个存储库之一将“控制”来自另一个存储库的提交,请参见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的这两个线性化列表,您现在遇到了困难的部分:构造希望提交的新的组合树图。每个 new 提交都将通过合并两个旧图中的每个提交进行一次提交。如果其中一张图是复杂的(如上述repoA)具有分支和合并,而没有一张(如上述repoB),则可能会特别棘手。

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

A--B   <-- A/master

O--P   <-- B/master

在简化的设置中,我想在新主控上的第一个提交是将CA的树结合在一起的提交O

C   <-- master

然后,我想在masterA(不是PA和{{1}而不是OB),并且作为我的最后一次提交,是OB的组合,所以我最终得到:

P

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

C--D--E   <-- master

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

(我不小心不给O加上断字,而是对所有其他字符都加了断字。在这种情况下,$ 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 会删除一些对阅读没有帮助的空行。)

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) A,所以现在让我们将这两个提交读入索引。如果我们必须在O中的树上添加前缀,则可以在此处进行操作:

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现在清空索引,然后读取提交A和P:

Cannot bind.

如果愿意,可以再次使用$ 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

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

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

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

master

,现在可以安全地删除$ git log --decorate --oneline --graph * fad84f8 (HEAD -> master) combine B and P * eb8fa3c combine A and P * 7c629d8 combine-A-and-O A/master引用(以及两个遥控器)。这里有一个特殊之处:由于我们直接在索引中完成了所有工作,而无需理会工作树,因此工作树仍然是完全空的:

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

  • 清空索引。
  • 使用适当的git rev-list选项,从repo-A的子图中的提交中提取树状树-在您的情况下,这是--prefix,即空字符串,但是在其他情况下,它将是带有斜杠的目录名称。
  • 从仓库repo-B的子图中提交提交到另一棵适当的--prefix=中,从而在树中形成扬声,因此--prefixA的条目之间没有冲突。
  • 使用B来编写树。其输出是下一步的树哈希ID。
  • git write-tree与适当的git commit-tree参数一起使用以设置新提交的父对象。向其提供适当的(组合的或类似的)提交消息文本。使用环境变量-pGIT_AUTHOR_NAMEGIT_AUTHOR_EMAILGIT_AUTHOR_DATEGIT_COMMITTER_NAMEGIT_COMMITTER_EMAIL来控制作者和提交者的名称和日期。 GIT_COMMITTER_DATE的输出是哈希ID,它是某些后续提交的父级。

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

git commit-tree

每个此类哈希ID。

答案 1 :(得分:3)

  

[鉴于所有project的内容都在srcinclude中,并且所有tests的内容都在shortlong中,]      

如果我签出4个月前在项目中创建的提交,我希望看到project/srcproject/include出现在此提交中,但我也想拥有{{1 }}和tests/short,与它们在当时(然后是单独的)测试存储库中的时间相同。 […]

     

已经有可以执行此操作的工具吗?

有一个,名为tests/long。到目前为止,最简单的实现是遍历git filter-branch的历史并查找对应于project提交内容的“ the”,这是一个草图:

tests

如果您的“测试”历史记录包含成千上万次提交,它将变得很慢;如果您正在谈论linux repo或类似规模的东西,则它会便宜得多,可以预先生成按日期排序的测试列表并逐步执行

答案 2 :(得分:2)

我认为您应该合并两个存储库以创建2个分支(git fetch,不合并)。然后以交互方式使一个分支变基,在每次提交时停止,然后git cherry-pick将相应的提交执行到当前分支中。然后继续交互式基础更改到下一个提交(这将保存“已编辑”的提交而不进行任何修改)。

也许甚至可以实现自动化。您可以使用git rebase --interactive -x在每次提交之后执行git cherry-pick来代替交互式变基和手动挑选。问题是如何找出对樱桃采摘的承诺。我认为应该是second-branch~count。可以在编辑rebase-todo文件时在交互式rebase之前编辑该计数。