有没有办法恢复在重新设置期间意外跳过的提交?

mil*_*ton 7 git git-rebase

如果在重新执行基准操作期间偶然跳过了有用的提交,是否有希望Git保留可以重新应用的引用?

这是一个具有许多二进制文件的非交互基础,使用,我花了很长时间来触发愉快的心情git rebase --skip,因此根本没有错误消息,只是糟糕的态度。

这似乎是硬盘崩溃的恢复方案,但与其追逐幻像inode,还不如采用一种方法来过滤掉内部丢失的树对象.git/objects并使它们恢复活动。

tor*_*rek 5

当您运行时git rebase(是否互动),git基本上会执行一系列cherry-pick操作以将您的原始提交链复制到新链中。让我们使用o原始提交,并为分支branch从分支出来绘制提交图片段main

        o1 - o2 - o3 - o4   <-- branch
      /
..- * - x                   <-- main
Run Code Online (Sandbox Code Playgroud)

现在,您可以运行git rebase将所有旧o提交复制到新n提交,但是基于off x的尖端main,而不是基于off *的合并基础点。为了使它更像发生的事情,让我们“意外地”将其中一个排除在外:

        o1 - o2 - o3 - o4   <-- ???
      /
..- * - x                   <-- main
          \
            n1 - n3 - n4    <-- branch
Run Code Online (Sandbox Code Playgroud)

???上面的标签表示指向或指向commit的git参考(分支名称,标签名称或任何其他合适的标签)o4。 只要有一个指向它们的名称,您所有的旧提交都仍在其中。如果没有名字,它们仍然会停留直到git gc清除掉(但是您不希望这样发生,所以不要运行git gc:-))。

那么,重要的问题是:“我们(和git)可以使用什么名称o4?” 事实证明,至少有两个:

  • “ reflog”中的一个或多个,以及
  • 一拼ORIG_HEAD

ORIG_HEAD一个是最容易使用,但该名称也使用其他命令(git merge例如),所以你必须看到,如果它仍然是正确的:

$ git log ORIG_HEAD
Run Code Online (Sandbox Code Playgroud)

如果那给您正确的链,请给自己一个更永久的名称,指向commit o4。这可以是分支名称(因此,您可以用新名称“恢复”旧分支),标签名称,或者实际上是其他名称,但branch和tag很简单:

$ git branch zombie ORIG_HEAD
Run Code Online (Sandbox Code Playgroud)

(你不会这样做,而当你获得更舒适的使用Git,你可以跳过这一步,但它可能是好做的再说吧。)


如果ORIG_HEAD遭到重击(例如,被另一个变基,合并,或其他原因)怎么办?好吧,那么这里有更新。

有一个reflog HEAD,默认情况下,每个分支名称都有另一个reflog。在这种情况下,将使用以下引用日志branch

$ git reflog branch
$ git log -g branch
Run Code Online (Sandbox Code Playgroud)

但是您可以仅使用它git reflog来显示HEAD(这比较吵,这就是为什么只看一个branch可能更好):

$ git reflog
$ git log -g
Run Code Online (Sandbox Code Playgroud)

在所有输出的某个位置,您应该能够找到commit o4。您可能会发现许多其他类似的提交o4,这就是为什么git log -g会有所帮助的原因,因为它将使您找到真正的(或正确的)提交o4

无论如何,假设您最终使用了reflog样式的“相对名称”(如branch@{1}branch@{yesterday}),则可以找到原始SHA-1或使用该相对名称再次使僵尸版本复活branch

$ git branch zombie branch@{yesterday}
Run Code Online (Sandbox Code Playgroud)

要么:

$ git branch zombie feedd0gf00d
Run Code Online (Sandbox Code Playgroud)

管他呢。


所有这一切都为您提供了一个名称,zombie在该图形的绘图中有三个问号。您仍然必须使用它来查找已删除的提交,在本例中为commit o2。您可以通过原始SHA-1(通过阅读git log)找到它,然后重新调整其基数并将其拉入其中,或对其进行挑选,以将副本附加到n4,或其他方式。

如果您想做的所有事情都branch重新设置为commit o4,那么您甚至可以完全省去僵尸分支,git reset --hard而在分支上做一会儿branch

$ git checkout branch           # if needed
$ git reset --hard feedd0gf00d
Run Code Online (Sandbox Code Playgroud)

要么:

$ git reset --hard ORIG_HEAD
Run Code Online (Sandbox Code Playgroud)

注意,后面的东西reset --hard就是任何提交ID。该--hard品牌reset消灭你的工作树,并与目标替代它提交,而reset行动本身告诉git的:“使当前分支指向提交-ID我正要给你,不管任何的分支尖端提交它现在就命名。”

换句话说,git rebase完成后发现o2制作n1 - n3 - n4链时被遗漏了,如果立即使用git reset --hard ORIG_HEAD,git会更改:1

        o1 - o2 - o3 - o4   <-- ORIG_HEAD
      /
..- * - x                   <-- main
          \
            n1 - n3 - n4    <-- HEAD=branch
Run Code Online (Sandbox Code Playgroud)

对此:

        o1 - o2 - o3 - o4   <-- ORIG_HEAD, HEAD=branch
      /
..- * - x                   <-- main
          \
            n1 - n3 - n4    [abandoned]
Run Code Online (Sandbox Code Playgroud)

[abandoned]n提交实际上仍然是在回购,当然:有指向一个名字n4在reflogs!

(引用日志的条目最终到期,在默认情况下,经过30到90天,这取决于细节尚未有趣的,而一旦过期,并且没有任何名称由发现n4o4也好,然后 git gc将清理并删除它们。)


1请注意,我已经在HEAD=该图中添加了表示法,以指示您所在的分支。这些HEAD=东西实际上是git如何跟踪您所在的分支的一个很好的近似。在.git目录中,有一个名为的文件HEAD,该文件仅包含当前分支的名称!2 如果您在文件中写了一个新名称,则git会更改其对您所在的分支的想法(不更改其他任何内容)。就是git reset --soft这样:将新名称写入HEAD。(使用--mixed会增加一些操作:git reset然后更新索引/临时区域;使用--hard会增加更多操作:git reset然后清除工作目录中的内容,将其替换为放入目录中的任何内容。HEAD 文件。)

2在“分离式HEAD”模式下,该文件包含当前提交的原始SHA-1 ,而不是当前分支的名称。实际上,这是“在分支上”和处于“分离头”模式之间的真正区别。当git想知道当前的提交什么的时候,它会看文件HEAD。如果它具有原始的SHA-1,那就是答案。如果它具有分支名称,则git读取分支名称以获取原始SHA-1。这些是仅有的两个允许的设置- HEAD文件中应无其他内容。