合并没有共同历史记录但具有共同文件的 git 存储库

kdb*_*kdb 4 git git-merge

我必须合并两个单独的 git 存储库 ORIG 和 WORK。WORK 是作为 ORIG 子目录的衍生品创建的,其中有一些尚未提交给 ORIG 的实验性更改。

~> mkdir WORK
~> cp -a ORIG/src/* WORK
~> cd WORK
~/WORK> # apply some experimental changes to WORK
~/WORK> git init
~/WORK> git add .
~/WORK> git commit -m "Entirely disconnected commit."
Run Code Online (Sandbox Code Playgroud)

因此,WORK 不知道它源自 ORIG,并且缺少src文件名的前缀。

是否有可能

  • 识别 ORIG 中创建 WORK 的提交,并且
  • 更改 WORK 以便存在完整文件路径(即./src/FILE而不是),./FILE
  • 将存储库合并在一起而不丢失历史记录?

目前我已经解决了这个问题

  • git mv'将 WORK 中的每个文件复制到新创建的./src目录中,
  • 添加 ORIG 作为遥控器,
  • 融入ORIG/master工作master

    git merge -X theirs --allow-unrelated-histories ORIG/master
    
    Run Code Online (Sandbox Code Playgroud)

    使用theirs合并策略,并手动重新应用更改,用于git diff查找相关部分,

但结果充其量只是一段不洁的历史:

  • 历史记录并不代表 WORK 和 ORIG 提交的共同祖先。
  • 在WORK的历史记录中,文件出现在根目录中,而不是在WORK的历史记录中./src,来自外部的文件./src根本不存在。

我如何产生一个干净的、合并的历史?

tor*_*rek 6

\n

是否有可能

\n
\n\n

不知怎的,这是相当开放的,所以是的。

\n\n
\n
    \n
  • 识别 ORIG 中创建 WORK 的提交
  • \n
\n
\n\n

最简单的方法就是:记住它。另一种方法是找到一个提交,其中该提交的源树与您保存的子树相匹配。这很困难(但并非不可能,如果您愿意进行精确的子树匹配,并且速度相当快:使用子树的哈希 ID),但打开了多个匹配的可能性:取决于源存储库,看起来很可能许多提交都有ORIG一个与根提交树匹配的子树WORK。在这种情况下,有可能但不能保证它们中的任何一个都是合适的。

\n\n

问题是,它并没有真正给你买任何东西\xe2\x80\x94,好吧,还没有。它可能会使合并步骤更容易(见下文)。

\n\n
\n
    \n
  • 更改 WORK 以便存在完整文件路径(即./src/FILE而不是)./FILE
  • \n
\n
\n\n

是的,有点;或不,取决于你的意思。您可以使用git filter-branch创建存储库的副本,并在副本中进行此更改。副本不再与原始版本兼容,但如果您打算举办卖旗日并将每个人都切换到副本,那么这非常简单。

\n\n
\n
    \n
  • 将存储库合并在一起而不丢失历史记录?
  • \n
\n
\n\n

这是真正棘手的部分。

\n\n

Git 永远不会真正丢失历史:在 Git 中,历史就是(是?)提交。提交是永久且不变的。但是,Git通过分支名称(以及其他名称,例如标签)来记住提交,因此,如果您强制分支名称停止记住某些提交\xe2\x80\x94,则将所有已过滤的提交复制git filter-branch到新的提交后会发生什么commits\xe2\x80\x94 那么这些提交实际上就被忘记了。最终,如果您删除所有查找这些提交的能力,Git 将通过垃圾收集真正删除它们:。git gc

\n\n

同样,这就是git filter-branch它的工作方式:您告诉它将每个提交复制到一个新的提交,新的提交与原始提交非常相似,只是每个提交FILE都已重命名为src/FILE. 然后,您使所有分支名称都指向新副本的最后一个,而不是原始副本的最后一个。您删除所有保存的原始名称(git filter-branch复制原始引​​用以防万一),删除所有其他备用安全带和安全线(git reflog expire等),并强制执行垃圾收集通过,然后,您原来的提交集消失了,并且您只有替换提交。

\n\n

但是:提交是快照。 您拥有 中的所有快照ORIG,您可以向其中添加您喜欢的所有快照WORK(或通过 制作的修改后的替换副本git filter-branch)。结果只是提交的总和。这不是一部两套作品交织在一起的历史:它只是一部实际上说“在<日期>这些作品被合并在一起的历史,在那之前我们就有了这两个独立的历史” 。例如,ORIG可能看起来像这样:

\n\n
root--o--(history graph)---o   <-- master\n       \\                  /\n        o--(branchy)--o--o   <-- feature\n
Run Code Online (Sandbox Code Playgroud)\n\n

您的过滤结果WORK可能如下所示:

\n\n
            o--o\n           /    \\\nroot2--o--o------o   <-- master\n
Run Code Online (Sandbox Code Playgroud)\n\n

将两者放入一个存储库中,将WORK\'s的名称更改master为其他名称,您将获得:

\n\n
            o--o\n           /    \\\nroot2--o--o------o   <-- workmaster\n\nroot--o--(history graph)---o   <-- master\n       \\                  /\n        o--(branchy)--o--o   <-- feature\n
Run Code Online (Sandbox Code Playgroud)\n\n

您现在可以运行git checkout master; git merge workmaster,解决所有合并冲突\xe2\x80\x94Git 会抱怨src/*两个提示提交中的每个文件master都有添加/添加冲突,因为共同的起点是“无文件”\xe2\x80\ x94 并从合并结果中进行提交:

\n\n
                       o--o\n                      /    \\\nroot2--o-------------o------o   <-- workmaster\n                             \\\nroot--o--(history graph)---o--o   <-- master\n       \\                  /\n        o--(branchy)--o--o   <-- feature\n
Run Code Online (Sandbox Code Playgroud)\n\n

现在您有了一个ORIG基于 - 的存储库,其中包含一个新的提交,该提交将两个历史记录连接起来。

\n\n

如果这足以满足您的目的,那么您现在就完成了。如果没有,其余的可能不会真正有帮助,但无论如何我都会概述一下。

\n\n

使合并更容易和/或对历史记录大惊小怪

\n\n

直截了当git merge很困难,因为所有文件都存在冲突。但是,如果您找到了文件全部匹配的点,则可以使用git replace进行临时移植,而不是像上面那样进行合并。然后,您可以更轻松地合并,甚至可能使替换永久化(使用另一个filter-branch,以及这意味着的所有内容)。

\n\n

X我们从与上面相同的绘图开始,但选择“root2”与主存储库中的某些提交匹配的点。请注意,我也给这里的孩子贴上了标签root2

\n\n
            o--o\n           /    \\\nroot2--Y--o------o   <-- workmaster\n\nroot--o--...--X----...-----o   <-- master\n       \\                  /\n        o--(branchy)--o--o   <-- feature\n
Run Code Online (Sandbox Code Playgroud)\n\n

我们现在用来git replace告诉 Git:不要查看 commit Y,而是查看新的替换提交Y\',这样大多数 Git 都会看到:

\n\n
                       o--o\n                      /    \\\n                Y\'---o------o   <-- workmaster\n               /\nroot--o--...--X----...-----o   <-- master\n       \\                  /\n        o--(branchy)--o--o   <-- feature\n
Run Code Online (Sandbox Code Playgroud)\n\n

提交Yroot2仍然在那里,只是 Git 不再查看它们(除了git gc、 或使用该--no-replace-objects选项运行的任何命令)。

\n\n

Y为了进行此替换,我们在 \xe2\x80\x94之后找到子提交,root2幸运的话只有一个,但如果有多个,我们可以将git replace它们全部 \xe2\x80\x94 并运行:

\n\n
git replace --graft <hash-of-Y> <hash-of-X>\n
Run Code Online (Sandbox Code Playgroud)\n\n

这使得替代者提交Y\'。这给了我们上面制作的绘图,现在git merge将被视为X合并两个分支提示的公共提交。

\n\n

我们的合并会更容易(也许),我们得到:

\n\n
                       o--o\n                      /    \\\n                Y\'---o------o   <-- workmaster\n               /             \\\nroot--o--...--X----...-----o--o   <-- master\n       \\                  /\n        o--(branchy)--o--o   <-- feature\n
Run Code Online (Sandbox Code Playgroud)\n\n

作为我们的结果。

\n\n

如果我们运行无过滤器git filter-branch\xe2\x80\x94 并且确保使用git --no-replace-objects filter-branch\xe2\x80\x94,过滤器分支将复制存储库而不使用原始存储库Yroot2提交,而是使用XY\'。换句话说,这些移植物现在永久存在于我们新的、再次重写的存储库中(另一个卖旗日转换,尽管运气好或计划良好,但在同一天,因此只有一个卖旗日)。

\n