版本控制在于文件的控制,git的控制方法在于为每个文件生成(key,object)的结构。git利用sha-1加密算法,对每一个文件生成一个唯一的字符序列(明文大小不超过2^64位,对于普通文件,这个大小都可以满足)作为hash_key。对于sha-1算法,明文(对于git来讲,就是我们的文件内容)不变,其sha-1值不会改变,所以只要文件改变,就会生成一对新的(key,object)[对于key,当中还有一些细微的处理,不仅仅只是sha-1算法,这里我们只是需要理解,git为一个文件生成了一个唯一的key]。
使用git
init初始化一个本地仓库,打开隐藏目录.git,其内容如下图。可以看到一个objects的目录,里面只有info和pack两个空文件夹。初始化的时候不存在任何object,也就是没有任何文件被记录下来。
1.blob对象
我们在工作目录下添加一个文件file_1.txt,里面只要一个字符串"file1 content",使用git
hash-object [文件名],可以查看其经过算法生成的hash-key.这个一个40个字符长度的序列。其序列为d9039017ab6c958678a334446aadfe5047266027
这个时候object目录下还是空,使用git
add file_1.txt之后,object里会多一个对象,下面详细来解析这个对象。首先看看object目录发生了什么事情
多了一个d9的目录
d9目录下
可以看到40位的hash-key 前两位作为目录名,后38位作为文件名,标识了这个object对象,这个对象里面的内容就是刚才file_1.txt里的内容,可以查看这个对象的内容和对象类型:
git
cat-file -p [hash-key] 可以查看已经存在的object对象内容
git
cat-file -t [hash-key] 可以查看已经存在的object对象类型
git object有四种类型,这是目前我们接触到的第一种类型blob,用来储存文件类容,它的具体内容就是刚刚新建的txt里的字符串file1 content。
2.tree对象
blob对应文件的内容,tree对象可以理解为目录,它的树节点信息包含文件名,hash-key,文件类型、权限等等。这样就可以组织整个需要控制文件的结构
下面我们再往工作目录下添加一个目录dir_1,在dir_1添加一个文件1.txt,类容为"1.txt content"。使用git add 将内容加入到暂存区(也称index,目前不是本文的重点,会在后续章节中详解)。使用git
hash-object来查看生成的key值。
对于文件可以看到生成了hash-key,但是对于目录很明显没有达到预期的效果。我们看看object目录
只存在一个6d的目录,也就是6dc2bcda0c359c6dbb917dec90ca4a8d078ff789对应的1.txt文件,这时我们的目录并没有生成tree对象,tree对象是在commit的过程中生成的,其生成会根据.git目录下的index文件的内容来创建。git
add的操作就是将文件的信息保存到index文件中,在commit时,根据index的内容来生成tree对象。
使用git
ls-files --stage命令,我们看看index里的类容
可以看到index包含了创建tree对象的信息
文件类型(100644),ash-key,目录结构和文件名。
下面我们进行第一次commit,生成commit对象,同时生成tree对象。我们这里具体看看tree对象,master是分支名,master^{tree},表示master分支所指向的tree对象。
可以看到这个tree对象是我们的工作目录,目录下还有一个dir_1的tree对象,和file_1的blob对象,下面看看dir_1对应的tree对象的内容,这个tree对象只包含1.txt的信息。
目前我们的git仓库的内部结构如下:
3.commit对象
介绍tree对象时,提到过commit,只有在commit的时候,才会根据index记录的内容生成tree对象,那么commit对象里只有两个类容:1.代表工作目录的tree对象的key,上一个commit的key。
现在看看我们的object目录:
目前我们的对象数量还不多,每个目录里有一个对象,就是五个对象,刚才的总体tree图,只包含了四个对象,我们使用git
log查看commit的历史
90对应的文件夹里面的文件就是我们的commit对象,它指向工作目录tree,和上一次的commit,这是第一个commit
,所以上一个commit不存在。
对象类型为commit
内容指向工作目录tree,所以能获取到一个commit,就可以完整得到当前的文件状况,现在我们的完整的object图如下:
现在我们在工作加入一个新的目录dir_2,和该目录下文件2.txt,内容为"content 2",在add和commit之后,我们在看看新的commit的信息
新的commit指向了上一个commit,还指向了一个新生成的tree,这个tree表示了新的工作目录情况,看看这tree的类容:
这个tree包含了当前的文件目录和内容,现在我们的对象完整的图如下:
可以看到commit对象指向了工作目录tree,这样只要切换commit,就可以随意切换我们的版本类容。现有有9个object对象了,我们来看看.git/objects目录,对象还很少,没有在出现同一目录下的两个文件的情况。
4.tag对象
git最重要的只有上述三种对象:blob(记录文件内容),tree(目录结构),commit(工作目录tree,提交历史)。对于tag只是一种指向某个commit的对象,它标识了一个commit,并且永久指向它,为该定义一个更加友好的名字而已,这里不作演示。
小结:本文通过讲解git内部的四种对象来解释git的内部工作机制,对于熟悉git使用的人,可以帮助其深入理解。对于版本控制的初学者,只需要了解大概,可以在了解其他章节之后再进一步理解