加入子树的提交历史

Join commit history of a subtree

有一个 git 存储库,其中的一部分是从另一个存储库复制粘贴的,并在某个时候在一次提交中全部提交。

从那以后发生了很多变化。

我想跨多个分支向该子树添加过去的提交历史记录。有没有一种方法可以轻松做到这一点?

假设您有(或可以创建)一个 git 存储库,其中包含您要添加的历史记录,那么就可以完成。首先要决定是否要重写历史。

在我看来,如果您可以重写历史记录,那是更好的选择。问题是它需要每个使用 repo 的人的一定程度的合作。 (对于这种彻底的改变,理想情况下你会安排一个日期,届时每个人都会将所有工作推到原点 - 不必合并或任何东西,但必须全部在一个单一的原点回购中 - 然后丢弃他们的克隆,这样他们就可以在重写后 re-clone。)

但如果重写不切实际,还有另一种选择:您可以使用git replace在repo-by-repo的基础上拼接历史。请参阅 git replace 文档以获取警告列表,但最明显的问题是您必须对每个想要查看合并历史记录的克隆进行设置。

无论如何,一旦您决定了要走的路并做好了任何必要的准备(即,如果您要进行艰苦的训练,请让每个人都努力 cut-over),您会想要将其他历史导入回购。您很可能希望创建原始镜像克隆并完成他们的工作。

git clone --mirror <origin-url>

将添加的代码的历史回购添加为远程并从中获取

git remote add history <history-repo-url>
git fetch history

现在 history 中的某处应该是一个提交,当代码添加到您的存储库时,文件是从该提交复制的。这是历史记录的简化图表:

A -- B -- C -- D -- E <--(master)

a -- b -- c -- d <--(history/master)

可能 c 处的代码作为提交 B 的一部分被复制到您的存储库中。真实的历史可能更复杂,但在我能想到的任何情况下都无关紧要。您需要做的是检查将文件添加到您的存储库的提交(示例中的 B)。在这个例子中,它只是 master 的第三个祖先(在 first-parent 链接之后);实际上,您可能需要查找其提交 ID。

git checkuot master~3

现在,如果 only ting B 所做的是将 c 中的文件添加到您的存储库,那么您可能想要完全替换它.所以你会检查它的父

git checkout HEAD^

如果 B 进行了其他更改,那么您需要保留它们。具体如何执行可能取决于这些更改是否需要添加代码。 (如果不是,您可能希望在合并历史之前提交其他更改;如果是这样,您可能希望 re-add 他们之后。)而不是分支到三个 similar-but-different 过程,现在我假设这些文件是在他们自己的提交中添加的。所以现在您已经检出该提交的父项。

接下来,您将合并其他历史记录。在我们的示例中,它是 history/master 的父级;同样,您可能需要一个不同的表达式来标识提交,或者可能只需要查找其提交 ID。

更大的问题是,您希望代码位于您的存储库的子目录中;但大概它是另一个回购协议的根源。有几种方法可以解决这个问题;这是其中之一。

git merge --s ours --no-commit --allow-unrelated history/master^
git read-tree --prefix=<path-to-subdirectory> history/master^
git commit

(您的工作树现在可能缺少您合并的文件,因此您会看到未暂存的删除;您可以使用 git restore 刷新工作树。)

现在你有这样的东西:

          A -- B -- C -- D -- E <--(master)
           \
            M <-(HEAD)
           /
a -- b -- c -- d <--(history/master)

M 应该与 B 具有相同的内容 (TREE)(您可以使用 git diff 进行验证),但它具有添加的历史记录。所以剩下的就是re-parentC。这个 re-parenting 步骤是彻底重写的地方;因此,如果您不打算进行重写,那么您可以在此处标记新合并并将其留给单独的克隆使用 git replace

您可以使用 git filter-branch 执行 re-parenting;但是 git filter-branch 又是一个旧工具,它的文档建议您改用 git filter-repo。我不熟悉较新的工具,可能不应该花时间传播使用旧工具的方法,所以在这一步我会推荐你​​参考文档。 (通常,如果您 google git <any-git-command> 不难找到任何命令的官方文档,只要您知道要使用哪个命令即可。)

最后,您可以删除 history 遥控器,然后您就有了一个适合用作 origin(或从中创建新源)的新存储库。

请注意,此过程确实会在您的存储库中留下两个不同的历史记录。从“当前”提交中,您将能够“查看”任何文件的完整历史记录,但是如果您 checkout 进入一个历史记录,那么另一个将从您的索引和工作树中消失,直到您返回到较新的共享历史。

拥有真正统一的历史要困难得多,但在技术上并非不可能。您可以使用 filter-repo 重写“其他”历史记录,使其看起来总是在其子目录中,但是您必须弄清楚如何合并历史记录的时间线,而我只看到手动方法这样做。