我刚刚在 https://github.com/aic-sri-international/aic-util 克隆了 GitHub 存储库,并且一些(但不是全部)文件被认为已被 IntelliJ IDEA IDE 和 git status
修改。
存储库中共有 549 个 Java 文件,其中 49 个由 git status
表示已修改。我没有看到任何明显的模式,哪些文件被认为是被修改的,哪些不是。
IntelliJ IDEA 上的一个差异表示文件的“内容与存储库的内容相同”。
即使对文件使用 git restore
后,它仍然显示为已修改!
git diff
显示如下:
@@ -1,67 +1,67 @@
-Line 1
-Line 2
...
-Line 67
+Line 1
+Line 2
...
+Line 67
我想知道这是否可能是由于 CRLF
/LF
的差异造成的。我在 macOS 上,原始项目是在 Windows 上开发的。但是,使用 cat -ve
会显示使用 CRLF
的修改文件和未修改文件。
这是什么原因?
编辑;简短的解决方案:选择的答案非常详细,但要从中得出解决方案需要仔细阅读。
本质上,由于某种原因,存储库存储了 CRLF
文件,即使它应该包含 LF
文件。只需将修改后的文件与 git add . --renormalize
与最新版本的 Git(等于或高于 2.16)一起添加并提交,即可在存储库中以正确的 LF 格式创建一个新提交。
答案 0 :(得分:3)
存储库中的文件是“坏的”(在某种意义上)。如果拥有存储库的人会“修复”(在某种意义上)提交的文件,问题就会消失。
理解这个问题的关键在于克隆实际的存储库并访问有问题的提交。存储库(如评论中所述)是 https://github.com/aic-sri-international/aic-util,而一个有问题的提交,也许是您正在查看的提交,是 60fa84abe4357b4fc0acb3d18cefc5b3c40958b6
(在我写这篇文章时他们的 master
) .
在这次提交中,我们有一个 .gitattributes
文件,由这两行组成:
* text=auto
*.java text eol=crlf
我们还——这很关键——将 blob 对象 存储在存储库中,并带有文字 CRLF 行结尾。例如,对象 97d1f597b409bdd68ad21d98495f85a40e199cbf
是一个 blob 并保存此提交的 src/main/java/com/sri/ai/util/AICUtilConfiguration.java
版本,并且——正如 vis
1 向我们展示的——它包含:
$ git cat-file -p 97d1f597b409bdd68ad21d98495f85a40e199cbf | head | vis
/*\^M
* Copyright (c) 2013, SRI International\^M
* All rights reserved.\^M
* Licensed under the The BSD 3-Clause License;\^M
* you may not use this file except in compliance with the License.\^M
* You may obtain a copy of the License at:\^M
* \^M
* http://opensource.org/licenses/BSD-3-Clause\^M
* \^M
* Redistribution and use in source and binary forms, with or without\^M
这意味着提交的文件包含 CRLF 行尾。
没有提交的文件可以更改。提交 60fa84abe4357b4fc0acb3d18cefc5b3c40958b6
包含 此文件的此版本,带有 CRLF 行结尾。这将永远是真实的。任何地方都无法对此做任何事情。
git status
说它是“修改过的”?这可能至少略微取决于您的 Git 年份,但正如 OP 所问的那样,我确实这么说:
$ git status
On branch master
Your branch is up to date with `origin/master`
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: src/main/java/com/sri/ai/util/AICUtilConfiguration.java
[many more, snipped]
<块引用>
我想知道这是否是由于 CRLF/LF 差异造成的
是的。
*.java text eol=crlf
文件中的 .gitattributes
行相当于对 Git 的两组指令:
git restore
) 或 from Git 索引到我的工作树 (git checkout
, git switch
,大多数其他此类命令),将任何仅 LF 的行结尾更改为 CRLF 行结尾。这条指令没有任何问题,但是由于提交中文件中的每一行——以 blob 对象的形式存储——已经有 CRLF 行结尾,这条指令不会做任何事情。
如果我不对文件 git add
运行 src/main/java/com/sri/ai/util/AICUtilConfiguration.java
,Git 将不会更新索引副本。因此,索引副本(目前仍然具有 CRLF 行结尾)将进入我所做的任何 新 提交,从而使 已提交 副本具有 CRLF 行结尾的奇怪现象永久存在。< /p>
如果我确实运行 git add
,Git 可能会或可能不会实际更新文件,具体取决于 Git 年份和其他详细信息。这里的问题是 Git 试图变得非常聪明。如果我真的改变了工作树副本——例如,向它添加评论或其他东西——那么 git add
真的必须重新压缩和重新 Git-ify 这个更新的工作树副本,以及更新副本的东西进入 Git 的索引。这将遵守上面的第 2 条指令,因此将所有 CRLF 转换为仅 LF。
如果我然后使用此版本的文件提交,则该文件将在提交中具有仅 LF 的行结尾,采用 Git 化的对象形式。在 49 个(您的计数)或 129 个(我的计数).java
个文件中,我们将减少一个文件。
如果我们git checkout
或git switch
-到新的提交,我们将不会看到此更改(用LF 替换CRLF),因为我们< em>see,在我们的工作树中,遵守规则 #1。但是这个文件将不再显示为“已修改”,因为 Git 知道规则 #2 将 CRLF-s 取出,将文件转回 Git 索引中的仅 LF 文件。也就是说,我们的工作树、索引和 HEAD
提交都将协调一致。
在某些版本的 Git 中,Git 在这里的“智能”太聪明了,如果我们不以任何方式接触文件,git add
将不会修复文件。但是,如果我们有 git add --renormalize
(Git 2.16 或更高版本),我们可以使用它来修复文件。或者,在我正在测试的机器上的 Git 版本中(Git 2.27.0,有点落后于最新版本,但远远超过 2.16),一个简单的 git add .
就足够了,即使没有 {{ 1}}。
这些年来,Git 中处理行尾转换的代码有点奇怪——也许“脆弱”这个词甚至更合适。 Git 1.7 和 1.8 版本中出现了各种问题,一些属性在 Git 2.10 中修复了一段时间,等等。因此,并非每个版本的 Git 都像现代(2.27、2.30 等)Git 一样。
还有一些非 Git 程序(例如带有内部 JGit libraries 的 Eclipse)可以读写 Git 存储库。从 Git 的角度来看,这些不一定“正确”地工作。它们应该兼容,但是考虑到 Git 本身时不时地进行 CRLF 转换“错误”,如果 JGit 与 Git 做的事情不完全相同,谁会抱怨呢?也许问题不是特定的 Git 版本,而是特定的 something-else 版本。
如果存储库本身添加了修复内部文件的新提交,以便它们具有仅 LF 行结尾,但保留 --renormalize
指令以在您的工作树副本中使用 CRLF 行结尾,您将始终看到CRLF 行在您的工作树、任何新的克隆或结帐中结束。如果你不喜欢这个——例如,如果你在 Mac 或 Linux 上并且不想,例如,让 vim 使用其神奇的“文件格式”检测来隐藏 CRLF 行结尾,有一个简单的解决方法。您可以在您的克隆中创建一个 .gitattributes
文件。在该文件中,您可以编写 .git/info/attributes
。这一行覆盖了 *.java text eol=lf
行,因此 Git 将使用它的两个规则:
.gitattributes
上:将 CRLF 转换为仅 LF在新克隆之后必须这样做很烦人,但设置起来很容易。请注意,git add
不是不是一个提交的文件,并且不在您的工作树中,因此无论您签出哪个提交,Git 都不会更新它。
1.git/info/attributes
命令将 control-M 字符转换为 vis
以使其在此处可见;此命令存在于 macOS 和其他基于 BSD 或 BSD 派生的系统上。 \^M
能够对文本进行编码,以便在各种设备之间传输,否则这些设备可能会破坏某些编码或使它们不可见(您当然也可以使用 atob 或 uuencode 执行此操作,但这些根本不是人类可读的)。
如果您有 vis
,它也会使 control-M 可见,但不是以完全可逆的方式:包含文字字符序列 ^ M 变得与包含控制-M 的一个没有区别。尝试,例如,cat -v
与 printf 'foo^Mbar\r\n' | cat -v
以了解为什么 printf 'foo\\^Mbar\r\n' | vis
更胜一筹:
vis
请注意,sh-3.2$ printf 'foo^Mbar\r\n' | cat -v
foo^Mbar^M
sh-3.2$ printf 'foo\\^Mbar\r\n' | vis
foo\134^Mbar\^M
有许多标志来控制其输出编码,并非所有编码都是完全可逆的——但默认输出是。