Git:将文件的历史记录从一个仓库复制到另一个仓库

13
我有两个Git仓库,分别为A和B,两者都包含名为file1.cc的文件。是否可能将repo A中的file1.cc的历史合并/复制到repo B中的file1.cc?
问题在于我们已经将文件从repo A移动到repo B,所有文件的历史记录都丢失了。但是现在一些开发人员已经开始在repo B上工作并推送了他们的更改。因此,现在我想将一些文件的历史记录从repo A合并/复制到repo B中,这仅适用于某些文件。是否可能实现?或者一旦文件的历史记录丢失就永远丢失了?
请帮忙解答。谢谢。

1
你能详细说明一下为什么你想这样做吗? - Tim Biegeleisen
1个回答

17

可以做到,但可能并不容易。首先要明确一点:没有“移动文件的历史”这种说法。只有移动提交,所以如果您想要代表文件子集的历史记录,则创建这些提交是第一个挑战。

最简单的方法是转移所有历史记录。(实际上,如果你将Repo B作为Repo A的浅克隆,那么你可以取消浅层克隆就完成了。但我猜你不是这样创建Repo B的...)

无论如何,由于您要从Repo A移动到Repo B,也许有一些特定的历史记录需要删除。这可能是一个完整的话题,但让我们假设您只想要几个文件的历史记录。

在所有您想要的文件(且没有其他文件)都在子目录中的特殊情况下,并且您想要(或者至少可以接受)将这些文件移动到存储库的根目录中,则可以使用filter-branch--subdirectory-filter

更普遍地,如果我们假设路径不应该更改,并且您想要的文件可以出现在树的任何位置,那么您可以使用filter-branch--index-filter

git filter-branch --index-filter 'git rm --cached --ignore-unmatch each file or *glob* you do NOT want' --prune-empty -- all

如果仓库有很多提交记录,那可能需要一段时间。如果要删除的文件列表不是微不足道的,您可能希望将多个git rm命令放在一个shell脚本中,并使用它作为--index-filter参数,而不是像上面展示的那样内联。

无论哪种方式,希望您已经有了想要移植到Repo B的历史记录。

cd repo-b
git remote add repo-a path/to/repo-a
git fetch repo-a

现在你的 Repo B 中有:

... A -- B <--(repo-a/master)
  \
   (repo-a/other-branches-maybe)

B' -- C -- D (master)(origin/master)

所以我在这里做出一个假设,即来自 Repo A 最后一个 master 提交的 TREE - 正是我们历史重写创建 B 的那个提交的一部分,或者至少是这棵树的某个部分 - 作为根提交被导入到了 Repo B 中。

现在你有三个选择:重新父提交、变基或替换

由于我认为最近的历史状态比旧的历史状态更重要,并且旧的历史只是为了参考而添加的,最安全的方法是将 C 的父提交设置为 B。(你也可以选择将 B' 的父提交设置为 A,但我假设这并没有太大的区别...)

因此,参照 https://git-scm.com/docs/git-filter-branch 上的 filter-branch 文档,您可以:

# be sure you're on master
echo "$commit-id $graft-id" >> .git/info/grafts
git filter-branch $graft-id..HEAD

其中$commit-idB的SHA,$graft-idC的SHA。

如果历史记录之间存在一定的一致性,则变基可能会更简单,但会引入在D处修改树的可能性。 如果您决定尝试变基,

git rebase --onto repo-A/master B' master

其中B'是Repo B根提交的SHA ID。(或者

git rebase --interactive --onto repo-A/master --root master

然后删除B'条目。两种选项都会重写提交CD。(即使重新设置父级保证了TREE不变,提交仍然被替换)。你的开发人员必须将其视为上游rebase(请参见“从上游rebase恢复”的git rebase文档)。为了减轻这种情况,我通常建议进行协调切换,让开发人员检入他们拥有的所有内容,丢弃他们的克隆,然后您进行重写,他们从新的存储库重新克隆。

如果你想避免重写,可以使用第三个选项:git replace。已知它有一些怪癖,并且需要正确设置每个克隆才能“查看”拼接的历史记录。

因此,要支持此操作,只需标记B(可能还包括B'):

git tag old-history repo-a/master
git tag new-root B'

(其中B'是适当的SHA值ID或等效表达式)。

当有人克隆仓库时,他们将只看到新的历史记录,但他们可以说

git replace new-root old-history

这将掩盖历史中的中断。

完成重定向、变基或替换后,您可以删除repo-a远程仓库。


这确实是我想要实现的。非常感谢。 - Paul Varghese
我也尝试使用gitpython编写脚本,但是需要一些时间 :(. - Paul Varghese
这对我非常有用,但一开始让我困惑的是,在你的--index-filter示例中的单引号在我的浏览器中看起来像反引号(`),花了我几次尝试才意识到它们应该是单引号(')。 - rbhitchcock
抱歉,那是我的错误;每当我发布带有大量单引号和反引号的文章时,就会出现拼写错误。 - Mark Adelsberger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接