Git Pack 文件中对象 Header 结构分析
在 Git Pack 文件中,每个对象都有一个 Header,Header 的结构如上图所示,由 2 部分组成,分别是对象类型和对象大小。
在第一个 byte
的第一个 bit
是 MSB(Most Significant Bit),只是否继续读取下一个 byte
,如果为 1
,则继续读取下一个 byte
,如果为 0
,则表示当前 byte
是最后一个 byte
。这个规则就是之前 Git Pack 文件中对象长度的变长编码的编解码解析 中提到的 标志位
的编码规则。
接下来的 3 个 bit
是对象类型,Git 中的对象类型有 6 种,分别是:
OBJ_COMMIT = 1
OBJ_TREE = 2
OBJ_BLOB = 3
OBJ_TAG = 4
OBJ_OFS_DELTA = 6
OBJ_REF_DELTA = 7
前 4 种对象类型是 Git 的基本的对象类型,后 2 种对象类型是 Git Pack 文件中的特殊对象类型,分别是 偏移量差异对象
和 引用差异对象
。
后续会继续分析这些内部对象,希望最终能组成一个系列的文章
fn parse_object_header<R: Read>(mut reader: R) -> std::io::Result<(ObjectType, usize)> {
let mut buffer = [0; 1];
reader.read_exact(&mut buffer)?;
let byte = buffer[0];
let object_type = byte >> 4 & 0x70;
let mut initial_size = (byte & 0xf) as usize;
// Recursively read the following bytes if the MSB is set
if byte & 0x80 != 0 {
initial_size = parse_object_size(&mut reader, initial_size, 0)?;
}
Ok((object_type, initial_size))
}
byte >> 4 & 0x70
的逻辑是先将byte
右移 4 位,然后与0x70
进行&
操作,这样就可以得到byte
的 1 - 3 位,也就是对象类型。byte & 0xf
的逻辑是直接与0xf
进行&
操作,这样就可以得到byte
的后 4 位,也就是对象大小在第一个byte
的值。byte & 0x80
是判断byte
的 MSB 是否为1
,
如果 MSB 为 0
,则表示当前 byte
是最后一个 byte
;如果是 1
的话就是后面一个 byte
同样是记录对象大小,就需要继续读取下一个 byte
。
如果读取第二个 byte
,则需要将第二个 byte
中得到的 size
左移 4
位,加入第一个 byte
的 size
进行解码计算; 后面再读取的 byte
时,需要右移 4 + 7
位,加入之前的 size
进行解码计算,直到读取到的 byte
的 MSB 为 0
,则表示当前 byte
是最后一个 byte
,这样就可以得到对象的大小。
这里需要注意的就是左移位数是 n * 7 + 4
,第一次是 n = 0
移动 4
位 ,然后 n = 1
移动 11
位,n = 2
移动 18
位,以此类推进行计算。