有没有办法恢复在rebase期间意外跳过的提交? [英] Is there a way to recover a commit that was accidentally skipped during a rebase?

查看:123
本文介绍了有没有办法恢复在rebase期间意外跳过的提交?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当在rebase操作中偶然跳过一个有用的提交时,有没有希望Git保存它可以被重新应用的引用?



它是一个非交互式的rebase,里面有很多二进制文件,我用 git rebase --skip 去了很长的一段愉快的心情,所以根本没有错误信息,只是一个糟糕的态度。

这似乎是一个硬盘崩溃恢复的情况,但不是追逐幻像inode,应该有一种方法来过滤 .git / objects 并让它们恢复活力。 git rebase (交互式或不),git基本上会执行一系列 cherry-pick 操作来复制原始提交 - 连锁到一个新的连锁店。让我们对原始提交使用 o ,然后为分支分支绘制分支 main

  o1  -  o2  -  o3  -  o4 < -  branch 
/
..- * - x < - main

现在你可能会运行 git rebase 将所有旧的 o 提交复制到新的 n 提交,但基于 x main 的提示,而不是基于 * ,旧的合并基点。为了使它更像发生的事情,让我们不小心留下一个:

  o1  -  o2  -  o3  -  o4< - ? 
/
..- * - x < - 主
\
n1 - n3 - n4 < - 分支

上面的 ??? 标签表示git引用(分支名称,标签名称,或任何其他合适的标签),指向或指出提交 o4 。只要有一个名字指向他们,那么您所有的旧提交仍然存在。如果有 no 这个名字,他们仍然坚持到 git gc 清除它们(但你不希望发生这种情况,所以不要运行 git gc :-))。



然后,重要的问题是:我们可以用什么名字(和git)用来查找 o4 ?事实证明,至少有两个:


  • 一个或多个在reflog中,和

  • 一个拼写 ORIG_HEAD



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

  $ git log ORIG_HEAD 

如果这给了你正确的链,给自己一个更持久的名字指向提交 o4 。这可以是一个分支名称(因此您可以使用新名称复活旧分支),或标签名称,或者其他任何名称,但分支和标签都很简单:

  $ git branch zombie ORIG_HEAD 



<不要 来做到这一点,并且随着你对git的使用更加舒适,你可以跳过这一步,但在此之前可能很好。)






如果 ORIG_HEAD 遭到重击(例如,通过另一个rebase或merge或其他)?那么,有reflogs。
$ b

有一个reflog为 HEAD ,默认情况下,每个分支的另一个reflog -名称。在这种情况下,使用的将是分支的reflog:

  $ git reflog branch 
$ git log -g branch

但是您可以使用 git reflog 来显示 HEAD 中的一个(这个是噪音较大的,这就是为什么只看着 branch 可能会更好):

  $ git reflog 
$ git log -g

在所有输出中的某处,您应该能够找到提交 O4 。您可能会发现很多类似于 o4 的其他提交,这就是为什么 git log -g 可以提供帮助,因为它会让你找到真正的(或正确的) o4



无论如何,假设你最终想出了一个reflog样式相对名称(如分支@ {1} 分支@ {昨天} ),您可以找到原始SHA-1或使用该相对名称再次重新生成分支的僵尸版本:

  $ git branch zombie branch @ {昨天} 

或:

  $ git branch zombie feedd0gf00d 

或其他。






所有这些都会给你一个名字, zombie ,其中图形的图形中有三个问号。您仍然必须使用它来查找已删除的提交,在这种情况下,请提交 o2 。您可以通过原始SHA-1(通过阅读 git log )找到它,然后重新绑定并拉入其中,或者选择它以将副本追加到 n4 ,或其他。



如果您只想设置分支返回提交 o4 ,你甚至可以完全免除僵尸分支,只需做一个 git reset --hard 分支分支

  $ git checkout分支#如果需要的话
$ git reset --hard feedd0gf00d

或者:

  $ git reset --hard ORIG_HEAD 

请注意, reset --hard 之后的内容只是任何 commit-ID。 - hard 使得 reset 清除工作树并将其替换为目标提交,而 reset action本身告诉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

至此:

  o1 -  o2  -  o3  -  o4 < -  ORIG_HEAD,HEAD =分支
/
- * - x < - 主
\
n1 - n3 - n4 [弃置]

[弃置] n 提交链实际上仍然存在于回购库中,当然:在reflog中有一个名称指向 n4

(reflog条目最终会过期 - 默认情况下为30到90天,具体取决于不感兴趣的细节 - 一旦它们过期并且没有 code> git gc 会清除并删除它们。)






1 请注意,我已将 HEAD = 符号添加到此图表中,以指明您正在使用哪个分支。这个 HEAD = 这个东西实际上是一个相当不错的git跟踪你正在分支的分支的近似值。在 .git 目录中,有一个名为 HEAD 的文件,该文件仅包含当前分支的名称! 2 如果你在文件中写了一个新名字,git会改变你对哪个分支的想法(不需要改变其他的)。这正是 git reset --soft 的作用:在 HEAD 中写入一个新名称。 (使用 - mixed 添加更多操作: git reset 然后更新索引/分段区域;并使用 - hard 增加了更多内容: git reset 然后清除工作目录内容,把它放到 HEAD 文件中。)



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


When it happens that a useful commit is accidentally skipped during a rebase operation, is there any hope that Git keeps a reference of it that could be reapplied?

It was a non-interactive rebase with lots of binary files where I went too long into a happy-trigger mood using git rebase --skip, so there were no error messages at all, just a lousy attitude.

This seems a hard-disk crashing recovery scenario, but instead of chasing phantom inodes, there should be a way to filter lost tree objects inside .git/objects and getting them back alive.

解决方案

When you run git rebase (interactive or not), git basically does a series of cherry-pick operations to copy your original commit-chain to a new chain. Let's use o for the original commits, and draw the commit-graph fragment for branch branch coming off branch main:

        o1 - o2 - o3 - o4   <-- branch
      /
..- * - x                   <-- main

Now you might run git rebase to copy all the old o commits to new n commits, but based off x, the tip of main, rather than based off *, the old merge-base point. To make it even more like what happened, let's "accidentally" leave one out:

        o1 - o2 - o3 - o4   <-- ???
      /
..- * - x                   <-- main
          \
            n1 - n3 - n4    <-- branch

The ??? label above represents the git reference (branch-name, tag-name, or any other suitable label) that points or pointed to commit o4. All your old commits are still in there as long as there's a name pointing to them. If there's no name, they still stick around until git gc cleans them out (but you don't want that to happen so don't run git gc :-) ).

The important question, then, is: "what name or names can we (and git) use to find o4?" It turns out there are at least two:

  • one or more in a "reflog", and
  • one spelled ORIG_HEAD.

The ORIG_HEAD one is the easiest to use, but that name is also used by other commands (git merge, for instance) so you have to see if it's still correct:

$ git log ORIG_HEAD

If that gives you the right chain, give yourself a more permanent name pointing to commit o4. This can be a branch name (you thus "resurrect" the old branch under a new name), or a tag name, or indeed any other name but branch and tag are the easy ones:

$ git branch zombie ORIG_HEAD

(You don't have to do this, and as you get more comfortable with git you can skip this step, but it's probably good to do until then.)


What if ORIG_HEAD has been whacked (e.g., by another rebase, or merge, or whatever)? Well, then there are reflogs.

There's one reflog for HEAD, and by default, another reflog for each branch-name. In this case the one to use would be the reflog for branch:

$ git reflog branch
$ git log -g branch

but you can just use git reflog to show the one for HEAD (this one is noisier, which is why looking at the one just for branch might be better):

$ git reflog
$ git log -g

Somewhere in all that output, you should be able to find commit o4. You might find lots of other commits that resemble o4, which is why git log -g can be helpful as it will let you find the real (or correct) o4.

In any case, assuming you eventually come up with a reflog style "relative name" (like branch@{1} or branch@{yesterday}), you can find the raw SHA-1, or use that relative name, to once again resurrect the zombie version of branch:

$ git branch zombie branch@{yesterday}

or:

$ git branch zombie feedd0gf00d

or whatever.


All this does is give you a name, zombie, where there were three question-marks in the drawing of the graph. You still have to use that to find the dropped commit, in this case commit o2. You can find it by raw SHA-1 (by reading through git log) and re-rebase and pull that one in, or cherry-pick it to append a copy to n4, or whatever.

If all you want to do is set branch back to commit o4, you can even dispense with the zombie branch entirely, and just do a git reset --hard while on branch branch:

$ git checkout branch           # if needed
$ git reset --hard feedd0gf00d

or:

$ git reset --hard ORIG_HEAD

Note that the thing after reset --hard is just any commit-ID. The --hard makes reset wipe out your work-tree and replace it with the target commit, while the reset action itself tells git: "make the current branch point to the commit-ID I'm about to give you, regardless of whatever branch-tip-commit it names right now."

In other words, after your git rebase finishes and you discover you left out o2 when making the n1 - n3 - n4 chain, if you immediately git reset --hard ORIG_HEAD, git changes this:1

        o1 - o2 - o3 - o4   <-- ORIG_HEAD
      /
..- * - x                   <-- main
          \
            n1 - n3 - n4    <-- HEAD=branch

to this:

        o1 - o2 - o3 - o4   <-- ORIG_HEAD, HEAD=branch
      /
..- * - x                   <-- main
          \
            n1 - n3 - n4    [abandoned]

The [abandoned] chain of n commits is actually still in the repo, of course: there's a name pointing to n4 in the reflogs!

(The reflog entries eventually expire—by default, after 30 to 90 days, depending on details not yet interesting—and once they expire and there is no name by which to find n4 or o4 or whatever, then git gc will clean up and remove them.)


1Note that I've added the HEAD= notation to this graph, to indicate which branch you're on. This HEAD= stuff is actually a pretty good approximation to how git keeps track of which branch you're on. In the .git directory, there's a file named HEAD, and that file simply contains the name of the current branch!2 If you write a new name in the file, git changes its idea of which branch you're on (without changing anything else). That's exactly what git reset --soft does: write a new name into HEAD. (Using --mixed adds a little more action: git reset then updates the index/staging-area; and using --hard adds even more: git reset then wipes out work-directory contents, replacing them with whatever you've had it put into the HEAD file.)

2In "detached HEAD" mode, the file contains the raw SHA-1 of the current commit, instead of the name of the current branch. That, in fact, is the real difference between being "on a branch" and being in "detached HEAD" mode. When git wants to know what the current commit is, it looks at the file HEAD. If it has a raw SHA-1, that's the answer. If it has a branch name, git reads the branch-name to get the raw SHA-1. Those are the only two allowed setups—nothing else should be in the HEAD file.

这篇关于有没有办法恢复在rebase期间意外跳过的提交?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆