每个Git提交对象都指向一个树对象。 每个提交树对象都存储它的所有条目吗?或者它仅添加新条目并且仅包含提交父级的增量?
例如Linux源代码具有100万个提交和数千个对象(master具有70,000个)。如果每个提交对象都包含所有对象的条目,从长远来看,它将占用巨大的空间。同样,即使提交/推动单行更改,也要进行大量处理和转移。
我了解Git的理念是存储快照而不是文件增量,但在那种情况下,仅存储更改的文件。
在下面的示例中,即使仅添加了一个文件,70951b429e0e1191a8c1d9e34248cd76453ef544仍包含(或显示为containig)所有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
答案 0 :(得分:2)
每个提交树对象都存储它的所有条目吗?或者它仅添加新条目并且仅包含提交父级的增量?
Git将存储增量与修订增量分开。从中进行重构(但经过存储压缩)的对象是完整快照。
当看起来有巨大的胜利时,Git将打包对象数据库;在那之后,树木(像其他所有树木一样)几乎全部被三角压缩,只是……不一定针对它们的父母。目标是存储压缩。 Git看起来比父母远得多。
答案 1 :(得分:2)
树对象本身总是完整的。它代表目录的一层=层次结构。因此,如果您有一个目录src
,并且其中的目录名为foo
和bar
,并且每个目录都包含内容,则对于src
,您将具有顶层树对象, src/foo
和src/bar
。
但是,文件中的实际数据存储为blob。如果文件没有更改,则Git不会存储该文件的新副本:它仅引用现有的blob对象。对于树也是如此,因此,如果仅更改src/foo
中的文件,则将获得顶层src
和src/foo
的新树对象,而不是{{1} }。
现在,当Git打包对象时,它将获取每个对象并将其针对大小和类型相似的其他对象进行删除。因此,如果您仅修改了一棵树中的一个条目,则该树可能会被打包,以使其主要引用另一棵树,并且仅包含新条目的文字数据。同样,对文件的小的更改也会以经过修饰的方式打包,因此,对文件的小的更改将导致对该文件的另一个副本的引用以及少量的文字内容。
这只是包装形式;如果Git需要读取一个实际的对象,它将解析每个增量并将其拉入内存,以便它可以读取数据。松散的对象被压缩存储,但没有被删除。使用src/bar
定期进行打包。
答案 2 :(得分:2)
从逻辑上讲,每个提交都保存着每个文件的完整快照(嗯,提交中的每个文件)。
如果您选择一个提交(例如,通过其哈希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哈希ID 9a6c8d12dea8859b821b2ba705f7efd6cc914aa5
会显示两次:一次用于a.txt
,一次用于b.txt
。
a.txt
和b.txt
的内容仅一份。由此我们可以得出结论,无论在 a.txt
和在 b.txt
中,内容都是相同的。
因此,如果您提交100个文件,然后进行一个新提交,其中99个文件与先前提交的99个文件相同,则您只是重用 99个blob对象。它们不必再次存储。
Git通过这种方式自动对文件内容进行重复数据删除。
第二个聪明点发生在以后。最初,所有对象都存储为zlib压缩文件(.git/objects/
中的文件,尽管您不应指望此文件)。如果您更改文件中的几个字节并使用git add
,并且新的blob对象与某些已经存在的blob对象不是100%完全匹配,则会得到其中一个新对象。这些在内部称为 loose 对象。
当周围有足够的松散对象时,或者如果需要的话,可以更快地将Git 打包到一个 pack文件。这时,通常可以进行增量压缩的对象通常是。这种压缩是真正聪明的代码。
当您使用git fetch
或git push
时,Git会找出需要通过网络传输的对象,并构建一个所谓的 thin pack 。在这里您可以看到counting
和compressing objects
消息。然后,Git通过网络发送瘦包。另一端的Git固定了薄包装,使其成为常规(脂肪)包装。当打包文件太多时,Git会重新打包打包文件,使您从许多*.pack
和*.idx
文件中再次减少到几个(或一个)。 / p>
(这里偶尔会出现一些错误。最近有一个修复程序可以处理大量的打包文件。还有一些较旧的错误,其中遗留了太多松散的对象。有时偶尔会使用手册git gc
有助于解决这些错误,但过于频繁使用git gc
可能适得其反。)