Git 底层原理:Git 底层文件存储格式
Git 底层文件有 Git 对象、索引文件、pack 文件、分支/引用、HEAD指针等,
在《Git 底层原理: Git 对象》里面有讲解到,Git 是一个文件系统,最小单元是 Git 对象,Git 对象又分为 4种:blob
、tree
、commit
、tag
,那这些对象具体是以什么形式存储的?
首先按照《Git 底层原理: Git 对象》里面提到的步骤初始化一个简单的 git 仓库,查看生成的 git 对象:
1 2 3 4 5 6 7 8 9 10 11
| $ find .git/objects -type f | sort .git/objects/03/2ddd9205d65abd773af1610038c764f46a0b12 .git/objects/10/da3741b6e365b6795335e1e2d3ed5820e794cd .git/objects/39/fb0fbcac51f66b514fbd589a5b2bc0809ce664 .git/objects/41/20b5f61a582cb12d4dcdaab71c7ef1862dbbca .git/objects/45/c7a584f300657dba878a542a6ab3b510b63aa3 .git/objects/52/3d41ce82ea993e7c7df8be1292b2eac84d4659 .git/objects/56/64e303b5dc2e9ef8e14a0845d9486ec1920afd .git/objects/6f/b38b7118b554886e96fa736051f18d63a80c85 .git/objects/a0/e96b5ee9f1a3a73f340ff7d1d6fe2031291bb0 .git/objects/ae/c2e48cbf0a881d893ccdd9c0d4bbaf011b5b23
|
Git 对象
为了减少存储大小,git 对象都是使用 zlib 压缩存储的。git 对象由 <type>
+ <size>
+ <content>
组成:
<type>
: git 对象类型,有如下 4 种:blob
、tree
、commit
、tag
。
<size>
: git 对象的内容大小。
<content>
: git 对象内容。
如下是 4 种对象的数据存储格式:
这里可能会有人有疑惑,Git 对象并没有对自身数据做校验(checksum),这样会不会有人对数据进行修改?这个其实不用担心,所有的 Git 对象都会组成一个图(Graph),按照指向关系可以这么理解:refs
–> tag 对象
–> commit 对象
–> tree 对象
–> blob 对象
(实际上更为复杂),对象之间通过对方的 sha1 值来确定指向关系,所以要是篡改了对象的内容,那指向关系就会被破坏掉,git fsck
命令就会提示 "hash mismatch"
。
查看对象存储格式
git 提供了 cat-file
来解析 git 对象,并输出格式化可阅读的内容:
1 2 3 4 5 6 7 8 9 10 11 12 13
| $ git cat-file -p 6fb38 100644 blob 5664e303b5dc2e9ef8e14a0845d9486ec1920afd README.md 040000 tree 39fb0fbcac51f66b514fbd589a5b2bc0809ce664 doc 100644 blob aec2e48cbf0a881d893ccdd9c0d4bbaf011b5b23 file.txt
$ git cat-file -t 6fb38 tree
$ git cat-file -s 6fb38 103
|
同时,cat-file
也支持输出未格式化的内容:
1 2 3 4 5
| $ git cat-file tree 10da374 100644 README.mdVd���.���E�Hn�� �40000 doc9���Q�kQO�X�[+����d100644 file.txt��䌿 ��<���Ի�%
|
如果你想要不依赖 git 命令来查看 git 对象,可以使用 zlib 的一个解压工具:zlib-flate 来解压 git 对象,以 file.txt
第一次提交生成的 blob 对象 6fb38b7
为例:
1 2
| $ zlib-flate -uncompress < .git/objects/6f/b38b7118b554886e96fa736051f18d63a80c85 blob 11git-inside
|
根据上面的 blob 存储格式可以知道,其中 "blob"
是对象类型,"11"
是文件大小,"git-inside"
是文件内容。
这里提供一下 4 种对象的原始数据,仅供参考:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| $ zlib-flate -uncompress < .git/objects/6f/b38b7118b554886e96fa736051f18d63a80c85 blob 11git-inside
$ zlib-flate -uncompress < .git/objects/10/da3741b6e365b6795335e1e2d3ed5820e794cd | hexdump -C 00000000 74 72 65 65 20 31 30 33 00 31 30 30 36 34 34 20 |tree 103.100644 | 00000010 52 45 41 44 4d 45 2e 6d 64 00 56 64 e3 03 b5 dc |README.md.Vd....| 00000020 2e 9e f8 e1 4a 08 45 d9 48 6e c1 92 0a fd 34 30 |....J.E.Hn....40| 00000030 30 30 30 20 64 6f 63 00 39 fb 0f bc ac 51 f6 6b |000 doc.9....Q.k| 00000040 51 4f bd 58 9a 5b 2b c0 80 9c e6 64 31 30 30 36 |QO.X.[+....d1006| 00000050 34 34 20 66 69 6c 65 2e 74 78 74 00 ae c2 e4 8c |44 file.txt.....| 00000060 bf 0a 88 1d 89 3c cd d9 c0 d4 bb af 01 1b 5b 23 |.....<........[
$ zlib-flate -uncompress < .git/objects/a0/e96b5ee9f1a3a73f340ff7d1d6fe2031291bb0 commit 220tree 10da3741b6e365b6795335e1e2d3ed5820e794cd parent 523d41ce82ea993e7c7df8be1292b2eac84d4659 author xiaowenxia <775117471@qq.com> 1606913178 +0800 committer xiaowenxia <775117471@qq.com> 1606913178 +0800
second commit
$ zlib-flate -uncompress < .git/objects/03/2ddd9205d65abd773af1610038c764f46a0b12 tag 148object a0e96b5ee9f1a3a73f340ff7d1d6fe2031291bb0 type commit tag v0.0.2 tagger xiaowenxia <775117471@qq.com> 1606913178 +0800
this is annotated tag
|
这里使用到了 hexdump,hexdump 是一个 UNIX 命令,用来格式化输出二进制数据。
索引文件
索引文件默认路径为:.git/index
。索引文件用来存储暂存区的相关文件信息,当运行 git add
命令时会把工作区的变更文件信息添加到该索引文件中。索引文件以如下的格式存储暂存区内容:
读过源码的同学会发现,其实还有一个叫.git/index.lock
的文件,该文件存在时表示当前工作区被锁定,代表有 git 进程正在操作该仓库。
查看索引文件存储格式
使用 ls-files
可以读取索引文件存储的文件信息:
1 2 3 4
| $ git ls-files --stage 100644 5664e303b5dc2e9ef8e14a0845d9486ec1920afd 0 README.md 100644 45c7a584f300657dba878a542a6ab3b510b63aa3 0 doc/changelog 100644 aec2e48cbf0a881d893ccdd9c0d4bbaf011b5b23 0 file.txt
|
当然,ls-files
的输出内容也是经过格式化的。跟 Git 对象 不一样,索引文件 .git/indx
并没有经过 zlib 压缩,使用 hexdump
工具就可以直接查看原始数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| $ hexdump -C .git/index 00000000 44 49 52 43 00 00 00 02 00 00 00 03 5f cb 65 22 |DIRC........_.e"| 00000010 22 be 40 2c 5f cb 65 22 22 be 40 2c 01 00 00 04 |".@,_.e"".@,....| 00000020 01 3e 09 e3 00 00 81 a4 00 00 01 f6 00 00 00 14 |.>..............| 00000030 00 00 00 04 56 64 e3 03 b5 dc 2e 9e f8 e1 4a 08 |....Vd........J.| 00000040 45 d9 48 6e c1 92 0a fd 00 09 52 45 41 44 4d 45 |E.Hn......README| 00000050 2e 6d 64 00 5f cb 65 26 01 bd 63 4e 5f cb 65 26 |.md._.e&..cN_.e&| 00000060 01 bd 63 4e 01 00 00 04 01 3e 09 f4 00 00 81 a4 |..cN.....>......| 00000070 00 00 01 f6 00 00 00 14 00 00 00 07 45 c7 a5 84 |............E...| 00000080 f3 00 65 7d ba 87 8a 54 2a 6a b3 b5 10 b6 3a a3 |..e}...T*j....:.| 00000090 00 0d 64 6f 63 2f 63 68 61 6e 67 65 6c 6f 67 00 |..doc/changelog.| 000000a0 00 00 00 00 5f cb 65 1f 17 f9 45 e9 5f cb 65 1f |...._.e...E._.e.| 000000b0 17 f9 45 e9 01 00 00 04 01 3e 08 92 00 00 81 a4 |..E......>......| 000000c0 00 00 01 f6 00 00 00 14 00 00 00 1a ae c2 e4 8c |................| 000000d0 bf 0a 88 1d 89 3c cd d9 c0 d4 bb af 01 1b 5b 23 |.....<........[#| 000000e0 00 08 66 69 6c 65 2e 74 78 74 00 00 54 52 45 45 |..file.txt..TREE| 000000f0 00 00 00 35 00 33 20 31 0a 10 da 37 41 b6 e3 65 |...5.3 1...7A..e| 00000100 b6 79 53 35 e1 e2 d3 ed 58 20 e7 94 cd 64 6f 63 |.yS5....X ...doc| 00000110 00 31 20 30 0a 39 fb 0f bc ac 51 f6 6b 51 4f bd |.1 0.9....Q.kQO.| 00000120 58 9a 5b 2b c0 80 9c e6 64 ac 8f 88 7a 1e a4 d0 |X.[+....d...z...| 00000130 b9 83 8d 83 72 4e 7b 71 d2 d8 a0 a5 3d |....rN{q....=|
|
索引文件大部分内容都是以二进制存储的,可读性很差,喜欢钻研的同学可以去看源码。
pack 文件
pack文件用来合并压缩多个object对象的,可以方便进行网络传输(推送到远程仓库)。
*.pack
文件格式:
该图片来自于:https://developer.aliyun.com/article/761663 。
*.idx
文件格式:
该图片来自于:https://developer.aliyun.com/article/761663 。
HEAD 等指针文件
HEAD 具体路径为 .git/HEAD
,HEAD
实际上是一个指针,指向具体的引用或者 commit-id
,比如 HEAD 指向 master
分支时是如下内容:
1 2
| $ cat .git/HEAD ref: refs/heads/master
|
如果 checkout 了一个特定的 commit-id
时,那 HEAD 的值是这个 commit-id
。
1 2 3
| $ git checkout 523d41ce82ea993e7c7df8be1292b2eac84d4659 $ cat .git/HEAD 523d41ce82ea993e7c7df8be1292b2eac84d4659
|
另外,如果我 checkout 了指定的 tag
时,那 HEAD 的值是这个 tag
对应的 commit-id
。
同样的,.git/ORIG_HEAD
、.git/FETCH_HEAD
也是这样的存储方式。
引用
Git 引用名义上是指针,实际上是一个很简单的文件,这个文件存储的是指向的提交的 commit-id
:
1 2
| $ cat .git/refs/heads/master a0e96b5ee9f1a3a73f340ff7d1d6fe2031291bb0
|
附录
相关的 Git 源码
基于 git v2.29.2 版本。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| struct cache_header { uint32_t hdr_signature; uint32_t hdr_version; uint32_t hdr_entries; };
struct ondisk_cache_entry { struct cache_time ctime; struct cache_time mtime; uint32_t dev; uint32_t ino; uint32_t mode; uint32_t uid; uint32_t gid; uint32_t size; unsigned char data[GIT_MAX_RAWSZ + 2 * sizeof(uint16_t)]; char name[FLEX_ARRAY]; };
|
参考资料