6.1.2 Git基本原理

与CVS或者SVN等一般软件不同,要想掌握Git,必须对其实现原理有深入的理解;否则只能停留在表面的简单操作上,一旦遇到复杂问题就无从下手。

了解Git的基本原理需要从Git仓库的数据结构入手。Git把一个项目所有的版本和历史数据保存在一个Git仓库(Repository)里。仓库通常位于项目根目录下的.git子目录,主要包含两种数据:对象存储(object store)和索引(index)。

对象存储(object store)

对象存储(object store)中的对象共有4种类型:

  • Blob - 一个blob对象对应着项目的一个文件的内容(不包括文件名、创建/修改时间等信息)。项目的每个文件的每个不同版本都对应着一个blob对象。
  • Tree - 一个tree对象对应着一层目录结构,每个目录项包括文件名、文件对应的blob对象或者另一个tree对象的引用(可以看出,利用blob和tree对象可以实现一个完整的多层次目录结构)。
  • Commit - 一个commit对象保存着用户的一次commit数据,包括:作者信息、提交日期和日志,以及一个指向tree对象的引用(注意commit对象并不包括版本之间的diff差异,后详)。
  • Tag - 一个tag对象含有对另一个对象(通常是commit对象)的引用,以及一些附加的注释信息,主要供人类阅读。

举例来说,假设一个项目的目录结构如下:

.
├── hello.txt
└── sub-dir
    └── world.txt

同时假设这个项目只进行了一次commit(包含以上两个文件和一个子目录)并且该commit上有一个tag,那么项目对应的Git对象存贮应如下图所示:

git-object-store

需要注意的是:我们在前面提到的“对像引用”,如Tree对象代表的目录中每个目录项含有的对blob或者另一个tree对象的引用,commit对象含有的对tree对象的引用,以及tag对象对一个commit对象的引用,不是依赖文件系统的路径来表示的,而是由对象内容得出的SHA1哈希代码,类似670a245535fe6316eb2316c1103b1a88bb519334。因此上图可修正如下:

git-object-store2

在项目的根目录下执行

find .git/objects/

可以看到项目的Git仓库中的所有的对象,类似如下:

.git/objects/
.git/objects//0d
.git/objects//0d/8ad63a4626b776800bbed61c2dac660c5037e2
.git/objects//1d
.git/objects//1d/5230840c2573394e0ae86f30e01e03062804fc
.git/objects//38
.git/objects//38/8df287b748962a5a1e28a36172fce3240b53e8
.git/objects//4a
.git/objects//4a/3ec58d02c75da1d303d55a08cd172508256525
.git/objects//5a
.git/objects//5a/e514f1ff6a54b6de97db5418038b4b183d8764
.git/objects//67
.git/objects//67/0a245535fe6316eb2316c1103b1a88bb519334
.git/objects//info
.git/objects//pack

它们都是以对象内容的SHA1码来命名的。你还可以通过以下命令查看其内容:

git cat-file -p 0d8ad63

以上0d8ad630d8ad63a4626b776800bbed61c2dac660c5037e2的短前缀——在不引起冲突的情况下可以使用短前缀代替完整的SHA1码。

Git通过对象内容的SHA1哈希码来存贮、引用对象的方式称作通过内容寻址的存贮(Content-Addressable Storage)

现在我们进一步:如果修改了hellt.txt的内容并提交一个新的commit,对象存贮如何变化?它变成了这样:

git-object-store3

仔细对比一下前后两张图,Git的奥妙就在这里

  • hello.txt的内容改变后,Git新增了一个blob对象,对应着hello.txt的新内容,并且有一个新的SHA1代码。
  • 由于hello.txt的SHA1代码改变了,因此包含这个blob对象的tree对象也发生了变化(因为要更新对hello.txt的引用)。同样地,Git也新增了一个tree对象对应着新内容。同时,sub-dir的内容没有变化,因此新的tree对象仍然引用着原来的sub-dir对象(而不是拷贝一份)。
  • 新的commit对象包含这个新的tree对象的引用,同时指向之前的commit对象——这样就形成了历史版本纪录,并且你随时可以恢复任何一个历史版本!

索引(index)

根对象存贮相比,索引就简单的多:它不过是一个临时文件,用来存贮你对项目做的一些改变,如增加、删除、修改文件或者目录等。一般地,你使用git addgit rm等命令把你修改过的文件、目录提交到这个索引中去,然后应用git commit命令来完成一次提交。

results matching ""

    No results matching ""