Git的回滚和撤销操作

简述

版本控制系统可以帮助我们回到某个历史版本,那么在 Git 里,如何回滚操作,撤销某次提交或恢复到某个提交呢?以下我们根据需要回滚的常见场景来介绍这些操作的最佳办法:

本地修改但未 push 到远程

撤销本地的修改

场景: 你修改了文件,但还没有 commit 。你想要恢复到修改前 —— 就像上次 commit 的时候一模一样。

方法: git checkout -- <bad filename>

原理: git checkout 会把工作目录里的文件修改到 Git 之前记录的某个状态。你可以提供一个你想返回的分支名或特定 SHA ,或者在缺省情况下,Git 会认为你希望 checkout 的是 HEAD,当前 checkout 分支的最后一次 commit。

提醒:你用这种方法“撤销”的任何修改真的会完全消失。因为它们从来没有被提交过,所以之后 Git 也无法帮助我们恢复它们。你要确保自己了解你在这个操作里扔掉的东西是什么!(也许可以先利用 git diff 确认一下)

重置本地的修改

景: 你在本地 commit 了一些东西(还没有 push),但是你发现这些提交是错误的不需要的,你希望撤销前面的几次提交 —— 就像它们从来没有发生过一样。

方法: git reset <last good SHA> 

原理: git reset 会把你的代码库历史返回到指定的 SHA 状态。 这样就像是这些提交从来没有发生过。缺省情况下, git reset 会保留工作目录。这样,提交是没有了,但是修改内容还在磁盘上。这是一种安全的选择。

示例说明:以下我们模拟了 4 次提交,最近的 3 次提交是错误的,我们希望回到如下红框处所示的“正确的提交”

 

第一步,我们找到要回到的这次提交的 commit,如上图所示是 f5142d4e5a69a9250daa7612de399d78d0db1fd8

第二步,执行 git reset f5142d4e5a69a9250daa7612de399d78d0db1fd8

 

执行后,我们可以看到通过 3 次 commit 提交的内容取消暂存但仍然被保留到了工作目录。你可以继续编辑修正错误后,然后继续通过 git add 、git commit 操作进行提交。

但通常我们会希望一步就“撤销”提交以及修改内容 —— 这就是 --hard 选项的功能。但这个是一个危险操作,这是 reset 命令唯一的危险用法,--hard 选项会真正地销毁数据,它强制覆盖了工作目录中的文件。只有当你确定废弃修改时使用 --hard 选项。虽然我们可以通过下一节介绍的 reflog 来找回曾经 commit 过的,但对于未 commit 过的内容将无法恢复了。

 

 

在撤销本地修改之后再恢复

场景: 你提交了几个 commit,然后用 git reset --hard 撤销了这些修改(见上一节使用了 --hard 选项),然后你意识到 reset 错了,想要恢复。

方法: git reflog 和 git reset 或 git checkout

原理: git reflog 对于恢复项目历史是一个超棒的资源。你可以恢复几乎 任何东西 —— 任何你 commit 过的东西 —— 只要通过 reflog。

你可能已经熟悉了 git log 命令,它会显示 commit 的列表。 git reflog 也是类似的,不过它显示的是一个 HEAD 发生改变的时间列表.

注意事项:

  • 它涉及的只是 HEAD 的改变。在你切换分支、用 git commit 进行提交、以及用 git reset 撤销 commit 时,HEAD 会改变,但当你用  git checkout -- <bad filename> 撤销时(正如我们在前面讲到的情况),HEAD 并不会改变 —— 如前所述,这些修改从来没有被提交过,因此 reflog 也无法帮助我们恢复它们。
  • git reflog 不会永远保持。Git 会定期清理那些 “用不到的” 对象。不要指望几个月前的提交还一直躺在那里。
  • reflog只存在于本地 —— 这是一个记录你在你自己的仓库里做过什么的日志。其他人拷贝的仓库里的引用日志不会和你的相同;而你新克隆一个仓库的时候,reflog 是空的,因为你在仓库里还没有操作。你的 reflog 就是你的,只是你的。你不能用 git reflog 来恢复另一个开发者没有 push 过的 commit。

示例说明:通过 git reflog 命令可以查看引用日志

 

那么,怎么利用 reflog 来“恢复”之前“撤销”的 commit 呢?它取决于你想做到的到底是什么:

  • 如果你希望准确地恢复项目的历史到某个时间点,用 git reset --hard <SHA>
  • 如果你希望重建工作目录里的一个或多个文件,让它们恢复到某个时间点的状态,用 git checkout <SHA> -- <filename>
  • 如果你希望把这些 commit 里的某一个重新提交到你的代码库里,用 git cherry-pick <SHA>

 

修正最后一个 commit 消息

场景: 你在最后一条 commit 消息里有个笔误,已经执行了 git commit -m "Fxies bug #42",但在 git push 之前你意识到消息应该是 “Fixes bug #42″。

方法: git commit --amend 或 git commit --amend -m "Fixes bug #42"

原理: git commit --amend 会用一个新的 commit 更新并替换最近的 commit ,这个新的 commit 会把任何修改内容和上一个 commit 的内容结合起来。如果当前没有其他新的修改,这个操作就只会把上次的 commit 消息重写一遍。如果你想通过添加或修改文件来更改提交的快照,也可以通过类似的操作来完成。 通过修改文件然后运行 git addgit rm一个已追踪的文件,随后运行git commit --amend拿走当前的暂存区域并使其做为新提交的快照。

 

已经 push 到远程

回滚其中的某个 commit

场景: 你已经执行了 git push, 把你的修改发送到了远程,但是这些 commit 中其中一个是有问题的,你需要回滚那一个 commit.

方法: git revert <SHA>

原理: git revert 会产生一个新的 commit,它和指定 SHA 对应的 commit 是相反的(或者说是反转的)。如果原先的 commit 是“物质”,新的 commit 就是“反物质” —— 任何从原先的 commit 里删除的内容会在新的 commit 里被加回去,任何在原先的 commit 里加入的内容会在新的 commit  里被删除。

这是 Git 最安全、最基本的撤销场景,因为它并不会改变历史 —— 所以你现在可以  git push 新的“反转” commit 来抵消你错误提交的 commit。

示例说明:

第一步,确认要回滚的 commit。如下,需要回滚红框的 commit 的内容,保留红框上面的 commit 的内容,如要回滚 commit 3f4e41ee4c7ffe2a49ffab0343185c03e5c57f30

 

第二步,执行 git revert 3f4e41ee4c7ffe2a49ffab0343185c03e5c57f30

 

这时,可以看到产生了一个新的 commit

第三步,回滚完成后执行 git push 提交到远程,远程可以看到如下内容

 

回滚到某个 commit

场景: 你已经执行了 git push, 把你的修改发送到了远程,但是这些 commit 都是有问题的,而且这些 commit 没有合并线,你需要回滚到之前的 commit.(注:如果这些 commit 中有合并线,即有过其他合并,这个方法不适用)

方法: git revert HEAD..<SHA>

原理: git revert 会产生一个新的 commit,它和指定 SHA 对应的 commit 是相反的(或者说是反转的)。如果原先的 commit 是“物质”,新的 commit 就是“反物质” —— 任何从原先的 commit 里删除的内容会在新的 commit 里被加回去,任何在原先的 commit 里加入的内容会在新的 commit  里被删除。

这是 Git 最安全、最基本的撤销场景,因为它并不会改变历史 —— 所以你现在可以  git push 新的“反转” commit 来抵消你错误提交的 commit。

示例说明:

第一步,确认要回滚到的 commit。如下,需要回滚到红框的 commit 的内容,也就是保留红框里的 commit 的内容,要回滚到 88c5918b02c48c7f306559592c40aa1ffc3139fe 这个 commit

 

第二步,执行 git revert 88c5918b02c48c7f306559592c40aa1ffc3139fe..HEAD,HEAD 是最新的版本,这个表示要从 HEAD 开始,回滚到 88c5918b02c48c7f306559592c40aa1ffc3139fe 版本,也就是要逐个回滚上图的 HEAD(59f0201fbf7940204dcffa7fdb951539f4934c9f)、983c12915afade3c7dbab4eab8d3a0ef1b0f0eb7、(如果中间还有的话)直到保留 88c5918b02c48c7f306559592c40aa1ffc3139fe 这个 commit

 

这时,可以看到产生了一个新的 commit

第三步,回滚完成后执行 git push 提交到远程,远程可以看到如下内容

 

 

回滚某次合并

场景: 你完成了一次合并,并执行了 git push, 把你的修改发送到了远程,但是这次合并的内容是有问题的,你需要回滚到合并之前.

方法: git revert -m [要被保留下来的那个父节点] [要回滚的 commit]

-m [要被保留下来的那个父节点]标记指出 “mainline” 需要被保留下来的父结点。 当你完成一个合并,,新提交有两个父结点:第一个是目标分支,第二个是要合并入目标分支的源分支的最新 commit。对于上述场景,如从 branch 合并到 master,要回滚 branch 合并到 master 的内容,这里就是要保留第一个父节点,也就是 git revert -m 1

原理: git revert 会产生一个新的 commit,它和指定 SHA 对应的 commit 是相反的(或者说是反转的)。如果原先的 commit 是“物质”,新的 commit 就是“反物质” —— 任何从原先的 commit 里删除的内容会在新的 commit 里被加回去,任何在原先的 commit 里加入的内容会在新的 commit  里被删除。

这是 Git 最安全、最基本的撤销场景,因为它并不会改变历史 —— 所以你现在可以  git push 新的“反转” commit 来抵消你错误提交的 commit。

示例说明:如从 branch 合并到 master,要回滚 branch 合并到 master 的内容

第一步,确认要回滚的合并的 commit。如下,需要回滚红框的合并产生的 commit 的内容,这时合并产生的 commit 是 5a4228c92c359a53bc99cf17e68eebc59763fbc2

 

 

第二步,执行 git revert  -m 1 5a4228c92c359a53bc99cf17e68eebc59763fbc2,-m 1指保留 master 的 commit,5a4228c92c359a53bc99cf17e68eebc59763fbc2 是要回滚的 commit

 

这时,可以看到产生了一个新的 commit

第三步,回滚完成后执行 git push 提交到远程,远程可以看到如下内容。由于我们只回滚合并操作,因此如下“merge 后的提交”是在合并后的提交,这个 commit 的修改内容不会被回滚。

 

回滚后,被回滚的合并的提交依然在提交历史中。 如果你尝试再次合并被回滚的 branch 分支, Git 会感到困惑:

 

这时,可以通过撤消那次回滚的的合并,然后再创建一个新的合并提交完成再次的合并操作,如上就是对回滚产生的 commit 进行 revert:

 

         

原文地址:https://www.cnblogs.com/xiaofeng-fu/p/15339396.html