zoukankan      html  css  js  c++  java
  • git引用^和~的区别

    这篇git文章必转:解答我一直的疑惑

    http://www.cnblogs.com/hutaoer/archive/2013/05/14/3078191.html

    一. 引子

    在git操作中,我们可以使用checkout命令检出某个状态下文件,也可以使用reset命令重置到某个状态,这里所说的“某个状态”其实对应的就是一个提交(commit).

    我们可以把一个git仓库想象成一棵树,每个commit就是树上的一个节点。家家都有一本自己的祖谱。祖谱记录了一个家族的生命史,它不仅记录着该家族的来源、迁徙的轨迹,还包罗了该家族生息、繁衍、婚姻、文化、族规、家约等历史文化的全过程。类似的,每个git仓库都有一本自己的祖谱,仓库中commit ID的繁衍,HEAD指针的迁徙,分支的增加、更新,同样的记录着一个仓库从无到有的点点滴滴。

    在git中,我们其实可以通过^和~来定位某个具体的commit,而不用每次都去敲繁琐的hash值。为了便于大家理解,先把结论放在前面:

    1. “^”代表父提交,当一个提交有多个父提交时,可以通过在”^”后面跟上一个数字,表示第几个父提交,”^”相当于”^1”.
    2. ~<n>相当于连续的<n>个”^”.
    3. checkout只会移动HEAD指针,reset会改变HEAD的引用值。

    使用git log –graph 命令,可以查看自己仓库的当前分支提交ID的树状图,如下图所示。

    使用git log –pretty=raw命令,可以查看commit之间的父子关系,如下图所示,需要注意的是最开始的commit是没有父提交的。

    二. 困惑

    在使用git的过程中,你也许会有很多的困惑。

    在使用reset或checkout命令的时候,需要一个<commit>参数,但是每次都输入commit hash值是一件比较麻烦的事情。首先你得去查询下日志,然后再用键盘将前面几位hash值输入。有时候你一次还搞不定,突然开个小差,暗恋下女神,想一想基友,都容易把hash值遗忘或弄错。肿么办???

    又话说突然间,一堆带有hash值的符号出现在生活中,HEAD^1~4,<commit>~3^2,我擦!这是TMD玩意儿?不懂啊,使用过程中,HEAD和引用各种乱窜,根本不听从我的指挥,哎呀,妈呀!我成了git的奴隶,从此生活不再美好。肿么办???

    不,生活还要继续,要和git做朋友。做朋友当然先要摸清楚朋友的性情和脾气咯,有了好友,生活才会充满希望。

    三. 解惑

    古有“射人先射马,擒贼先擒王”,今有“git仓库顺藤摸瓜”。既然commit形成的树状图,表明了各个commit之间的关系,那么我们也可以顺着这棵树去查询commit的值。一般情况下,一个commit都会有一个父提交,那么通过<commit>^这个表达式,就可以访问到其父提交的ID值;使用<commit>~也可以达到同样的功效哦。

    我们知道每提交一次,HEAD就会自动移到版本库中最近的一次提交。那么HEAD^就代表了最近一次提交的父提交,HEAD~也是同样的道理;但是如果你想当然的认为^和~的用法相同,那就错了,其实它们的区别还是蛮大的。

    四. 详解

    我们来通过一个具体的例子,来讲解一下^和~的用法区别,同时在checkout或reset的过程中,看看HEAD和引用的变化。

    查看HEAD和引用的值

    我们可以通过命令来查看HEAD和引用的值,也可以通过当前仓库下的.git目录去访问。当前分支为master时,我们查看HEAD的值,命令如下:

    $ cat .git/HEAD
    ref: refs/heads/master

    然后,我们可以查看master引用的值

    $ cat .git/refs/heads/master
    3b0370b.......  # hash code

    master分支上初始化,并提交一次 

    在master分支上新建一个提交”c1”,生成commit ID 973c,这时候master引用指向973c,HEAD指向master引用。

    $ git init
    Initialized empty Git repository
    $ echo c1 >> a
    $ git add a
    $ git commit
    [master (root-commit) 973c5dd] c1
     1 files changed, 1 insertions(+), 0 deletions(-)
     create mode 100644 a
    $ git log --oneline
    973c5dd c1

    对应的图如下所示:

    基于master新建br1分支,并提交两次

    接下来在master分支基础上新建分支”br1”,并在”br1”上提交”c2”,commit ID为1c73,这时候HEAD指向br1,br1引用指向”c2”对应提交1c73.

    $ git checkout -b br1
    Switched to a new branch 'br1'
    $ echo c2 >> b
    $ git add b
    $ git commit
    [br1 1c7383c] c2
     1 file changed, 1 insertion(+)
     create mode 100644 b
    $ git log --oneline
    1c7383c c2
    973c5dd c1

    对应的图如下所示:

    在分支”br1”上,提交”c3”,commit ID为4927,此时HEAD指向br1,br1引用指向”c3”对应提交4927.

    $ echo c3 >> b
    $ git commit -a -m "c3"
    [br1 4927c6c] c3
     1 file changed, 1 insertion(+)
    $ git log --oneline
    4927c6c c3
    1c7383c c2
    973c5dd c1

    对应的图如下所示:

      

     

    切换到master分支,基于master分支新建br2分支,并提交两次

    我们先切回到master分支,然后新建分支br2,先后提交”c4”和”c5”,对应的ID分别是”86ba”和”063f”,这时候HEAD指向br2,br2引用指向”c5”的对应提交063f.git 命令如下:

    $ git chechout master
    Switched to branch 'master'
    $ git checkout -b br2
    Switched to a new branch 'br2'
    $ echo c4 >> c
    $ git add c
    $ git commit -m "c4"
    [br2 86ba564] c4
     1 file changed, 1 insertion(+)
     create mode 100644 c
    $ git log --oneline
    86ba564 c4
    973c5dd c1
    $ echo c5 >> c
    $ git commit -a -m "c5"
    [br2 063f6e6] c5
     1 file changed, 1 insertion(+)
    $ git log --oneline
    063f6e6 c5
    86ba564 c4
    973c5dd c1

    对应的图如下所示:

      

     

    切换到master分支,基于master分支创建br3分支,并提交两次

    这个操作同分支br2上类似,先从br2分支切换到master分支,然后新建分支br3,分别提交”c6”和”c7”,对应的ID分别是”50f1”和”4f9c”,这时候HEAD指向br3,br2引用指向”c7”的对应提交4f9c,git 命令如下:

    $ git chechout master
    Switched to branch 'master'
    $ git checkout -b br3
    Switched to a new branch 'br3'
    $ echo c6 >> d
    $ git add d
    $ git commit -m "c6"
    [br3 50f14f6] c6
     1 file changed, 1 insertion(+)
     create mode 100644 d
    $ git log --oneline
    50f14f6 c6
    973c5dd c1
    $ echo c7 >> c
    $ git commit -a -m "c7"
    [br2 4f9ca79] c7
     1 file changed, 1 insertion(+)
    $ git log --oneline
    4f9ca79 c7
    50f14f6 c6
    973c5dd c1

    对应的图如下所示:

    切换到master分支,合并br1,br2和br3分支

    先切换到master分支,然后合并br1 br2 br3,会新生成一个提交3b03.

    $ git checkout master
    $ git merge br1 br2 br3
     3 files changed, 6 insertions(+)
     create mode 100644 b
     create mode 100644 c
     create mode 100644 d
    $ git log --oneline
    3b0370b Merge braches 'br1', 'br2' and 'br3'
    4f9ca79 c7
    50f14f6 c6
    063f6e6 c5
    86ba564 c4
    4927c6c c3
    1c7383c c2
    973c5dd c1

    这时候,运用git log –oneline –graph查看生成的树状图,如下所示.

    从上图分析,在第1条红线上的commit顺序是: 3b03→4927→1c73→973c

    第2条红线上的commit顺序是:3b03→063f→86ba→973c

    第3条黄线上的commit顺序是:3b03→4f9c→50f1→973c

    这3条线的从左至右的顺序非常重要,因为HEAD^1对应的就是第1条红线的提交4927,HEAD^2对应的是第2条绿线的063f提交,HEAD^3对应的是第3条黄线的4f9c提交。3b03没有第4个父提交,因此也没有第4条线,这时候访问HEAD^n(n>3)都会报错。

    因此从任何一条线上,我们都可以追溯到”c1”的commit,但是每条线上的中间节点,只能通过这条线上的节点去访问。

    操作同上类似,最后的状态如下,这时候HEAD指向master,master引用指向”c8”的对应提交3b03.

    对应的图如下所示:

    我们再来看看3b03对应节点的父提交,如下图所示:

    从图得知,3b03一共有三个父提交,分别是4927,063f,4f9c.

    reset与checkou的区别

    在master分支上,当前提交为3b03,使用git reset –hard HEAD^,将master重置到HEAD的父提交;该命令也可以写成git reset –hard HEAD^1

    $ git reset --hard HEAD^
    HEAD is now at 4927c6c c3

    对应的图如下所示:


    这时候,HEAD还是指向master分支,但是master引用的commit值已经变成了4927,即3b03的第一个父提交的ID.
    
    

    然后,我们再重置到”c8”的commit”3b03”,git reset –hard 3b03,然后使用命令git checkout HEAD~ ,git 操作如下:

    $ git reset --hard 3b03
    HEAD is now at 3b0370b Merge branches 'br1', 'br2' and 'br3'
    $ git checkout HEAD~
    HEAD is now at 4927c6c... c3

    对应的图如下所示:

    这时候,HEAD指向了commit 4927,即3b03的第一个父提交ID,但是master引用还是对应的3b03.

    从上面的测试,我们可以得出以下结论:

    1. HEAD^,HEAD^1和HEAD~三个表达式都是代表了HEAD的父提交
    2. reset <commit>的时候,HEAD不变,但是HEAD指向的引用值会变成相应的<commit>值;checkout <commit>的时候,HEAD直接变成<commit>值,但原来引用中保存的值不变。

    ^n和~n的区别

    (<commit>|HEAD)^n,指的是HEAD的第n个父提交(HEAD有多个父提交的情况下),如果HEAD有N个父提交,那么n取值为n < = N.

    (<commit>|HEAD)~n,指的是HEAD的第n个祖先提交,用一个等式来说明就是:(<commit>|HEAD)~n = (<commit>|HEAD)^^^….(^的个数为n).我们通过例子来验证一下吧。

    我们沿用上面演示用的仓库,先检出到master分支,再使用git checkout HEAD^2,看看我们检出了哪个commit

    $ git checkout master
    $ git checkout HEAD^2
    HEAD is now at 063f6e6... c5

    我们发现”c5”对应的commit值063f正是3b03第二个父提交的commit 对应的图如下所示:

    现在再切回master分支,git checkout master

    然后使用git checkout HEAD^3,那么按照规律,就应该检出3b03的第三个父提交的commit,即”c7”的commit值4f9c.

    $ git checkout master
    Previous HEAD position was 063f6e6... c5
    Switched to branch 'master'
    $ git checkout HEAD^3
    HEAD is now at 4f9ca79... c7

    对应的图如下所示:

    果然没错,一切都在我们的预料之中!

    现在验证下HEAD~的用法,切换到master分支,然后git checkout HEAD~2

    $ git checkout master
    $ git checkout HEAD~2
    HEAD is now at 1c7383c... c2

    这时候HEAD悄然来到了”c2”的commit 1c73,因此,HEAD~2 相当于HEAD的第一个父提交的第一个父提交。即HEAD~2 = HEAD^^ = HEAD^1^1, 符合预期!好开心的哟!

    五.总结

    1. “^”代表父提交,当一个提交有多个父提交时,可以通过在”^”后面跟上一个数字,表示第几个父提交,”^”相当于”^1”.
    2. ~<n>相当于连续的<n>个”^”.
    3. checkout只会移动HEAD指针,reset会改变HEAD的引用值。

    现在看到^和~两个符号,再也不会彷徨和害怕了,因为我们知道了它们之间的关系及区别,从此我们过上了幸福的生活。

      

  • 相关阅读:
    Constants and Variables
    随想
    C#基础篇之语言和框架介绍
    Python基础19 实例方法 类方法 静态方法 私有变量 私有方法 属性
    Python基础18 实例变量 类变量 构造方法
    Python基础17 嵌套函数 函数类型和Lambda表达式 三大基础函数 filter() map() reduce()
    Python基础16 函数返回值 作用区域 生成器
    Python基础11 List插入,删除,替换和其他常用方法 insert() remove() pop() reverse() copy() clear() index() count()
    Python基础15 函数的定义 使用关键字参数调用 参数默认值 可变参数
    Python基础14 字典的创建修改访问和遍历 popitem() keys() values() items()
  • 原文地址:https://www.cnblogs.com/biglucky/p/5083121.html
Copyright © 2011-2022 走看看