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

时间 更新备注
2018-03-02 新建文章
2018-06-10 添加和revert&checkout的对比
2019-01-18 更新链接

目录

引言

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

git checkout hotfix
git reset HEAD~2

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

image
image

如果你仔细研究reset命令本身就知道,它本身做的事情就是重置HEAD(当前分支的版本顶端)到另外一个commit

Reset解惑

让我们跟着 reset 看看它都做了什么。它以一种简单可预见的方式直接操纵这三棵树。它做了三个基本操作。 第 1 步:移动 HEAD

reset 做的第一件事是移动 HEAD 的指向。这与改变 HEAD 自身不同(checkout 所做的); reset 移动 HEAD 指向的分支。这意味着如果 HEAD 设置为 master 分支(例如,你正在 master 分支上),运行 git reset 9e5e64a将会使master指向9e5e64a。

image

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)
image

记住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

  1. soft

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

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

image
image
  1. hard

reset 要做的的第三件事情就是让工作目录看起来像索引。如果使用 --hard 选项,它将会继续这一步。

Git reflog

image

--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
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重置范围

前面讲述了 reset 基本形式的行为,不过你还可以给它提供一个作用路径。若指定了一个路径,reset 将会跳 过第 1 步,并且将它的作用范围限定为指定的文件或文件集合。这样做自然有它的道理,因为 HEAD 只是一个指 针,你无法让它同时指向两个提交中各自的一部分。不过索引和工作目录 可以部分更新,所以重置会继续进行 第2、3步。

现在,假如我们运行git reset file.txt(这其实是git reset --mixed HEAD file.txt的简写形 式,因为你既没有指定一个提交的 SHA-1 或分支,也没有指定 --soft 或 --hard),它会:

  1. 移动 HEAD 分支的指向 (已跳过)
  2. 让索引看起来像 HEAD (到此处停止)

所以它本质上只是将 file.txt 从 HEAD 复制到索引中。

image

更进一步,我们可以不让 Git 从 HEAD 拉取数据,而是通过具体指定一个提交来拉取该文件的对应版本。我们只需运行类似 于git reset eb43bf file.txt的命令即可。

image

压缩提交

我们来看看如何利用这种新的功能来做一些有趣的事情 - 压缩提交。

假设你的一系列提交信息中有 “oops.”、“WIP” 和 “forgot this file”,聪明的你就能使用 reset 来轻松快 速地将它们压缩成单个提交,也显出你的聪明。(压缩提交 展示了另一种方式,不过在本例中用 reset 更简 单。)

假设你有一个项目,第一次提交中有一个文件,第二次提交增加了一个新的文件并修改了第一个文件,第三次提

交再次修改了第一个文件。由于第二次提交是一个未完成的工作,因此你想要压缩它。

image

那么可以运行git reset --soft HEAD~2来将HEAD分支移动到一个旧一点的提交上(即你想要保留的第 一个提交):

image

注意,这时候因为是在上次提交的后,的Index暂存区和Working Directory是保持一致的,所以可以直接提交。

然后只需再次运行git commit:

image

现在你可以查看可到达的历史,即将会推送的历史,现在看起来有个 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指向的地方
image

reset和revert

  • git revert可以用在公共分支上,git reset应该用在私有分支上.

git revert用于记录一些新的提交以反转一些早期提交的影响(通常只是一个错误的提交)。如果你想扔掉工作目录中所有未提交的更改,你应该看到git-reset,特别是--hard选项。如果你想提取特定文件,就像在另一个提交中那样,你应该看到git-checkout,特别是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 命令一样。

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

  • --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

参考

  1. git reset soft,hard,mixed之区别深解
  2. Git - git-reset Documentation
  3. 代码回滚:git reset、git checkout和git revert区别和联系 - houpy - 博客园
  4. Pro Git(中文版)

Git Revert

  1. Git Revert | Atlassian Git Tutorial
  2. Learn how to undo changes in Git using Bitbucket Cloud
  3. 5.2 代码回滚:Reset、Checkout、Revert 的选择 · geeeeeeeeek/git-recipes Wiki
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容

  • 1,查看所有远程分支:%git branch -r 2, 拉取远程分支并创建本地分支git checkout -...
    will666阅读 2,004评论 0 18
  • git branch 查看本地所有分支 git status 查看当前状态 git commit 提交 git b...
    猿万阅读 4,968评论 1 45
  • 我们常常期待能遇到一个何以琛,却忘了自己根本不是赵默笙的事实。在锦瑟流年中,在怀揣着少女梦的同时,我们都慢慢成为了...
    菩提有树阅读 761评论 2 3
  • 谈起跑步,已经有一年有余。2015年的夏天因为大学同学的远程带领以及工作一年后压抑的情绪,我开始跑步了。 ...
    陈佳熠阅读 360评论 3 2
  • 百无聊赖的日子慢到不能再慢,送完萧晓晓回家,我没打车准备自己走路溜达回家,自从我稀里糊涂的成了萧晓晓的男友后,开始...
    忆如素阅读 2,906评论 7 10