原文: http://gitbook.liuhui998.com/7_5.html
对象头部
(object header)由1个或以上的字节按序组成, 它指出了后面所跟数据的类型及展开后的尺寸. 头部的每一个字节有
7
位用于数据, 第
1
位用于说明头部是否还有后续字节. 如果第1位是'1', 你需要再读入1个字节(译注: 即下一字节仍属于头部), 否则下一字节就是数据. 第一个字节的前3位指定了数据的类型, 具体含义参见下表.
一、打包文件索引
首先, 我们来看一下打包文件索引, 基本上它只是一系列指向打包文件内位置的书签.
打包文件索引有两个版本.
版本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程序读出打包文件的头部:
<p style="margin-top: 0px; margin-bottom: 10px; padding-top: 0px; padding-bottom: 0px;"></p><div><span class="kwd" style="color: rgb(0, 0, 136);">def</span><span class="pln"> read_pack_header</span></div><div><span class="pln"> sig </span><span class="pun" style="color: rgb(102, 102, 0);">=</span><span class="pln"> </span><span class="lit" style="color: rgb(0, 102, 102);">@session</span><span class="pun" style="color: rgb(102, 102, 0);">.</span><span class="pln">recv</span><span class="pun" style="color: rgb(102, 102, 0);">(</span><span class="lit" style="color: rgb(0, 102, 102);">4</span><span class="pun" style="color: rgb(102, 102, 0);">)</span></div><div><span class="pln"> ver </span><span class="pun" style="color: rgb(102, 102, 0);">=</span><span class="pln"> </span><span class="lit" style="color: rgb(0, 102, 102);">@session</span><span class="pun" style="color: rgb(102, 102, 0);">.</span><span class="pln">recv</span><span class="pun" style="color: rgb(102, 102, 0);">(</span><span class="lit" style="color: rgb(0, 102, 102);">4</span><span class="pun" style="color: rgb(102, 102, 0);">).</span><span class="pln">unpack</span><span class="pun" style="color: rgb(102, 102, 0);">(</span><span class="str" style="color: rgb(0, 136, 0);">"N"</span><span class="pun" style="color: rgb(102, 102, 0);">)[</span><span class="lit" style="color: rgb(0, 102, 102);">0</span><span class="pun" style="color: rgb(102, 102, 0);">]</span></div><div><span class="pln"> entries </span><span class="pun" style="color: rgb(102, 102, 0);">=</span><span class="pln"> </span><span class="lit" style="color: rgb(0, 102, 102);">@session</span><span class="pun" style="color: rgb(102, 102, 0);">.</span><span class="pln">recv</span><span class="pun" style="color: rgb(102, 102, 0);">(</span><span class="lit" style="color: rgb(0, 102, 102);">4</span><span class="pun" style="color: rgb(102, 102, 0);">).</span><span class="pln">unpack</span><span class="pun" style="color: rgb(102, 102, 0);">(</span><span class="str" style="color: rgb(0, 136, 0);">"N"</span><span class="pun" style="color: rgb(102, 102, 0);">)[</span><span class="lit" style="color: rgb(0, 102, 102);">0</span><span class="pun" style="color: rgb(102, 102, 0);">]</span></div><div><span class="pln"> </span><span class="pun" style="color: rgb(102, 102, 0);">[</span><span class="pln">sig</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln"> ver</span><span class="pun" style="color: rgb(102, 102, 0);">,</span><span class="pln"> entries</span><span class="pun" style="color: rgb(102, 102, 0);">]</span></div><div><span class="kwd" style="color: rgb(0, 0, 136);">end</span></div><p style="margin-top: 0px; margin-bottom: 10px; padding-top: 0px; padding-bottom: 0px;"></p>
头部
之后是一系列按照SHA值排序的打包对象, 每一个打包对象包含了头部和内容.
打包文件的尾部
是该文件中所有(已排序)SHA值的SHA1校验值(20字节长)(译注: 即按照排序好的顺序进行迭代SHA1运算).
(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, 等等).