此文章施工中…

二进制格式

前后顺序均按从低位到高位.

<index>     ::= <header/12-byte> <entries/8k-byte> <extensions> <checksum>

<header>    ::= <signature/4-byte> <version/4-byte> <entry_cnt/32-bit>
<signature> ::= b'DIRC' 
<version>   ::= (0002, 0003, 0004)                          in ASCII format
<entry_cnt> ::= the amount of entries below                 in u32   format

<entries>   ::= [<entry> <padding>]
<entry>     ::= <c_time/32-bit> <c_time_ns/32-bit>
                <m_time/32-bit> <m_time_ns/32-bit>
                <dev/32-bit>    <ino/32-bit>
                <mode/32-bit>
                <uid/32-bit>    <gid/32-bit>
                <file_size/32-bit>
                <sha-1/20-byte>
                <flags/16-bit>
                <path_name>

<c_time>    ::= 以秒为单位的最后一次文件元信息改变时间      in u32 format
<c_time_ns> ::= c_time 的纳秒部分                           in u32 format

<m_time>    ::= 以秒为单位的最后一次文件改变时间            in u32 format
<m_time_ns> ::= m_time 的纳秒部分                           in u32 format

<dev>       ::= 文件的设备号
<ino>       ::= 文件的 ino (Infomation NOde) 号,            in u32 format
                与 <dev> 一起在能同一台机器上唯一地确定某个文件

<mode>      ::= <unused_0/16-bit> <obj_type/4-bit> <unused_0/3-bit> <unix-permission/9-bit>
<obj_type>  ::= (1000, 1010, 1110) 三选一 
                分别代表文件类型为 (普通文件, 符号链接, 子模块链接(Gitlink))
<unix-permission> ::= 常见的 Unix 权限位, 就是 644 / 777 那种

<uid>       ::= 文件所有者的用户 ID                         in u32 format
<gid>       ::= 文件所有者的用户组 ID                       in u32 format

<file_size> ::= 文件的大小, 按字节记                        in u32 format
<sha-1>     ::= 对象的 SHA-1                                in u160 format (20-byte 长的二进制数)

<flags>     ::= <assume-valid/1-bit> <extended/1-bit> <stage/2-bit> <path_len/12-bit>
<assume-vaild> ::= Flag, 为 1 时 Git 会假定此文件未变动, 从而允许你让 Git 忽略该文件的改变
<extended>  ::= Flag, 在 version 2 中一定为 0
<stage>     ::= 描述其属于同路径名对象的哪个 Slot
<path_len>  ::= 路径名长度, 如果大于等于 0xFFF(4095) 的话就是 0xFFF

<path_name> ::= 路径名, 统一用 "/" 作为路径分隔符

直接按以上二进制格式储存于 .git/index 文件中, 不需要压缩

一个例子

现在我们的工作目录里有两个文件: a.txtb/c.txt.

$ tree .
├─b
│ └─c.txt
└─a.txt
$ cat .git/index | xxd
00000000: 4449 5243 0000 0002 0000 0002 6026 33b5  DIRC........`&3.
00000010: 053f fd99 6026 33b5 053f fd99 0000 0802  .?..`&3..?......
00000020: 0050 008b 0000 81a4 0000 03e8 0000 03e8  .P..............
00000030: 0000 0005 81c5 45ef ebe5 f57d 4cab 2ba9  ......E....}L.+.
00000040: ec29 4c4b 0cad f672 0005 612e 7478 7400  .)LK...r..a.txt.
00000050: 0000 0000 6026 6662 15c4 8f97 6026 6662  ....`&fb....`&fb
00000060: 15c4 8f97 0000 0802 0056 0b99 0000 81a4  .........V......
00000070: 0000 03e8 0000 03e8 0000 0005 9c9d dc2c  ...............,
00000080: c36e c58f 5fc7 6c7c 5157 cfc0 46dd 79ea  .n.._.l|QW..F.y.
00000090: 0007 622f 632e 7478 7400 0000 5452 4545  ..b/c.txt...TREE
000000a0: 0000 0033 0032 2031 0a05 e780 1182 a544  ...3.2 1.......D
000000b0: c4ab bf92 588d 3d2a b043 91ef 1562 0031  ....X.=*.C...b.1
000000c0: 2030 0afe 7ce1 8c5d 3590 42f6 eb43 e81c   0..|..]5.B..C..
000000d0: f711 9240 dd36 8137 fd86 0a4c e3d2 cdd2  ...@.6.7...L....
000000e0: c822 c701 1d2f dc6e 5c97 68              .".../.n\.h

我们把它按上文的字段分开一个一个看:

# 文件头
4449 5243 0000 0002 0000 0002 

# 第一个条目
6026 33b5 053f fd99 6026 
33b5 053f fd99 0000 0802  
0050 008b 0000 81a4 0000 
03e8 0000 03e8 0000 0005 
81c5 45ef ebe5 f57d 4cab 
2ba9 ec29 4c4b 0cad f672 
0005 612e 7478 7400 0000 0000 

# 第二个条目
6026 6662 15c4 8f97 
6026 6662 15c4 8f97 0000 
0802 0056 0b99 0000 81a4  
0000 03e8 0000 03e8 0000 
0005 9c9d dc2c c36e c58f 
5fc7 6c7c 5157 cfc0 46dd 
79ea 0007 622f 632e 7478 
7400 0000            

# 扩展 & Hash checksum
5452 4545  
0000 0033 0032 2031 0a05 e780 1182 a544  
c4ab bf92 588d 3d2a b043 91ef 1562 0031  
2030 0afe 7ce1 8c5d 3590 42f6 eb43 e81c  
f711 9240 dd36 8137 fd86 0a4c e3d2 cdd2  
c822 c701 1d2f dc6e 5c97 68              

# ---------- 下面是详细解释 -----------------

# 文件头
4449 5243  # 4-byte signature: DIRC
0000 0002  # 4-byte ASCII version: 2
0000 0002  # 32-bit entry count: 2

# 条目 1: a.txt
6026 33b5  # 32-bit: c_time
053f fd99  # 32-bit: c_time_ns
6026 33b5  # 32-bit: m_time
053f fd99  # 32-bit: m_time_ns

0000 0802  # 32-bit: dev
0050 008b  # 32-bit: ino

0000 81a4  # 32-bit: mode: 4-bit obj_type / 3-bit unused / 9-bit unix permission
           # 81a4: 1000 000 110100100 : 1000 for file, 000 for unused, 110/100/100 for mode 644

0000 03e8  # 32-bit: uid
0000 03e8  # 32-bit: gid

0000 0005  # 32-bit: size of the file from stat(2), means len('1234\n') == 5

81c5 45ef ebe5 f57d 4cab # 20-byte: the SHA-1 of the object in a SHA-1 repo
2ba9 ec29 4c4b 0cad f672 # 

0005 # flags: 1-bit / 1-bit / 2-bit / 12-bit size of its path name below: 5

612e 7478 74  # its path name: 'a.txt'
00 0000 0000  # padding: 让每一个条目的大小是 8-byte 的倍数, 并且保证至少有一个 NUL (size: 72 bytes)

# 条目 2: b/c.txt
# 相信读者可以自行解读下面的字段了
6026 6662 
15c4 8f97 
6026 6662 
15c4 8f97 

0000 0802 
0056 0b99 

0000 81a4  

0000 03e8 
0000 03e8 

0000 0005 

9c9d dc2c c36e c58f 5fc7 
6c7c 5157 cfc0 46dd 79ea 

0007 

622f 632e 7478 74  # b/c.txt
00 0000            

# 扩展 & Hash checksum: 略

Stage 字段的含义

在 Git 合并分支的时候有用.

例子取自参考里的 SO 问题.

假如我们现在有两个分支 A, B, 工作目录下原本有三个文件 x, y, z. 现在你:

A 分支:

  • 修改了 x 的内容并且将它的名字改成了 t
  • 修改了 y 的内容
  • z 的内容保持不变

B 分支:

  • 修改了 x 的内容
  • 删除了 y
  • z 的内容保持不变

这时候你在 A 分支, 想要把它合并到 B 分支:

(on git:A)$ git merge B

这时候 Git 就会要求你手动合并冲突, 此时的 index 文件内容如下:

(on git:A)$ git ls-file --stage
100644  4362ab...   1   t   # 指向保存了原来的 x 文件内容的 blob
100644  49db92...   2   t   # 指向保存了 当前分支A 中 t(原来的x) 文件内容的 blob
100644  04b399...   3   t   # 指向保存了 要被合并分支B 中 x 文件内容的 blob
100644  366b52...   1   y   # 指向保存了原来的 y 文件内容的 blob
100644  6fecb1...   2   y   # 指向保存了 当前分支A 中 y 文件
                            # 因为在 B 分支中 y 被删除了, 所以没有 stage 为 3 的 B分支 y 文件 blob
100644  7129c6...   0   z   # 没有冲突的正常文件 stage 号为 0

当你解决完冲突合并之后, 比如说你:

  • 选择了 t 文件
  • 删除了 y 文件

那么合并成功后 index 会变成这样:

(on git:B)$ git ls-file --stage
100644  49db92...   0   t
100644  7129c6...   0   z

参考:

二进制格式: Git Manuel. 请特别注意它的排版方式, 可以的话建议先看完前文再去看这个

Stat 字段的意义: Python os.stat

Assume-vaild flag 的意义: SO-1: 描述了 Assume-vaild flag 对应的 Git 高层术语, SO-2: 描述了该高层术语的一种应用场景, Git Manuel: 描述了如何设置这一 Flag

Stage/2-bit 的含义参考: 描述了其对应的高层术语 Slot, SO: 解释了其用途

实现参考: Dulwich index.py, Gin