一、打包文件索引
首先, 我们来看一下打包文件索引, 基本上它只是一系列指向打包文件内位置的书签.
打包文件索引有两个版本.
版本1 的格式用于Git 1.6版本之前, 版本2的格式用于Git 1.6及以后的版本.
但是版本2可以被Git 1.5.2及以上的Git读取, 同时也被后向移植(backport)到了1.4.4.5版本.
版本2 包含了每个对象的 CRC校验值 , 因此在重打包的过程中, 压缩过的对象可以直接进行包间拷贝(from pack to pack)而不用担心数据损坏. 版本2的打包文件索引同时亦支持 大于4G的打包文件 .
在两个版本格式中, sha1表 存储的是对象的SHA1值,并把它们按照其SHA1值进行排序(以便于对这个表进行二分搜索), offset表 存储的是 sha1表 中对应位置的对象在打包文件中的偏移值。
为了加速对象的查找,git使用了分段的思想,在打包文件中包含了一个 fanout表 。 fanout表 用一种特殊的方法指向 offset/sha1 表。
简单的说应该是这样的, fanout[ 0 ] 表示的是 SHA1值 以 0x00 开头的 所有的对象中 S HA1值 最小的对象在 offset/sha1 表中的偏移; fanout[1] 表示的是 SHA1值 以 0x01 开头的 所有的对象中 SHA1 值最小的对象在 offset/sha1 表中的偏移; fanout[2] 表示的是 SHA1值 以 0x02 开头的 所有的对象中 SHA1 值最小的对象在 offset/sha1 表中的偏移。以此类推, f anout[254] 表示的是 SHA1值 以 0xfe 开头的 所有的对象中 SHA1 值最小的对象在 offset/sha1 表中的偏移, f anout[255] 表示的是 SHA1值 以 0xff 开头的 所有的对象中 SHA1 值最大的对象在 offset/sha1 表中的偏移,它同时也表示了当前 offset/sha1 表 的大小。
因此通过SHA1值查找一个对象时,首先通过 SHA1值 的 前两位在 f anout表 中,确定它在 offset/sha1表 的一个区间范围,然后再在 sha1表 中根据 SHA1值 使用二分法进行查找。这样通过 f anout表 对于最坏的情况就减少了 8次 二分搜索 迭代 。
在第1版中, offset(偏移) 和 SHA值 存在在同一位置. 但是在第2版中, SHA值, CRC值和offset被放在不同的表中. 两个版本的文件最后都是索引文件以及指向的打包文件的CRC校验值.
很重要的一点是, 要从打包文件中提取(extract)出一个对象, 索引文件不是必不可少的. 索引文件的作用是帮助用户快速地从打包文件中提取对象. 那些"上传打包"(upload-pack)和"取回打包"(receive-pack)程序(译注: 实现push和fetch协议的程序)使用打包文件格式(packfile format)去传输对象, 但是没有使用索引 .因为 索引可以在上传或者取回打包文件之后通过扫描打包文件重新建立.
二、打包文件格式
打包文件格式是很简单的. 它有一个头部(header)和一系列打包过的对象(每个都有自己的header和body), 还有一个校验尾部(trailer). 前4个字节是字符串'PACK', 它用于确保你找到了打包文件的起始位置. 紧接着是4个字节的打包文件版本号, 之后的4个字节指出了此文件中入口(entry)的个数. 你可以用下面Ruby程序读出打包文件的头部:
def read_pack_header
sig = @session . recv ( 4 )
ver = @session . recv ( 4 ). unpack ( "N" )[ 0 ]
entries = @session . recv ( 4 ). unpack ( "N" )[ 0 ]
[ sig , ver , entries ]
end
头部 之后是一系列按照SHA值排序的打包对象, 每一个打包对象包含了头部和内容. 打包文件的尾部 是该文件中所有(已排序)SHA值的SHA1校验值(20字节长)(译注: 即按照排序好的顺序进行迭代SHA1运算).
对象头部 (object header)由1个或以上的字节按序组成, 它指出了后面所跟数据的类型及展开后的尺寸. 头部的每一个字节有
7 位用于数据, 第 1 位用于说明头部是否还有后续字节. 如果第1位是'1', 你需要再读入1个字节(译注: 即下一字节仍属于头部), 否则下一字节就是数据. 第一个字节的前3位指定了数据的类型, 具体含义参见下表.
(3个位可以组合成为8个数. 在当前的使用中, 0(000)是'未定义', 5(101)目前未被使用.)
这里我们举一个由两个字节组成的头部的例子. 第1个字节的前3位说明了数据的类型是提交(commit), 余下的4位和第2个字节的7位组成的数字是144, 说明数据展开后的长度是144字节.
值得注意的一点是, 对象头部中包含的'尺寸'不是后面跟着的数据的长度, 而是 数据展开之后的长度 . 因此, 打包索引文件中的偏移是很有用的, 有了它你不必展开每一个对象就可以得到下一个头部的起始位置.
对于非delta对象, 数据部分就只是zlib压缩后的数据流. 对于那两种delta对象, 数据部分包含了它所依赖的基对象(base object)以及用于重构对象的delta(差异)数据. 数据的前20个字节称为ref-delta, 它是基对象SHA值的前20个字节. ofs-delta存储了基对象在同一打包文件中的偏移. 任何情况下, 有两个约束必须严格遵守:
1、 delta对象和基对象必须位于同一打包文件;
2、 delta对象和基对象的类型必须一致(即tree对tree, blob对blob, 等等).