深入理解git

之前用过git,感觉很神奇。很好用之际,膜拜linux,他能迅速写出这么给力的版本控制系统而我不能理解其原理,惭愧之极。

今儿偶然看到一篇国外网友写的文章介绍git原理,看完茅塞顿开。遂翻译之,以记之!

原文地址:http://maryrosecook.com/blog/post/git-from-the-inside-out

=================================================================================================================================

step by step!

1、新建文件夹,应用git初始化之

~ $ mkdir alpha
~ $ cd alpha
~/alpha $ mkdir data
~/alpha $ printf 'a' > data/letter.txt
~/alpha $ git init
          Initialized empty Git repository

现在alpha文件夹是这个样子滴:

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

.git文件夹就是git生成的,以后git干的所有事儿都在这里头。


2、添加一些文件

~/alpha $ git add data/letter.txt

用户使用git add命令将data/letter.txt添加到git中。这会产生两个效果:

首先在.git/objects/文件夹中创建一个新的二进制文件。

这个二进制文件就是data/letter.txt文件中的内容。这个二进制文件的名字是由其内容经过哈希运算而来。比如,文件中的内容是a,那么git将a哈希运算得到2e65efe2a145dda7ee51d1741299f848e5bf752e这一串字符,它是唯一的。这个二进制文件就放在.git/object/2e/文件夹下面。注意到2e/这个文件夹名,它取自那一长串哈希名字的前两个字母,剩下的字符串才是正宗的二进制文件名:.git/objects/2e/65efe2a145dda7ee51d1741299f848e5bf752e.

上面已经说了,通过git add 命令git已经把文件内容存放在/objects文件夹中,所以这时候用户删除了工作区的/data/letter.txt也不会影响git中/objects中的二进制文件。

其次,git add命令为文件建立索引。索引列表包含了所有git所跟踪的文件。索引文件放在.git/index中。索引文件每一行映射一个文件内容的哈希值(就是刚才说的那个哈希值)。通过git add后,index文件中添加的一行看起来这个样子:

data/letter.txt 2e65efe2a145dda7ee51d1741299f848e5bf752e

now, 用户又新建文件:
~/alpha $ printf '1234' > data/number.txt

现在文件夹结构如下:

alpha
└── data
    └── letter.txt
    └── number.txt

git add一下:

~/alpha $ git add data

同样,git为number.txt新建二进制文件,然后添加对应索引。现在索引文件有两行了:

data/letter.txt 2e65efe2a145dda7ee51d1741299f848e5bf752e
data/number.txt 274c0052dd5408f8ae2bc8440029ff67d79bc5c3

当用户改变number.txt中的内容,再次git add,那么同样地,根据新内容新建二建文件,更新index文件中data/number.txt行对应的哈希值。


3、提交

~/alpha $ git commit -m 'a1'
          [master (root-commit) 774b54a] a1

用户提交了一个a1提交。commit命令三步走:

 1.建立树图用来表示提交版本的内容。

 2.建立commit对象。

 3.指向commit时的分支。

* 建立树图

git通过索引建立树图来表示当前项目状态,记录着项目的每个文件的位置和内容。该图有两种类型:二进制文件和树。其中二进制文件就是上面所说的。树是commit命令时生成的。树表示了工作区的目录。

下面这个树对象记录着刚才commit所提交的data/目录中的内容:

100664 blob 2e65efe2a145dda7ee51d1741299f848e5bf752e letter.txt
100664 blob 56a6051ca2b02b04ef92d5150c9ef600403cb1de number.txt

来看第一行,第一部分表示文件的权限。第二部分表示文件的类型是二进制文件。第三部分表示二进制二进制文件的哈希值。第四部分是文件名。

下面这个树对象记录着刚才commit所提交的alpha/目录中的内容:

040000 tree 0eed1217a2947f4930583229987d90fe5e8e0b74 data

这行的第二部分tree表示这是一个树对象,指向刚才解释的那两行。第四部分的data其实就是上面所说的data/目录。

* 建立commit对象

git commit 经过上面的建图一步后,下一步就是建commit对象了。这个commit Object就是一个text文件,放在.git/objects/ 中:

tree ffe298c3ce8bb07326f888907996eaa48d266db4
author Mary Rose Cook <mary@maryrosecook.com> 1424798436 -0500
committer Mary Rose Cook <mary@maryrosecook.com> 1424798436 -0500

a1

a1提交指向了它的树图:


经过前三步,最后就是指向当前分支


什么是当前分支?git在.git/HEAD目录中找到HEAD文件,里面内容:

ref: refs/heads/master

就是说HEAD指向master。master是当前分支。

HEAD和master都是refs。一个ref是git的标签,用来标示用户的commit。

刚才已经创建了提交,所以在文件.git/refs/heads/master中生成一个commit Object对象a1的哈希值:

74ac3ad9cde0b265d2b4f1c778b283a6e2ffbafd

现在有了HEAD和master的git图如下:


再次提交


讲过第一次提交后,工作区文件和git管理的文件如下图:


当用户修改/data/number.txt中的内容为2,git中的index和HEAD所管理的文件并不会改变,因为这只是在工作区中的修改。所以图是这样:



好,下面git add一下:

~/alpha $ git add data/number.txt

根据上面已经讲解的,创建对应二进制文件,添加索引。那么图就是这样地:


提交以下,命名为a2:

~/alpha $ git commit -m 'a2'
          [master f0af7e6] a2

根上面讲解一样,commit分三步走:

第一步

先建树图用来表示索引中的内容。在index文件中,对应data/number.txt那一行已经改变了。所以代表data那个树应该跟着新建:

100664 blob 2e65efe2a145dda7ee51d1741299f848e5bf752e letter.txt
100664 blob d8263ee9860594d2806b0dfd1bfd17528b0ba2a4 number.txt

data树改变了,那么alpha树也跟着变,哈希值重新计算:

040000 tree 40b0318811470aaacc577485777d7a6780e51f0b data

第二步

建立新的commit Object

tree ce72afb5ff229a39f6cce47b00d1b0ed60fe3556
parent 774b54a193d6cfdd081e581a007d2e11f784b9fe
author Mary Rose Cook <mary@maryrosecook.com> 1424813101 -0500
committer Mary Rose Cook <mary@maryrosecook.com> 1424813101 -0500

a2

第一行的提交对象利用新的哈希值指向新的alpha树。第二行指向a1提交,也就是当前提交的父提交。为了找到父提交,git在HEAD中找到当前分支master,然后再master文件中找到a1的哈希值。


第三步

当前分支指向当前提交


4、检出一个提交

~/alpha $ git checkout 37888c2
          You are in 'detached HEAD' state...

git checkout后面跟的是某次提交的哈希值。该哈希值可以使用git log 查找出来。

在这个checkout过程中有四个步骤:1、git通过哈希值获得a2提交,根据a2提交得到其指向的树图。2、将树图中的文件实体写入到工作区中。3、git将树图中的文件实体写入到index文件中。4、HEAD指向a2 commit。

将HEAD直接指向某个提交的哈希值,会使仓库处于HEAD游离态。注意下面这个图的HEAD直接指向a2 commit,而不再直向master.

现在进行如下操作:

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

用户将number.txt的内容改为3,让后提交改变。git通过HEAD找到a3提交的父提交a2。然后更新HEAD使其指向新提交a3.现在仓库仍然处于游离态,因为HEAD没有指向一个分支(比如master)。现在的状态如下图:


5、建立新分支


~/alpha $ git branch deputy

用户新建分支,名为deputy。在这个过程中git其实仅仅生成了一个新文件.git/refs/heads/deputy,这个文件的内容是当前HEAD指向的a3提交的哈希值。

由此可以看出,git新建分支是多么的轻松。国外网友称作lightweight。

HEAD仍然处于游离态,因为HEAD还是直接指向a3 commit。


6、切换到一个分支

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

用户切换到master分支上。这个过程分四步走:1、git通过master找到a2提交,得到a2所指的树图。2、git将树图中包含的文件全部写入到对应工作区中,这会使data/number.txt的内容变为2。3、更新索引文件,这会使index文件中的data/number.txt对应的项的哈希值有所改变,变为内容为2的二进制哈希值。4、git将HEAD指向master,其实就是将master文件的内容变为:

ref: refs/heads/master


7、合并分支。

这里说的合并分支一般指的是从子孙分支向祖先分支合并。如果反过来,git什么都不会做的。

~/alpha $ git merge deputy
          Fast-forward

向master分支中合并deputy分支。git发现a2是a3的祖先,就进行快速合并:将a3的树图中的文件写入到工作区中,更新index,将master指向a3。


8、合并两个来自不同分支的提交

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

这是在master分支上的一个提交。

~/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

这是在deputy分支的一个提交。

图就是这个样子的:


~/alpha $ git merge master -m 'b4'
          Merge made by the 'recursive' strategy.

这个过程有点复杂,但是跟前面讲过的原理差不多,只不过步数多了一些。直接看图吧:

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

~/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

~/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

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

出现冲突:

<<<<<<< HEAD
6
=======
5
>>>>>>> deputy

这时,索引文件看起来这个样子:

0 data/letter.txt 63d8dbd40c23542e740659a7168a0ce3138ea748
1 data/number.txt bf0d87ab1b2b0ec1a11a3973d2845b42413d9767
2 data/number.txt 62f9457511f879886bb7728c986fe10b0ece6bcb
3 data/number.txt 7813681f5b41c028345ca62a2be376bae70b7f61
由于冲突(多个版本)造成了/data/number.txt有多个索引


处理冲突:

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

现在index文件就好了:

0 data/letter.txt 63d8dbd40c23542e740659a7168a0ce3138ea748
0 data/number.txt 9d607966b721abde8931ddd052181fae905db503

~/alpha $ git commit -m 'b11'
          [master 251a513] b11


9、删除文件


下面这个图包括了提交历史,树,二进制文件,最后一次提交,工作区和索引


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


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


10、连接运程仓库

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

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



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

这个过程大致是:将12的二进制文件拷贝到alpha/.git/objects/目录下,设置ref文件alpha/.git/refs/remotes/bravo/master的内容为12这个提交的哈希值,并且设置文件alpha/.git/FETCH_HEAD的内容为:

94cd04d93ae88a1f53a4646532b1e8cdfbc0977f branch 'master' of ../bravo

这行表示通过fetch命令获得的12提交是从bravo得到的。


11、合并FETCH_HEAD


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


  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值