Git保存的是文件的快照,即:会保存每个文件的每个版本的完整内容,若是一个文件只做了一个小小的改动,比如只修改了一个字符,那岂不是除了这个字符的其他内容全部存了两遍?那能否让Git只完整的保存其中一个,另一个以版本差异的方式保存呢?其实,Git是可以做到的,Git最初向磁盘中存储对象时所使用的格式被称为“松散(loose)”对象格式,但是,Git还使用了一种更效的存储机制,被称为“包文件(packfile)”的二进制文件,以节省空间和提高效率。
首先,我们准备一个较大的文件repo.rb(22k),并将其添加到版本库:
$ git add repo.rb
warning: LF will be replaced by CRLF in repo.rb.
The file will have its original line endings in your working directory
$ git commit -m "added repo.rb"
[master a6a3d60] added repo.rb
1 file changed, 709 insertions(+)
create mode 100644 repo.rb
$ git cat-file -p master^{tree}
100644 blob e212b11d921e98c6444eb6c946de48ca39da9131 new.txt
100644 blob 033b4468fa6b2a9547a70d88d1bbe8bf3f9ed0d5 repo.rb
040000 tree 1e18318519c5897cbbe86691f3c484c825b776e5 sub
100644 blob 3093e3da8ea43704f4768d791376eda3c056c6ef test.txt
$ git cat-file -s 033b4468fa6b2a9547a70d88d1bbe8bf3f9ed0d5
22044
此时生成的数据对象为033b4468,然后,对文件repo.rb进行修改并提交:
$ echo '# testing' >> repo.rb
$ git commit -am "modify repo a bit"
warning: LF will be replaced by CRLF in repo.rb.
The file will have its original line endings in your working directory
[master ffca670] modify repo a bit
1 file changed, 1 insertion(+)
$ git cat-file -p master^{tree}
100644 blob e212b11d921e98c6444eb6c946de48ca39da9131 new.txt
100644 blob b042a60ef7dff760008df33cee372b945b6e884e repo.rb
040000 tree 1e18318519c5897cbbe86691f3c484c825b776e5 sub
100644 blob 3093e3da8ea43704f4768d791376eda3c056c6ef test.txt
$ git cat-file -s b042a60ef7dff760008df33cee372b945b6e884e
22054
此时新生成的数据对象为b042a60e,并且文件的大小由22044变成了22054。当我们向远程服务器执行推送时,或者手动执行git gc命令时,Git都会将松散的对象打成包文件:
$ git gc
Enumerating objects: 19, done.
Counting objects: 100% (19/19), done.
Delta compression using up to 8 threads
Compressing objects: 100% (12/12), done.
Writing objects: 100% (19/19), done.
Total 19 (delta 1), reused 0 (delta 0), pack-reused 0
这个时候再查看 objects 目录,我们会发现大部分的对象都不见了,与此同时在.git/objects/pack目录下面出现了一对新文件:
$ find .git/objects/ -type f
.git/objects/info/commit-graph
.git/objects/info/packs
.git/objects/pack/pack-b0bc3e789c301ef29e409784de730539be4d9481.idx
.git/objects/pack/pack-b0bc3e789c301ef29e409784de730539be4d9481.pack
可以通过git verify-pack命令查看已打包的内容:
$ git verify-pack -v .git/objects/pack/pack-b0bc3e789c301ef29e409784de730539be4d9481.idx
ffca6702b866e0ea55b5814978c5bddc74341b0d commit 224 152 12
a6a3d60b6b016c4004668e3f4631238227ca4a3b commit 220 148 164
adce7c0d7c4ebe520a5015d99a0cc6c04cb86312 commit 219 148 312
0bcaf18821af42d243343a6cbb6042faf3d9ff22 commit 220 150 460
acda5662c5b9d21a9b35a44e71d7ba3adfe6bcf6 tag 141 129 610
6696fbe9f7c1cd84b1404c1c784c36ab0af7cd50 commit 171 120 739
12df1c8e96c426882aa65af9fc703b1e7aa1c3df tree 136 137 859
1e18318519c5897cbbe86691f3c484c825b776e5 tree 39 50 996
9d1957e6a0fd284aa17a9824ccea897d045b5a7b tree 136 137 1046
ec6ef32dc11b034bd34d2a05becfc9738debe59a tree 101 106 1183
4af7fccde05f38392e4f6d43f5b92e356b0301dc tree 71 76 1289
30eb43eb353c7fc2352f8e57ea0ebecb5fbbc468 tree 36 47 1365
e212b11d921e98c6444eb6c946de48ca39da9131 blob 16 26 1412
b042a60ef7dff760008df33cee372b945b6e884e blob 22054 5799 1438
033b4468fa6b2a9547a70d88d1bbe8bf3f9ed0d5 blob 9 20 7237 1 b042a60ef7dff760008df33cee372b945b6e884e
387bf721cf870334f40cb87829a818f9fa421285 blob 14 23 7257
3093e3da8ea43704f4768d791376eda3c056c6ef blob 10 19 7280
786ae1f3a311f95e95ce47702dcfd9e9ed2cb27b blob 14 23 7299
dffc3d5ed8d6a2f7982ff91e720d3c0baca0d6db blob 10 19 7322
non delta: 18 objects
chain length = 1: 1 object
.git/objects/pack/pack-b0bc3e789c301ef29e409784de730539be4d9481.pack: ok
此处,033b4468这个数据对象是repo.rb文件的第一个版本,它引用了数据对象b042a60e,即repo.rb的第二个版本,第三列显示的是对象的大小,数据对象b042a60e占用了22k,而数据对象033b4468仅占用了9字节,并且,是repo.rb的第二个版本保存了完整的内容,而第一个版本是以差异方式保存的,这是因为绝大多数情况下,需要快速访问文件的最新版本。