7.1 回退到之前的提交

要回到之前的提交,首先使用 git log 查找提交的哈希值。要临时回到该提交,可以使用以下命令“分离 HEAD”:

git checkout 789abcd

这将使你处于提交 789abcd。你现在可以在该旧提交的基础上创建新的提交,而不会影响你当前所在的分支。你可以使用 branch 或 checkout -b 将所做的更改放入一个正式的分支。

要回退到之前的提交,同时保留更改:

git reset --soft 789abcd

要回退最后一次提交:

git reset --soft HEAD~

要永久丢弃某个特定提交之后所做的所有更改,可以使用:

git reset --hard 789abcd

要永久丢弃最后一次提交之后所做的所有更改:

git reset --hard HEAD~

注意:虽然你可以使用 reflog 和 reset 恢复被丢弃的提交,但未提交的更改无法恢复。为了避免这种情况,建议使用 git stash; git reset,而不是 git reset --hard。

7.2 撤销更改

撤销工作副本中的文件或目录的更改。

git checkout -- file.txt

如果在当前目录及其所有子目录中递归使用,将撤销工作副本中的所有更改。

git checkout -- .

如果只想撤销部分更改,可以使用 --patch。对于每个更改,系统都会提示你是否要撤销。

git checkout --patch -- dir

要撤销已添加到索引的更改:

git reset --hard

不带 --hard 标志时,这将执行软重置。

如果你还有尚未推送到远程的本地提交,也可以执行软重置。然后你可以重新工作并提交更改。

git reset HEAD~2

上述示例将撤销最后两次提交,并将文件返回到工作副本。然后你可以进一步更改并提交。

注意:除了软重置之外,所有这些操作都会永久删除更改。为了更安全,可以分别使用 git stash -p 或 git stash。稍后可以使用 stash pop 撤销,或者使用 stash drop 永久删除。

7.3 使用 reflog

如果你在变基操作中出错了,一种重新开始的方法是回到变基之前的提交。你可以使用 reflog(它记录了过去 90 天内你所做的所有操作,此时间可以配置):

$ git reflog
4a5cbb3 HEAD@{0}: rebase finished: returning to refs/heads/foo
4a5cbb3 HEAD@{1}: rebase: fixed such and such
904f7f0 HEAD@{2}: rebase: checkout upstream/master
3cbe20a HEAD@{3}: commit: fixed such and such
...

你可以看到变基之前的提交是 HEAD@{3}(你也可以检出哈希值):

git checkout HEAD@{3}

现在你可以创建一个新分支/删除旧分支/重新尝试变基。

你也可以直接重置到 reflog 中的一个点,但只有当你完全确定这是你想要的操作时才这么做:

git reset --hard HEAD@{3}

这将使你的当前 Git 树与该点时的状态一致(见“撤销更改”)。

如果你只是暂时查看在另一个分支上变基时分支的效果,但不想保留结果,可以使用此方法。

7.4 撤销合并

撤销尚未推送到远程的合并

如果你还没有将合并推送到远程仓库,可以按照撤销提交的相同步骤操作,但有一些微妙的差异。

重置是最简单的选项,因为它将撤销合并提交以及从分支添加的任何提交。

然而,你需要知道要重置回的 SHA 值,这可能会比较棘手,因为你的 git log 现在会显示两个分支的提交。如果你重置到错误的提交(例如,另一个分支上的一个提交),可能会破坏已提交的工作。

git reset --hard <last commit from the branch you are on>

或者,假设合并是你最近的提交。

git reset HEAD~

撤销操作更安全,因为它不会破坏已提交的工作,但涉及更多工作,因为你需要在再次合并分支之前撤销撤销操作(见下一节)。

撤销已推送到远程的合并

假设你合并了一个新功能(feature/add-gremlins):

git merge feature/add-gremlins
...
# 解决任何合并冲突
git commit # 提交合并
...
git push
...
501b75d..17a51fd master -> master

之后你发现刚刚合并的功能破坏了其他开发者的系统,必须立即撤销合并,而修复该功能本身需要太长时间,所以你只想撤销合并。

git revert -m 1 17a51fd
...
git push
...
17a51fd..e443799 master -> master

此时,系统中的小妖精已被清除,你的同事也不再抱怨了。然而,我们还没有完成。一旦你修复了 add-gremlins 功能的问题,你需要撤销此撤销操作,然后才能再次合并。

git checkout feature/add-gremlins
...
# 修复错误的各种提交
git checkout master
...
git revert e443799
...
git merge feature/add-gremlins
...
# 修复由错误修复引入的任何合并冲突
git commit # 提交合并
...
git push

此时,你的功能已成功添加。然而,鉴于此类错误通常是由合并冲突引入的,有时不同的工作流会更有帮助,因为它允许你在分支上修复合并冲突。

git checkout feature/add-gremlins
...
# 合并 master 并立即撤销撤销操作。这将使你的分支处于与 master 合并前相同的破坏状态。
git merge master
...
git revert e443799
...
# 现在继续修复错误(此处有各种提交)
git checkout master
...
# 此时不需要再次撤销撤销操作,因为之前已经完成
git merge feature/add-gremlins
...
# 修复由错误修复引入的任何合并冲突
git commit # 提交合并
...
git push

7.5 撤销一些现有提交

使用 git revert 撤销现有提交,尤其是当这些提交已经推送到远程仓库时。

它会记录一些新提交以撤销一些早期提交的效果,你可以安全地推送这些提交,而不会重写历史。

除非你希望让所有其他用户都感到不满,否则不要使用 git push --force。永远不要重写公共历史。

例如,如果你刚刚推送了一个包含错误的提交,并且需要将其回滚,可以执行以下操作:

git revert HEAD~1
git push

现在你可以自由地在本地撤销撤销提交,修复代码,并推送正确的代码:

git revert HEAD~1
work .. work .. work ..
git add -A
git commit -m "Update error code"
git push

如果你想要撤销的提交已经在历史记录中更早的位置,你只需传递提交哈希值。Git 将创建一个反向提交,撤销你的原始提交,你可以安全地将其推送到远程仓库。

git revert 912aaf0228338d0c8fb8cca0a064b0161a451fdc
git push

7.6 撤销/重做一系列提交

假设你想要撤销十几个提交,但只想撤销其中的几个。

git rebase -i <earlier SHA>

-i 将变基置于“交互模式”。它从上述变基开始,但在重放任何提交之前会暂停,允许你在重放每个提交时温和地修改每个提交。rebase -i 将在你的默认文本编辑器中打开,显示正在应用的提交列表,如下所示:

撤销/重做一系列提交

要丢弃一个提交,只需在编辑器中删除该行。如果你不再希望项目中包含这些错误提交,可以删除上述第 1 行和第 3-4 行。如果你想要将两个提交合并在一起,可以使用 squash 或 fixup 命令。

撤销/重做一系列提交