如何在Git中撤销最近的本地提交?

26051
我不小心提交了错误的文件到Git,但还没有将提交推送到服务器。
我该如何从本地仓库撤销这些提交?

684
你知道git需要什么?就是"git undo",这样一来,git对于我们这些凡人所犯下的错误的处理名声就消失了。实现的方法是在执行任何"git"命令之前将当前状态推送到git堆栈上。这会影响性能,所以最好增加一个配置标志来决定是否启用它。 - Yimin Rong
57
可以使用Git的“alias”功能来完成这个操作:https://git-scm.com/book/zh/v2/Git-基础-Git-别名 - Edric
134
针对VsCode用户,只需按下ctrl+shift+G,然后单击三个点,即“更多选项”,接着再点击“撤销上一次提交”。请注意,翻译过程中不会添加解释或其他任何额外信息。 - ashad
29
您要撤销的是什么?有很多不同的功能用例,其中“撤销”意味着完全不同的事情。我敢打赌,添加一个新的花哨的“魔法棒”只会更加混淆事情。 - Romain Valeri
50
不太相信,人们仍然会弄乱并撤销不应该撤销的事情。但更重要的是,git reflog 已经接近你所描述的内容,但它给用户更多的控制权来确定需要(撤销)做的事情。但是请注意,“撤销”在各个地方的工作方式不同,人们会期望这个功能实现许多不同的事情。撤销上一个提交?撤销上一个操作?如果最后一个操作是推送,如何撤销,(重置并推送)还是(撤销并推送)? - Romain Valeri
显示剩余10条评论
107个回答

28514
撤销提交和重做
$ git commit -m "Something terribly misguided" # (0: Your Accident)
$ git reset HEAD~                              # (1)
[ edit files as necessary ]                    # (2)
$ git add .                                    # (3)
$ git commit -c ORIG_HEAD                      # (4)
  1. git reset 是负责撤销的命令。它会撤销你最后一次提交,同时保留你的工作树(磁盘上文件的状态)不变。你需要再次添加它们,然后才能再次提交。
  2. 工作树文件进行更正。
  3. git add 任何你想要包含在新提交中的内容。
  4. 提交更改,重用旧的提交消息。 reset 将旧的头指针复制到 .git/ORIG_HEAD使用 -c ORIG_HEAD 进行提交 将打开一个编辑器,最初包含旧提交的日志消息,并允许你进行编辑。如果你不需要编辑消息,可以使用-C选项。

或者,要编辑先前的提交(或仅编辑其提交消息),可以使用commit --amend将当前索引中的更改添加到先前的提交中。

要删除(而不是还原)已推送到服务器的提交,需要使用git push origin main --force[-with-lease]来重写历史记录。使用--force几乎总是一个坏主意;最好使用--force-with-lease,正如git手册中所述

如果已经发布了历史记录,您应该了解重写历史记录的影响。


进一步阅读

{{link1:您可以使用 git reflog 命令来确定您希望还原的提交的 SHA-1 值。一旦您获得了这个值,按照上述解释的命令顺序进行操作。


HEAD~HEAD~1是一样的。如果你想要取消多个提交,可以参考这篇文章git中的HEAD是什么?


593
如果提交到了错误的分支,你可以使用git checkout theRightBranch命令将所有已经暂存的更改切换到正确的分支上。就像我刚刚所做的一样。 - Frank Shearar
583
如果您正在使用DOS操作系统,那么需要使用命令git reset --soft HEAD~1来代替git reset --soft HEAD^。因为在DOS中,符号^是一个续行符号,所以在此处无法正确使用。另外,--soft是默认选项,如果愿意可以省略这个参数,直接使用git reset HEAD~1即可。 - Ryan Lundy
160
zsh用户可能会遇到以下提示:zsh: no matches found: HEAD^ - 您需要转义符号 ^,例如 git reset --soft HEAD\^ - tnajdek
24
如果出现意外情况,例如执行了“git commit -a”时应该省略“-a”,那么答案就不正确。这种情况下,最好不要省略“--soft”选项(这将导致使用默认的“--mixed”),然后您可以重新暂存您想要提交的更改。 - dmansfield
57
我已经谷歌搜索并访问了这个页面大约50次,每次看到第一行代码 git commit -m "Something terribly misguided" 我总是会发出轻笑声。 - 100pic
显示剩余18条评论

12607
撤销一个提交如果你不知道它是如何工作的,可能会有点可怕。但是如果你理解了它,实际上它非常容易。我将向你展示4种不同的撤销提交的方法。
假设你有这样一个情况,其中C是你的HEAD,(F)是你的文件的状态。
   (F)
A-B-C
    ↑
  master

选项1: git reset --hard 你想要"销毁提交C并且丢弃所有未提交的更改"。你可以执行以下操作:
git reset --hard HEAD~1

结果是:
 (F)
A-B
  ↑
master

现在B是HEAD。因为你使用了--hard,你的文件被重置为提交B时的状态。
选项2:git reset
也许提交C并不是一个灾难,只是有点偏离。你想撤销这个提交,但在做更好的提交之前保留你的更改,以便稍作编辑。从这里重新开始,以C作为你的HEAD:
   (F)
A-B-C
    ↑
  master

做这个时,不要加上--hard
git reset HEAD~1

在这种情况下,结果是:
   (F)
A-B-C
  ↑
master

在这两种情况下,HEAD只是指向最新提交的指针。当你执行git reset HEAD~1时,你告诉Git将HEAD指针向后移动一个提交。但是(除非你使用--hard),你保留了文件的原样。所以现在git status显示了你在C中检入的更改。你没有丢失任何东西!
选项3:git reset --soft 为了最轻微的操作,你甚至可以撤销你的提交,但保留你的文件和你的链接。
git reset --soft HEAD~1

这不仅不会影响你的文件,甚至不会影响你的索引。当你执行`git status`命令时,你会发现索引中的文件与之前一样。实际上,在这个命令之后,你甚至可以执行`git commit`,这样你就会重新提交刚刚的提交。
选项4:你执行了`git reset --hard`命令,需要恢复代码。
还有一件事:假设你像第一个例子中那样删除了一个提交,但后来发现你仍然需要它?那就太倒霉了,对吧?
不,还有一种方法可以找回它。输入以下内容。
git reflog

你会看到一个(部分)提交SHAs(即哈希值)的列表,这些是你移动过的。找到你删除的提交,并执行以下操作:
git checkout -b someNewBranchName shaYouDestroyed

你现在已经复活了那个提交。在Git中,提交实际上在大约90天内不会被销毁,所以你通常可以回去找回一个你不想丢弃的提交。

73
注意!如果你错误提交是一个(快进)合并操作,这个命令可能不会按照你的预期进行!如果你所在的分支是一个合并提交(例如:将特性分支合并到主分支),则 git reset --hard~1 命令将把主分支指向特性分支中最后一个提交。在这种情况下,应该使用特定的提交ID而不是相对命令。 - Chris Kerekes
27
请注意,HEAD~1 中的数字可以替换为任何正整数,例如 HEAD~3。尽管这似乎很明显,但初学者(比如我)在运行 git 命令时都会非常小心谨慎,因此他们可能不想通过测试来冒险搞砸一些东西。 - Šime Vidas
175
关键点遗漏:如果所说的提交已经被“推送”到远程,任何“撤销”操作,无论多么简单,都会给将来在本地副本中有此提交的其他用户在执行“git pull”时带来巨大的痛苦和困扰。因此,如果提交已经被“推送”,请执行以下操作:git revert git push origin : - FractalSpace
36
@FractalSpace,这不会造成“巨大的痛苦和折磨”。我与一个团队使用Git时曾经做过几次强制推送。唯一需要做的就是沟通。 - Ryan Lundy
39
在我的工作场所里,搞乱整个团队的工作流程,然后告诉他们如何修复破烂的事情,并不被称为“沟通”。重写git历史记录是一种破坏性操作,会导致仓库的某些部分被删除。在明确且安全的替代方案可用的情况下,坚持使用它是不负责任的。 - FractalSpace
显示剩余29条评论

2690

如果你已经将你的提交推送到远程仓库,那么撤销你最后一次提交有两种方法:

如何撤销本地提交

假设我已经在本地进行了提交,但现在我想要删除该提交。

git log
    commit 101: bad commit    # Latest commit. This would be called 'HEAD'.
    commit 100: good commit   # Second to last commit. This is the one we want.

为了将所有内容恢复到上次提交之前的状态,我们需要重置到 HEAD 之前的提交:

git reset --soft HEAD^     # Use --soft if you want to keep your changes
git reset --hard HEAD^     # Use --hard if you don't care about keeping the changes you made

现在,git log将显示我们最后一次提交已被删除。
如何撤销公共提交:
如果您已经将您的提交公开,您需要创建一个新的提交来“撤消”您之前提交(当前HEAD)所做的更改。
git revert HEAD

您的更改现在将被还原并准备好提交:

git commit -m 'restoring the file I removed by accident'
git log
    commit 102: restoring the file I removed by accident
    commit 101: removing a file we don't need
    commit 100: adding a file that we need

更多信息,请查看Git 基础 - 撤销操作


136
我认为这个答案是最清晰的。git revert HEAD^ 不是前一个版本,而是前一个版本的前一个。我执行了 git revert HEAD 然后再次推送,它就好了 :) - nacho4d
9
如果Git在您尝试这些命令时询问“More?”,请使用此答案中的备用语法:https://dev59.com/LWYq5IYBdhLWcg3wxjc7#14204318。 - tar
撤销操作删除了我添加到代码库的一些文件。请谨慎使用! - carloswm85
git reset --soft HEAD~1 和 reset --soft HEAD^ 有什么区别? - Jorge Tovar
也许我只是没看到,但你提供的最后一个链接似乎没有涵盖这个具体问题。(我知道你说这是额外信息,我只是想知道我有没有错过什么。) - NerdOnTour
令人困惑。我刚刚在这里执行了“撤消公共提交”,结果显示“您的分支领先于'origin/current-branch' 1个提交 / 使用"git push" ... / **没有要提交的内容...**”。已经成功还原了HEAD(有问题的提交)。但现在它要求进行推送,而不是提交。看起来很合理,但似乎与你的情况不同。叹气。 - mike rodent

1934

添加/删除文件以使您所需的内容达到理想状态:

git rm classdir
git add sourcedir

然后修改提交:

git commit --amend

之前错误的提交将被编辑以反映新的索引状态 - 换句话说,就像您一开始没有犯错误一样。

请注意,仅在未推送时才应执行此操作。如果您已经推送了,那么您只需正常提交一个修复即可。


23
这将删除我所有的文件并导致我失去更改。 - egorlitvinenko
更新:然而,我使用reflog恢复了它。但是该收据对于最初的提交无效。 - egorlitvinenko
10
请使用 git rm --cached 命令将文件从 Git 索引中删除但保留在文件系统中! - xuiqzy
"git --amend --no-edit" 会将你最近的修改提交到它们之前的提交记录中。 - alex

1274

这将添加一个新的提交,其中删除了添加的文件。

git rm yourfiles/*.class
git commit -a -m "deleted all class files in folder 'yourfiles'"

或者你可以重写历史以撤销最后一次提交。

警告:这个命令将永久删除你提交的.java文件(和任何其他文件)所做的修改,并从你的工作目录中删除所有更改:

git reset --hard HEAD~1

hard resetHEAD-1 会将你的工作副本恢复到错误提交前的状态。


28
当然!:] git commit -a -m ""git commit -am "" - trejder
另一个使用 stash 的“快捷”方法;如果您想要取消暂存所有更改(撤销 git add),只需运行 git stash,然后运行 git stash pop - seanriordan08
14
天啊……这个答案应该被删除。git reset --hard HEAD-1非常危险——它不仅仅是"撤销提交",还会删除你所有的更改,这不是提问者所要求的。不幸的是我使用了这个回答(StackOverflow无缘由地将其显示在获得26k赞的已采纳答案之前),现在我将努力恢复我所有的更改。 - Jack
3
对于那些像我一样没有阅读而只是运行了上述命令的人,希望你们现在已经吸取教训了XD... 要撤销命令,可以使用 git reflog 查找被丢弃的提交,然后运行 git reset --hard $1,其中 $1 是你被丢弃的提交。 - seantsang

945

修改最后一次提交

替换索引中的文件:

git rm --cached *.class
git add *.java

如果这是一个私有分支,修改提交记录:

git commit --amend

或者,如果这是一个共享的分支,请创建一个新的提交:

git commit -m 'Replace .class files with .java files'

(如果要更改以前的提交(commit),请使用强大的交互式变基(interactive rebase)。)


ProTip™:将*.class添加到gitignore文件中可避免此问题再次发生。


撤销一个提交(commit)

如果你需要更改最近一次提交(commit),则修改(commit)是理想的解决方案,但更通用的解决方案是使用reset命令。

您可以使用以下命令将Git重置到任何提交(commit):

git reset @~N

其中NHEAD之前的提交次数,@~会重置到上一个提交。

你可以使用以下命令而不是修改提交:

git reset @~
git add *.java
git commit -m "Add .java files"

查看 git help reset,特别是关于--soft--mixed--hard的部分,以更好地了解它的作用。

Reflog

如果您出错了,可以随时使用 reflog 来查找丢失的提交记录:

$ git reset @~
$ git reflog
c4f708b HEAD@{0}: reset: moving to @~
2c52489 HEAD@{1}: commit: added some .class files
$ git reset 2c52489
... and you're back where you started


6
请注意,对于未来阅读者 - git revert 是一个单独的命令,它基本上是“重置”单个提交。 - BenKoshy
补充一下@BenKoshy的回复 - 请注意,git revert会创建一个新的提交来反转给定的更改。这是使用git reset的更安全的选择。 - gsan

832

使用git revert <commit-id>命令。

要获取提交ID,只需使用git log命令。


22
“cherry pick the commit” 是什么意思?在我的情况中,我在错误的分支上编辑了一个文件并提交了更改,后来发现自己在错误的分支上。使用“git reset --soft HEAD~1”将我重新回到提交之前的状态,但现在如果我切换到正确的分支,如何撤销对错误分支文件的更改,并将它们(在同名文件中)应用到正确的分支中? - astronomerdave
我刚刚使用了 git revert commit-id 命令,完美解决了问题。当然接下来你需要推送你的更改。 - Casey Robinson
11
我认为应该是 git cherry-pick <<错误提交的哈希值>>,@astronomerdave。来自差不多晚了2年参加派对的先生。 - Tom Howard
@Kris:不要使用cherry-pick,而是使用rebase。因为它更高级的cherry-picking。 - Eugen Konkov
3
只有在已经推送了提交后,我才会使用还原。否则,重置是更好的选择。不要忘记,还原会创建一个新的提交,通常这不是目标。 - Hola Soy Edu Feliz Navidad

687

如果您计划完全撤消本地提交,无论您在提交上所做的更改如何,如果您不担心任何问题,只需执行以下命令。

git reset --hard HEAD^1

(这个命令将忽略您的整个提交,您的更改将从本地工作树中完全丢失)。如果您想要撤消提交,但希望保留更改在暂存区(就像在 git add 之后提交之前一样),则执行以下命令。

)

git reset --soft HEAD^1

现在您的已提交文件进入了暂存区。假设您想要将文件从暂存区移除,因为需要编辑一些错误的内容,请执行以下命令

git reset HEAD

现在已经将提交的文件从暂存区移动到未暂存区。现在文件已准备好进行编辑,所以您想要更改的任何内容都需要进行编辑并添加,然后进行新的提交。

了解更多(链接失效)存档版本


19
@SMR,在你的例子中,所有指针都指向当前 HEAD。HEAD^ = HEAD^1。同时,HEAD^1 = HEAD1。当你使用HEAD2时,和^符号之间有所不同。如果你使用2表示“第一个父提交的第一个父提交”,或者说“祖父”。 - Madhan Ayyasamy
git reset --hard HEAD^1 给我返回了这个错误信息:"fatal: ambiguous argument 'HEAD1': unknown revision or path not in the working tree." - Rob Mosher

616

如果您已经安装了Git Extras,则可以运行git undo来撤消最新的提交。git undo 3将撤消最近的三个提交。


577

我想撤销我们共享仓库中最新的五个提交。我查找了要回滚到的修订ID,然后输入了以下内容。

prompt> git reset --hard 5a7404742c85
HEAD is now at 5a74047 Added one more page to catalogue
prompt> git push origin master --force
Total 0 (delta 0), reused 0 (delta 0)
remote: bb/acl: neoneye is allowed. accepted payload.
To git@bitbucket.org:thecompany/prometheus.git
 + 09a6480...5a74047 master -> master (forced update)
prompt>

33
在共享代码库中修改历史记录通常是一个非常糟糕的想法。我假设你知道自己在做什么,但我只希望未来的读者也能明白。 - Brad Koch
是的,回滚操作很危险。在推送之前,请确保您的工作副本处于所需状态。推送时,不需要的提交将被永久删除。 - neoneye
9
就像在现实世界中,如果你想改写历史,你需要一个阴谋:每个人都必须“参与”阴谋(至少是所有了解历史的人,即曾经从这个分支上拉取过代码的人)。 - Mikko Rantalainen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接