GIT科普系列5:index in git【转】

来源:互联网 发布:毕向东java笔记 编辑:程序博客网 时间:2024/05/16 04:29

http://blog.csdn.net/zssureqh/article/details/53056095

背景:

git的使用其实没有想象中的那么复杂,平日里真正使用的指令都是极少数、最简单格式的。之所以使用过程中感觉操作复杂、冲突不断,究其根源是对git的设计理念缺乏了解,没有搞清楚git实现版本控制的巧妙之处。之前GIT科普系列试图从底层机制来入手讲解GIT的使用,抱着“授人以鱼不如授人以渔”的心态,但是对于希望快速学习和掌握GIT使用方法的读者来说可能收效甚微。如何才能有效、清晰的讲清楚GIT的使用,我也一直在思考,不过还没有理出头绪。但“实践出真知”是放之四海而皆准的准则,即使我讲的再透彻也需要使用者在平日里多加练习。本文介绍一下git设计的巧妙之处——index文件,即通常所说的缓冲区

index的生成&解析

index文件在初始仓库创建时是不存在的,需要新增文件并git add之后才会首次生成,所以说index称之为缓冲区的说法还有待商榷(待有时间查看详细源码和实现机制后再补充,这里就不喷了)。生成index文件的具体步骤如下: 
1)git init 创建本地中央仓库 
这里写图片描述
2)添加任意一个文本文件 

这里写图片描述

3)git add 添加到缓冲区 
这里写图片描述

4)查看index文件 
随后发现.git仓库目录下新多出了一个index二进制文件,使用UE打开如下: 
这里写图片描述
从上图可以看出index文件比较简单,大概在100字节左右,具体信息如下:

00000000h: 44 49 52 43 00 00 00 02 00 00 00 01 58 1E A5 A2 ; DIRC........X.ア00000010h: 17 43 89 F8 58 1E A5 C4 07 DA B8 88 00 00 00 00 ; .C夬X.ツ.诟?...00000020h: 00 00 00 00 00 00 81 A4 00 00 00 00 00 00 00 00 ; ......仱........00000030h: 00 00 00 21 BA E6 DA 35 08 38 71 84 97 CB F9 07 ; ...!烘?.8q剹所.00000040h: 19 EF B7 A9 99 BF F3 44 00 12 67 69 74 2D 72 65 ; .锓矿D..git-re00000050h: 70 6F 2D 6C 6F 63 61 6C 2E 74 78 74 00 00 00 00 ; po-local.txt....00000060h: 00 00 00 00 81 6B FF 12 C1 43 63 02 61 70 C4 3B ; ....乲.罜c.ap?00000070h: FF 49 DE 81 0C 62 56 48                         ; I迊.bVH
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

先简单的介绍几个字节的含义(详情参见官方文档git index format: 
- 44 49 52 43:DIRC=dircached,是index文件的标记,即index类型文件的魔数 
- 00 00 00 02:index文件的版本号,这里是2.0 
- 00 00 00 01:这里记录的是index文件内部缓冲的记录(entry)数目 
- …… 
具体到我们刚才生成的index文件,如下图所示: 

这里写图片描述

5)与此同时在.git仓库的objects目录下也多出了一个子目录,内部新增了一个二进制文件(这个是BLOB对象) 
这里写图片描述
6)git commit提交

发现index新增了一个TREE条目。该条目内容如下:

00000000h: 44 49 52 43 00 00 00 02 00 00 00 01 58 1E A5 A2 ; DIRC........X.ア00000010h: 17 43 89 F8 58 1E A5 C4 07 DA B8 88 00 00 00 00 ; .C夬X.ツ.诟?...00000020h: 00 00 00 00 00 00 81 A4 00 00 00 00 00 00 00 00 ; ......仱........00000030h: 00 00 00 21 BA E6 DA 35 08 38 71 84 97 CB F9 07 ; ...!烘?.8q剹所.00000040h: 19 EF B7 A9 99 BF F3 44 00 12 67 69 74 2D 72 65 ; .锓矿D..git-re00000050h: 70 6F 2D 6C 6F 63 61 6C 2E 74 78 74 00 00 00 00 ; po-local.txt....00000060h: 00 00 00 00 54 52 45 45 00 00 00 19 00 31 20 30 ; ....TREE.....1 000000070h: 0A 3F E3 4E 65 BF 48 2D DD B5 1E 89 DF 23 5C 7F ; .?鉔e縃-莸.夁#\00000080h: DE 7C 0C C5 E8 16 DC 3E 0F 66 D6 32 8A 3F 68 25 ; 迀.盆.?.f??h%00000090h: A0 5B DA 1D EE E0 B0 A8 C1                      ; 燵?钹皑?
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

与此同时在objects目录下新增了两个子目录,其内容如下: 
这里写图片描述
【备注】:这里使用了git自带的git cat-file和git hash-object指令,详情可参见官网。这里可以简单的理解为查看和写入git基于内容检索文件系统的工具,其中git cat-file是查看已写入记录的内容和类型(类似于解密);git hash-object是查看当前文件写入git文件系统(即所谓的版本库)内部后真实内容。

对比看一下index中新增的TREE对象,其实就是object存储TREE对象的目录和文件名的拼接(或者说object内部对象的归档模式就是将SHA-1码拆分得来的) 
这里写图片描述

index的传输

了解了index的基本格式和存储内容之后,我们看一下index在分布式版本管理仓库之前是如何传输的。 
在本地clone一个上文创建的本地仓库,git clone 可以直接输入路径来实现仓库的克隆,这与我们常见的通过url来实现本质是一样的,都是拷贝.git文件夹内的相关内容。 
具体操作如下图: 
这里写图片描述
.git 目录下的所有内容已经顺利拷贝到了新的目录,并且已经将git-repo-local.txt文件checkout到了新的work tree目录下(之前博文介绍过git clone是复合指令,相当于git fetch + git merge) 
我们对比一下两个版本库中的index文件实际内容: 
这里写图片描述
除了记录的文件头修改时间和文件内容修改时间不同之前,其他信息完全一致。所以我们可以看出在clone版本库时,实际就是传输的.git目录下的内容

上面提到的是最简单的index文件的传输,默认是一个干净的git仓库,但是使用过程中git仓库所处的环境会变化,每个环境下index的传输也会不同,接下来让我们再看一下不同状态下index的传输:

1)clone,运行git add后的仓库

这里写图片描述

在远程仓库中运行git add指令后(此时不运行commit指令,在实际使用过程中强烈建议大家不要通过git commit -a将git add和git commit合并使用),会在.git目录下新增一个blob对象。如上图所示index中entry的数据由1变成了2;第二条entry记录记录了新增的文件。与此同时相较于没有运行git add指令时刻相比,后续的TREE条目也发生了改变,应该是一条新的TREE记录(猜测应该是一个临时的TREE对象,但此时并未保存到.git目录中)。此刻通过git clone拷贝后的新的仓库中,index文件依然是远程仓库中最后一次commit后的状态,虽然.git目录中已经将远程仓库中git add后的文件拷贝过来了,但是却并没有更新到index中,因此在git clone后的新仓库中我们是无法查看到新增的文件的。

但是上文提到过git基于内容检索文件系统的两个指令git cat-filegit hash-object。既然远程仓库中git add后的文件已经记录到了文件系统内部(如上图的.git目录下的5c文件夹),那么我们尝试手动复原一下看看: 
这里写图片描述
由此可以看出,git add的内容我们也是可以获取到的。

2)clone,运行git commit后的仓库

在上述第一种情况下继续执行git commit 指令,重新查看index文件,以及.git目录下的objects目录: 
这里写图片描述
从上图可以看出运行git commit后,在仓库中会新增两个二进制文件,分别是新的commit和tree对象,其中tree对象内部存储的是commit时刻index中所有entry对应的文件的hashobject索引(这就是基于内容检索文件系统的优点),从而实现增量存储,减少数据冗余。 
【备注】:这里说的减少冗余指的是每一次提交对于未修改的部分会直接指向源文件,而不需要重复存储。但是相较于其他版本管理系统,本文中我们着重介绍的git的index缓冲区可以认为是git的一种备份容灾机制,这部分冗余的目的是为了更好地监控work tree和repo。

此时我们重新新建一个目录,拷贝上述运行git commit后的仓库: 
这里写图片描述
运行结果如下:可以看出本地work tree提交的两个文件已经自动到了我们新创建的仓库中,两者的index文件除了对应entry条目的生成和创建时间不同以外,信息完全一致,都包含两个index entry条目和一个TREE对象。 
【备注】:此时的TREE对象是已经存储到git文件系统的实实在在的TREE对象,上文第一种情况的TREE对象是临时的,目的是为了加快commit提交时刻tree和commit对象的hashobject的运算效率。

index的切换

上一章节学习了index文件在不同版本库之间的传输,使用的指令是git clone(包含git pull 、git checkout、git fetch、git merge等子指令)。现在我们来看一下另一个种情况:在同一个版本库下不同分支切换会对index有什么影响

借用上章节演示的刚从其他版本库clone到本地的仓库为例,我这里路径是D:\ZSDevelops\Git\GitStudy\git-clone-from-local3\git-repo-local,此刻的版本库时纯净的,言外之意是指该版本库的仓库repo(即.git目录)、缓冲区index、工作目录work tree三者的状态一致。

下面我们来看一下不同情况下切换分支对index的影响:

1)master未作任何修改,切换分支

这里写图片描述
如图所示,除了元信息,诸如文件最后修改时间,index内容并没有发生任何变化。By the way,此时顺嘴唠叨一句。这种情况我们可以看到.git目录下的HEAD文件在变动,通过cat指令可以看到HEAD文件内容指向的是当前操作分支的最近一次提交,所以只要分支切换HEAD文件就会随之改变。

2)master分支修改并add,新分支未修改

这里写图片描述
从上图可以看出,git add 后.git的objects文件夹内新增了一个blob对象(按照我们之前的讲解,这个blob存储的是新的修改)。切换分支后并没有对index有任何的影像,命令行也没有出现任何的错误或警告。 
依然可以使用git cat-file来查看一下新增的blob对象内容: 
这里写图片描述

3)master分支修改并commit,新分支未修改

这里写图片描述
在上述情况下提交后,.git仓库objects目录又会新增commit和tree两个二进制文件,与此同时对比切换前后的index文件,我们可以发现index文件中的TREE记录发生了改变,分别指向不同的TREE对象,而不同的TREE对象又分别指向不同的git-repo-local.txt文件——这就是实现版本管理的神奇之处,对每一次的修改都会进行存储,从而可以精准高效的保存整个修改历史。

4)master分支修改并commit,新分支修改未提交

这里写图片描述
如上图所示新分支修改后,相应文件的时间戳信息会改变。但是TREE指向的依然是之前的对象。此刻切换分支时,出现了错误信息,从信息描述中可以看出此时的切换会导致本地修改被覆盖。这是什么原因呢?

仔细分析一下我们上面举例的三种情况,在checkout切换分支时,index文件的TREE记录会发生变化,指向之前最后一次commit提交生成的tree对象(该对象保存在了.git的objects目录下,所以来回改变指向而不会丢失)。而此刻我们新分支的修改仅仅是在index的tree记录中临时存储的,如果切换了分支会导致index中的tree记录被修改,因此就丢失了我们目前的修改

5)master分支修改并commit,新分支修改并add但未提交

这里写图片描述
这种情况与第4中情况类似,依然会出现错误提示。但是唯一的好处是对于本地的修改在.git的objects目录下已经新增了一个blob对象,通过某些方法还是有可能复原的(^_^)。

总结:

本博文从分析index文件的生成、格式和变化入手,来了解git的版本管理机制。任何的道理都是相通的,操作系统的文件系统、数据库的管理系统,以及本文讲的git版本管理系统,其实都是类似的。只不过传统文件系统和数据库管理系统我们比较熟悉,例如windows系统就是按照分区、目录、文件来管理所有的数据;数据库系统就是通过表、字段来管理所有数据。git采用的是基于内容寻址的文件系统(Content-Addressable Storage),从上文的操作流程我们也可以感觉到所有的变动都会通过特殊的编码(git hash-object指令可以看到编码后的结果)方式转换成二进制文件放到.git目录下。 
这里写图片描述 
下图是对上述变动的整体流程的一个总结,通过此次index的讲解,可以慢慢体会一下git的版本管理机制,再加上平日里的练习,我相信一定会逐步掌握git的使用。 
这里写图片描述





作者:zssure@163.com
时间:2016-11-06

版权声明:本文为zssure原创文章,转载请注明出处,未经允许不得转载。
原创粉丝点击