但行好事
莫论前程❤

Git教程(1)—–Git重置

分支游标master探秘

​ 先来看看当有心的提交发生的时候,文件 .git/refs/heads/master的内容如何改变.首先在工作区创建一个新文件,姑且叫作new-commit.txt,然后提交到版本库中 ,

$ touch new-commit.txt
$ git add new-commit.txt
$ git commit -m "does master follow this new commit?"
[master 4902dc3] does master follow this new commit? 
    0 files changed, 0 insertions(+), 0 deletions(-) 
create mode 100644 new- commit. txt

来看看master分支指向的提交ID是否改变了.

  • 可以看出在版本库引用空间(.git/refs/目录)下的master文件内容的确改变了,指向了新的提交.
    $ cat  .git/refs/heads/master
    4902dc375672fbf52a226e0354100b75d4fe31e3
    
  • 再用git log查看一下提交日志,可以看到 刚刚完成的提交.

$ git log --graph --oneline
*  4902dc3 does master follow this new commit?

​ 引用refs/heads/master就好像是一个游标,在有新的提交发生的时候指向了新的提交.可是如果只可上,不可下,就不能成为”游标”. Git提供了git reset命令,可以将”游标”指向任意一个存在的提交ID.

​ 下面的示例就尝试认为地更改游标.(注意下面的命令中使用了–hard参数,会破坏工作区未提交的改动,慎用).

$ git reset --hard HEAD^
HEAD is now at e695606 which version checked in ?

​ 还记得上一章介绍的HEAD^代表了HEAD的父提交吗? 这条命令就相当于将master重置到上一个老的提交上,我们来看一下master文件的内容是否更改了.

$ cat .git/refs/heads/master
e695606fc5e31b2ff9038a48a3d363f4c21a3d86

​ 果然,master分支的引用文件的指向更改为前一次提交的ID了,而且新添加的文件new-commit.txt也丢失了.

​ 重置命令不仅可以重置到前一次提交,而且还可以直接使用提交ID重置到任何一次提交.

  • 通过git log 查询到最早的提交ID
$ git log 
* e695606 which version checked in? 
* a0c641e who does commit? 
* 9e8a761 initialized.
  • 然后重置到最早的一次提交.
$ git reset --hard 9e8a761
HEAD is now at 9e8a761 initialized.
  • 重置后会发现welcome.txt也回退到原始版本库,曾经的修改都丢失了.

    使用重置命令很危险,会彻底地丢弃历史.那么,还能够通过浏览提交历史的方法找到丢弃的提交ID,再使用重置命令恢复历史吗?

    !!!!!不可能!!!!!

    因为重置让提交历史也改变了,提交日志如下:

$ git log
commit 9e8a761ff9dd343a1380032884f488a2422c495a 
    Author: Jiang Xin <jiangxin@ossxp.com> Date: Sun Nov 28 12: 48: 26 2010 + 0800 

initialized.

用reflog挽救错误的重置

​ 如果没有记下重置前master分支指向的提交ID,想要重置回原来的提交似乎是一件麻烦的事情(去对象库中一个一个地找).幸好Git提供了一个挽救机制,通过,git/logs目录下日志文件记录了分支的变更.默认非裸版本库(带有工作区)都提供分支日志功能,这是因为带有工作区的版本库都有如下设置:

$ git config core.logallrefupdates
true

查看一下master分支的日志文件.git/logs/refs/heads/master中的内容.下面的命令显示了该文件的最后几行.为了排版的需要,还将输出中的40位的SHA1提交ID缩短.

$ tail -5 .git/logs/refs/heads/master
dca47ab a0c641e Jiang Xin <...> 1290999606 + 0800 
    commit (amend): who does commit? 
a0c641e e695606 Jiang Xin <...> 1291022581 + 0800 
    commit: which version checked in? 
e695606 4902dc3 Jiang Xin <...> 1291435985 + 0800 
    commit: does master follow ... 
4902dc3 e695606 Jiang Xin <...> 1291436302 + 0800 
    HEAD^: updating HEAD 
e695606 9e8a761 Jiang Xin <...> 1291436382 + 0800 
    9e8a761: updating HEAD

​ 可以看出这个文件记录了master分支指向的变迁,最新的变迁追加到文件的末尾,因此最后出现.最后一行可以看出因为执行了git reset –hard命令.指向的提交ID由e695606改变为9e8a761.

​ Git提供了一个git reflog命令,对这个文件进行操作.使用show子命令可以显示此文件的内容.

$ git reflog show master | head -5
9e8a761 master@{ 0}: 9e8a761: updating HEAD 
e695606 master@{ 1}: HEAD^: updating HEAD 
4902dc3 master@{ 2}: commit: does master follow this new commit? 
e695606 master@{ 3}: commit: which version checked in? 
a0c641e master@{ 4}: commit (amend): who does commit?

​ 查看git reflog的输出和直接查看日志文件最大的不同在于显示顺序的不同,即最新改变放在了最前面显示,而且只显示每次改变的最终的SHA1哈希值.还有个重要的区别在于git reflog命令的输出中还提供了一个方便易记的表达式: <refname>@{<n>}.这个表达式的含义是引用<refname>之前第<n>次改变时的SHA1哈希值.

​ 那么将引用master切换到两次变更之前的值,可以使用下面的命令.

  • 重置master为两次改变之前的值.
$ git reset --hard master@{2}
HEAD is now at 4902dc3 does master follow this new commit?
  • 重置后工作区中的文件new-commit.txt又回来了。
  • 提交历史也回来了。
$ git log --oneline
4902dc3 does master follow this new commit? 
e695606 which version checked in? 
a0c641e who does commit? 9e8a761 initialized.

此时如果再用git reflog查看,会看到恢复master的操作也记录在日志中了.

$ git reflog show master | head -5 
4902dc3 master@{ 0}: master@{ 2}: updating HEAD 
9e8a761 master@{ 1}: 9e8a761: updating HEAD 
e695606 master@{ 2}: HEAD^: updating HEAD 
4902dc3 master@{ 3}: commit: does master follow this new commit? 
e695606 master@{ 4}: commit: which version checked in?

深入了解git reset命令

​ 重置命令(git reset)是Git最常用的命令之一,也是最危险最容易误用的命令.

用法一: 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>的用法)则会重置引用.根据不同的选项,可以对暂存区或工作区进行重置.

参照下面的版本库模型图,来看一看不同的参数对第二种重置语法的影响

1531753285361

​ 重置命令与版本库关系图

命令格式: git reset [--soft | --mixed | --hard] [<commit>]
  • 使用参数–hard 如:git reset --hard <commit>

    会执行上图中的全部动作1,2,3即:

    1. 替换引用的指向.引用指向新的提交ID
    2. 替换暂存区,替换后暂存区的内容和引用指向的目录树一致.
    3. 替换工作区.替换后,工作区的内容变得和暂存区一致,也和HEAD所指向的目录树内容相同
  • 使用参数–soft,如: git reset --soft &lt;commit&gt;

    会执行上图中的操作1. 即只更改引用的指向,不改变暂存区和工作区

  • 使用参数–mixed或不使用参数(默认为–mixed) 如: git reset &lt;commit&gt;

    会执行上图中的操作1和2. 即改变引用的指向及重置暂存区,但是不改变工作区

  • 命令: git reset

    仅用HEAD指向的目录树重置暂存区,工作区不会受到影响,相当于将之前用git add 命令更新到暂存区的内容撤出暂存区.引用也未改变,因为引用重置到HEAD相当于,没有重置

  • 命令 git reset HEAD

    同上

  • 命令: git reset --filename

    仅将文件filename的改动撤出暂存区,暂存区中其他文件不改变.相当于对命令git add filename的反向操作.

  • 命令: git reset HEAD filename

    同上

  • 命令: git reset --soft HEAD^

    工作区和暂存区不改变.但是引用向前回退一次,当对最新提交的提交说明或提交的更改不满意时,撤销最新的提交以便重新提交.

    修补提交命令git commit --amend.用于对最新的提交进行重新提交已修补错误的提交说明或错误的提交文件. 修补提交命令实际上相当于执行了下面的 两天命令

    (注:文件.git/COMMIT_EDITMSG保存了上次的提交日志.)

    git reset --soft HEAD^
    git commit -e -F .git/COMMIT_EDITMSG
    
  • 命令: git reset HEAD^

    工作区不改变,但是暂存区会回退到上一次提交之前,引用也会回退一次.

  • 命令: git reset --mixed HEAD^

    同上

  • 命令: git reset --hard HEAD^

    彻底撤销最近的提交.引用回退到前一次,而且工作区和暂存区都会回退到上一次提交的状态,自上一次以来的提交全部丢失.

赞(1) 打赏
未经允许不得转载:刘鹏博客 » Git教程(1)—–Git重置
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!

 

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏