如何撤消 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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 04:17:54  来源:igfitidea点击:

How to undo a git commit --amend

git

提问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使用以下事件序列:

  1. Read the ID (SHA-1 hash, like a123456...) of the current commit (via HEAD, 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).
  2. Turn the index (aka staging-area) into a tree. This produces another ID; let's call this ID T(for Tree).
  3. Write a new commit with parent = Cand tree = T. This new commit gets another ID. Let's call this N(for New).
  4. Update the branch with the new commit ID N.
  1. 读取a123456...当前提交的 ID(SHA-1 哈希,如)(通过HEAD,它为我们提供当前分支)。我们将此 ID 称为C(用于 Current)。请注意,当前提交有一个父提交;让我们称其为P(对于 Parent)。
  2. 将索引(又名暂存区)变成一棵树。这会产生另一个 ID;我们将此 ID 称为T(对于 Tree)。
  3. 使用 parent = C和 tree = T编写新提交。这个新提交获得另一个 ID。我们称之为N(新的)。
  4. 使用新的提交 ID N更新分支。

When using --amendGit 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.

使用--amendGit 时会稍微改变这个过程。它仍然像以前一样写一个新的提交,但在第 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 Nwe get:

当我们进行新的提交时,N我们得到:

...--P--C--N   <-- branch

When we use --amend, we get this instead:

当我们使用时--amend,我们会得到这个:

       C
      /
...--P--N   <-- branch

Note that commit Cis still in the repository; it's just been shoved aside, up out of the way, so that new commit Ncan 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--Cchain is still in there, fully intact. You can find commit Cthrough the reflogs, and this is what the @{1}syntax does. (Specifically, this is short for currentbranch@{1},2which means "where currentbranchpointed 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 branchpoints to C, which points back to P.

现在branch指向C,又指向P

What happens to N? The same thing that happened to Cbefore: 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 --softflag to git resetkeeps 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 N2will be our new new (second new?) commit.

N2我们新的(第二个新的?)提交在哪里。

We can even make git commitre-use the commit message from commit N. The git commitcommand has a --reuse-messageargument, also spelled -C; all we have to do is give it something that lets it find the original new commit Nfrom which to copy the message, to make N2with. How do we do that? The answer is: it's in the reflog, just as Cwas 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 resetjust updated it, moving it from Cto N. We haven't yet madenew commit N2. (Once we do that, Nwill be @{2}, but we haven't yet.)

记住,@{1}意思是“它刚才在哪儿”,git reset只是更新了它,把它从 移动CN。我们还没有进行新的 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 HEADis 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 HEADitself, is a bit inconsistent.

2如果HEAD是分离的,这样没有当前的分支,意思就变成了HEAD@{1}。请注意,@本身是 的缩写HEAD,因此引用当前分支而不是自身的事实有点不一致。@{n}HEAD

To see how they differ, consider git checkout developfollowed by git checkout master(assuming both branches exist). The first checkoutchanges HEADto point to develop, and the second changes HEADto point to master. This means that master@{1}is whatever commit masterpointed to, before the last update to master; but HEAD@{1}is the commit developpoints 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 checkoutcommands, @{1}means master@{1}now, HEAD@{1}means the same commit as developnow, 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}意味着与developnow相同的提交,并且@意味着HEAD。如果你感到困惑,好吧,我也是,显然我并不孤单:见评论。)