在Git中如何通过SHA哈希值回退到一个提交?

741

我不清楚git revert的工作原理。例如,我想要回滚到距离头部六个提交的提交,撤销中间提交中的所有更改。

假设它的SHA哈希值为56e05fced214c44a37759efa2dfc25a65d8ae98d。那么为什么我不能只是执行类似以下的操作:

git revert 56e05fced214c44a37759efa2dfc25a65d8ae98d

1
尽管这个问题比现在标记为重复的那个问题要旧,但是那个问题有一个更好的答案。http://meta.stackexchange.com/questions/147643/should-i-vote-to-close-a-duplicate-question-even-though-its-much-newer-and-ha/147651#147651 - GS - Apologise to Monica
20
这个问题以及这里的最佳答案可能会让Git用户感到困惑。为了帮助理解术语,您不应该“回退到”一个提交。您可以选择重置到一个提交(就像使用时间机器回到过去),或者还原一个提交(就像把一个提交拿出来,好像它从未存在过-但是它确实保留了还原信息在历史记录中,允许您还原还原操作,如果您想要的话)。此外,请注意,在处理冲突时不应使用-m标志并输入提交消息。 Git提供的自动消息在查看历史记录时更加详细。 - alexrogers
1
@alexrogins 拉出一个提交就好像它从未存在过是什么意思?我也不确定“撤销一个撤销”是什么意思 - 感谢您的评论,提供了有用的信息,只是想更详细地了解您的观点。 - Joe
2
@Joe,如果您添加了一行代码并提交了该行,则如果要还原它,则将撤消该行代码(无论它在历史记录中的哪个位置,不必是最后一次提交)。这样就会产生一个还原提交。如果您撤消该还原提交,则实际上是撤消了撤消操作(即重新执行原始行)。 - alexrogers
9个回答

1343

如果想在当前HEAD的基础上提交一个不同提交记录的精确状态,同时撤销所有中间提交记录,那么可以使用 reset 命令创建正确的索引状态来进行提交。

# Reset the index and working tree to the desired tree
# Ensure you have no uncommitted changes that you want to keep
git reset --hard 56e05fced

# Move the branch pointer back to the previous HEAD
git reset --soft "HEAD@{1}"

git commit -m "Revert to 56e05fced"

90
第一条命令是 git reset --hard 56e05fced,之后跳过最后一个 git reset --hard 命令。那么,这样做不就等同于(而且还可以少敲一条命令)吗? - Mark Longair
27
当我这样做时,工作树里出现了一堆未被跟踪的文件。然而,查看历史记录后,我发现那些文件在“还原到SHA”提交中确实有对应的删除提交。所以,在最后执行 git reset --hard 之后,你可以执行 git clean -f -d 命令来清理任何残留的未跟踪文件。此外,非常感谢,这帮助我解决了危机! - nzifnab
5
我需要确认您是否无条件执行 git reset --soft HEAD@{1} 命令?也就是说,始终使用值为 1 吗? - deprecated
7
是的,除非你想丢弃分支末尾的提交记录,否则可以使用git reset --soft HEAD@{1}将指针移回调用git reset 56e05fced前的"HEAD"。如果使用更高的数字(例如 git reset --soft HEAD@{2}),则会将新提交附加到以前的提交上。也就是说,增加数字实际上会丢弃N-1个提交,其中N是您用数字替换1的数字。请运行git reflog查看git reset 56e05fced添加的引用日志条目。 - 0b10011
5
如果不将"@Tom HEAD@{1}"引用为"'HEAD{@1}'",那么它对我(可能是所有zsh用户)将无法起作用。 - jilen
显示剩余9条评论

183

git-revert 的作用是创建一个撤销给定提交所做更改的提交,从而创建一个与给定提交相反(或互补)的提交。因此,

git revert <SHA-1>

应该可以并且实际上是有效的。

如果你想要回到一个指定的提交版本,并且由于这部分历史记录尚未发布,你需要使用git-reset,而不是git-revert:

git reset --hard <SHA-1>

请注意,使用--hard选项将使您失去工作目录中的任何未提交更改。

附加说明

顺便提一下,也许不是很明显,但无论文档在哪里说到<commit><commit-ish>(或<object>),您都可以放置一个SHA-1标识符(完整或缩短)。


9
如果在进行硬重置之前你的历史记录已经被推送到远程了,那么你需要使用 git push -f 强制推送新的重置分支,但是请注意:这可能会意外删除其他用户的提交,或者如果没有删除新的提交,它将强制其他用户重新同步他们的工作与重置分支,所以请先确保你的协作者已经同意这样做。 - user456814
4
这似乎是最好的答案。它还清楚地解释了git revert和git reset之间的区别。 - kta
对于简单地撤销已提交的更改(包括已推送的更改),在这个答案的第一部分中使用 git revert <SHA-1> 是最干净和最简单的解决方案。它的好处是能够清晰地展示历史记录,同时撤销的提交也会像正常提交一样显示出来。也不需要强制推送。 - undefined

91

回滚到特定的提交(commit)最好的方法是:

git reset --hard <commit-id>

那么:

git push <reponame> -f

43
新手应该意识到 push -f 可能会破坏历史记录。不过,有时这正是你想要的 :) - Jared Beck
1
有时候你真的很高兴历史记录被删除了...一直在寻找这个-f选项,谢谢! - Antoine
谢谢!为了保持字面意思,我必须键入 -> git push origin master -f,其中<reponame>对我来说不能仅仅是origin。 - SWoo
正如上面提到的,如果我们想让我们的仓库指向特定的提交而不保留历史记录,则使用上述步骤,否则我们可以使用git revert。 - minhas23
有没有办法知道是谁在特定分支上进行了重置(即回滚提交)和强制推送? - datnt

90

此命令会还原指定的提交,也就是添加与其相反的提交。如果你想检出早期版本,请执行以下操作:

git checkout 56e05fced214c44a37759efa2dfc25a65d8ae98d

1
那我可以将这个与头部合并吗?如果我预计会有很多冲突,我能强制提交为当前的头部,并覆盖任何冲突吗? - JP Silvashy
1
我不确定您所说的是哪个head。您可以将head移回到此提交(例如通过删除和创建分支)。如果您想将“合并”提交到head,从而有效地撤消中间提交,则可以使用带有“ours”策略的合并。选择您的选项并阅读manpages。力量在等待着您去使用它;-) - Michael Krelin - hacker
有道理,我问的原因是 git 现在告诉我我不在任何分支上。 - JP Silvashy
9
因为你不是。如果你输入git branch,你会清楚地看到它。你可以执行例如git checkout -b mybranch 56e05这样的命令来在分支中获取它。 - Michael Krelin - hacker

80

更新:

如果中间没有合并提交,这个答案提供了一种更简单的方法:https://dev59.com/im855IYBdhLWcg3w1oLa#21718540

但是如果有一个或多个合并提交,那个答案就不适用了,所以请使用这个(适用于所有情况)。

原始答案:

# Create a backup of master branch
git branch backup_master

# Point master to '56e05fce' and
# make working directory the same with '56e05fce'
git reset --hard 56e05fce

# Point master back to 'backup_master' and
# leave working directory the same with '56e05fce'.
git reset --soft backup_master

# Now working directory is the same '56e05fce' and
# master points to the original revision. Then we create a commit.
git commit -a -m "Revert to 56e05fce"

# Delete unused branch
git branch -d backup_master

两个命令git reset --hardgit reset --soft在这里是神奇的。第一个命令改变了工作目录,但同时也改变了head(当前分支)。我们通过第二个命令来修复head。

3
你的提交中的 -a 不是必需的。 - splicer
5
完美。在我的看法中,这应该成为Git CLI中的一个单一命令。 - Gabriel Queiroz Silva
3
非常好!这种方法比git revert 56e05fce..HEAD更好,因为它仅撤销了一个提交。 - knocte
2
我撤回之前的话,这个更简单:https://dev59.com/im855IYBdhLWcg3w1oLa - knocte
5
@knocte实际上不是,你给出的链接并不更简单,即使它有成千上万的赞同票。原因是如果范围内有一个或多个合并提交,它根本不起作用,而这种情况经常发生。这篇回答确实应该是最佳答案。 - Pedro A
显示剩余5条评论

79
如果您的更改已经被推送到一个公共共享的远程仓库,并且想要撤销从 HEAD 到 之间的所有提交,那么您可以向 git revert 命令传递一个提交范围。
git revert 56e05f..HEAD

它会还原56e05fHEAD之间的所有提交(不包括范围开始点56e05f)。


2
请注意,如果您要还原几百个提交,这可能需要一些时间,因为您必须逐个提交每个还原。 - splicer
10
@splicer 你不需要逐个还原每个提交,你可以传递 --no-edit 选项以避免必须编写单独的提交消息,或者你可以使用 --no-commit 一次性提交所有还原。 - user456814
@Cupcake 你说得对,HEAD..56e05f 对我不起作用,但是 56e05f..HEAD 就可以了。 - Inder Kumar Rathore
3
这绝对是我首选的回滚方式,无论你是否已经推送。我将其添加到了我的全局~/.gitconfig文件的别名部分中:rollback = "!git revert --no-commit $1..HEAD #"- 现在我只需要直觉地运行 $ git rollback a1s2d3 即可。 - DannyB
3
这似乎非常接近我想要的,但是我需要撤销大约30个提交,但是在一半的时候,它在合并提交时失败,并显示“错误:提交6b3d9b3e05a9cd9fc1dbbebdd170bf083de02519是一个合并提交,但未给出-m选项。致命错误:撤消失败”- 有什么建议吗?我尝试添加-m,但不太确定如何在此情况下使用。 - Brad Parks

6
这是更易懂的内容:
git checkout 56e05fced -- .
git add .
git commit -m 'Revert to 56e05fced'

为了证明它起作用:

git diff 56e05fced

6
抱歉,一般来说这并不正确。我想只有存在的路径才会被更新,所以如果文件自“56e05fced”以来已被删除,则使用“git checkout 56e05fced -- .”不会将其暂存。 - Mark Longair
1
这个解决方案不会删除自 56e05fced 以来新增的文件,就像 git reset --hardgit revert 命令一样。如果你真的想恢复到 56e05fced 的状态,而不是使用 git checkout,那么你应该使用那些命令。 - user456814
注意:这将使您处于分离的 HEAD 状态。不建议这样做! - alexrogers
1
这对我有用,但在这个上下文中"--"是什么意思? - user755921
在类似Bash的命令中,双破折号表示命令选项的结尾,因此在双破折号之后添加的任何值都不会被解释为选项。在这种情况下,点(.)不会被解释为选项,而是作为checkout命令的<pathspec>。 - snaphuman

3
应该很简单:

应该很简单:

git reset --hard 56e05f

这将使您回到特定的时间点。


4
这个东西非常危险,因为它会擦除所有历史记录,包括其他人的工作。请注意这一点! - alexrogers

-2
这可能有效:
git checkout 56e05f
echo ref: refs/heads/master > .git/HEAD
git commit

3
这基本上与 git reset --hard 56e05f 做的一样,只是这种方法更不安全并且更加hacky。你最好使用Charle的解决方案 或者 Jakub的解决方案 - user456814

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