Git Pack 文件中对象 Header 结构分析

在 Git Pack 文件中,每个对象都有一个 Header,Header 的结构如上图所示,由 2 部分组成,分别是对象类型和对象大小。

在第一个 byte 的第一个 bitMSB(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))
}
  1. byte >> 4 & 0x70 的逻辑是先将 byte 右移 4 位,然后与 0x70 进行 & 操作,这样就可以得到 byte 的 1 - 3 位,也就是对象类型。
  2. byte & 0xf 的逻辑是直接与 0xf 进行 & 操作,这样就可以得到 byte 的后 4 位,也就是对象大小在第一个 byte 的值。
  3. byte & 0x80 是判断 byte 的 MSB 是否为 1

如果 MSB 为 0,则表示当前 byte 是最后一个 byte;如果是 1 的话就是后面一个 byte 同样是记录对象大小,就需要继续读取下一个 byte

如果读取第二个 byte,则需要将第二个 byte 中得到的 size 左移 4 位,加入第一个 bytesize 进行解码计算; 后面再读取的 byte 时,需要右移 4 + 7 位,加入之前的 size 进行解码计算,直到读取到的 byte 的 MSB 为 0,则表示当前 byte 是最后一个 byte,这样就可以得到对象的大小。

这里需要注意的就是左移位数是 n * 7 + 4 ,第一次是 n = 0 移动 4 位 ,然后 n = 1 移动 11 位,n = 2 移动 18 位,以此类推进行计算。