Git笔记

Git 笔记系列(六)—— Git常用命令-Reset

2018-04-14  本文已影响43人  吃蘑菇De大灰狼
时间 更新备注
2018-03-02 新建文章
2018-06-10 添加和revert&checkout的对比

目录

Reset

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

git checkout hotfix
git reset HEAD~2

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

image image
如果你仔细研究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

  1. soft

--soft参数告诉Git重置HEAD到另外一个commit,但也到此为止。如果你指定--soft参数,Git将停止在那里而什么也不会根本变化。这意味着index,working copy都不会做任何变化,所有的在original HEAD和你重置到的那个commit之间的所有变更集都放在stage(index)区域中。

image
  1. 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!

image
  1. 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

image
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 没有移动这些分支。这对于快速查看项目旧版本来说非常有用。

image

reset和revert

git revert用于记录一些新的提交以反转一些早期提交的影响(通常只是一个错误的提交)。如果你想扔掉工作目录中所有未提交的更改,你应该看到git-reset [1],特别是--hard选项。如果你想提取特定文件,就像在另一个提交中那样,你应该看到git-checkout [1],特别是git checkout <commit> -- <filename>语法。请谨慎使用这些替代方法,因为它们都会丢弃工作目录中的未提交更改。

reset是用来修改提交历史的,想象这种情况,如果你在2天前提交了一个东西,突然发现这次提交是有问题的。

这个时候你有两个选择,要么使用git revert(推荐),要么使用git reset。

image

上图可以看到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 命令一样。

除了在当前分支上操作,你还可以通过传入这些标记来修改你的缓存区或工作目录:

把这些标记想成定义 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

参考

  1. git reset soft,hard,mixed之区别深解
  2. Git - git-reset Documentation
  3. 代码回滚:git reset、git checkout和git revert区别和联系 - houpy - 博客园
  4. 5.2 代码回滚:Reset、Checkout、Revert 的选择 · geeeeeeeeek/git-recipes Wiki
上一篇 下一篇

猜你喜欢

热点阅读