我不小心输入了git commit --amend。这是一个错误,因为我意识到提交实际上是全新的,它应该用新消息提交。我想做一个新的提交。我该如何撤消这个?
答案 0 :(得分:99)
PetSerAl's comment是关键。这是两个命令序列,可以完成你想要的任务:
git reset --soft @{1}
git commit -C @{1}
以及如何运作的解释。
当你进行新的提交时,Git通常 1 使用这一系列事件:
a123456...
)(通过HEAD
,它为我们提供当前分支)。我们将此ID称为 C (对于当前)。请注意,此当前提交具有父提交;让我们将其ID P (对于父母)称为。使用--amend
时Git会稍微改变一下这个过程。它仍然像以前一样编写新的提交,但是在步骤3中,不是使用parent = C 编写新提交,而是使用parent = P 写入它。
从图中我们可以得出这种方式。我们从一个以P--C
结尾的提交图开始,由branch
指向:
...--P--C <-- branch
当我们进行新的提交N
时,我们得到:
...--P--C--N <-- branch
当我们使用--amend
时,我们会改为:
C
/
...--P--N <-- branch
请注意,提交C
仍在存储库中;它只是被推到一边,以便新的提交N
可以指回旧的父P
。
在git commit --amend
之后,您认识到想要的目的是让链看起来像:
...--P--C--N <-- branch
我们无法完全这样做 - 我们无法改变N
;一旦它存储在repo中,Git永远不会更改任何提交(或任何其他对象),但请注意...--P--C
链仍然在那里,完整无缺。您可以通过reflog找到commit C
,这就是@{1}
语法的作用。 (具体来说,这是currentbranch@{1}
, 2 的缩写,这意味着&#34;其中 currentbranch
指出前一步&#34;,这是&#34;提交C
&#34;。)
所以,我们现在运行git reset --soft @{1}
,这样做:
C <-- branch
/
...--P--N
现在branch
指向C
,指向P
。
N
会怎样?与C
之前发生过同样的事情:它通过reflog保存了一段时间。
我们并不真正需要它(虽然它可能会派上用场),因为--soft
标志git reset
保持索引/暂存区域不变(与工作树一起) )。这意味着我们现在可以通过运行另一个git commit
再次进行新的提交。它将经历相同的四个步骤(从HEAD
读取ID,创建树,创建新提交,并更新分支):
C--N2 <-- branch
/
...--P--N
其中N2
将是我们的新新(第二个新的?)提交。
我们甚至可以使git commit
重用来自提交N
的提交消息。 git commit
命令有一个--reuse-message
参数,拼写为-C
;我们所要做的就是给它一些东西,让它找到原始的新提交N
,从中复制消息,用N2
。我们怎么做?答案是:它在reflog中,就像我们需要C
时git reset
一样。
事实上,它是@{1}
!
请注意,@{1}
表示&#34;刚刚刚出现的地方&#34;,git reset
刚刚更新了它,将其从C
移至N
。我们尚未制作新提交N2
。 (一旦我们这样做,N
将是@{2}
,但我们还没有。)
所以,把它们放在一起,我们得到:
git reset --soft @{1}
git commit -C @{1}
1 此描述所包含的位置包括您修改合并,何时使用分离的HEAD以及何时使用备用索引。尽管如此,如何修改描述也很明显。
2 如果分离了HEAD
,那么 没有当前分支,则含义变为HEAD@{1}
。请注意,@
本身是HEAD
的缩写,因此@{n}
引用当前分支而不是HEAD
本身的事实有点不一致。
要了解它们之间的区别,请考虑git checkout develop
后跟git checkout master
(假设两个分支都存在)。第一个checkout
更改HEAD
以指向develop
,第二个更改HEAD
以指向master
。这意味着master@{1}
是在master
的最后一次更新之前指向的任何提交master
;但HEAD@{1}
是现在提交的develop
- 可能是其他一些提交。
(回顾:在这两个git checkout
命令后,@{1}
表示master@{1}
现在,HEAD@{1}
表示与develop
现在相同的提交,{{1 }}表示@
。如果你感到困惑,那么我也是,显然我并不孤单:看看评论。)