通过交错提交合并两个不同的 git 存储库

Xav*_*det 6 git

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

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

project
    /src
    /include
tests
    /short
    /long
Run Code Online (Sandbox Code Playgroud)

我希望最终得到一个包含两个目录projecttests.

我不能简单地使用此答案此答案此站点中描述的技术合并这两个存储库:它们会导致存储库在合并之前具有两个不同的历史记录,并且在检查过去的提交时,您可以选择srcinclude,或shortlong,但您并不拥有当时出现的全部四个。

如果我签出 4 个月前创建的提交project,我希望看到project/srcproject/include出现在该提交中,但我也希望同时在(当时是单独的)存储库中看到tests/short和。test/longtest

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

应该可以逐一检查每个存储库中的所有提交,按存储库中的时间排序,并提交生成的文件。是否已经有一个工具可以做到这一点?

tor*_*rek 3

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

\n\n
\n\n

博士的回答很好,但如果我自己做这件事并且想让它变得非常整洁和干净,我会使用不同的方法。

\n\n

如果两个存储库的树不重叠,那么当然可以执行此操作\xe2\x80\x94,并且通过绕过通常的 Git 机制,直接进入底层git read-tree命令,您可以将其自动化。(这是VonC 最近的评论拒绝了我关于 Git 和 Mercurial 非常相似的说法,这就是事实:如果您绕过顶级 Git 命令,您将获得在 Mercurial 中几乎无法轻松获得的东西。)

\n\n

正如博士的回答一样,您可以通过组合两个存储库提交数据库来启动此过程git fetch。(您可以在第三个存储库中执行此操作,我建议这样做,因为如果您决定要调整某些参数,或者通过将存储库 A 添加到存储库 B 或存储库 B,可以更轻松地从头开始重新启动该过程到repo A.)但在那之后,一切都出现了分歧。

\n\n

您现在有两个不相交的提交 DAG:

\n\n
        D--...--K\n       /         \\\nA--B--C           M--N   <-- repoA/master\n       \\         /\n        E--...--L\n\nO--P--Q--...--Z   <-- repoB/master\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n

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

\n\n

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

\n\n

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

\n\n
A--B   <-- A/master\n\nO--P   <-- B/master\n
Run Code Online (Sandbox Code Playgroud)\n\n

在我的简化设置中,我想在我的新 master 上进行第一次提交,该提交结合了和 的CAO

\n\n
C   <-- master\n
Run Code Online (Sandbox Code Playgroud)\n\n

然后我想将 and 的组合作为我的第二次提交master(not Aand and not and Either),并作为我的最后一次提交,将and的组合PAOBOBP,这样我最终会得到:

\n\n
C--D--E   <-- master\n\nwith:\n    C = A+O\n    D = A+P\n    E = B+P\n
Run Code Online (Sandbox Code Playgroud)\n\n

因此,这里我们位于一个新的空存储库中,只是我们已在项目 A 和 B 中读取了内容:

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

(我不小心没有将提交 O 连接起来,但将所有其他连接连接起来。sed在本例中,这是为了删除一些对阅读没有真正帮助的空行。)

\n\n
$ git status\nOn branch master\n\nNo commits yet\n\nnothing to commit (create/copy files and use "git add" to track)\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n
$ git status\nOn branch master\n\nNo commits yet\n\nnothing to commit (create/copy files and use "git add" to track)\n
Run Code Online (Sandbox Code Playgroud)\n\n

我们希望我们的第一个提交能够合并AO,所以现在让我们将这两个提交读入索引。如果我们必须向树添加一个前缀A我们可以在这里这样做:

\n\n
$ git read-tree --prefix= ff40069\n$ git ls-files --stage\n100644 7a1c6130c652b6ea92f4d19183693727e32c9ac4 0       A/file\n$ git read-tree --prefix= 51955b1\n$ git ls-files --stage\n100644 7a1c6130c652b6ea92f4d19183693727e32c9ac4 0       A/file\n100644 f6284744575ecfc520293b33122d4a99548045e4 0       B/start\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

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

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

\n\n
$ git read-tree --empty\n$ git read-tree --prefix= ff40069\n$ git read-tree --prefix= 7b9921a\n
Run Code Online (Sandbox Code Playgroud)\n\n

如果您愿意,您可以使用以下命令检查结果git ls-file --stage

\n\n
$ git ls-files --stage\n100644 7a1c6130c652b6ea92f4d19183693727e32c9ac4 0       A/file\n100644 d7941926464291df213061d48784da98f8602d6c 0       B/another\n100644 f6284744575ecfc520293b33122d4a99548045e4 0       B/start\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

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

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

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

(我在这里使用符号名称来获取最后两次提交,但哈希 IDgit rev-list当然可以很好地工作。)我们现在可以看到这三个提交,全部都在master

\n\n
$ git log --decorate --oneline --graph\n* fad84f8 (HEAD -> master) combine B and P\n* eb8fa3c combine A and P\n* 7c629d8 combine-A-and-O\n
Run Code Online (Sandbox Code Playgroud)\n\n

现在可以安全地删除A/masterB/master引用(以及两个遥控器)。有一个特点:由于我们直接在索引中完成所有工作,而不用担心工作树,因此工作树仍然完全是空的:

\n\n
$ ls\n$ git status -s\n D A/file\n D A/new\n D B/another\n D B/start\n
Run Code Online (Sandbox Code Playgroud)\n\n

为了最后解决这个问题,我们应该运行git checkout HEAD -- .

\n\n
$ git checkout HEAD -- .\n$ git status -s\n$ git status\nOn branch master\nnothing to commit, working tree clean\n
Run Code Online (Sandbox Code Playgroud)\n\n

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

\n\n

在实践中,您可能希望使用git write-tree和来进行新的提交,git commit-tree而不是使用 。git commit您可以编写一个小脚本(使用您喜欢的任何语言)来运行git rev-list以收集要组合的提交的哈希 ID。该脚本必须检查这些提交\xe2\x80\x94,例如,通过查看作者身份和日期、或文件内容或任何\xe2\x80\x94来决定如何交织提交。然后,在做出有关交织以及提供哪些分支合并结构的决定后,脚本可以开始重复执行以下步骤的过程:

\n\n
    \n
  • 清空索引。
  • \n
  • 从 repo-A 的子图中的提交中拉入树,使用任何--prefix合适的选项\xe2\x80\x94在你的情况下,这是--prefix=,即空字符串,但在其他情况下它将是一个目录名称带有尾部斜杠)。
  • \n
  • 使用另一个适当的 ,从 repo-B 的子图中的提交中拉入树中,以便和 的--prefix条目之间不会发生冲突。AB
  • \n
  • 用来git write-tree写树。它的输出是下一步的树哈希 ID。
  • \n
  • git commit-tree与适当的-p参数一起使用来设置新提交的父级。向其提供适当的(组合的或其他的)提交消息文本。使用环境变量GIT_AUTHOR_NAMEGIT_AUTHOR_EMAILGIT_AUTHOR_DATEGIT_COMMITTER_NAMEGIT_COMMITTER_EMAILGIT_COMMITTER_DATE来控制作者和提交者的姓名和日期。输出git commit-tree是哈希 ID,它是某些后续提交的父级。
  • \n
\n\n

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

\n\n
git branch <name> <hash>\n
Run Code Online (Sandbox Code Playgroud)\n\n

对于每个这样的哈希 ID。

\n