据我所知,Git的blob将SHA1哈希作为文件名,以便不复制存储库中的文件。
例如,如果文件A的内容为“abc”并且SHA1散列为“12345”,则只要内容不变,则提交/分支可以指向相同的SHA1。
但是,如果将文件A修改为“def”以使SHA散列为“23456”,会发生什么? Git是否存储文件A和修改过的文件A(不仅仅是区别,而是整个文件)?
以下“Git社区图书”回答了我的大部分问题。
值得注意的是,这与您可能熟悉的大多数SCM系统有很大不同。 Subversion,CVS,Perforce,Mercurial等都使用Delta Storage系统 - 它们存储一个提交和下一个提交之间的差异。 Git不会这样做 - 它会在每次提交时存储项目中所有文件在此树结构中的外观。这是使用Git时要理解的一个非常重要的概念。
答案 0 :(得分:7)
git按内容而不是差异存储文件,因此在您的示例中,A(“abc”和“def”)的两个版本都将存储在对象数据库中。
它可以更好地存储整个对象,因为通过比较它们的SHA很容易看出文件的两个版本是否相同。有关如何存储对象的详细信息,请查看git-book。这样做效果更好,因为如果使用差异跟踪文件,则需要文件的整个历史记录来重建它。在集中式系统中很容易做到,但在分布式系统中却不容易,文件可能会有很多不同的变化。
Git直接从对象执行diff。
答案 1 :(得分:4)
git的设计目标之一就是速度。考虑将git中的对象存储为增量而不是唯一对象。
如果按SHA1哈希存储每个唯一blob,则从该SHA1哈希中检索内容只需要一个固定的计算。如果你开始存储增量,你将不得不重建对象,计算将不再被修复,并且可以不受限制地增加,具体取决于实现。
理解设计的一个好方法是查看实际的存储库(注意:电子邮件):
$ git cat-file commit HEAD
tree 21f9601e608cf62360fca43cd7f0bf05bb65bd23
parent 11507e17a7c823c379202ae344aa59fe5370a4fd
author John Doe <jd@example.com> 1273816361 -0400
committer John Doe <jd@example.com> 1273816361 -0400
Important Work
$ git ls-tree HEAD
100644 blob 2f6d9912344c299670551c9e9684a7cae800ec5d .gitignore
...
100644 blob a3ddeb9dd0541b80981f2f78bbc500579a13459a COPYING
040000 tree f1ac0acae2a4ab31c2a79b71f08ebd651136d706 contrib
...
从这两个命令可以看出,提交只是一些元数据,一个或多个父项和一个树。树包含一个或多个blob和树。
知道,您可以开始考虑各种存储库操作的复杂性。分支的尖端只是指向提交哈希的指针。因此,从那开始,列出历史只是遍历父母的问题。列出树的内容,只是意味着遍历树和所有子树。检索文件内容如上所述。
当然,总是存在一种权衡,虽然它确实在文件级提供自动重复数据删除,但这种模式的空间效率非常低,因为每个唯一文件只需要存储一次。使用packfile可以有效减轻这种情况。 Delta存储(在svn等中使用)在没有压缩的情况下更节省空间,但git最终可以更有效地存储。
要进行差异提交,您可以看到可以从比较树哈希开始,然后如果它们不匹配,则遍历树并比较其blob和树,依此类推。由于模型是围绕原子提交设计的,因此文件差异更加昂贵,但并非不合理。