据我了解,一些VCS存储修订版之间的差异,因为,差异有时很小 - 源代码中的一行被更改或者后续修订中添加了注释.另一方面,Git为每个修订版存储压缩的"快照".
如果只做了很小的改动(大文本文件中的一行),Git如何处理这个?它是否存储了两个几乎完全相同的副本?我想,这将是对空间的低效利用.
Gre*_*ill 17
它是否存储了两个几乎完全相同的副本?我想,这将是对空间的低效利用.
是的,Git确实如此,至少在开始时.当你进行提交时,Git在.git/objects/
树下创建一个(略微压缩的)源文件副本,其名称基于内容的SHA1(这些被称为"松散"对象).您可以查看这些文件,如果您对格式感到好奇,那么这样做是值得的.
需要记住的是,Git是为了速度而构建的,并不关心存储库数据的大小.当Git想要一个旧版本来查看它时,它所要做的就是从.git/objects/
树中原样读取文件.没有应用增量,只是使用zlib解压缩的原始读取字节(这是非常快).
现在,您可以正确地观察到,在使用存储库一段时间之后,文件.git/objects/
中将包含很多源文件的副本,所有这些都只是略有不同.这就是"打包"文件的用武之地.当你创建一个包文件(自动或手动)时,Git会收集所有文件对象,以一种压缩方式对它们进行排序,并使用数字将它们压缩成包文件不同的技术.
创建包文件时使用的技术之一确实是delta压缩.Git会注意到两个对象看起来非常相似,并存储其中一个对象以及它们之间的差异.请注意,这仅作为原始数据在纯粹的对象基础上完成,而不考虑事物的提交顺序或分支的排列方式.就Git的其余部分而言,低级包文件格式是一个实现细节.
请记住,Git仍然是为速度而构建的,因此打包文件不一定是您可能获得的绝对最佳压缩.包文件创建中有很多启发式方法与速度和大小之间的权衡相关.
当Git想要读取一个对象并且它不是一个"松散"的对象时,它将查看包文件(它们在中.git/objects/pack/
)以查看它是否可以在那里找到.当Git找到正确的包文件时,它从包文件中提取对象,应用重建原始文件对象所需的任何算法(增量分辨率,解压缩等).Git的更高级别部分并不关心pack文件如何存储数据,这是一个很好的关注点分离并简化了应用程序代码.
如果您想了解更多相关信息,我建议您阅读Pro Git书籍,特别是章节
git如何存储实际提交的文件在存储库的生命周期中有所不同,但让我们从基础知识开始.
将文件提交到存储库时,会生成一个新文件,该文件的完整副本.SHA1是根据其内容计算的,这是该文件的"对象ID".
你可以在下面找到这个文件 .git\objects\SH\A1-hash
该SH\A1-hash
有我的指示SHA1的前两个字符作为文件夹名称和38其余部分用作目录中的文件名的方式.
然后修改此文件,将其添加到索引并提交.
这再次存储为一个全新的文件,索引方式与上面完全相同.
这很容易测试,但请记住,每当您进行更改1个文件的提交时,您将获得3个git对象:
所以,是的,git将文件存储为完整的快照.请注意,这些文件是压缩的,因此它们不会占用与此文件的两个完整副本相同的空间,但它们占用的空间与此文件的两个完整压缩副本相同.
如果添加的文件不能很好地适应压缩(想想jpg,png或zip文件),那么是的,这将占用大量空间.
在某些时候,Git可能决定打包你的存储库,在这里Git可能会决定在这个packfile中使用delta压缩(压缩和存储文件之间的差异).但是,Git的其余部分没有看到这一点,因为这是Git内部基础文件访问之上的抽象.各种Git命令实现仍然会看到"un-deltified"(如果有这样一个单词)文件.
现在,各种命令总是会隐藏这一点,因为你使用的大多数git命令,如果实现得好,会隐藏你,开发人员的所有底层抽象和优化,而是专注于你可能想要看到的内容.
因此,如果您查看这些文件,一些命令将显示差异,其中底层文件不会存储为差异,只是因为差异对您(开发人员)更有意义.
如果您改为使用管道命令,您将看到更多的blob.
如果你想看看所有这些在实践中是如何运作的,你只需要知道一个命令,就是这样git cat-file -p SHA1
.
这是一种测试方法:
git log
并复制提交的SHA1执行git cat-file SHA1-of-commit
,你会看到这样的事情:
tree d7d68c5b2ecc58da225c953e35b0797a4805b844
author Lasse Vågsæther Karlsen <lassevagsaether.karlsen@visma.com> 1491986419 +0200
committer Lasse Vågsæther Karlsen <lassevagsaether.karlsen@visma.com> 1491986419 +0200
First copy
Run Code Online (Sandbox Code Playgroud)现在制作一个SHA1 id的副本tree
,这是树对象的对象id,然后执行git cat-file SHA1-of-tree-object
,你会看到如下所示:
100644 blob 3b5d02884e6a17f20ed7938bf9e534f1bd0d195e Temp.7z
Run Code Online (Sandbox Code Playgroud)
这告诉你索引包含1个文件(1行),带有文件名Temp.7z
,并告诉你它的SHA1 id.复制此ID.
git cat-file -p SHA1-of-blob
,您将看到您添加的文件的内容.Git的存储模型根本不是神奇的或复杂的,但是在那里有很多优化和抽象,以避免浪费空间,重复数据删除等等.
Git 使用补丁或hunks
. 它计算两个版本之间引入的差异并存储它。
存储两个几乎相同的副本?我认为这对空间的利用效率很低。
Git 扫描您的代码(启发式)并且仅存储一次差异。如果 git 在多个文件中找到相同的代码,它会生成hunk
相似的代码并将指向它的指针存储在原始位置。
为了简单起见 - 它比下面解释的要复杂得多,使它简单以便您可以更容易地理解它。
一旦你的代码被扫描, git 搜索之前提交的更改,如果发现更改, git 将旧的更改拆分为一个块。
如果您在文件中间添加代码,那么它将被分成 3 个块(顶部 = 旧代码,中间 - 新代码,底部 - 旧代码),现在您将有 3 个块。下次 git 扫描你的代码时,他将使用这 3 个块来搜索更改。
例如:假设您有一堆文件,每个文件顶部都有许可协议,并且所有文件中的许可协议都是相同的。
Git 将扫描文件,第一个块将存储为补丁,在所有其他文件上,git 将放置一个指向该块的指针。
这样 git 以非常有效的方式存储信息。
如果您想查看它的操作,请使用git add -p
并选择s
拆分。
正如上面所解释的,hunk 是一个 diff,这里有一些关于它的内容。
hunk
是一个与 diff 相关的术语,下面是 git 如何直观地显示它(补丁):
该格式以与上下文格式相同的两行标头开头,只不过原始文件前面是
---
,新文件前面是+++
。接下来是一个或多个包含文件中行差异的更改块。
未更改的上下文行前面有一个空格字符,添加行前面有一个加号,删除行前面有一个减号。
更多信息:
https://github.com/mirage/ocaml-git/blob/master/doc/pack-heuristics.txt