如何撤消git commit --amend

时间:2016-06-23 20:17:47

标签: git

我不小心输入了git commit --amend。这是一个错误,因为我意识到提交实际上是全新的,它应该用新消息提交。我想做一个新的提交。我该如何撤消这个?

1 个答案:

答案 0 :(得分:99)

PetSerAl's comment是关键。这是两个命令序列,可以完成你想要的任务:

git reset --soft @{1}
git commit -C @{1}

以及如何运作的解释。

描述

当你进行新的提交时,Git通常 1 使用这一系列事件:

  1. 读取当前提交的ID(SHA-1哈希,如a123456...)(通过HEAD,它为我们提供当前分支)。我们将此ID称为 C (对于当前)。请注意,此当前提交具有​​父提交;让我们将其ID P (对于父母)称为。
  2. 将索引(也称为临时区域)转换为树。这会产生另一个ID;让我们称这个ID为 T (对于Tree)。
  3. 使用parent = C 和tree = T 编写新提交。这个新提交获得另一个ID。我们称之为 N (对于新)。
  4. 使用新提交ID N 更新分支。
  5. 使用--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中,就像我们需要Cgit 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 }}表示@。如果你感到困惑,那么我也是,显然我并不孤单:看看评论。)