Git 笔记系列(六)—— Git常用命令-Reset
时间 | 更新备注 |
---|---|
2018-03-02 | 新建文章 |
2018-06-10 | 添加和revert&checkout的对比 |
目录
- Git 笔记系列(一)—— Git简介
- Git 笔记系列(二)—— Git工作流程
- Git 笔记系列(三)—— Git常用命令-一览
- Git 笔记系列(四)—— Git常用命令-Checkout
- Git 笔记系列(五)—— Git常用命令-Branch
- Git 笔记系列(六)—— Git常用命令-Reset
- Git 笔记系列(七)—— Git常用命令-Rebase
- Git 笔记系列(八)—— Git常用命令-Stash等
- Git 笔记系列(九)—— Git常用命令-Fork
- Git 笔记系列(十)—— Git进阶
- Git 笔记系列(十一)—— Git常见问题
- Git 笔记系列(十二)—— Git进阶, 巧用Git工具
- Git 笔记系列(十三)—— Git进阶, Git的奇技赢巧
Reset
在提交层面上,reset 将一个分支的末端指向另一个提交。这可以用来移除当前分支的一些提交。比如,下面这两条命令让 hotfix 分支向后回退了两个提交。
git checkout hotfix
git reset HEAD~2
hotfix 分支末端的两个提交现在变成了悬挂提交。也就是说,下次 Git 执行垃圾回收的时候,这两个提交会被删除。换句话说,如果你想扔掉这两个提交,你可以这么做。reset 操作如下图所示:
![](https://img.haomeiwen.com/i225323/b4d36abdd71a424c.jpg)
![](http://upload-images.jianshu.io/upload_images/225323-91ff9c17c5deda90.jpg)
如果你仔细研究reset命令本身就知道,它本身做的事情就是重置HEAD(当前分支的版本顶端)到另外一个commit。
reset用法
重置命令(git reset
)是Git
最常用的命令之一,也是最危险,最容易误用的命令。来看看git reset
命令的用法。
用法一:git reset [-q] [<commit>] [--] <paths>...
用法二:git reset [--soft | --mixed | --hard | --merge | --keep] [-q] [<commit>]
上面列出了两个用法,其中<commit>
都是可选项,可以使用引用或者提交ID
,如果省略 <commit>
则相当于使用了HEAD
的指向作为提交ID
。
上面列出的两种用法的区别在于,第一种用法在命令中包含路径<paths>
。为了避免路径和引用(或者提交ID
)同名而冲突,可以在<paths>
前用两个连续的短线(减号)作为分隔。
第一种用法(包含了路径<paths>
的用法)不会重置引用,更不会改变工作区,而是用指定提交状态(<commit>
)下的文件(<paths>
)替换掉暂存区中的文件。例如命令git reset HEAD <paths>
相当于取消之前执行的git add <paths>
命令时改变的暂存区。
第二种用法(不使用路径<paths>的用法)则会重置引用。根据不同的选项,可以对暂存区或者工作区进行重置。参照下面的版本库模型图,来看一看不同的参数对第二种重置语法的影响。
reset注意
当你传入HEAD
以外的其他提交的时候要格小心,因为 reset 操作会重写当前分支的历史。正如 rebase 黄金法则所说的,在公共分支上这样做可能会引起严重的后果。
当执行 “git reset HEAD” 命令时,暂存区的目录树会被重写,被 master 分支指向的目录树所替
reset Parameters
- soft
--soft参数告诉Git重置HEAD到另外一个commit,但也到此为止。如果你指定--soft参数,Git将停止在那里而什么也不会根本变化。这意味着index,working copy都不会做任何变化,所有的在original HEAD和你重置到的那个commit之间的所有变更集都放在stage(index)区域中。
![](http://upload-images.jianshu.io/upload_images/225323-07d38f54b5a92342.jpg)
- hard
--hard参数将会blow out everything.它将重置HEAD返回到另外一个commit(取决于~12的参数),重置index以便反映HEAD的变化,并且重置working copy也使得其完全匹配起来。这是一个比较危险的动作,具有破坏性,数据因此可能会丢失!如果真是发生了数据丢失又希望找回来,那么只有使用:git reflog命令了。makes everything match the commit you have reset to.你的所有本地修改将丢失。如果我们希望彻底丢掉本地修改但是又不希望更改branch所指向的commit,则执行git reset --hard = git reset --hard HEAD
. i.e. don't change the branch but get rid of all local changes.另外一个场景是简单地移动branch从一个到另一个commit而保持index/work区域同步。这将确实令你丢失你的工作,因为它将修改你的work tree!
![](http://upload-images.jianshu.io/upload_images/225323-be68e89fdc54d0fe.jpg)
- mixed(default)
--mixed是reset的默认参数,也就是当你不指定任何参数时的参数。它将重置HEAD到另外一个commit,并且重置index以便和HEAD相匹配,但是也到此为止。working copy不会被更改。所有该branch上从original HEAD(commit)到你重置到的那个commit之间的所有变更将作为local modifications保存在working area中,(被标示为local modification or untracked via git status),但是并未staged的状态,你可以重新检视然后再做修改和commit
![](http://upload-images.jianshu.io/upload_images/225323-cae8417d585d7f8b.jpg)
working index HEAD target working index HEAD
----------------------------------------------------
A B C D --soft A B D
--mixed A D D
--hard D D D
--merge (disallowed)
working index HEAD target working index HEAD
----------------------------------------------------
A B C C --soft A B C
--mixed A C C
--hard C C C
--merge (disallowed)
reset和checkout
checkout这个命令做的不过是将HEAD移到一个新的分支,然后更新工作目录。因为这可能会覆盖本地的修改,Git 强制你提交或者缓存工作目录中的所有更改,不然在 checkout 的时候这些更改都会丢失。和 git reset 不一样的是,git checkout 没有移动这些分支。这对于快速查看项目旧版本来说非常有用。
-
reset会把working directory里的所有内容都更新掉
-
checkout不会去修改你在Working Directory里修改过的文件
-
reset把branch移动到HEAD指向的地方
-
checkout则把HEAD移动到另一个分支
![](http://upload-images.jianshu.io/upload_images/225323-f483392024b52bc4.jpg)
reset和revert
git revert用于记录一些新的提交以反转一些早期提交的影响(通常只是一个错误的提交)。如果你想扔掉工作目录中所有未提交的更改,你应该看到git-reset [1],特别是--hard选项。如果你想提取特定文件,就像在另一个提交中那样,你应该看到git-checkout [1],特别是git checkout <commit> -- <filename>语法。请谨慎使用这些替代方法,因为它们都会丢弃工作目录中的未提交更改。
reset是用来修改提交历史的,想象这种情况,如果你在2天前提交了一个东西,突然发现这次提交是有问题的。
这个时候你有两个选择,要么使用git revert(推荐),要么使用git reset。
![](http://upload-images.jianshu.io/upload_images/225323-0d48f60c7ef7be83.jpg)
上图可以看到git reset是会修改版本历史的,他会丢弃掉一些版本历史。
而git revert是根据那个commit逆向生成一个新的commit,版本历史是不会被破坏的。
相比git reset,它不会改变现在的提交历史。因此,git revert可以用在公共分支上,git reset应该用在私有分支上。
你也可以把git revert当作撤销已经提交的更改,而git reset HEAD用来撤销没有提交的更改。
就像git checkout 一样,git revert 也有可能会重写文件。所以,Git会在你执行revert之前要求你提交或者缓存你工作目录中的更改。
如果你的更改还没有共享给别人,git reset
是撤销这些更改的简单方法。当你开发一个功能的时候发现「糟糕,我做了什么?我应该重新来过!」时,reset 就像是 go-to 命令一样。
除了在当前分支上操作,你还可以通过传入这些标记来修改你的缓存区或工作目录:
- --soft – 缓存区和工作目录都不会被改变
- --mixed – 默认选项。缓存区和你指定的提交同步,但工作目录不受影响
- --hard – 缓存区和工作目录都同步到你指定的提交
把这些标记想成定义 git reset
操作的作用域就容易理解多了
相比 git reset,它不会改变现在的提交历史。因此,git revert 可以用在公共分支上,git reset 应该用在私有分支上。
一个牛人的解释:
总的来说,git reset命令是用来将当前branch重置到另外一个commit的,而这个动作可能会将index以及work tree同样影响。比如如果你的master branch(当前checked out)是下面这个样子:
- A - B - C (HEAD, master)
HEAD和master branch tip是在一起的,而你希望将master指向到B,而不是C,那么你执行
git reset B以便移动master branch到B那个commit:
- A - B (HEAD, master) # - C is still here, but there's no branch pointing to it anymore
注意:git reset和checkout是不一样的。如果你运行git checkout B,那么你讲得到:
- A - B (HEAD) - C (master)
这时HEAD和master branch就不在一个点上了,你进入detached HEAD State
. HEAD,work tree,index都指向了B,但是master branch却依然指向C。如果在这个点上,你执行一个新的commit D,那么你讲得到下面(当然这可能并不是你想要的,你可能想要的是创一个branch做bug fix):
- A - B - C (master)
\
D (HEAD)
记住git reset不会产生commits,它仅仅更新一个branch(branch本身就是一个指向一个commit的指针)指向另外一个commit(Head和branch Tip同时移动保持一致).其他的仅剩对于index和work tree(working directory)有什么影响。git checkout xxxCommit则只影响HEAD,如果xxxCommit和一个branch tip是一致的话,则HEAD和branch相匹配,如果xxxCommit并不和任何branch tip相一致,则git进入detached HEAD
状态
总结
简单总结一下,其实就是--soft 、--mixed以及--hard是指代三个不同的恢复等级。使用--soft就仅仅将Head头指针恢复,已经add的缓存以及工作空间的所有东西都不变。如果使用--mixed,就将Head头指针恢复掉,已经add的缓存也会丢失掉,工作空间的代码什么的是不变的。如果使用--hard,那么一切就全都恢复了,Head头指针变,add的缓存消失,本地工作区的代码的也恢复到指定之前版本的状态。
命令 | 作用域 | 常用情景 |
---|---|---|
git reset | 提交层面 | 在私有分支上舍弃一些没有提交的更改 |
git reset | 文件层面 | 将文件从缓存区中移除 |
git checkout | 提交层面 | 切换分支或查看旧版本 |
git checkout | 文件层面 | 舍弃工作目录中的更改 |
git revert | 提交层面 | 在公共分支上回滚更改 |
git revert | 文件层面 | (然而并没有) |
head index work dir wd safe
Commit Level
reset --soft [commit] REF NO NO YES
reset [commit] REF YES NO YES
reset --hard [commit] REF YES YES NO
checkout [commit] HEAD YES YES YES
File Level
reset (commit) [file] NO YES NO YES
checkout (commit) [file] NO YES YES NO