从项目的遗留版本中恢复 git 历史记录

Recover git history from legacy version of project

长话短说:

  1. 项目正在迁移到新的 git 存储库服务器
  2. 有人只复制项目文件并将整个项目推送到新服务器作为初始提交
  3. 工作从新的初始提交开始继续了相当长的一段时间
  4. 我设法找到了遗留项目的旧本地副本(在切换到新服务器之前)并想将旧的 git 历史记录插入到项目的当前版本中(在当前版本开始之前)历史)。在旧的本地项目中有一些额外的不需要的提交

分支基本上是这样的:

                 old_master
                /
A--B--C--D--E--F

                  origin/new_master
                 /
init--G--H--I--J

其中提交:new_master -> init = old_master -> D

所以最终结果会是这样的:

                       origin/new_master
                      /
A--B--C--D--G--H--I--J

在历史上也有类似的困境,可以通过采摘樱桃来解决。在我的情况下,有大量具有复杂分支的提交可能难以挑选。有没有使用 rebaserebase --onto 的有效方法?

Is there an efficient way to do this using rebase or rebase --onto?

一般情况下不会,因为可能存在分支和合并。 (如果新存储库中的历史是严格线性的,那么你可以用一个简单的 git rebase --onto 来做到这一点。它并不完全 有效 但它只是机器时间,所以谁在乎如何效率高吗?)

这个问题的一般解决方案是移植,通过git replace

让我们看看如果使用上图将原始存储库和新存储库 git fetch 都放入 第三个 (否则完全为空)存储库中会发生什么。你最终得到:

A--B--C--D--E--F   <-- old/master

D'--G--H--I--J   <-- new/master

(请注意,第三个存储库还没有自己的 master)。我没有将 new/master 链中的第一个提交称为 init,而是将其称为 D' 因为大概它与提交 ​​snapshot 相同 Dold/master 中,但它具有不同的 哈希 .

没有任何东西——地球上没有力量——可以改变这些现有提交中的任何。但是,如果我们将提交 G 复制到父级为 D 的新提交 G' 怎么办?然后我们得到这个:

A--B--C--D--E--F   <-- old/master
          \
           G'

       D'--G--H--I--J   <-- new/master

目前,新提交 G' 只是挂在存储库中,我们无法 找到 它。让我们添加一个 name,通过它我们可以找到 G'。现在,我们称它为 graft:

A--B--C--D--E--F   <-- old/master
          \
           G'  <-- graft

       D'--G--H--I--J   <-- new/master

现在,如果我们能以某种方式得到 Git,当它沿着 J-then-I-then-H-then-G-然后-D'(然后停止)链,到,在最后一刻,G切换到它的移植物G'?也就是说,我们将建立某种虚线连接:

A--B--C--D--E--F   <-- old/master
          \
           G'  <-- graft
           :
       D'--G--H--I--J   <-- new/master

并说服Git 运行 git log as show J then I then H then G' 然后 D 然后 C 然后 B 然后 A.

它现在看起来像历史是这样读的,尽管实际上不是这样。1

这正是 git replace 所做的。它使 替换对象 。在提交的情况下,替换可以采用 graft 的形式,如 G'。 Git 没有使用魔术名称 graft,而是使用了一个更魔术的名称,refs/replace/<em>hash</em>,其中 hash 是实际提交的哈希 ID G。有些时候,您不需要知道这一点,而有些时候,您需要知道。

这种替换提交嫁接的问题是 git clone 默认情况下不会 clone 替换。2 所以你的第三个存储库在克隆时有点奇怪。有时这正是您想要的,如果是这样,那很好。有时不是,如果是这样,请考虑使用 git filter-branch 或类似于 convert 移植物到移植物所在的 fourth 存储库现在是永久性的,因为提交被复制到 new 提交(具有新的和不同的哈希 ID),其中 real——但重写的——历史使用嫁接历史,而不是原始历史。


1哲学问题:还是这样?历史是按照的方式解读的,还是Git向你解读历史的方式?

2这实际上是 Git 对脚注 1 中哲学问题的回答。你,却是被记录真实的历史。在克隆时,Git 克隆 真实的 历史,并忽略嫁接。在克隆之后,您可以要求 Git 也复制移植物 ,但这不是默认设置。

除此之外,还可以运行 git --no-replace-objects log看到真实的历史,在看嫁接历史的时候,每一个嫁接都有一个可选的装饰标记,这样你仔细看就可以看到.