时间 | 更新备注 |
---|---|
2018-03-02 | 新建文章 |
2018-06-10 | 添加和revert&checkout的对比 |
2019-01-18 | 更新链接 |
目录
- Git 笔记系列(一)—— Git简介
- Git 笔记系列(二)—— Git工作流程
- Git 笔记系列(三)—— Git常用命令-一览
- Git 笔记系列(四)—— Git常用命令-Checkout
- Git 笔记系列(五)—— Git常用命令-Branch
- Git 笔记系列(六)—— Git常用命令-Reset
- Git 笔记系列(七)—— Git常用命令-Rebase
- Git 笔记系列(八)—— Git常用命令-Stash等
- Git 笔记系列(九)—— Git进阶
引言
在提交层面上,reset将一个分支的末端指向另一个提交。这可以用来移除当前分支的一些提交。比如,下面这两条命令让 hotfix 分支向后回退了两个提交。
git checkout hotfix
git reset HEAD~2
hotfix 分支末端的两个提交现在变成了悬挂提交。也就是说,下次 Git 执行垃圾回收的时候,这两个提交会被删除。换句话说,如果你想扔掉这两个提交,你可以这么做。reset 操作如下图所示:
如果你仔细研究reset命令本身就知道,它本身做的事情就是重置HEAD(当前分支的版本顶端)到另外一个commit。
Reset解惑
让我们跟着 reset 看看它都做了什么。它以一种简单可预见的方式直接操纵这三棵树。它做了三个基本操作。 第 1 步:移动 HEAD
reset 做的第一件事是移动 HEAD 的指向。这与改变 HEAD 自身不同(checkout 所做的); reset 移动 HEAD
指向的分支。这意味着如果 HEAD 设置为 master 分支(例如,你正在 master 分支上),运行 git reset 9e5e64a
将会使master指向9e5e64a。
reset补充
总的来说,git reset
命令是用来将当前branch重置到另外一个commit的,而这个动作可能会将index以及work tree
同样影响。比如如果你的master branch
(当前checked out)是下面这个样子:
- A - B - C (HEAD, master)
HEAD和master branch
是在一起的,而你希望将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
状态。
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
reset 做的第一件事是移动 HEAD 的指向。
--soft参数告诉Git重置HEAD到另外一个commit,但也到此为止。如果你指定--soft参数,Git将停止在那里而什么也不会根本变化。这意味着index,working copy都不会做任何变化,所有的在original HEAD和你重置到的那个commit之间的所有变更集都放在stage(index)区域中。
- mixed(default默认选项)
接下来,reset 会用 HEAD 指向的当前快照的内容来更新索引。
--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
- hard
reset 要做的的第三件事情就是让工作目录看起来像索引。如果使用 --hard 选项,它将会继续这一步。
Git reflog
--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!
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重置范围
前面讲述了 reset 基本形式的行为,不过你还可以给它提供一个作用路径。若指定了一个路径,reset 将会跳 过第 1 步,并且将它的作用范围限定为指定的文件或文件集合。这样做自然有它的道理,因为 HEAD 只是一个指 针,你无法让它同时指向两个提交中各自的一部分。不过索引和工作目录 可以部分更新,所以重置会继续进行 第2、3步。
现在,假如我们运行git reset file.txt
(这其实是git reset --mixed HEAD file.txt
的简写形 式,因为你既没有指定一个提交的 SHA-1 或分支,也没有指定 --soft 或 --hard),它会:
- 移动 HEAD 分支的指向 (已跳过)
- 让索引看起来像 HEAD (到此处停止)
所以它本质上只是将 file.txt 从 HEAD 复制到索引中。
更进一步,我们可以不让 Git 从 HEAD
拉取数据,而是通过具体指定一个提交来拉取该文件的对应版本。我们只需运行类似 于git reset eb43bf file.txt
的命令即可。
压缩提交
我们来看看如何利用这种新的功能来做一些有趣的事情 - 压缩提交。
假设你的一系列提交信息中有 “oops.”、“WIP” 和 “forgot this file”,聪明的你就能使用 reset 来轻松快 速地将它们压缩成单个提交,也显出你的聪明。(压缩提交 展示了另一种方式,不过在本例中用 reset 更简 单。)
假设你有一个项目,第一次提交中有一个文件,第二次提交增加了一个新的文件并修改了第一个文件,第三次提
交再次修改了第一个文件。由于第二次提交是一个未完成的工作,因此你想要压缩它。
那么可以运行git reset --soft HEAD~2
来将HEAD分支移动到一个旧一点的提交上(即你想要保留的第 一个提交):
注意,这时候因为是在上次提交的后,的Index
暂存区和Working Directory
是保持一致的,所以可以直接提交。
然后只需再次运行git commit
:
现在你可以查看可到达的历史,即将会推送的历史,现在看起来有个 v1 版 file-a.txt
的提交,接着第二个提 交将 file-a.txt
修改成了 v3 版并增加了 file-b.txt
。包含 v2 版本的文件已经不在历史中了。
reset和checkout
checkout这个命令做的不过是将HEAD移到一个新的分支,然后更新工作目录。因为这可能会覆盖本地的修改,Git 强制你提交或者缓存工作目录中的所有更改,不然在 checkout 的时候这些更改都会丢失。和 git reset 不一样的是,git checkout 没有移动这些分支。这对于快速查看项目旧版本来说非常有用。
checkout对工作目录是安全的,它会通过检查来确保不会将已更改的文件吹走。
- checkout不会去修改你在Working Directory里修改过的文件
- checkout则把HEAD移动到另一个分支
- reset会不做检查把working directory里的所有内容都更新掉
- reset把branch移动到HEAD指向的地方
reset和revert
- git revert可以用在公共分支上,git reset应该用在私有分支上.
git revert
用于记录一些新的提交以反转一些早期提交的影响(通常只是一个错误的提交)。如果你想扔掉工作目录中所有未提交的更改,你应该看到git-reset
,特别是--hard选项。如果你想提取特定文件,就像在另一个提交中那样,你应该看到git-checkout
,特别是git checkout <commit> -- <filename>
语法。请谨慎使用这些替代方法,因为它们都会丢弃工作目录中的未提交更改。
reset是用来修改提交历史的,想象这种情况,如果你在2天前提交了一个东西,突然发现这次提交是有问题的。
这个时候你有两个选择,要么使用git revert(推荐),要么使用git reset。
上图可以看到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 应该用在私有分支上。
总结
简单总结一下,其实就是--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
参考
- git reset soft,hard,mixed之区别深解
- Git - git-reset Documentation
- 代码回滚:git reset、git checkout和git revert区别和联系 - houpy - 博客园
- Pro Git(中文版)