`git` 显示克隆后更改的文件,没有任何其他操作

`git` shows changed files after cloning, without any other actions

git clone git@github.com:erocarrera/pydot (35a8d858b) 在具有 git config core.autocrlf input 的 Debian 中显示:

modified:   test/graphs/b545.dot
modified:   test/graphs/b993.dot
modified:   test/graphs/cairo.dot

这些文件有 CRLF 行结尾,例如:

$ file test/graphs/cairo.dot
test/graphs/cairo.dot: UTF-8 Unicode text, with CRLF line terminators

.gitattributes 文件 contains:

*.py eol=lf
*.dot eol=lf
*.txt eol=lf
*.md eol=lf
*.yml eol=lf

*.png binary
*.ps binary

更改core.autocrlf 对这些文件的状态没有影响。删除 .gitattributes 也没有任何效果。使用 dos2unix 更改这些文件不会更改它们的状态(如预期的那样),并且使用 unix2dos 返回显示与 diff 与旧副本没有区别。 ls -lsa 的文件权限看起来没有变化。此外,据我所知,这些文件具有统一的行尾 vi -b (因此 unix2dosdos2unix 不应该从混合行尾转换为统一行尾,这可以解释这种奇怪的行为)。我正在使用 git 版本 2.11.0.

git 认为发生了什么变化?

有点相关:

  1. Git status shows files as changed even though contents are the same
  2. Files showing as modified directly after git clone
  3. Cloning a git repo, and it already has a dirty working directory... Whaaaaa?

在搜索多个讨论时,我没有找到解释此行为的答案。这个问题来自 pydot # 163.

更详细:

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 checkout -- <file>..." to discard changes in working directory)

    modified:   test/graphs/b545.dot
    modified:   test/graphs/b993.dot
    modified:   test/graphs/cairo.dot

no changes added to commit (use "git add" and/or "git commit -a")

git diff test/graphs/b993.dot

warning: CRLF will be replaced by LF in test/graphs/b993.dot.
The file will have its original line endings in your working directory.
diff --git a/test/graphs/b993.dot b/test/graphs/b993.dot
index e87e112..8aa0872 100644
--- a/test/graphs/b993.dot
+++ b/test/graphs/b993.dot
@@ -1,10 +1,10 @@
-diGraph G{
-graph [charset="utf8"]
-1[label="Umlaut"];
-2[label="ü"];
-3[label="ä"];
-4[label="ö"];
-1->2;
-1->3;
-1->4;
-}
+diGraph G{
+graph [charset="utf8"]
+1[label="Umlaut"];
+2[label="ü"];
+3[label="ä"];
+4[label="ö"];
+1->2;
+1->3;
+1->4;
+}

更新:

出于好奇,我提交了其中一个文件,转储了 git log -1 -p > diff,并且 vi -b diff 显示 git 规范化

  1 commit 2021d6adc1bc8978fa08d729b3f4d565f9b89651
  2 Author:
  3 Date:
  4 
  5     DRAFT: experiment to see what changed
  6 
  7 diff --git a/test/graphs/b545.dot b/test/graphs/b545.dot
  8 index ebd3e8f..2c33f91 100644
  9 --- a/test/graphs/b545.dot
 10 +++ b/test/graphs/b545.dot
 11 @@ -1,9 +1,9 @@
 12 -digraph g {^M
 13 -^M
 14 -"N11" ^M
 15 -  [^M
 16 -  shape = record^M
 17 -  label = "<p0>WFSt|1571       as Ref: 1338    D"^M
 18 -]^M
 19 -N11ne -> N11:p0^M
 20 -}^M
 21 +digraph g {
 22 +
 23 +"N11" 
 24 +  [
 25 +  shape = record
 26 +  label = "<p0>WFSt|1571       as Ref: 1338    D"
 27 +]
 28 +N11ne -> N11:p0
 29 +}

其他奇怪的观察结果:git checkout 克隆后的任何这些文件都没有任何效果。 上述提交之后,文件 b545.dot 在工作目录中继续具有 CLRF 行结尾。应用 dos2unix 后跟 unix2dos 并没有使 git 认为它已经改变(而在提交之前它确实发生了变化,可能是因为 committed 文件有 CLRF 行结尾)。

Changing core.autocrlf has no effect on the status of these files

它应该,但只有在再次克隆之后:

git config --global core.autocrlf false

git clone git@github.com:erocarrera/pydot pydot2
cd pydot2
git status

这会在全局范围内停用 core.autocrlf,但这只是为了测试。

这恰恰发生了因为那些文件以CRLF结尾提交的,但是.gitattributes文件说要以CRLF结尾提交它们仅 LF 结尾。

Git 可以并且将在两个地方进行 CRLF-vs-LF-only 转换:

  • 从索引提取到工作树期间。存储在提交或索引中的文件总是假定处于 "clean" 状态,但是当从索引中提取该文件到工作树时,Git 应该应用由.gitattributes 以 "change LF-only to CRLF" 的形式,例如,也以 Git 所谓的 涂抹过滤器 .

    [ 的形式=66=]
  • 在将文件从工作树复制回索引期间。存储在工作树中的文件处于 "smudged" 状态,因此此时,Git 应该应用任何 "cleaning" 转换:例如,将 CR-LF 更改为 LF-only,并应用 clean 过滤器.

请注意,这些转换 可以 在两个点上发生。这并不意味着它们 出现在两个点,只是这两个可能的位置。正如 .gitattributes 文档所述,实际转换为:

  • eol=lf:none 在索引 -> 工作树上; CR-LF 到 LF-only on work-tree -> index
  • eol=crlf:索引 -> 工作树上的 LF-only 到 CR-LF; none 在工作树上 -> 索引

现在,实际位于存储库中的文件,存储在提交中,是纯只读的。它可以 永远不会 在该提交中更改。更准确地说,提交标识(通过哈希 ID)一棵树,该树标识(通过哈希 ID)具有任何内容的 blob。这些散列 ID 本身就是对象内容的密码校验和,因此它们自然都是只读的:如果我们尝试更改内容,我们得到的是一个新的、不同的对象,具有新的、不同的散列 ID。

因为 git checkout 实际上是通过将原始哈希 ID 从提交的树复制到索引来工作的,所以存储在索引中的文件版本必须与存储在提交中的文件版本相同。

因此,如果不知何故——不管如何——提交的文件的形式与.gitattributes指示Git的方式不一致,这些文件将成为工作树中的 "dirty",而不管 没有对它们做任何事情!如果你要 git add 有问题的三个文件,那会将它们从工作树复制到索引,因此从它们的行尾删除回车 -returns 。因此,在 git status 术语中,它们已修改但尚未准备提交。

删除工作树版本中的回车 returns 使它们处于相同状态:它们根据索引中的内容进行了修改,因为 git add 现在将保留它们的 LF - 仅行结尾不变,生成索引中的新的不同文件。

一个更有趣的问题是:他们是如何进入错误状态的提交的?这不是我们可以回答的问题:只有那些做出这些提交的人可以产生那个答案。我们只能推测。实现此目的的一种方法是在没有 .gitattributes 生效的情况下添加和提交文件,然后将 .gitattributes 设置为生效而无需再次 git add-ing 文件。这样,CR-LF 结尾进入某人的索引并因此进入该用户的提交,即使 .gitattributes 文件 now 说(但之前没有说)任何new git add 应该去掉马车 returns.

感谢@torek 的解释(与我的 一致)。

总而言之,非对称 git 配置导致 commit(checkout(Index)) 不是恒等映射。在索引中使用 CRLF,此特定配置检查了 CRLF,但在输入转换生效后 (eol=lf),git 将提交 LF 而不是 CRLF。

造成这种混淆的根本原因是比较:

  • 我在工作目录中看到的文件,
  • 已提交文件。

这不显示文件是否已更改。应该比较的是 git 在应用输入转换后将提交 与已经提交的内容。显然,如果这两项不同,则文件 更改。

根据这一推理,可以声明存储库 "unstable",因为它认为自己在没有与世界交互的情况下被修改。这支持通过将提交的文件更改为 LF 或更改 .gitattributes(我更喜欢提交 LF)来避免这种状态。

在这种情况下,git 会为工作目录中的 LF 和 CRLF 提交 LF,因此 dos2unixunix2dos 不会对提交结果产生影响,因此两者都不会到文件的状态。