如何将 Git 仓库恢复到以前的提交状态?

7611

如何将当前状态回滚到某个提交点创建的快照?

如果执行git log,则会获得以下输出:

$ git log
commit a867b4af366350be2e7c21b8de9cc6504678a61b`
Author: Me <me@me.com>
Date:   Thu Nov 4 18:59:41 2010 -0400

blah blah blah...

commit 25eee4caef46ae64aa08e8ab3f988bc917ee1ce4
Author: Me <me@me.com>
Date:   Thu Nov 4 05:13:39 2010 -0400

more blah blah blah...

commit 0766c053c0ea2035e90f504928f8df3c9363b8bd
Author: Me <me@me.com>
Date:   Thu Nov 4 00:55:06 2010 -0400

And yet more blah blah...

commit 0d1d7fc32e5a947fbd92ee598033d85bfc445a50
Author: Me <me@me.com>
Date:   Wed Nov 3 23:56:08 2010 -0400

Yep, more blah blah.

我该如何回退到11月3日的提交,也就是提交0d1d7fc


10
相关问题:如何撤销最后一次 Git 提交? - user456814
116
这里有一篇来自Github的非常清晰详细的文章,介绍如何在Git中撤销各种操作。 - Aurelio
58
我喜欢 Git,但同一件理应非常简单的事情却有35个答案,这暴露出Git存在一个巨大的问题。或者是因为文档的原因? - The Muffin Man
41个回答

12143
这在很大程度上取决于你对“revert”一词的理解。
暂时切换到不同的提交
如果你想暂时回到某个提交,进行一些试验,然后再回到当前位置,你只需要检出所需的提交即可:
# This will detach your HEAD, that is, leave you with no branch checked out:
git checkout 0d1d7fc32

或者,如果你想在那里提交代码,顺便创建一个新的分支:
git checkout -b old-state 0d1d7fc32

要回到之前的地方,只需再次查看你所在的分支即可。(如果你做了更改,切换分支时通常需要处理这些更改。你可以重置以丢弃它们;你可以存储、切换、存储弹出以携带它们;如果你想在那里创建一个分支,你也可以将它们提交到一个分支上。)
彻底删除未发布的提交
另一方面,如果你真的想摆脱自从那时以来所做的一切,有两种可能性。一种是,如果你没有发布这些提交中的任何一个,只需简单地重置:
# This will destroy any local modifications.
# Don't do it if you have uncommitted work you want to keep.
git reset --hard 0d1d7fc32

# Alternatively, if there's work to keep:
git stash
git reset --hard 0d1d7fc32
git stash pop
# This saves the modifications, then reapplies that patch after resetting.
# You could get merge conflicts, if you've modified things which were
# changed since the commit you reset to.

如果你搞砸了,你已经丢掉了你的本地更改,但你至少可以通过重新设置来回到之前的状态。
用新的提交撤销已发布的提交
另一方面,如果你已经发布了工作,你可能不想重置分支,因为那实际上是重写历史。在这种情况下,你确实可以撤销提交。在许多企业组织中,“受保护”的分支的概念甚至会阻止在一些重要分支上重写历史。在这种情况下,撤销是你唯一的选择。
在Git中,撤销有一个非常具体的意义:创建一个相反的补丁来取消它。这样你就不会重写任何历史。
首先确定要撤销的提交。根据下面选择的技术,你要么只撤销合并提交,要么只撤销非合并提交。
# This lists all merge commits between 0d1d7fc and HEAD:
git log --merges --pretty=format:"%h" 0d1d7fc..HEAD | tr '\n' ' '

# This lists all non merge commits between 0d1d7fc and HEAD:
git log --no-merges --pretty=format:"%h" 0d1d7fc..HEAD | tr '\n' ' '

# This will create three separate revert commits, use non merge commits only:
git revert a867b4af 25eee4ca 0766c053

# It also takes ranges. This will revert the last two commits:
git revert HEAD~2..HEAD

#Similarly, you can revert a range of commits using commit hashes (non inclusive of first hash):
git revert 0d1d7fc..a867b4a

# Reverting a merge commit. You can also use a range of merge commits here.
git revert -m 1 <merge_commit_sha>

# To get just one, you could use `rebase -i` to squash them afterwards
# Or, you could do it manually (be sure to do this at top level of the repo)
# get your index and work tree into the desired state, without changing HEAD:
git checkout 0d1d7fc32 .

# Then commit. Be sure and write a good message describing what you just did
git commit

实际上,git-revert manpage在其描述中涵盖了很多内容。另一个有用的链接是这个git-scm.com部分讨论git-revert

如果你决定不想撤销,你可以撤销撤销(如此处所述),或者回到撤销之前的状态(参见前一部分)。

在这种情况下,你可能还会发现这个答案有帮助:
如何将HEAD移回到以前的位置?(分离的HEAD)和撤销提交


190
将“@Rod's comment on git revert HEAD~3 as the best way to revert back 3 commits is an important convention.”翻译为中文:@Rod对于使用git revert HEAD~3来回退3个提交作为最佳方式的评论是一个重要的约定。请注意,我已尽力使翻译内容保持原意并更加通俗易懂,但并未添加解释或其他额外内容。 - New Alexandria
36
你能将整个数字写出来吗?比如说:git reset --hard 0d1d7fc32e5a947fbd92ee598033d85bfc445a50 - Spoeken
21
是的,当然可以通过完整的SHA1来指定提交。我使用缩写哈希值使答案更易读,并且如果您在打字时也倾向于使用它们。如果您正在复制和粘贴,请使用完整的哈希值。请参见“在 man git rev-parse 中指定修订版本”以获取如何命名提交的完整说明。 - Cascabel
77
你可以使用 git revert --no-commit hash1 hash2 ...,然后将每个撤销操作合并提交到一个提交中,命令为 git commit -m "Message" - Mirko Akov
git revert并不是完全无害的。它也有一些问题:https://dev59.com/M6Lia4cB1Zd3GeqPfCwB - agent18
显示剩余8条评论

3528
这里有很多复杂而危险的答案,但实际上很简单:
git revert --no-commit 0d1d7fc3..HEAD
git commit

这将使一切从HEAD回滚到提交哈希(不包括),这意味着它将在工作树中重新创建该提交状态,就好像0d1d7fc3之后的每个提交都被撤销了。然后,您可以提交当前树,它将创建一个全新的提交,与您“恢复”到的提交基本相同。

(使用--no-commit标志可让git一次性还原所有提交-否则,您将需要为范围内的每个提交输入消息,从而在历史记录中产生不必要的新提交。)

这是一种安全且简便的回滚到先前状态的方法。不会破坏历史记录,因此可用于已经公开的提交。


关于合并提交的注意事项: 如果在 0766c053..HEAD(包括在内)之间的提交中有一个合并提交,那么会弹出一个错误(与未指定 -m 有关)。以下链接可能对遇到此问题的人有所帮助:为什么 git revert 抱怨缺少 -m 选项?(感谢 @timhc22 指出)

46
如果你真的想要单独的提交(而不是用一个大的提交还原所有更改),那么你可以使用--no-edit而不是--no-commit,这样你就不必为每个还原编辑提交信息。 - user456814
175
如果 0766c053..HEAD 之间的提交中有一个合并提交,则会出现一个错误(与未指定 -m 有关)。这可能对遇到此问题的人有所帮助:https://dev59.com/LG025IYBdhLWcg3wfmLw - timhc22
44
$ git revert --no-commit 53742ae..HEAD 返回 fatal: empty commit set passed。 (翻译:运行上述命令会返回“致命错误:传递了空提交集”) - Alex G
12
如果你在命令的末尾删掉 "..HEAD",那么你就可以只删除一个特定的提交。例如:git revert --no-commit 0766c053 将只移除用于 0766c053 的特定更改,而保留 0766c053 后面的所有更改不变。 - wensiso
3
正如@timhc22所提到的,如果中间有一个或多个合并提交(这种情况经常发生),则此方法不起作用。真正适用于所有情况且同样安全的答案在这里:https://dev59.com/gHI-5IYBdhLWcg3wW3Cp#15563149 - Pedro A
显示剩余9条评论

2080

不拘常规的程序员?

独自工作,只想让它正常运行?请按照下面的说明进行操作,这些步骤对我和许多人多年来都非常可靠。

与他人合作?Git很复杂。在执行任何冲动之前,请阅读本答案下面的评论,考虑其他答案,并与您的团队讨论。

将工作副本恢复到最近的提交状态

要忽略所有更改并还原到上一次提交:

git reset --hard HEAD

其中HEAD是当前分支的最新提交

将工作副本还原到旧提交

要将工作副本还原到比最近一次提交更早的提交:

# Resets index to former commit; replace '56e05fced' with your commit code
git reset 56e05fced 

# Moves pointer back to previous HEAD
git reset --soft HEAD@{1}

git commit -m "Revert to 56e05fced"

# Updates working copy to reflect the new commit
git reset --hard

# Push your changes to respective branch
git push -f

本文内容参考了一个类似的Stack Overflow问题,《如何在Git中通过SHA哈希还原到某个提交?》。


54
好的,我会尽力进行翻译。以下是需要翻译的内容:我曾经这样做过,但是后来我无法提交并推送到远程代码仓库。我想让一个特定的旧提交成为主提交... - Lennon
16
假设您已经更改了代码,并且提交并推送到远程仓库。现在您希望本地和远程仓库都看起来好像这些更改从未发生过一样。操作如下:首先运行 git reset --hard HEAD^,这将撤销最后一次提交并抛弃所有未提交的更改。然后,运行git push --force origin HEAD,它会用本地的代码覆盖远程仓库中的代码,以此来删除最后一次提交。请注意:这不是一个安全的方法,如果你无意中把机密信息推送到了远程仓库。可以假设所有机密信息都已泄露,请查看关于“--force”选项的注意事项 https://evilmartians.com/chronicles/git-push---force-and-how-to-deal-with-it - entropo
1
OP要求回滚。有人用重置的方式给出了答案。 - StefanBob
1
有史以来全世界最好的。 - KD.S.T.
1
@StefanBob “git revert 应该用于撤销公共分支上的更改,而 git reset 应该保留用于撤销私有分支上的更改。您还可以将 git revert 视为撤消已提交更改的工具,而 git reset HEAD 则是用于撤消未提交更改的工具。” - boulder_ruby
显示剩余4条评论

340

对我和可能其他人来说,最好的选择是Git重置选项:

git reset --hard <commitId> && git clean -f

这对我来说是最好的选择!它简单、快速、有效!


**注意:** 如评论所述,如果您与其他拥有旧提交副本的人共享分支,不要执行此操作。

同样来自评论,如果您想要一种不太“大胆”的方法,您可以使用:

git clean -i

注意:这个问题引起了很多关注。如果你正在处理其他人也在处理的分支,或者处理的远程分支已经与主分支合并(根据时间框架),请不要这样做。否则你将会遇到Git的麻烦。


62
强烈警告:如果你和其他人共享分支且这些人有旧版本的提交记录,不要这样做,因为使用硬重置将强制他们必须重新同步他们的工作与新重置的分支。如需详细解释如何安全地恢复提交记录而不会在使用硬重置时丢失工作,请参阅此答案 - user456814
3
值得注意的是,这将永久删除源目录中未提交的任何文件 :/ - kat
需要使用 git clean -i 命令吗? - BabyishTank

306
在回答问题之前,让我们先了解一些背景知识,解释一下什么是“HEAD”。
首先,“HEAD”是什么?
“HEAD”只是当前分支上最新提交的引用。任何时候只能有一个“HEAD”(除了“git worktree”)。
“HEAD”的内容存储在“.git/HEAD”中,其中包含当前提交的40字节SHA-1哈希值。

分离头指针

如果你不在最新的提交上,也就是说HEAD指向历史中的先前提交,这被称为分离头指针

Diagram illustrating the concept of detached HEAD

在命令行上,它看起来像这样 - SHA-1哈希代替分支名称,因为HEAD没有指向当前分支的末尾:

Running git checkout HEAD^0 in a terminal


从分离的HEAD中恢复的几个选项:


git checkout

git checkout <commit_id>
git checkout -b <new branch> <commit_id>
git checkout HEAD~X // x is the number of commits t go back

这将检出指向所需提交的新分支。此命令将检出到给定提交。
此时,您可以创建一个分支并从这个点开始工作:
# Checkout a given commit.
# Doing so will result in a `detached HEAD` which mean that the `HEAD`
# is not pointing to the latest so you will need to checkout branch
# in order to be able to update the code.
git checkout <commit-id>

# Create a new branch forked to the given commit
git checkout -b <branch name>

git reflog

您始终可以使用refloggit reflog将显示更新HEAD的任何更改,并检查所需的reflog条目将把HEAD设置回此提交。

每次修改HEAD时,都会在reflog中创建一个新条目。

git reflog
git checkout HEAD@{...}

这将使你返回到你想要的提交。

Running git reflog in a terminal


git reset HEAD --hard <commit_id>

将你的 HEAD 移回到所需的提交记录。

# This will destroy any local modifications.
# Don't do it if you have uncommitted work you want to keep.
git reset --hard 0d1d7fc32

# Alternatively, if there's work to keep:
git stash
git reset --hard 0d1d7fc32
git stash pop
# This saves the modifications, then reapplies that patch after resetting.
# You could get merge conflicts, if you've modified things which were
# changed since the commit you reset to.

这个模式说明了每个命令的作用。正如您所见,reset && checkout 修改了 HEAD

Diagram illustrating staging area and checking out HEAD


220
如果您想要“取消提交(uncommit)”,删除最后一次提交的消息,并将修改后的文件放回暂存区,您可以使用以下命令:

git reset --soft HEAD~1

git reset --soft HEAD~1
  • --soft 表示未提交的文件将被保留为工作文件,与 --hard 相反,后者会丢弃它们。
  • HEAD~1 是最后一次提交。如果您想回滚 3 次提交,可以使用 HEAD~3。如果您想回滚到特定的修订版本号,则可以使用其 SHA 哈希值。

在提交了错误的内容并且需要撤销上一次提交时,这是一条非常有用的命令。

来源: http://nakkaya.com/2009/09/24/git-delete-last-commit/


2
注意:这仅适用于您的本地存储库。如果您已将提交推送到远程,则仅在本地重置不会更改远程上的任何内容。尝试撤消远程更改更加复杂、危险且充满注意事项。 - entropo
谢谢。那似乎是问题的答案。我不明白为什么这不是最受赞同的答案。 - John Smith Optional
显然,这只是因为你的答案更近期。较旧的答案已经积累了更多的投票。 - John Smith Optional

219

您可以通过以下两个命令完成此操作:

git reset --hard [previous Commit SHA id here]
git push origin [branch Name] -f

这将删除您之前的Git提交记录。

如果您想保留更改,您也可以使用:

git reset --soft [previous Commit SHA id here]

然后它将保存您所做的更改。


156

最好的方式是:

git reset --hard <commidId> && git push --force

这将重置分支到特定的提交,然后将上传与本地相同的提交到远程服务器。

请注意使用 --force 标志会删除选定提交后的所有后续提交,无法恢复。


126

我已经尝试了很多种方法来撤销 Git 中的本地更改,但如果你只想还原到最新提交的状态,似乎这是最好的方法。

git add . && git checkout master -f

简要描述:

  • git revert不同,它不会创建任何提交。
  • git checkout <commithashcode>不同,它不会分离你的HEAD。
  • 它将覆盖所有本地更改并删除自上次提交以来添加的所有文件。
  • 它仅适用于分支名称,因此您只能通过这种方式还原到分支中的最新提交。

我发现了一种更方便简单的方法来实现以上结果:

git add . && git reset --hard HEAD

HEAD指向您当前分支的最新提交。

这段代码与boulder_ruby建议的相同,但我添加了git add .git reset --hard HEAD之前,以清除自上次提交以来创建的所有新文件,因为我认为这是大多数人期望恢复到最新提交时的情况。


119

好的,回到Git中以前的提交很容易...

撤销提交但不保留更改:

git reset --hard <commit>

撤销并保留更改:

git reset --soft <commit>

说明:通过使用git reset,您可以重置到特定状态。通常将它与提交哈希一起使用,如上所示。

但是,您会发现不同之处在于使用了两个标志--soft--hard,默认情况下git reset使用--soft标志,但始终使用该标志是一个好习惯,我解释每个标志:


--soft

正如上面所解释的默认标志,无需提供它,不会更改工作树,但它会添加所有已更改文件以准备提交,因此您回到提交状态,其中更改的文件变为未暂存状态。


--hard

使用此标志要小心。它将重置工作树和跟踪文件的所有更改,所有更改都将消失!


我还创建了下面的图像,这可能在使用Git的实际工作中发生:

Git reset to a commit


1
that is the answer - John Smith Optional
1
--soft 不会丢失更改。这非常重要!! - the Hutt

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