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
,其中 X
es 填充了一些独特的东西,例如。 (准确的临时名称在这里并不重要。)
要将数据 提供给 压缩和哈希函数,但是,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 转换获得 运行,如果已设置的话。
Git内部存储特定行尾吗?
我的猜测是,Git 仅存储行数据本身(行尾中性),并且根据操作系统,它将使用平台特定的行尾。
或者换句话说:每个行结尾都可以“混合”存储在同一个文件中吗?
我不是在谈论 .gitattributes
文件中的设置。
Git 将整个文件存储为“blob”,并且仅在读取(签出)或写入(索引)时进行转换。 Git 不 存储“中和线”。
所以是的,Git 可以保存带有混合行结尾的文件,但这通常是一个需要避免的坏习惯。
如6c510bee2013022fbce52f4b0ec0cc593fc0cc48
. 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
,其中 X
es 填充了一些独特的东西,例如。 (准确的临时名称在这里并不重要。)
要将数据 提供给 压缩和哈希函数,但是,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 转换获得 运行,如果已设置的话。