有没有办法恢复在rebase期间意外跳过的提交? [英] Is there a way to recover a commit that was accidentally skipped during a 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屋!