Git代码回滚:Reset、Checkout、Revert 的选择

一、详细学习Gitgit

https://github.com/geeeeeeeeek/git-recipes/wikigithub

二、git reset、git checkout、git revert 学习缓存

https://github.com/geeeeeeeeek/git-recipes/wiki/5.2-%E4%BB%A3%E7%A0%81%E5%9B%9E%E6%BB%9A%EF%BC%9AReset%E3%80%81Checkout%E3%80%81Revert-%E7%9A%84%E9%80%89%E6%8B%A9安全

git resetgit checkout 和 git revert 是你的 Git 工具箱中最有用的一些命令。它们都用来撤销代码仓库中的某些更改,而前两个命令不只能够做用于提交,还能够做用于特定文件。工具

由于它们很是类似,因此咱们常常会搞混,不知道什么场景下该用哪一个命令。在这篇文章中,咱们会比较 git resetgit checkout 和 git revert 最多见的用法。但愿你在看完后能游刃有余地使用这些命令来管理你的仓库。学习

[此处有图]spa

Git 仓库有三个主要组成——工做目录,缓存区和提交历史。这张图有助于理解每一个命令到底产生了哪些影响。当你阅读的时候,牢记这张图。code

提交层面的操做

你传给 git reset 和 git checkout 的参数决定了它们的做用域。若是你没有包含文件路径,这些操做对全部提交生效。咱们这一节要探讨的就是提交层面的操做。注意,git revert 没有文件层面的操做。ip

Reset

在提交层面上,reset 将一个分支的末端指向另外一个提交。这能够用来移除当前分支的一些提交。好比,下面这两条命令让 hotfix 分支向后回退了两个提交。ci

git checkout hotfix
git reset HEAD~2

hotfix 分支末端的两个提交如今变成了悬挂提交。也就是说,下次 Git 执行垃圾回收的时候,这两个提交会被删除。换句话说,若是你想扔掉这两个提交,你能够这么作。reset 操做以下图所示:

[此处有图]

若是你的更改尚未共享给别人,git reset 是撤销这些更改的简单方法。当你开发一个功能的时候发现「糟糕,我作了什么?我应该从新来过!」时,reset 就像是 go-to 命令同样。

除了在当前分支上操做,你还能够经过传入这些标记来修改你的缓存区或工做目录:

  • --soft – 缓存区和工做目录都不会被改变
  • --mixed – 默认选项。缓存区和你指定的提交同步,但工做目录不受影响
  • --hard – 缓存区和工做目录都同步到你指定的提交

把这些标记想成定义 git reset 操做的做用域就容易理解多了。

[此处有图]

这些标记每每和 HEAD 做为参数一块儿使用。好比,git reset --mixed HEAD 将你当前的改动从缓存区中移除,可是这些改动还留在工做目录中。另外一方面,若是你想彻底舍弃你没有提交的改动,你可使用 git reset --hard HEAD。这是 git reset 最经常使用的两种用法。

当你传入 HEAD 之外的其余提交的时候要格外当心,由于 reset 操做会重写当前分支的历史。正如 rebase 黄金法则所说的,在公共分支上这样作可能会引发严重的后果。

Checkout

你应该已经很是熟悉提交层面的 git checkout。当传入分支名时,能够切换到那个分支。

git checkout hotfix

上面这个命令作的不过是将HEAD移到一个新的分支,而后更新工做目录。由于这可能会覆盖本地的修改,Git 强制你提交或者缓存工做目录中的全部更改,否则在 checkout 的时候这些更改都会丢失。和 git reset 不同的是,git checkout 没有移动这些分支。

[此处有图]

除了分支以外,你还能够传入提交的引用来 checkout 到任意的提交。这和 checkout 到另外一个分支是彻底同样的:把 HEAD 移动到特定的提交。好比,下面这个命令会 checkout 到当前提交的祖父提交。

git checkout HEAD~2

 

这对于快速查看项目旧版原本说很是有用。但若是你当前的 HEAD 没有任何分支引用,那么这会形成 HEAD 分离。这是很是危险的,若是你接着添加新的提交,而后切换到别的分支以后就没办法回到以前添加的这些提交。所以,在为分离的 HEAD 添加新的提交的时候你应该建立一个新的分支。

Revert

Revert 撤销一个提交的同时会建立一个新的提交。这是一个安全的方法,由于它不会重写提交历史。好比,下面的命令会找出倒数第二个提交,而后建立一个新的提交来撤销这些更改,而后把这个提交加入项目中。

git checkout hotfix
git revert HEAD~2

以下图所示:

[此处有图]

相比 git reset,它不会改变如今的提交历史。所以,git revert 能够用在公共分支上,git reset 应该用在私有分支上。

你也能够把 git revert 看成撤销已经提交的更改,而 git reset HEAD 用来撤销没有提交的更改。

就像 git checkout 同样,git revert 也有可能会重写文件。因此,Git 会在你执行 revert 以前要求你提交或者缓存你工做目录中的更改。

文件层面的操做

git reset 和 git checkout 命令也接受文件路径做为参数。这时它的行为就大为不一样了。它不会做用于整份提交,参数将它限制于特定文件。

Reset

当检测到文件路径时,git reset 将缓存区同步到你指定的那个提交。好比,下面这个命令会将倒数第二个提交中的 foo.py 加入到缓存区中,供下一个提交使用。

git reset HEAD~2 foo.py

和提交层面的 git reset 同样,一般咱们使用HEAD而不是某个特定的提交。运行 git reset HEAD foo.py 会将当前的 foo.py 从缓存区中移除出去,而不会影响工做目录中对 foo.py 的更改。

[此处有图]

--soft--mixed 和 --hard 对文件层面的 git reset 毫无做用,由于缓存区中的文件必定会变化,而工做目录中的文件必定不变。

Checkout

Checkout 一个文件和带文件路径 git reset 很是像,除了它更改的是工做目录而不是缓存区。不像提交层面的 checkout 命令,它不会移动 HEAD引用,也就是你不会切换到别的分支上去。

[此处有图]

好比,下面这个命令将工做目录中的 foo.py 同步到了倒数第二个提交中的 foo.py

git checkout HEAD~2 foo.py

和提交层面相同的是,它能够用来检查项目的旧版本,但做用域被限制到了特定文件。

若是你缓存而且提交了 checkout 的文件,它具有将某个文件回撤到以前版本的效果。注意它撤销了这个文件后面全部的更改,而 git revert 命令只撤销某个特定提交的更改。

和 git reset 同样,这个命令一般和 HEAD 一块儿使用。好比 git checkout HEAD foo.py 等同于舍弃 foo.py 没有缓存的更改。这个行为和 git reset HEAD --hard 很像,但只影响特定文件。

总结

你如今已经掌握了 Git 仓库中撤销更改的全部工具。git resetgit checkout 和 git revert 命令比较容易混淆,但当你想起它们对工做目录、缓存区和提交历史的不一样影响,就会容易判断如今应该用哪一个命令。

下面这个表格总结了这些命令最经常使用的使用场景。记得常常对照这个表格,由于你使用 Git 时必定会常常用到。

命令 做用域 经常使用情景
git reset 提交层面 在私有分支上舍弃一些没有提交的更改
git reset 文件层面 将文件从缓存区中移除
git checkout 提交层面 切换分支或查看旧版本
git checkout 文件层面 舍弃工做目录中的更改
git revert 提交层面 在公共分支上回滚更改
git revert 文件层面 (然而并无)
相关文章
相关标签/搜索