git 行尾行为与文档不匹配

git line endings behavior does not match documentation

我看到 git 使用行结尾做事,这似乎与我在本网站和官方文档甚至它自己的警告消息上看到的所有内容相矛盾。 (也可能是我阅读理解不及格。)这是一个小复制品。

# repro.sh
git --version # 2.27.0.windows.1
mkdir empty
cd empty
echo '* text=auto !eol' > .gitattributes
echo hi > t.txt
git init
git config core.autocrlf false # I guess git attributes overrides this anyway?
git add t.txt # wait what?  warning: LF will be replaced by CRLF in t.txt???  I thought git likes LF?
git commit -m 'the plot thickens'
git cat-file -p `git rev-parse HEAD:t.txt` > temp.txt # get raw blob as its really stored, I hope
od -c t.txt # original file in working dir ends in LF
od -c temp.txt # file from git also ends in LF, despite git warning??
# end of script

这有意义吗?我认为 git 有时喜欢在“git 添加”时将 CRLF 转换为普通 LF,并在结帐时进行相反的操作,但我从未听说过它在 [=16= 上将普通 LF 转换为 CRLF ] 添加,因为警告似乎是威胁。然后它不这样做。签入的文件正是我工作目录中的文件,由 cat-file 验证。那么为什么要发出警告呢?怎么回事?

邮件本身似乎总是有点……错误?诡异的?措辞不当?——我不知道该怎么称呼它。该消息的 意图 是警告您似乎不一致:您将来查看文件的方式可能与您现在查看文件的方式不兼容。

考虑到这一点,让我们谈谈细节:

echo '* text=auto !eol' > .gitattributes

首先,在 text=auto 上:这将 text 属性设置为字符串值 auto,它告诉 Git:请猜猜每个文件是文本或二进制文件。我个人认为这是个坏主意:你不想 Git 去猜测。你应该告诉它。 Git的猜测通常都很好,但我不喜欢我的软件猜那么多。 :-)

无论如何,让我们继续!eol:这意味着将eol属性设置为未指定状态。这可能不是你想要的。它starts 未指定,所以如果您不想指定它,您可以不指定它。 ! 前缀的存在使您可以 更正 一些以前的设置:例如,如果默认值应为 eol=lf,您可能有:

* eol=lf

但由于 JPG 文件不应被修改,我们可以仅针对 *.jpg:

覆盖它
*.jpg !eol

(尽管 *.jpg binary 可能更好:它意味着 -diff -merge -text 并且对于 -texteol 属性变得 无关 ) .

所以,到目前为止我们得到的是:一个文件是文本当且仅当Git猜测它是文本,并且eol属性未指定.

git config core.autocrlf false # I guess git attributes overrides this anyway?

text 属性专门覆盖了这个属性。 The gitattributes documentation 部分表示:

If the text attribute is unspecified, Git uses the core.autocrlf configuration variable to determine if the file should be converted.

这并没有说明如果 text 属性被 指定 会发生什么(它是 auto),但返回只是一个位,我们发现 text=auto:

If Git decides that the content is text, its line endings are converted to LF on checkin. When the file has been committed with CRLF, no conversion is done.

这里只讨论checkin。文档没有说明这一点,但这确实是在 git add 期间,也就是 Git 可能会将 CRLF 转换为 LF-only。

git add t.txt # wait what?  warning: LF will be replaced by CRLF in t.txt???

Git 在 git add 期间发出这些警告(除非它们通过其他配置被抑制)当它发现任何可疑情况时。这些警告是,或者至少包括,你所看到的,我有时称之为措辞不当(因为没有更好的术语)。不过,我没有更好的方式来表达它们,但不会冗长到有问题。

警告:冗长=这里的描述有问题

只有两个内置 LF/CRLF 转换:

  • 将 CRLF 转换为仅 LF 的“进行中”转换:这仅在 git add 期间发生,并且仅当它被称为或似乎被称为-对于.

  • 将 LF-only 转换为 CRLF 的“即将退出”转换:这发生在 git checkoutgit reset --hardgit restore 期间(如果 运行带有显式或隐式的--worktree),以及其他类似的操作。但是,就像正在进行的 CRLF 到 LF 转换一样,它只会在它被调用或似乎被调用时发生。

这里发生的事情是 Git 可疑 你会在输出时发生 LF 到 CRLF 的转换,在某个时间未来。我 认为 你的设置现在不是这样配置的,因为你有 !eol 并且在 Linux 上(你在 Linux 上?也许不是: 你在版本字符串中说 windows )。因此,也许您的设置 现在以这种方式配置的,因为您有 !eol 并且在 Windows 上。我不使用 Windows,所以我不确定 在 Windows.

上的默认值 是什么

与此同时,t.txt,如在您的索引和工作树中所见,具有纯 LF-only 行尾。如果 Git 将执行一个正在进行的 LF 到转换(从索引副本到工作树副本),您的 工作树中的 t.txt 文件 会突然有 CRLF 行结尾。

这就是这条警告信息的意思。如果将来 Git 对文件进行文本转换,则提取现在 Git 索引中内容的结果将与 实际 文件不匹配在你的工作树现在。 Git 可以在这里做的一个转换是将LF-only变成CRLF,而t.txt目前是LF-only。

最后几步

git commit -m 'the plot thickens'

这里的情节并没有真正变厚。所有转换都发生在这一点之前。 commit 命令仅获取存储在 Git 索引中的 t.txt 文件(这是 Git 索引中的唯一文件,因为存储库是全新的)并创建一个承诺。

git cat-file -p `git rev-parse HEAD:t.txt` > temp.txt
# get raw blob as its really stored, I hope

确实如此,是的。您同样可以从索引中获取 :t.txt,或使用 git ls-files --stage 获取 blob 哈希 ID。

请注意 git commit 步骤 没有修改工作树副本 。它仍然完好无损。要强制 Git 将索引副本提取回工作树,首先删除工作树副本,然后使用任何 Git 命令重新创建它。这将 运行 提取步骤,根据您的各种配置的要求,它将(或不会)将 LF-only 转换为 CRLF:

rm t.txt
git checkout -- t.txt

您现在可以使用 od 或类似工具查看发生了什么。 \n 是否变成了 \r\n?这会告诉您 Git 如何解释此文件的当前设置(core.autocrlfcore.eol 以及 .git/info/attributes.gitattributes 中的各种属性。

注意:自 Git 2.8 以来,git ls-files --eol 已经能够告诉您更多关于这里发生的事情。它将分别:

  • 检查索引中的内容;
  • 检查工作树中的内容;和
  • 查看哪些属性适用

索引中的每个文件。