• 深入理解git版本管理原理


    全手打原创,转载请标明出处:https://www.cnblogs.com/dreamsqin/p/12966398.html, 多谢,=。=~
    (如果对你有帮助的话请帮我点个赞啦)

    日常项目管理中我们最常使用的git命令有addcommitpushpull,但其他不常使用的命令往往容易误操作,所以想深入的学习一下git操作命令底层原理到底是怎么样的,在阮一峰大大的日志里面看到了《Git from the inside out》,全文通过树状图的方式表示各分支节点之间的关系,以示例的方式阐述每种操作命令后底层文件及索引的变化。然而是全英文的,于是乎我只能每天抽点时间来翻译加学习,前前后后经历了一周,终于完成了,大家一起学起来吧。

    git init

    初始化git仓库(该操作会在当前目录下创建一个.git目录,里面可以放git配置或者项目历史记录:.git/objects)。

    例如:
    ~ $ mkdir alpha
    ~ $ cd alpha
    ~/alpha $ mkdir data
    ~/alpha $ printf 'a' > data/letter.txt
    ~/alpha $ printf '1234' > data/number.txt
    ~/alpha $ git init
    

    目录结构如下:

    alpha
    ├── data
    |   └── letter.txt
    |   └── number.txt
    └── .git
        ├── objects
        etc...
    

    .git目录及其内容是git相关的文件,除此之外所有其他文件统称为工作副本,为用户文件。

    git add

    在git仓库中添加一些文件。

    例如:
    ~/alpha $ git add data/letter.txt
    
    第一步:在.git/objects目录中创建一个新的blob文件(创建的blob文件包含data/letter.txt的压缩内容,文件名是由它的内容哈希得到)
    • git将data/letter.txt中的内容ahash计算得到2e65efe2a145dda7ee51d1741299f848e5bf752e,前两个字符被用作对象数据库中的目录名:.git/objects/2e/

    • hash散列值的剩余部分用作blob文件(被添加的文件中需要保存的内容)的名称:.git/objects/2e/65efe2a145dda7ee51d1741299f848e5bf752e

    第二步:将文件添加到索引中
    • 索引是一个列表,其中包含Git要跟踪的每个文件,它以.git/index文件的形式存储,其中每行指向跟踪的blob文件,包含文件内容的hash散列值。例如:data/letter.txt 2e65efe2a145dda7ee51d1741299f848e5bf752e

    • 注意:git add data命令执行,索引中只列出data目录中的文件,并不会列出data目录;

    • 以同样的方法我们将data/number.txt文件添加到git仓库后,当用户修改data/number.txt文件中内容,并重新执行git add命令时,git会根据更新后的内容创建一个新的blob,同时更新data/number.txt的索引条目以指向新的blob。


      例如:

    ~/alpha $ printf '1' > data/number.txt
    ~/alpha $ git add data
    

    git commit

    通过git commit命令创建a1提交。

    ~/alpha $ git commit -m 'a1'
                    [master (root-commit) 774b54a] a1
    
    第一步:创建一个树状图来表示提交的项目版本的内容(git通过索引创建树状图来记录项目的当前状态,这个树状图记录了项目中每个文件的位置和内容)

    树状图由两类对象组成:blobstrees

    • blobs:通过git add存储,表示文件内容;

    • trees:通过git commit存储,表示工作副本中的目录;


      例如:分别对应文件权限、条目类型、blob文件的hash散列值、文件名称;
      100664 blob 2e65efe2a145dda7ee51d1741299f848e5bf752e letter.txt
      100664 blob 56a6051ca2b02b04ef92d5150c9ef600403cb1de number.txt
      040000 tree 0eed1217a2947f4930583229987d90fe5e8e0b74 data

    a1提交的树状图:
    root→data→a(data/letter.txt) and 1(data/number.txt)

    第二步:创建一个提交对象(git commit在创建树状图之后就会创建一个提交对象,提交对象是.git/objects中的另一个文本文件)

    例如:

    tree ffe298c3ce8bb07326f888907996eaa48d266db4
    author Mary Rose Cook <mary@maryrosecook.com> 1424798436 -0500
    committer Mary Rose Cook <mary@maryrosecook.com> 1424798436 -0500
    
    a1
    
    • first line:指向树状图,其中hash散列值由工作副本的根目录生成(也就是alpha);
    • last line:提交信息;

    a1的提交对象,指向它的树状图:
    a1→root→data→a(data/letter.txt) and 1(data/number.txt)

    第三步:将当前分支指向新的提交对象(git在.git/HEAD的头文件中查找当前分支)
    • 例如:ref: refs/heads/master,表明HEAD指向master,所以master为当前分支;

    • 注意:首次提交时master的ref是不存在的,git会创建.git/refs/heads/master,并将其内容设置为提交对象的hash散列:74ac3ad9cde0b265d2b4f1c778b283a6e2ffbafd

    当前分支指向提交对象a1
    HEAD→master→a1→root→data→a(data/letter.txt) and 1(data/number.txt)

    git commit(非初次提交)

    下面为a1提交后的结构图,工作副本及索引已存在,此时三方的data/letter.txtdata/number.txt内容一致:

    修改data/number.txt的内容,工作副本更新:
    ~/alpha $ printf '2' > data/number.txt
    

    执行git add命令,在.git/objects目录中创建一个新的blob文件,并将文件添加到索引中:
    ~/alpha $ git add data/number.txt
    

    执行git commit命令:
    ~/alpha $ git commit -m 'a2'
    

    创建一个新的树状图来表示索引的内容:

    100664 blob 2e65efe2a145dda7ee51d1741299f848e5bf752e letter.txt
    100664 blob d8263ee9860594d2806b0dfd1bfd17528b0ba2a4 number.txt
    040000 tree 40b0318811470aaacc577485777d7a6780e51f0b data

    然后创建一个新的提交对象:

    tree ce72afb5ff229a39f6cce47b00d1b0ed60fe3556
    parent 774b54a193d6cfdd081e581a007d2e11f784b9fe
    author Mary Rose Cook <mary@maryrosecook.com> 1424813101 -0500
    committer Mary Rose Cook <mary@maryrosecook.com> 1424813101 -0500
    
    a2
    
    • first line:指向新的roottree对象;
    • second line:指向a1(为了找到父提交,git转到HEAD,跟着它转到master,最终找到a1提交的hash散列);
    • last line:提交信息;

    将当前分支指向新的提交对象:

    从结构图可以看出以下特性:
    • 文件内容是以对象树存储的。这意味着只有差异会存储在对象数据库中,上图中a2提交时复用了a1提交之前生成的blob(根据内容a生成)。同理,如果整个目录从一个commit到另一个commit时并没有发生改变,那么它的对象树以及下面的所有blobs对象和trees 对象都是可以复用的。通常,从一个commit到另一个commit的内容变更较少,这就是git可以在很小的空间中存储大量提交历史的原因。
    • 每个commit都会有一个parent这意味着一个仓库可以存储一个项目的所有历史记录。
    • refs是入口,指向了commit历史的一部分。每项commit都有自己独特的标识,用户通过类似树状结构的“族谱”将他们的工作组织起来,例如:refs具体为fix-for-bug-376。git则使用特殊的符号例如HEADMERGE_HEADFETCH_HEAD来支持通过用命令行操作提交历史。
    • .git/objects目录下的节点是不变的。也就是说,内容只能被编辑,不能被删除。添加的每个文件内容和创建的每个提交都能在.git/objects目录中找到。
    • refs是可变的。因此,ref的含义可以改变,master所指向的commit可能是目前项目的最佳版本,但是很快,它就会被更新更好的commit所取代。
    • 通过ref指向的工作副本和commit很容易回索,但其他的commit就不是。意思是最近的历史记录更容易找到,但也经常改变。换句话说就是git比较健忘,如果想要查找比较久远的提交记录就需要深度索引。

    git checkout(检出commit)

    通过git checkout命令+a2提交的hash散列值检出a2commit。

    例如:
    ~/alpha $ git checkout 37888c2
              You are in 'detached HEAD' state...
    
    第一步:git获取到a2提交及它所指向的树状图。
    第二步:git将树状图中的文件条目写入工作副本。

    这时内容并不会发生改变。因为此时HEAD就是通过master指向a2提交,所以a2对应的树状图内容也已经被写入工作副本中。

    第三步:git将树图中的文件条目写入索引。

    这也不会导致任何变化。因为索引已经包含了a2提交的内容。

    第四步:HEAD的内容被设置为a2提交的hash散列值。

    例如:f0af7e62679e144bb28c627ee3e8f7bdb235eee9

    通过设置HEAD的内容为hash散列值,会使Head直接指向a2而不是原本的master(仓库将被至于分离的HEAD):

    此时提交的commit很容易丢失。比如修改number.txt文件内容为3并提交修改,git会通过HEAD去获取a3提交的parent,而不是像之前一样利用ref实现跟踪和查找。最终由HEAD直接指向a3的提交对象(仓库仍处于分离的HEAD中,无论是a3还是之后的commit都没有在任何分支上)。

    例如:
    ~/alpha $ printf '3' > data/number.txt
    ~/alpha $ git add data/number.txt
    ~/alpha $ git commit -m 'a3'
              [detached HEAD 3645a0e] a3
    

    树状图结构如下:

    git branch(创建分支)

    通过git branch命令创建一个名为deputy的分支,实际就是在.git/refs/heads/deputy目录下创建了一个新文件,其中包含了HEAD所指向的hash散列值(a3提交的hash散列)。

    ~/alpha $ git branch deputy
    

    树状图结构如下(分支deputy的创建使得a3提交被添加到该分支,安全性就有了,不至于会丢失。但HEAD还是在分离状态,因为它仍然指向了commit):

    git checkout(检出分支)

    通过git checkout命令检出master分支。

    ~/alpha $ git checkout master
              Switched to branch 'master'
    
    第一步:git获取到master指向的a2提交及a2所指向的树状图。
    第二步:git将树状图中的文件条目写入工作副本。

    这时会将data/number.txt文件内容写为2

    第三步:git将树图中的文件条目写入索引。

    这时会将data/number.txt文件的条目更新为2blob文件的hash散列。

    第四步:git将HEAD的内容由hash散列值修改为ref: refs/heads/master,使得HEAD重新指向master

    树状图结构如下:

    git checkout(检出与工作副本不兼容的分支)

    本地修改文件data/number.txt内容后,通过git checkout命令检出deputy分支。

    ~/alpha $ printf '789' > data/number.txt
    ~/alpha $ git checkout deputy
              Your changes to these files would be overwritten
              by checkout:
                data/number.txt
              Commit your changes or stash them before you
              switch branches.
    

    很显然,checkout被git无情拒绝,原因是此时三方的文件内容不一致,必须先解决差异(git如果做覆盖操作会使信息丢失,如果做合并又太复杂):

    • HEAD指向mastermaster指向a2,而a2data/number.txt文件的内容为2
    • deputy指向a3,而a3data/number.txt文件的内容为3
    • 本地工作空间中data/number.txt文件的内容为789

    所以将误修改复原就可以解决了(假设不是误修改,那你需要将修改先提交到原分支):

    ~/alpha $ printf '2' > data/number.txt
    ~/alpha $ git checkout deputy
              Switched to branch 'deputy'
    

    树状图结构如下:

    git merge(合并父分支到子分支)

    通过git merge命令将master分支合并到deputy,合并两个分支其实就是合并两个commit,对于这样的合并git什么也不做。

    ~/alpha $ git merge master
              Already up-to-date.
    

    结构图中一系列的提交其实就是对仓库文件内容做的一系列修改,所以,如果是将父提交合并至子提交,git什么也不做,因为这些改变其实已经被合并了。

    git merge(合并子分支到父分支)

    先将分支切换回master

    ~/alpha $ git checkout master
              Switched to branch 'master'
    

    通过git merge命令将deputy分支合并到master

    ~/alpha $ git merge deputy
              Fast-forward
    

    git获取到子提交及它所指向的树状图,git将树状图中的文件条目写入工作副本和索引,git的fast-forwards操作将master指向了a3(如前面所说的,结构图中一系列的提交其实就是对仓库文件内容做的一系列修改,合并时,如果是将子提交合并至父提交,提交历史是不会改变的,只是合并双方之间差了一些修改,所以最终改变的是被合并分支的指向)。

    git merge(合并非直接关联分支)

    本地修改文件data/number.txt内容为4,并提交为a4master分支:

    ~/alpha $ printf '4' > data/number.txt
    ~/alpha $ git add data/number.txt
    ~/alpha $ git commit -m 'a4'
              [master 7b7bd9a] a4
    

    切换至deputy分支,本地修改文件data/letter.txt内容为b,并提交为b3deputy分支:

    ~/alpha $ git checkout deputy
              Switched to branch 'deputy'
    ~/alpha $ printf 'b' > data/letter.txt
    ~/alpha $ git add data/letter.txt
    ~/alpha $ git commit -m 'b3'
              [deputy 982dffb] b3
    

    从结构图可以看出以下特性:

    • commit可以共享parent。所以在提交历史中可以创建新的“族谱”。
    • commit可以包含多个parent所以不同的“族谱”可以通过一个包含两个parentcommit来连接(commit有两个parent的情况是通过merge实现)。

    例如:将master分支合并至deputy分支(由于git发现这两个分支对应的commit属于不同的“族谱”,所以需要合并commit,总共分为8步)

    ~/alpha $ git merge master -m 'b4'
              Merge made by the 'recursive' strategy.
    
    第一步:git将giver commit(也就是a4)的hash散列值写入alpha/.git/MERGE_HEAD文件

    这个文件的存在就是告诉git正在做合并操作。

    第二步:git查找base commit

    receiver commit(也就是b3)和giver commit(也就是a4)在历史记录中最近的共同祖先(通俗的说:两个“族谱”分道扬镳的节点,也就是a3)。

    第三步:git基于树状图为base commitreceiver commitgiver commit生成索引
    第四步:git生成一个diff

    diff可以理解为差异文件,其中组合了receiver commitgiver commitbase commit的更改,diff是一个指向被修改文件的路径列表(文件修改包括:add、remove、modify、conflict)。

    git通过获取出现在base commitreceiver commitgiver commit索引中的所有文件列表,对于每一个都进行比较,确定文件被修改后就向diff写入一个对应的条目,在本例中,diff有两个条目。

    • 一个是data/letter.txtbase commit中是areceiver commit中是bgiver commit中是a。git可以看到内容是由
      receiver修改的,而不是giver,所以diffdata/letter.txt对应的条目是一个modify,而不是conflict。
    • 另一个是data/number.txtbase commit中是3receiver commit中是3giver commit中是4,所以diffdata/number.txt对应的条目也是一个modify。
    第五步:git将diff中的修改应用于工作副本

    data/letter.txt中的内容被设置成bdata/number.txt中的内容被设置成4

    第六步:git将diff中的修改写入索引

    data/letter.txt对应的条目指向b的blob文件,data/number.txt对应的条目指向4的blob文件。

    第七步:git提交更新后的索引

    可以看到此时的提交就有两个parent

    tree 20294508aea3fb6f05fcc49adaecc2e6d60f7e7d
    parent 982dffb20f8d6a25a8554cc8d765fb9f3ff1333b
    parent 7b7bd9a5253f47360d5787095afc5ba56591bfe7
    author Mary Rose Cook <mary@maryrosecook.com> 1425596551 -0500
    committer Mary Rose Cook <mary@maryrosecook.com> 1425596551 -0500
    
    b4
    
    第八步:git将当前分支deputy指向最新的提交b4(将a4合并到b3的递归合并结果)

    git merge(合并非直接关联分支,且修改了相同文件)

    先切换至master分支,将deputy分支合并至master分支(也就是前面的将子分支合并到父分支,其实只是修改了master分支的commit指向):

    ~/alpha $ git checkout master
              Switched to branch 'master'
    ~/alpha $ git merge deputy
              Fast-forward
    

    此时切换至deputy分支,本地修改文件data/number.txt内容为5,并提交为b5deputy分支:

    ~/alpha $ git checkout deputy
              Switched to branch 'deputy'
    ~/alpha $ printf '5' > data/number.txt
    ~/alpha $ git add data/number.txt
    ~/alpha $ git commit -m 'b5'
              [deputy bd797c2] b5
    

    然后切换至master分支,本地修改文件data/number.txt内容为6,并提交为b6master分支:

    ~/alpha $ git checkout master
              Switched to branch 'master'
    ~/alpha $ printf '6' > data/number.txt
    ~/alpha $ git add data/number.txt
    ~/alpha $ git commit -m 'b6'
              [master 4c3ce18] b6
    

    最终,将deputy分支合并到master分支,很显然被git拒绝,原因是data/number.txt文件中的内容冲突了,自动合并失败:

    ~/alpha $ git merge deputy
              CONFLICT in data/number.txt
              Automatic merge failed; fix conflicts and
              commit the result.
    

    整个过程与上面合并时的前6步是一致的:alpha/.git/MERGE_HEAD文件设置;查找base commit;为base commitreceiver commitgiver commit生成索引;生成diff文件;将diff中的修改应用于工作副本;git将diff中的修改写入索引;但是由于冲突第7步的提交和第8步的ref更新不能正常执行。

    下面详细说明一下前面6步到底发生了什么导致最终的结果:

    第一步:git将giver commit(也就是b5)的hash散列值写入alpha/.git/MERGE_HEAD文件

    同样的,这个文件的存在就是告诉git正在做合并操作。

    第二步:git查找base commit

    receiver commit(也就是b6)和giver commit(也就是b5)在历史记录中最近的共同祖先(通俗的说:两个“族谱”分道扬镳的节点,也就是b4)。

    第三步:git基于树状图为base commitreceiver commitgiver commit生成索引
    第四步:git生成一个diff

    diff可以理解为差异文件,其中组合了receiver commitgiver commitbase commit的更改,diff是一个指向被修改文件的路径列表(文件修改包括:add、remove、modify、conflict)。

    git通过获取出现在base commitreceiver commitgiver commit索引中的所有文件列表,对于每一个都进行比较,确定文件被修改后就向diff写入一个对应的条目,在本例中,diff只有一个条目。

    • 也就是data/number.txtbase commit中是4receiver commit中是6giver commit中是5。条目被标记为conflict,因为data/number.txt的内容在receivergiverbase中是不同的。
    第五步:git将diff中的修改应用于工作副本

    对于冲突的部分,git会将两个版本都写入到工作副本的文件中。data/number.txt中的内容被设置成:

    <<<<<<< HEAD
    6
    =======
    5
    >>>>>>> deputy
    
    第六步:git将diff中的修改写入索引

    索引中的条目由其文件路径和stage共同组成唯一标识,对于没有冲突的文件,stage为0。合并前的索引如下(前面的0就是stage):

    0 data/letter.txt 63d8dbd40c23542e740659a7168a0ce3138ea748
    0 data/number.txt 62f9457511f879886bb7728c986fe10b0ece6bcb
    

    合并的diff被写入索引后:

    0 data/letter.txt 63d8dbd40c23542e740659a7168a0ce3138ea748
    1 data/number.txt bf0d87ab1b2b0ec1a11a3973d2845b42413d9767
    2 data/number.txt 62f9457511f879886bb7728c986fe10b0ece6bcb
    3 data/number.txt 7813681f5b41c028345ca62a2be376bae70b7f61
    

    stage为0的data/letter.txt条目与合并之前的条目相同,但是stage为0的data/number.txt条目已经没有了,在该位置上新增了3个新的条目。stage1条目包含了basedata/number.txt)内容的hash散列,stage2条目包含了receiverdata/number.txt)内容的hash散列,stage3条目包含了giverdata/number.txt)内容的hash散列。这三个条目的存在就是在告诉gitdata/number.txt是冲突的,所以合并就被中断了。

    此时,如果用户通过将data/number.txt的内容设置为11来整合两个冲突版本的内容,并通过git add将文件添加至索引中:

    ~/alpha $ printf '11' > data/number.txt
    ~/alpha $ git add data/number.txt
    

    整体过程就是:git add命令创建了一个包含11的blob文件,该操作就是就是告诉git冲突解决了,此时git就会从索引中移除stage为1、2、3的条目,并使用新blob文件的散列为data/number.txt添加一个stage为0的条目:

    0 data/letter.txt 63d8dbd40c23542e740659a7168a0ce3138ea748
    0 data/number.txt 9d607966b721abde8931ddd052181fae905db503
    
    第七步:用户通过git commit命令提交最新修改
    ~/alpha $ git commit -m 'b11'
              [master 251a513] b11
    

    git在仓库中看到.git/MERGE_HEAD,就知道合并正在进行中,它会检查索引并查看是否存在冲突,如果没有就会创建一个新的提交b11来记录已解决的合并内容,然后删除.git/MERGE_HEAD中的文件,此时合并就完成了。

    第八步:git将当前分支master指向新的提交。

    git rm(删除文件)

    下面是当前状态下最新的结构图:

    通过git rm命令删除data/letter.txt文件,文件首先会从本地的工作副本移除,接着文件条目会从索引中移除:

    ~/alpha $ git rm data/letter.txt
              rm 'data/letter.txt'
    

    此时结构图就变成了:

    通过git commit命令提交变更:

    ~/alpha $ git commit -m '11'
              [master d14c7d2] 11
    

    和之前一样,作为提交的一部分,git会构建一个表示索引内容的树状图,data/letter.txt不包括在树图中,因为它不在索引中。

    复制仓库

    ~/alpha $ cd ..
          ~ $ cp -R alpha bravo
    

    用户将alpha/仓库的内容复制到bravo/目录,目录结构就变成了:

    ~
    ├── alpha
    |   └── data
    |       └── number.txt
    └── bravo
        └── data
            └── number.txt
    

    而此时bravo/目录中也会有一个与之对应的git结构图:

    建立两个仓库的链接

    用户首先返回到alpha仓库:

    ~ $ cd alpha
    ~/alpha $ git remote add bravo ../bravo
    

    如果要将bravo设置为alpha的远程仓库,需要在alpha/.git/config文件中添加一些代码:

    [remote "bravo"]
        url = ../bravo/
    

    指定在../bravo目录中有一个名为bravo的远程仓库。

    从远程仓库上fetch分支

    用户首先进入bravo仓库,将data/number.txt的内容设置为12,并将修改提交给bravo上的master

    ~/alpha $ cd ../bravo
    ~/bravo $ printf '12' > data/number.txt
    ~/bravo $ git add data/number.txt
    ~/bravo $ git commit -m '12'
              [master 94cd04d] 12
    

    此时结构图如下:

    然后用户进入alpha仓库,想要把分支masterbravo取过来:

    ~/bravo $ cd ../alpha
    ~/alpha $ git fetch bravo master
              Unpacking objects: 100%
              From ../bravo
                * branch master -> FETCH_HEAD
    

    这个过程git有四个步骤:

    第一步:获取masterbravo上所指向的commit的散列

    也就是12 commit提交的散列。

    第二步:将12 commit依赖的所有对象(去除alpha仓库中已存在的)复制到alpha/.git/objects/

    包括提交对象本身、树图中指向的对象、12 commit的父提交以及它在树图中指向的对象。

    第三步:alpha/.git/refs/remotes/bravo/master中的ref被设置成12 commit提交的散列值
    第四步:alpha/.git/FETCH_HEAD的内容被设置成:
    94cd04d93ae88a1f53a4646532b1e8cdfbc0977f branch 'master' of ../bravo
    

    这表明刚刚的fetch命令从bravo获取了master12 commit,此时结构图就变成了:

    从结构图可以看出以下特性:

    • 对象是可以被拷贝的。也就是说历史记录可以在仓库之间共享。
    • 一个仓库可以存储远程仓库分支的ref,例如alpha/.git/refs/remotes/bravo/master。这意味着一个仓库可以在本地记录远程仓库上分支的状态。它在获取时是正确的,但是如果远程分支发生更改,它就会过期。

    合并FETCH_HEAD

    用户通过git merge命令合并FETCH_HEAD

    ~/alpha $ git merge FETCH_HEAD
              Updating d14c7d2..94cd04d
              Fast-forward
    

    FETCH_HEAD只是一个ref,它解析为12 commitgiver),HEAD指向11 commitreceiver)。git执行合并后将指向master12 commit

    从远程仓库上pull分支

    用户将master分支从bravo pullalphapull是“fetch FETCH_HEAD和 merge FETCH_HEAD”的缩写,所以最终git执行两个命令并反馈master已经是最新的了。

    ~/alpha $ git pull bravo master
              Already up-to-date.
    

    clone仓库

    用户移动到上层目录并clone alphacharlie

    ~/alpha $ cd ..
          ~ $ git clone alpha charlie
              Cloning into 'charlie'
    

    clonecharlie的结果与之前用户为了生成bravo仓库所使用的cp类似,git创建一个名为charlie的新目录,并将它初始化为git仓库,将alpha作为一个名为origin的远程仓库,fetch origin并合并FETCH_HEAD

    将分支push到从远程仓库中checkout的分支上

    用户回到alpha仓库,修改data/number.txt的值为13并将修改提交到master分支。

         ~ $ cd alpha
    ~/alpha $ printf '13' > data/number.txt
    ~/alpha $ git add data/number.txt
    ~/alpha $ git commit -m '13'
              [master 3238468] 13
    

    charlie设置为alpha的远程仓库:

    ~/alpha $ git remote add charlie ../charlie
    

    master分支pushcharlie

    ~/alpha $ git push charlie master
              Writing objects: 100%
              remote error: refusing to update checked out
              branch: refs/heads/master because it will make
              the index and work tree inconsistent
    

    13 commit关联的所有对象将被复制到charlie。但从上面的命令行反馈可以看到,push过程被中断,git拒绝push到远程检出(checkout)的分支。其实这是可以理解的,因为这样的push将更新远程索引和HEAD,如果有人正在编辑远程上的工作副本,就会导致混乱。

    此时,用户可以创建一个新分支,将13 commit合并到其中,并将该分支pushcharlie。但实际上,我们是想要一个可以随时可以push的仓库,一个中央存储库,用于pushpull,但是没有人直接进行commit提交,类似GitHub的远程仓库,想要一个裸(bare)存储库。

    clone一个裸(bare)仓库

    用户进入到上层目录,clone delta作为裸仓库:

    ~/alpha $ cd ..
          ~ $ git clone alpha delta --bare
              Cloning into bare repository 'delta'
    

    跟普通的clone有两个不同之处,首先config文件表明存储库是裸仓库,而原本存储在.git目录下的文件则存储在仓库的根目录下:

    delta
    ├── HEAD
    ├── config
    ├── objects
    └── refs
    

    此时的结构图如下:

    将分支push到裸(bare)仓库

    用户回到alpha仓库并设置delta作为它的远程仓库:

     ~ $ cd alpha
    ~/alpha $ git remote add delta ../delta
    

    修改data/number.txt的值为14并将修改提交到master分支:

    ~/alpha $ printf '14' > data/number.txt
    ~/alpha $ git add data/number.txt
    ~/alpha $ git commit -m '14'
              [master cb51da8] 14
    

    提交后结构图如下:

    接下来将master pushdelta

    ~/alpha $ git push delta master
              Writing objects: 100%
              To ../delta
                3238468..cb51da8 master -> master
    

    整个过程有3步:

    第一步:从alpha/.git/objects/拷贝14 commit提交相关的所有对象至delta/objects/
    第二步:delta/refs/heads/master的指向更新为14 commit
    第三步:alpha/.git/refs/remotes/delta/master的指向更新为14 commitalpha拥有了delta状态的最新记录

    现在的结构图如下:

    参考文献

    Git from the inside out:https://codewords.recurse.com/issues/two/git-from-the-inside-out

  • 相关阅读:
    matlab练习程序(单源最短路径Dijkstra)
    Android开发必须知道SERVICE的10件事
    Android 多种方式正确的加载图像,有效避免oom
    在Android中解决内存溢出 – OutOfMemoryError
    发布Android开源库,看这个文章就够了!
    发掘StateListAnimator的全部潜能
    Android开发中多进程共享数据
    Android使用FFMpeg实现推送视频直播流到服务器
    Android学Jni/Ndk 开发记录(一)
    一张图解释RxJava中的线程控制
  • 原文地址:https://www.cnblogs.com/dreamsqin/p/12966398.html
Copyright © 2020-2023  润新知