神刀安全网

Git:Reset,Checkout和Revert

Git:Reset,Checkout和Revert

前言

git reset, git checkout, git revert是git中常见的命令。这些命令可以回滚代码仓库中的改动,前两个命令可以被用来回滚commit和文件。

这几个命令是如此的相似,很容易在开发过程中搞混。因此这篇文章将会比较git reset, git checkout,git revert这三个命令各自的使用场景,希望你能在看完后学会如何使用他们。

Git:Reset,Checkout和Revert

了解一下这三个命令各自的作用域是很有帮助的:工作区,暂存区,历史记录区。在阅读这篇文章的时候要牢记这些作用域。

Commit级别的操作

传入git reset和git checkout的参数决定了它们的作用域。参数中不包含文件路径的话,这两个命令就作用于整个commit。我们将会讨论这样的操作。注意git revert不支持文件级别的操作。

Reset

在commit级别上,reset操作会将当前分支的HEAD指向另外一个commit。这样可以在当前分支上移除部分commit。举个例子,下面的命令将hotfix分支倒退两个commit

git checkout hotfix git reset HEAD~2

hotfix分支后面的两次commit现在处于悬空状态,也就是说,git下次执行垃圾回收时会将这两个commit删除。如下图所示

Git:Reset,Checkout和Revert
Before Resetting

Git:Reset,Checkout和Revert
After Resetting

使用git reset能够取消没有公开的改动。你在开发一个新的feature后,有时会想:“靠,这个代码什么什么鬼,我要重新写一遍。”这时就应该使用这个命令。

除了移动当前的分支,你也可以通过一些参数,让git reset来切换暂存区或者是工作区。

  • –soft,暂存区和工作区不会被切换
  • –mixed,暂存区会更新至指定的commit,工作区不会收到影响,这是默认的选项
  • –hard,暂存区和工作区同时更新到指定的commit

可以认为这些参数是用来确定git reset命令的作用域

Git:Reset,Checkout和Revert

git reset经常会带HEAD作为参数,比如

git reset --mixed HEAD

这个命令会让所有暂存区的内容失效,但是工作区不收到影响。另一方面,如果想要完全丢弃没有提交的改动,应该使用

git reset --hard HEAD

这是git reset的两种最经常使用方法。

使用git reset,如果传入某个commit而不是HEAD的时候,要格外小心,因为会改写当前分支的提交历史,在公开的分支执行这样的操作时很危险的。

Checkout

你应该已经对commit级别的git checkout非常熟悉了。传入一个分支名时,会切换到那个分支

git checkout hotfix

这个命令实际上是将HEAD指向另外一个分支,并且将工作区更新到那个分支。因为这个命令有可能覆盖掉本地的改动,如果工作区的更新可能会丢失,git会强制你将这些更新提交或者是stash。和git reset不同,git checkout不会移动分支。

Git:Reset,Checkout和Revert
Before Checking Out

Git:Reset,Checkout和Revert
After Checking Out

git checkout也可以指定某个commit,这就像切换一个分支一样:git会将HEAD指向那个commit。下面的命令会切换到当前commit的祖父节点

git checkout HEAD~2
Git:Reset,Checkout和Revert

在查看工程的某个旧版本的时候,这个命令是很有用的。但是,没有分支指向当前的HEAD,所以分支会处于detached状态。此时加入新的commit会是非常危险的,因为如果切换到其他的分支后,将没有办法回到这次的commit。出于这个原因,在提交commit到detached HEAD之前,应该新建一个分支。

Revert

revert通过创建一个新的commit来取消之前的某个commit。因为它不会重写commit的提交历史,所以是回滚代码的安全方式。举个例子,下面的命令的命令会找到倒数第二个commit,然后创建一个新的commit来回滚那次提交的改动。

git checkout hotfix git revert HEAD~2

如图所示

Git:Reset,Checkout和Revert
Before Reverting

Git:Reset,Checkout和Revert
After Reverting

和git reset相比,git revert不会改变已有的提交历史。出于这个原因,在公开的分支上回滚代码时,应该使用git revert,而在私有的分支上回滚代码,应该使用git reset。
可以认为git revert回滚了已经提交的改动,而git reset HEAD回滚了海没有提交的改动。

和git checkout一样,git revert有可能覆盖掉工作区的文件,所以如果有这个可能的话,git会要求你提交或者是stash那些可能被覆盖的改动。

文件级别的操作

git reset和git checkout也接受文件路径作为参数。这样会改变这两个命令的行为,将作用域限定在单个文件上。

Reset

当传入文件路径时,git reset会将暂存区更新成指定commit的内容。举个例子,下面的命令会获取foo.py这个文件倒数第二次提交的版本,并将其保存到暂存区,来进行下一次提交。

git reset HEAD~2 foo.py

就像git reset在commit级别的操作一样,在文件级别的操作通常被用在HEAD上。执行命令git reset HEAD foo.py会将foo.py这个文件从暂存区移除,同时这个文件的改动会保存在工作区。

Git:Reset,Checkout和Revert

如果工作在文件级别,–soft,–mixed,–hard将失去作用,因为暂存区的镜像总是会被更新,而工作区不会被改变。

Checkout

git checkout一个文件有点像git reset,除了它改变的是工作区,而不是暂存区。不像这个命令的commit级别版本,这个命令不会移动HEAD,也就是说不会切换分支。

Git:Reset,Checkout和Revert

举个例子,下面的命令会将工作区的foo.py替换成版本库中倒数第二次提交的foo.py。

git checkout HEAD~2 foo.py

就像git checkout的commit级别版本一样,这样可以查看项目之前的某个版本,但是作用域限制在某个文件上。

将这个文件添加到缓存去,并且提交到版本库,就相当于将这个文件回滚到了之前的版本。注意这样会移除那个文件在HEAD~2那次提交后的所有改动,而git revert只会取消某次commit的改动。

和git reset一样,git checkout通常作用于HEAD上。举个例子,

git checkout HEAD foo.py

会丢弃foo.py这个文件所有没有提交到暂存区的改动。这个命令有点像

git reset HEAD --hard

但是只会对单个文件起作用。

总结

现在你应该知道怎么回滚代码了。git reset,git checkout和git revert确实容易让人搞混,但是当你知道他们分别的作用域时,就很容易从中进行选择了。

下面的表格总结了这三个命令最常用的场景,记住它们,对使用git进行代码回滚会有很大的帮助。

Command Scope Common use cases
git reset Commit-level Discard commits in a private branch or throw away uncommited changes
git reset File-level Unstage a file
git checkout Commit-level Switch between branches or inspect old snapshots
git checkout File-level Discard changes in the working directory
git revert Commit-level Undo commits in a public branch
git revert File-level (N/A)

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » Git:Reset,Checkout和Revert

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址