日常项目管理中我们最常使用的git命令有add、commit、push、pull,但其他不常使用的命令往往容易误操作,所以想深入的学习一下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通过索引创建树状图来记录项目的当前状态,这个树状图记录了项目中每个文件的位置和内容)
树状图由两类对象组成:blobs和trees
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 1424798436 -0500
committer Mary Rose Cook 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.txt 和data/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 1424813101 -0500
committer Mary Rose Cook 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则使用特殊的符号例如HEAD、MERGE_HEAD、FETCH_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指向master,master指向a2,而a2中data/number.txt文件的内容为2;
deputy指向a3,而a3中data/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,并提交为a4至master分支:
~/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,并提交为b3至deputy分支:
~/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。所以不同的“族谱”可以通过一个包含两个parent的commit来连接(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 commit、receiver commit、giver commit生成索引
第四步:git生成一个diff
diff可以理解为差异文件,其中组合了receiver commit和giver commit对base commit的更改,diff是一个指向被修改文件的路径列表(文件修改包括:add、remove、modify、conflict)。
git通过获取出现在base commit、receiver commit、giver commit索引中的所有文件列表,对于每一个都进行比较,确定文件被修改后就向diff写入一个对应的条目,在本例中,diff有两个条目。
一个是data/letter.txt,base commit中是a,receiver commit中是b,giver commit中是a。git可以看到内容是由
receiver修改的,而不是giver,所以diff中data/letter.txt对应的条目是一个modify,而不是conflict。
另一个是data/number.txt,base commit中是3,receiver commit中是3,giver commit中是4,所以diff中data/number.txt对应的条目也是一个modify。
第五步:git将diff中的修改应用于工作副本
data/letter.txt中的内容被设置成b,data/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 1425596551 -0500
committer Mary Rose Cook 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,并提交为b5至deputy分支:
~/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,并提交为b6至master分支:
~/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 commit、receiver commit、giver 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 commit、receiver commit、giver commit生成索引
第四步:git生成一个diff
diff可以理解为差异文件,其中组合了receiver commit和giver commit对base commit的更改,diff是一个指向被修改文件的路径列表(文件修改包括:add、remove、modify、conflict)。
git通过获取出现在base commit、receiver commit、giver commit索引中的所有文件列表,对于每一个都进行比较,确定文件被修改后就向diff写入一个对应的条目,在本例中,diff只有一个条目。
也就是data/number.txt,base commit中是4,receiver commit中是6,giver commit中是5。条目被标记为conflict,因为data/number.txt的内容在receiver、giver和base中是不同的。
第五步: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条目包含了base(data/number.txt)内容的hash散列,stage2条目包含了receiver(data/number.txt)内容的hash散列,stage3条目包含了giver(data/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仓库,想要把分支master从bravo取过来:
~/bravo $ cd ../alpha
~/alpha $ git fetch bravo master
Unpacking objects: 100%
From ../bravo
* branch master -> FETCH_HEAD
这个过程git有四个步骤:
第一步:获取master在bravo上所指向的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获取了master的12 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 commit(giver),HEAD指向11 commit(receiver)。git执行合并后将指向master→12 commit:
从远程仓库上pull分支
用户将master分支从bravo pull到alpha,pull是“fetch FETCH_HEAD和 merge FETCH_HEAD”的缩写,所以最终git执行两个命令并反馈master已经是最新的了。
~/alpha $ git pull bravo master
Already up-to-date.
clone仓库
用户移动到上层目录并clone alpha到charlie:
~/alpha $ cd ..
~ $ git clone alpha charlie
Cloning into 'charlie'
clone到charlie的结果与之前用户为了生成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分支push到charlie:
~/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合并到其中,并将该分支push到charlie。但实际上,我们是想要一个可以随时可以push的仓库,一个中央存储库,用于push和pull,但是没有人直接进行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 push到delta:
~/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 commit,alpha拥有了delta状态的最新记录
现在的结构图如下:
参考文献