如何撤消 git commit --amend
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/38001038/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to undo a git commit --amend
提问by Jwan622
I accidentally typed in a git commit --amend. This is a mistake because I realized that the commit is actually entirely new and it should be committed with a new message. I want to make a new commit. How do I undo this?
我不小心输入了 git commit --amend。这是一个错误,因为我意识到提交实际上是全新的,应该使用新消息提交。我想做一个新的提交。如何撤消此操作?
回答by torek
PetSerAl's commentis the key. Here's the two command sequence to do just what you want:
PetSerAl 的评论是关键。这是执行您想要的操作的两个命令序列:
git reset --soft @{1}
git commit -C @{1}
and an explanation of how this works.
并解释这是如何工作的。
Description
描述
When you make a new commit, Git usually1uses this sequence of events:
当您进行新的提交时,Git 通常1使用以下事件序列:
- Read the ID (SHA-1 hash, like
a123456...
) of the current commit (viaHEAD
, which gives us the current branch). Let's call this ID C(for Current). Note that this current commit has a parent commit; let's call its ID P(for Parent). - Turn the index (aka staging-area) into a tree. This produces another ID; let's call this ID T(for Tree).
- Write a new commit with parent = Cand tree = T. This new commit gets another ID. Let's call this N(for New).
- Update the branch with the new commit ID N.
- 读取
a123456...
当前提交的 ID(SHA-1 哈希,如)(通过HEAD
,它为我们提供当前分支)。我们将此 ID 称为C(用于 Current)。请注意,当前提交有一个父提交;让我们称其为P(对于 Parent)。 - 将索引(又名暂存区)变成一棵树。这会产生另一个 ID;我们将此 ID 称为T(对于 Tree)。
- 使用 parent = C和 tree = T编写新提交。这个新提交获得另一个 ID。我们称之为N(新的)。
- 使用新的提交 ID N更新分支。
When using --amend
Git changes the process a bit. It still writes a new commit as before, but in step 3, instead of writing the new commit with parent = C, it writes it with parent = P.
使用--amend
Git 时会稍微改变这个过程。它仍然像以前一样写一个新的提交,但在第 3 步中,它不是用 parent = C写新提交,而是用 parent = P写它。
Picture
图片
Pictorially, we can draw what happened this way. We start with a commit graph that ends in P--C
, pointed-to by branch
:
从形象上讲,我们可以用这种方式画出发生的事情。我们从一个以 结尾的提交图开始,P--C
由 指向branch
:
...--P--C <-- branch
When we make the new commit N
we get:
当我们进行新的提交时,N
我们得到:
...--P--C--N <-- branch
When we use --amend
, we get this instead:
当我们使用时--amend
,我们会得到这个:
C
/
...--P--N <-- branch
Note that commit C
is still in the repository; it's just been shoved aside, up out of the way, so that new commit N
can point back to old parent P
.
需要注意的是承诺C
是仍然在仓库中; 它只是被推到一边,让开,这样新的提交N
就可以指向旧的 parent P
。
Goal
目标
What you realized you want, after the git commit --amend
, is to have the chain look instead like:
在, 之后你意识到你想要的git commit --amend
是让链看起来像:
...--P--C--N <-- branch
We can't quitedo this—we can't change N
; Git can never change anycommit (or any other object) once it's stored in the repo—but note that the ...--P--C
chain is still in there, fully intact. You can find commit C
through the reflogs, and this is what the @{1}
syntax does. (Specifically, this is short for currentbranch@{1}
,2which means "where currentbranch
pointed one step ago", which was "to commit C
".)
我们不能完全做到这一点——我们不能改变N
;一旦任何提交(或任何其他对象)存储在存储库中,Git 就永远不能更改它——但请注意,...--P--C
链仍然在那里,完全完好无损。您可以C
通过引用日志找到提交,这就是@{1}
语法的作用。(具体来说,这是currentbranch@{1}
, 2 的缩写,意思是“currentbranch
前一步指向的地方”,即“提交C
”。)
So, we now run git reset --soft @{1}
, which does this:
因此,我们现在运行git reset --soft @{1}
,它执行以下操作:
C <-- branch
/
...--P--N
Now branch
points to C
, which points back to P
.
现在branch
指向C
,又指向P
。
What happens to N
? The same thing that happened to C
before: it's saved for a while through the reflog.
会发生什么N
?与C
之前发生的相同的事情:它通过 reflog 保存了一段时间。
We don't really need it (although it may come in handy), because the --soft
flag to git reset
keeps the index / staging-area untouched (along with the work-tree). This means we can make a new commit again now, by just running another git commit
. It will go through the same four steps (read the ID from HEAD
, create the tree, create a new commit, and update the branch):
我们并不真正需要它(尽管它可能会派上用场),因为保持索引/暂存区不变的--soft
标志git reset
(以及工作树)。这意味着我们现在可以再次进行新的提交,只需运行另一个git commit
. 它将经历相同的四个步骤(从 读取 ID HEAD
、创建树、创建新提交和更新分支):
C--N2 <-- branch
/
...--P--N
where N2
will be our new new (second new?) commit.
N2
我们新的(第二个新的?)提交在哪里。
We can even make git commit
re-use the commit message from commit N
. The git commit
command has a --reuse-message
argument, also spelled -C
; all we have to do is give it something that lets it find the original new commit N
from which to copy the message, to make N2
with. How do we do that? The answer is: it's in the reflog, just as C
was when we needed to do the git reset
.
我们甚至可以git commit
重新使用来自 commit 的提交消息N
。该git commit
命令有一个--reuse-message
参数,也拼写为-C
; 我们所要做的就是给它一些东西,让它找到原始的新提交N
,从中复制消息,进行制作N2
。我们怎么做?答案是:它在 reflog 中,就像C
我们需要执行git reset
.
In fact, it's the same @{1}
!
其实是一样的@{1}
!
Remember, @{1}
means "where it was just a moment ago", and git reset
just updated it, moving it from C
to N
. We haven't yet madenew commit N2
. (Once we do that, N
will be @{2}
, but we haven't yet.)
记住,@{1}
意思是“它刚才在哪儿”,git reset
只是更新了它,把它从 移动C
到N
。我们还没有进行新的 commit N2
。(一旦我们这样做,N
将会是@{2}
,但我们还没有。)
So, putting it all together, we get:
所以,把它们放在一起,我们得到:
git reset --soft @{1}
git commit -C @{1}
1The places this description breaks down include when you're amending a merge, when you're on a detached HEAD, and when you use an alternative index. Even then, though, it's pretty obvious how to modify the description.
1此描述分解的地方包括何时修改合并、何时在分离的 HEAD 上以及何时使用替代索引。尽管如此,如何修改描述还是很明显的。
2If HEAD
is detached, so that there isno current branch, the meaning becomes HEAD@{1}
. Note that @
by itself is short for HEAD
, so the fact that @{n}
refers to the current branch, rather than to HEAD
itself, is a bit inconsistent.
2如果HEAD
是分离的,这样就没有当前的分支,意思就变成了HEAD@{1}
。请注意,@
本身是 的缩写HEAD
,因此引用当前分支而不是自身的事实有点不一致。@{n}
HEAD
To see how they differ, consider git checkout develop
followed by git checkout master
(assuming both branches exist). The first checkout
changes HEAD
to point to develop
, and the second changes HEAD
to point to master
. This means that master@{1}
is whatever commit master
pointed to, before the last update to master
; but HEAD@{1}
is the commit develop
points to now—probably some other commit.
要查看它们有何不同,请考虑git checkout develop
后跟git checkout master
(假设两个分支都存在)。第一个checkout
更改HEAD
为指向develop
,第二个更改HEAD
为指向master
。这意味着这master@{1}
是master
在最后一次更新之前指向的任何提交master
;但HEAD@{1}
提交develop
指向现在——可能是其他一些提交。
(Recap: after these two git checkout
commands, @{1}
means master@{1}
now, HEAD@{1}
means the same commit as develop
now, and @
means HEAD
. If you're confused, well, so was I, and apparently I am not alone: see the comments.)
(回顾:在这两个git checkout
命令之后,@{1}
意味着master@{1}
now,HEAD@{1}
意味着与develop
now相同的提交,并且@
意味着HEAD
。如果你感到困惑,好吧,我也是,显然我并不孤单:见评论。)