首页 > 解决方案 > git 内容跟踪和内容单元

问题描述

对于 git 的想法是内容跟踪而不是文件跟踪,我的困惑来自以下场景:如果我在一次提交中添加两个文件 A、B 到一个 git 存储库中,A 和 B 有重叠(和不同)的内容,git比较两个新文件 A 和 B?对于 A 或 B 的修订,我猜只存储了增量差异,但是对于一次提交的两个新文件,git 可以检测到共同的内容吗?如果是基于内容的,对象文件夹中 blob 的单位是什么?我认为这是每个 blob 一个文件,至少对于新文件?

标签: git

解决方案


如果我在一次提交中将两个文件 A、B 添加到一个 git 存储库中,则 A 和 B 的内容重叠......

我不清楚这里的“重叠内容”是什么意思。也许您的意思是相同的内容?

git会比较两个新文件A和B吗?

仅当您告诉它这样做时,请参阅下面的有关blob 对象的更多详细信息。

对于 A 或 B 的修订,我想只存储增量差异......

事实并非如此。

让我们仔细看看提交中存储的内容。这是Git 存储库中为 Git 提交的内容083378cc35c4dbcc607e4cdd24a5fca440163d17(尽管我已经替换@可能,也许,减少了发送给 Junio Hamano 的垃圾邮件):

$ git cat-file -p HEAD | sed 's/@/ /'
tree 79674d33d6f9f2c9ff29258f8c748aa785de8dc3
parent 88bd37a2d0f9ed504ac49fcecf6371d9fafc2a67
author Junio C Hamano <gitster pobox.com> 1575578639 -0800
committer Junio C Hamano <gitster pobox.com> 1575579169 -0800

The third batch

Signed-off-by: Junio C Hamano <gitster pobox.com>

这实际上是提交对象的内容。请注意tree前面的行:我们现在可以使用git cat-file -p 79674d33d6f9f2c9ff29258f8c748aa785de8dc3or来查看保存此提交的树git ls-tree 79674d33d6f9f2c9ff29258f8c748aa785de8dc3。在这种情况下,输出是相同的,除了如果我们使用git ls-tree,我们可以让它递归到树中的任何子树。

我们想要递归,因为它显示了提交中存储的每个文件。所以我们将使用git ls -r它。不过,我不会引用结果,因为它超过 3000 行:

$ git ls-tree -r 79674d33d6f9f2c9ff29258f8c748aa785de8dc3 | wc -l
    3680

所以 Git 中的这个提交提到了 3680 个存储文件、符号链接和子模块哈希。我们可以按它们的 stored 对它们进行分组mode,这是输出中每一行的第一个字段:

$ git ls-tree -r 79674d33d6f9f2c9ff29258f8c748aa785de8dc3 | cut -f1 -d' ' | sort -u
100644
100755
120000
160000

如果是基于内容的,对象文件夹中 blob 的单位是什么?

一个blob,或者更准确地说,一个类型的对象blob,是一个保存一些数据的对象。上面的100644100755120000mode 对象标识 blob。(该160000对象是一个子模块的 gitlink,在这里不是很有趣。)让我们看看实际的符号链接,因为实际上只有一个:

$ git ls-tree -r 79674d33d6f9f2c9ff29258f8c748aa785de8dc3 | grep '^120000 '
120000 blob 091dd024b349d6bc908371eddb7c594059c4fd70    RelNotes

现在让我们看看这个 blob 对象中有什么091dd024b349d6bc908371eddb7c594059c4fd70

$ git cat-file -p 091dd024b349d6bc908371eddb7c594059c4fd70
Documentation/RelNotes/2.25.0.txt$ 

(注意缺少最后的换行符)。此 blob 包含名为 的符号链接的目标RelNotes

比较,例如:

$ git rev-parse HEAD:GIT-VERSION-GEN
22e8d83d98551298b769022f6fdd606225c34be5
$ git cat-file -p 22e8d83d98551298b769022f6fdd606225c34be5 | head -4
#!/bin/sh

GVF=GIT-VERSION-FILE
DEF_VER=v2.24.GIT

因此,对于文件(mode 100644mode 100755),blob 对象保存文件的数据。

Blob 对象的名称是其哈希 ID,就像任何 Git 对象的名称是其哈希 ID 一样。哈希 ID 是根据对象的类型和内容计算的:

$ python3
...
>>> import hashlib
>>> h = hashlib.sha1()
>>> data = open("GIT-VERSION-GEN", "rb").read()
>>> len(data)
754
>>> h.update(b'blob 754\0')
>>> h.update(data)
>>> h.hexdigest()
'22e8d83d98551298b769022f6fdd606225c34be5'

该内容就是哈希ID 的GIT-VERSION-GEN原因22e8d83d98551298b769022f6fdd606225c34be5:它是对文字字符串blob 754(其中 754 是数据字节数)执行 SHA-1 校验和算法的结果,然后是 ASCII NUL,然后是数据字节本身。

因此,如果您事先知道某个文件将包含此数据(<em>任何文件),则该文件的blob的哈希 ID将为22e8d83d98551298b769022f6fdd606225c34be5.

毕竟,我们可以回到您最初的评论和问题:如果文件AB您的提交具有相同的内容,则它们的树条目具有相同的哈希 ID。如果它们具有不同的内容,则它们的树条目具有不同的哈希 ID。

为这两个文件提供名称AB)和模式字符串(100644= 不可执行, = 可执行)的是树条目。100755您进行的任何存储文件的提交A都会B为它们存储两个树条目。这些树条目中的哈希 ID 将是 blob 对象(重复两次)或包含Aand内容B(相同或不同)的对象(每个不同)的哈希 ID。

Git 并没有比较AB到这里的内容。Git 简单地说:我需要一个 blob 对象来保存 的内容A,计算校验和,并发现是否已经存在这样的 blob 对象(然后被重用)(在这种情况下,保存内容的临时对象“去活”,就像是,一旦提交发生)。然后 Git 对 file 做了同样的事情B。如果 in 的内容与 in 的内容B相同A,那么当 Git 计算完校验和时,该对象肯定已经存在,Git 只是重新使用它。1

一旦该对象的哈希 ID 位于其哈希 ID 位于存储库中可访问其哈希 ID 的提交中的树中,该对象将保留在 Git 存储库中。也就是说,Git 的垃圾收集器,git gc偶尔会运行并执行以下操作:

  • 查找可从任何标签名称或其他引用访问的所有标签对象
  • 查找所有通过分支名称、标签名称或任何其他引用或任何可达标签对象可直接访问的提交
  • 通过可访问的提交递归地查找所有可访问的提交
  • 从任何可达的提交或标记中找到所有可达的树,或者递归地,找到任何可达的树
  • 查找可从任何树或标记或任何索引条目访问的所有 blob

(上面所有的“或标签”项都是因为轻量级和带注释的标签都可以直接指向任何各种对象类型,尽管指向带注释标签对象的轻量级标签当然只是称为带注释的标签)。

所有这些对象都是可达的。(请注意,有每个工作树的引用,包括每个工作树的 HEAD 和每个工作树的索引文件;git gc从在 Git 2.5 中引入添加的工作树到在 Git 2.15 中修复此错误为止,无法扫描这些文件。)可访问对象被保留。如果满足其他条件(修剪时间和各种打包问题),则可以删除无法访问的对象。

每个新提交都会存储一个完整且完整的快照。快照是通过将索引写为一系列 Git 树对象来生成的,顶层树保存对象,如果提交被签出,其内容将进入结果工作树的顶层。(实际git checkout过程首先将树读入索引表示,这就是扩展各种路径名的方式,在顶级树中的树的情况下。从这个意义上说,Git确实存储目录,但它们' 没有使用权限进行注释,并且在内部,Git 首先将它们全部展平到索引中,因此它只需要处理文件。)


1如果两个文件散列到相同的 blob 散列 ID 怎么办?答案是:Git 不能同时存储这两个文件。Git 只是假设这永远不会发生——到目前为止,这是行得通的。另请参阅新发现的 SHA-1 冲突如何影响 Git?


对象不一定单独存储

如果您获取一个大文件(例如许多兆字节)并对它进行小幅更改并将结果存储在新的提交中,您最初会得到两个独立的 blob 对象,Git 称之为松散对象格式。存储在.git/objects目录中的这两个对象是 zlib 压缩的,但它们可能仍然相当大。

但是,在对象在存储库中一段时间​​后,Git 的垃圾收集器会运行git repack. 2 这会收集各个目标文件并进一步压缩它们。它使用一种不依赖于文本文件格式的增量编码形式:二进制文件可以在这里进行增量压缩。一旦某个对象被打包,它的一部分可能会与其他使用它作为基础对象的对象共享。准确描述这个过程非常困难。3 不过,一般来说,这些大的 blob 将在包文件中进行增量压缩。

然而,结果是在对象级别上,两个大对象是完全不同的。在包装级别,它们可能具有混合在一起的部分(或如您在上面所说的“重叠”)。但是任何对象都不能更改:它的身份它的哈希 ID,它完全由它的内容决定。所以这样做是安全的,只要基础对象从未从包中移除。(也没有可以更换,所以这不是问题。包可能会变得太大这是一个问题。)


2这可以调整,甚至禁用;请参阅文档

3完美解决打包问题太难了,所以Git 使用了一些启发式方法,这里记录了这些方法


推荐阅读