Git 是否在内部存储行尾?

Does Git internally store Line Endings?

Git内部存储特定行尾吗?

我的猜测是,Git 仅存储行数据本身(行尾中性),并且根据操作系统,它将使用平台特定的行尾。

或者换句话说:每个行结尾都可以“混合”存储在同一个文件中吗?

我不是在谈论 .gitattributes 文件中的设置。

Git 将整个文件存储为“blob”,并且仅在读取(签出)或写入(索引)时进行转换。 Git 存储“中和线”。

所以是的,Git 可以保存带有混合行结尾的文件,但这通常是一个需要避免的坏习惯。

, Git just stores a raw data block. Linus originally didn't have any sort of CRLF line ending hackery in Git: the initial release in 2005 did not do it at all. The first line-ending conversion code was introduced into Git in Feb 2007, in commit6c510bee2013022fbce52f4b0ec0cc593fc0cc48. The .gitattributes file itself was introduced a bit later, in April 2007, in commit d0bfd026a8241d544c339944976927b388d61a5e.

不过,理解这些的真正关键是记下每个文件的 索引副本 工作树 每个文件的副本。请记住,索引实际上包含 提议的下一次提交 ,或者至少是它的快照。 (下一次提交的元数据是在您 运行 git commit 或任何其他命令生成时生成的。)任何 现有 提交的内容是神圣不可侵犯的:没有任何东西,甚至 Git 本身,都无法改变它们。

提取方

当你第一次检查一些提交时——例如,git checkout <em>branch</em> or git switch <em>branch</em>,或与原始哈希 ID 相同(尽管 git switch 要求在这种情况下使用 --detach 标志) —Git 将从该提交中 填写 Git 的索引,并 填写 您的工作树。 (Git 索引中的任何先前提交和您的工作树首先从这两个地方删除,并在 Checkout another branch when there are uncommitted changes on the current branch 中概述了一些花哨的警告。)

索引得到提交中的确切内容。这意味着,如果一个提交的文件奇怪地混合了 LF-only 行和 CRLF 行,或者是一个像 JPG 这样的二进制文件,带有随机散布的二进制数据,一个天真的程序会 认为 是行尾,嗯,这也是索引中的内容。更准确地说,索引中文件的“副本”实际上只是 Git 中某些 blob 原始哈希 ID的数据库。包含一些现有的已提交文件的 blob 对象是只读的,因此很容易 shared。因此,为了进行初始检查,Git 只让索引共享该副本。 blob 哈希 ID 进入索引,存储在提交中列出的 文件名 下的槽零条目中。1 此 blob 存储在 Git 的对象数据库中,并且被压缩——以 松散对象 的形式——或 very 压缩的形式打包 对象。 Git 可以阅读任何一个;没有人也没有什么可以 重写 一个,尽管 Git 可以很容易地制造新的(不同的)松散物体。

然而,

工作树副本是另一回事。 Git必须解压缩 blob 对象。这意味着读取压缩的 blob 字节并 运行 对其进行 zlib 解压缩代码,以获取表示文件内容的不同字节,如您希望看到的那样。因为 Git 已经在做 这个 工作,所以这里是 Git 做更多 工作的理想场所: Git 可以用 CRLF 行结尾替换 LF-only 行结尾。2

因此,当 Git 提取 一个 索引副本 到您的工作树中时, Git 可以将 LF -only 行到 CRLF 行。如果某个文件被标记(通过 .gitattributes 或任何其他方式)需要此转换,则 Git 会执行;如果文件的 LF 前面没有先有 CR,Git 确保它将写入的内容 您的工作树文件首先有 CR,然后是 LF。

这是 git checkout 的一面。3 让我们暂停一下脚注,然后看看 git add 的一面。


1从技术上讲,提交列出了 tree 对象的 tree hash ID代表快照。该树对象包含名称组成部分和哈希 ID。散列 ID 可能是子树的那些,包含更多的名称组件,或者是代表应该检查的文件的 blob。好吧,散列 ID 可能是符号链接或 git 模块的散列 ID,但这些相对较少:common 树条目用于子树和文件 blob。

2此转换(仅 LF 到 CRLF)是 内置到 Git 的行结束转换的文件提取码。您可以使用 Git 的 涂抹过滤器 添加您自己的任意额外转换,但这些由您自己编写。 (请注意,Git-LFS 使用预先编写的污迹过滤器,从网络上其他地方的单独存储区域提取“大型”文件系统。这是 Git 的附加组件,不是 Git 的一部分Git 本身。)

3直到新的 git restore 命令进入 Git 2.23,只有索引到工作树的转换才进行这种转换。现在 git restore 可以直接从提交中提取文件到您的工作树,还有一个地方可以做到这一点。注意 git checkout -- <em>path/to/file</em> 或者 git checkout <em>commit</em> -- <em>path to file</em> 首先写入 Git 的索引,只有 然后 到你的工作树,所以这个特定的代码路径通过索引到工作树函数。这就是为什么这个新的 git restore 功能值得一个脚注:在 Git 2.23 之前,您必须先让 Git 在 Git 的索引上涂鸦;现在你可以避免了。


git add那边

当您 运行 git add——例如,包括来自 git commit -a 的隐含添加时——Git 做实际的艰苦工作 time,而不是等待更晚的git commit。如果您使用 git commit -a,提交可能会在几毫秒内发生,但 逻辑 仍然相同:首先,Git 执行 git add.

git add的重点是更新Git的索引。我们必须首先更新索引——提议的下一次提交(或其快照)。只有当提议的提交与我们想要提交的内容匹配时,我们才能提交。

由于索引包含路径名和 blob 哈希 ID(和文件模式),Git 此时必须将您的工作树文件转换为 blob。为此,Git 必须首先将该 blob 生成为一个新的松散对象——或者至少,弄清楚它的哈希 ID 将是什么 ,如果它这样做的话.事实证明,找出散列 ID 是什么的最快方法 是继续并开始编写对象,使用 zlib 的压缩器压缩 -while 计算散列。因为我们还不知道对象的名称(散列 ID),所以我们只使用一个临时名称:.git/objects/tmpXXXXXXX,其中 Xes 填充了一些独特的东西,例如。 (准确的临时名称在这里并不重要。)

要将数据 提供给 压缩和哈希函数,但是,Git 必须 读取 的工作树副本文件。如果工作树副本被标记(通过 .gitattributes 或任何其他机制)为 需要转换 ,那么,这是检查 CR 后跟 LF 和 删除 CR 部分,以便我们得到 LF-ony 行尾。这样,zlib 压缩器的散列函数 都将获得 LF-only 行。4

通过散列函数和压缩器输入整个文件后,Git 现在拥有正确的 blob 散列 ID。 Git 检查对象是否已作为散列对象存在。如果是这样,Git 只是重新使用该现有对象,删除临时文件。5 否则,Git 重命名临时文件以使其成为有效的松散对象在 .git/objects/。现在该文件有一个对象,这就是 Git 索引中的内容。请注意,此时,整个文件 已转换为仅 LF 行尾,无论之前的提交是什么。


4和以前一样,这个“删除紧跟 LF 的 CR”过滤器是 内置于 Git 在这里,在这些代码路径中。您可以自己进行任意过滤,使用 clean filter,但您必须自己编写。 (不出所料——如果你已经阅读并理解了脚注 2——这也是 Git-LFS 自动提供的附加组件:如果文件“大”并且要存储在别处,Git-LFS 的“干净”过滤器将文件存储在其他地方,并生成 LFS 数据,以便以后的检出可以检索该文件,作为“干净”的数据。)

5Git 应该检查——可能是可选的,因为这可能很慢——这不是哈希冲突的结果。我不认为它目前这样做。 意外 哈希冲突的可能性很小,可以忽略不计,但考虑到 breaking SHA-1 的存在证明,在我看来,可选检查是个好主意。


这一切只有在启用时才会发生

为了 Git 对文件进行更改,该文件必须标记为“应该修改”。将 core.autocrlf 设置为 true 可以标记 一些 文件: Git 现在将尝试猜测某些文件是文本文件还是二进制文件。 .gitattributes 中的列表文件可以标记 一些 文件,具体为文本,或具体为二进制文件,还可以将它们标记为特定转换。

不过,唯一的内置转换是:

  • 提取:将 LF-only 转换为 CRLF
  • 添加:将 CRLF 转换为仅 LF

有些设置启用两种转换,有些设置仅启用附加转换(旧 crlf=input,旧版本 Git,现代 eol=lf [=241] =]).

请注意,由于 blob-to-working-tree 提取永远不会删除 CR-before-LF,现有提交的文件在其内部 blob 形式中具有 CRLF 结尾(或者一致, 或 mixed) 始终作为工作树中具有 CRLF 结尾的文件检出。如果您不触摸处于此状态的签出文件,Git 会注意到您没有触摸它,并且不会 添加 该文件,因此它会继续在 next 提交中具有混合或一致的 CRLF 结尾。

git add --renormalize 标志旨在强制 Git 重新添加文件,即使它们看起来没有受到影响。这样他们就可以通过 CRLF-to-LF-only 转换获得 运行,如果已设置的话。