每个git提交的树对象内容中存储了哪些信息?

4
每个 Git 提交对象都指向一个树对象。每个 commit-tree 对象是否将其所有条目一起存储,还是只添加新条目并仅包含与提交的父对象的增量?
例如,Linux 源代码有 100 万次提交和数千个对象(主分支有 70,000 个)。如果每个提交对象都包含所有对象的条目,则从长远来看需要大量空间。即使提交/推送了单行更改,也需要大量处理和传输。
我理解 Git 的哲学是为文件存储快照而不是增量,但在这种情况下,只有更改的文件被存储。
在下面的示例中,70951b429e0e1191a8c1d9e34248cd76453ef544 包含(或显示为包含)所有 5 个文件,即使只添加了单个文件。
[test]$ls
a.txt  b.txt  c.txt  d.txt
[test]$echo r5 > e.txt
[test]$git add -A && git commit -m "r5"
[master 51f6941] r5
[test]$git cat-file -p 51f6941
tree 70951b429e0e1191a8c1d9e34248cd76453ef544
[test]$git cat-file -p 70951b429e0e1191a8c1d9e34248cd76453ef544
100644 blob 9a6c8d12dea8859b821b2ba705f7efd6cc914aa5    a.txt
100644 blob 9a6c8d12dea8859b821b2ba705f7efd6cc914aa5    b.txt
100644 blob b6693b64f528de38cde5533acd781fde743bc3df    c.txt
100644 blob 91174caefafdc81d34e302874c86c6e4d5212075    d.txt
100644 blob 29f4cfc46ba3a0bde55bce8f44ac3590e2108da4    e.txt

相关链接:https://dev59.com/v2Ag5IYBdhLWcg3w9e_m - Ciro Santilli OurBigBook.com
3个回答

7

每个提交(commit)逻辑上都包含了一个完整的文件快照(也就是在提交时该版本库中的所有文件)。

如果你通过其哈希 ID 选择了一个提交,然后运行git checkout命令,将会把该提交中的文件填充到工作目录中,也就是说,你的工作目录将变成该提交的快照。如果你切换到另外一个提交,而那个提交比当前少了三个文件,Git 将会移除这三个文件(并更新剩余的文件)。

但是,如果每个提交对象都包含所有对象的条目,那么从长远来看会占用大量空间。

但实际上,并不是这样的。其中涉及到两个非常巧妙的技巧:

第一个技巧体现在这里:

[test]$git cat-file -p 70951b429e0e1191a8c1d9e34248cd76453ef544
100644 blob 9a6c8d12dea8859b821b2ba705f7efd6cc914aa5    a.txt
100644 blob 9a6c8d12dea8859b821b2ba705f7efd6cc914aa5    b.txt
100644 blob b6693b64f528de38cde5533acd781fde743bc3df    c.txt
100644 blob 91174caefafdc81d34e302874c86c6e4d5212075    d.txt
100644 blob 29f4cfc46ba3a0bde55bce8f44ac3590e2108da4    e.txt
请注意,Blob哈希ID9a6c8d12dea8859b821b2ba705f7efd6cc914aa5出现了两次:一次是在a.txt中,另一次是在b.txt中。 a.txtb.txt的内容只有一个副本。从这可以得出结论,无论是a.txt还是b.txt中的内容,它们的内容相同。
因此,如果你提交了100个文件,然后进行了新的提交,其中99个文件与前一次提交的99个文件相同,那么你刚刚重复使用了99个Blob对象。它们不必再次存储。
Git会自动以这种方式去重文件内容。
第二个巧妙之处发生在稍后。最初,所有对象都作为zlib压缩文件存储(在.git/objects/中的文件,尽管你不应该指望这样)。如果你更改了文件中的几个字节,并使用git add,而新的Blob对象与某个已存在的Blob对象不完全匹配,则会生成一个新的loose对象。这些对象在内部称为loose对象。
当有足够多的loose对象时,或者如果需要的话,Git会将这些loose对象打包成一个pack file。此时,通常可以将可以有利用增量压缩的对象进行压缩。这种压缩是真正巧妙的代码。
当你使用git fetchgit push时,Git会确定需要传输哪些对象,并构建所谓的thin pack。这就是你看到的countingcompressing objects信息。然后,Git将thin-pack通过网络发送;另一端的Git会修复thin-pack,使其成为常规(fat)pack。当pack文件太多时,Git会repackpack文件,将多个*.pack*.idx文件减少到仅有几个(或仅有一个)。
(这里偶尔会出现一些错误。最近有修复大量pack文件的问题。还存在一些旧的bug,会留下过多的loose对象。偶尔手动运行git gc有时有帮助解决这些问题,但是过于频繁地使用git gc可能会适得其反。)

4
一个树对象本身就是完整的。它代表目录层次结构中的一级。所以如果你有一个名为src的目录,里面有叫做foobar的目录,每个目录内都有内容,你将会得到顶层、srcsrc/foosrc/bar的树对象。
然而,文件中的实际数据是存储为blob对象的。如果一个文件没有更改,Git不会存储它的新副本:它只是引用现有的blob对象。这也适用于树,所以如果你只是改变了src/foo中的一个文件,你会得到顶层、srcsrc/foo的新树对象,但不包括src/bar
现在,当Git打包对象时,它会取出每个对象并对其进行与大小和类型相似的其他对象的差分操作。因此,如果你只修改了树中的一个条目,那么该树很可能被打包成大部分引用另一棵树,并且只包含新条目的文本内容。类似地,小文件的更改也会以增量的方式打包,因此文件的小更改将导致引用该文件的另一个副本以及少量文本内容。
这只是打包形式;如果Git需要读取实际对象,则会解析每个增量并将其拉入内存,以便读取数据。松散的对象存储为压缩格式,但不进行差分操作。定期使用git gc进行打包。

2
每个commit-tree对象是否都包含它的所有条目,或者只添加新条目并仅包含与父提交的增量?
Git将存储增量与版本增量分开。从存储压缩重新构建出来的对象是完整的快照。
当Git认为有大量可用空间时,它会对对象数据库进行打包;之后,树(像其他所有东西一样)几乎完全是增量压缩的,只是...不一定是相对于它们的父级。目标是存储压缩。Git比仅仅查找父级更远。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接