[注意:这毕竟是文件名大小写问题;请参阅下面的“编辑”。]
...在克隆 repo 后立即具有相同的修改文件
这意味着正在发生以下两种情况之一:
- 文件正在发生变化,或者
- Git 认为哪个文件与您机器上的实际情况不符。
如果我手动更改文件,我现在可以在修改后的列表中看到它两次。
这不太有意义。如果您包含一个剪切和粘贴您的意思的内容会有所帮助。
[编辑: 每个 cmets,这有点像——我会使用实际文本,但我必须重新创建它,因为我自己没有问题——如下:
(boilerplate snipped)
modified: CRM-RestAPI/Web.config
modified: CRM-RestAPI/web.config
虽然 Git 将它们视为两个单独的文件(因为它们将位于区分大小写的文件系统上),但如果您的 OS 确实区分大小写-折叠(Windows 或 MacOS 默认会这样做),将只有一个文件,以大写或小写 W 命名,而不是两者都命名,在两种情况下都使用两个字母。这是我在下面描述的一般问题的一个具体示例,Git 将文件名存储为几乎任意字节字符串,但并非所有操作系统都这样做。]
您需要定位和解决问题的背景
由于尚不清楚问题究竟是什么,因此尚无法解决。这可能是行尾问题,也可能是其他问题。您需要以下信息。
每个文件存在三个版本
在大多数情况下(总是包括任何新的克隆),您可以看到的每个文件都存在于三个版本中。这三个应该通常是相同的,但它们可以不同(有意或无意)。
无论如何,您都有一个当前提交,您可以通过以下方式找到其哈希 ID:
git rev-parse HEAD
当您签出不同的提交或进行新的提交时,当前提交的哈希 ID 会发生变化,但总是有一些当前提交(此处不会出现异常)。
每个提交都会列出一堆文件,如果您 git checkout 那个特定的提交,应该签出这些文件。如果需要,您可以使用以下命令查看这些文件:
git ls-tree -r <commit-hash>
它向您详细展示了与该提交相关的每个文件。
每个提交都是只读的 - 存储在此提交中的文件,在此哈希 ID 下,永久存储在此处1,并且永远不会更改。
每个文件的 second 副本保存在 Git 的 index 中。这就是你用git update-index --assume-unchanged 操作的东西。索引是 Git 用于许多事情的中心数据结构,但最好将其描述为 您(和 Git)在哪里构建您将进行的下一次提交。 因此,索引通常开始于 em>完全匹配当前提交。当前提交中的每个文件也在索引中,采用 Git 使用的相同特殊的、仅限 Git 的压缩格式。 (从技术上讲,索引只是共享提交的文件副本。)索引副本和提交副本之间的重要区别在于索引副本可以被覆盖,之后索引不再共享提交的文件版本。索引副本仍然是特殊的仅 Git 压缩格式,但与提交的副本不同,您可以覆盖索引副本。
每个文件的最后一个副本是您实际使用的那个。该文件在计算机上是正常的日常格式,而不是特殊的仅 Git 格式。因为它是正常形式,它会受到你的系统施加的任何限制,这就是我们进入有趣部分的地方。
1无论如何,与提交本身一样永久。如果您让 Git 忘记提交,文件本身就会消失,除非有其他提交正在/正在共享它们。
HEAD、索引和工作树
我们可以通过稍微标记一下这三个副本:
HEAD index work-tree
--------- --------- ---------
README.md README.md README.md
somefile somefile somefile
等等。 Git 在这些不同版本之间复制文件,除了 HEAD(已提交)版本始终是只读的,因此要“更改”已提交版本,Git 会从其中的任何内容构建一个 new 提交索引现在。
git status 命令通过首先将每个文件的HEAD 版本与每个文件的索引版本进行比较来告诉您这些。如果这里有什么不同,git status 会打印文件名并告诉您这是一个可以提交的更改。然后,它将每个文件的索引版本与每个文件的工作树版本进行比较。如果这里有什么不同,git status 会打印文件名并告诉你这是一个尚未准备提交的更改。
git checkout 命令将文件从提交复制到索引和工作树,或从索引复制到工作树。 (这些应该是单独的命令——并且在某一时刻。)git reset 命令将文件从提交复制到索引,但不复制到工作树。 git add 命令将文件从工作树复制到索引。 git commit 命令从索引中的任何内容进行 new 提交,然后安排事情以便 HEAD 现在引用新提交。
感兴趣的东西,或者,寻找什么
现在您已经知道各个部分的内容,这就是可能出错的地方。
HEAD 和索引不需要使用计算机本机名称格式
存储在提交和索引中的文件的名称只是 Git 中的字节字符串。正如短语所说,Git 通常是“编码不可知论者”,只是它使用斜杠将目录名称与子目录和文件分开,并使用 ASCII NUL 字节来终止这些字节串。这允许 Git 使用 UTF-8 对文件名进行编码,因为 UTF-8 编码从不将斜杠 / (ASCII 0x2f) 以外的任何字符编码为字节码 0x2f。如果您在使用反斜杠而不是斜杠的系统上,则它也允许在内部使用正斜杠,或者 Git 根据需要翻译斜杠,以便一切正常。
这也意味着 Git 的文件名是区分大小写的:文件README 和文件readme 完全不同,这和两个不同的文件Readme 不同和ReadMe。目录名称也是如此。
同时,您自己的计算机可能有一个区分大小写的文件系统:这里只有一个文件,其名称是您选择的第一个文件。如果您有一个名为ReadMe 的文件并且您打开README,您会得到ReadMe,而不是一个名为README 的新文件。 (在 Windows 和 MacOS 上默认是这种情况。)
同样,如果您的计算机规范化名称如schön,则此名称有两种不同的 UTF-8 拼写,Git 会将它们视为两个不同的文件名,但您的计算机将将它们视为引用一个文件。 (MacOS 就是这种情况;我不确定 Windows。)
如果这是问题所在,则相当普遍且难以处理。最好的办法是启动一个 Unix 或 Linux 系统,它不进行大小写折叠和规范化,并与存储库一起消除有问题的文件名。然后,您可以检查任何已修复的提交,因为这些提交不再提供会影响您的操作系统的名称。
行尾和其他过滤器
除了文件名之外,您还看到 Git 可以调整行尾。在 Linux 或类 Unix 系统上创建的存储库通常使用仅换行(仅 LF)行结尾,而要在 Windows 系统上编辑的文件可能需要回车换行序列(CR-LF 或 CRLF 结尾)。为了实现跨系统工作,Git 提供了进行一些偷偷摸摸的行尾更改的能力,但不是必需的。
这通常的工作方式是 Git 将一些文件称为 clean,而将一些文件称为 smudged。存储在提交和索引中的内容(即压缩的仅 Git 格式)始终被假定为 clean。每当 Git 将一个文件从索引复制到工作树时,它涂抹该文件,并且每当它从工作树复制同一个文件回到索引时,它清理 em> 文件。
如果启用 CRLF 行尾,则污迹过程包括将 LF-only 更改为 CRLF,清理过程包括将 CRLF 更改为 LF-only。2 这意味着只要所有文件在存储库是真正干净的,它们在您的 Windows 系统上被正确地弄脏了,并且当您 git add 它们在您 git commit 清理的文件之前重新清理它们。
但是——这是这里的关键点——所有这个过程都是可选的。 Linux 用户不需要支付任何费用,因为他们将所有这些都关闭了,而且 Linux 端然后,Git 存储库将工作树中的任何内容存储到索引版本中,即使工作树文件具有 CRLF 行结尾。然后可以提交这些内容,以便提交的内容包括 CRLF结局。
如果您将此类文件提取到 Windows 机器上并打开 CRLF 清理,则索引->工作树转换会单独保留 CRLF。但是现在所有工作树文件都将其 CRLF 更改为 LF-only,因此它们不再匹配!它们都立即更改(但尚未准备好提交)。
这种情况也非常棘手,因为 Git 会通过各种方式尝试知道工作树文件何时被弄脏或清理,而无需运行所有的弄脏和清理过程。 (在某些情况下它有点慢——非常慢——所以这通常很重要。)但这意味着文件的“变化”在某种程度上是不可预测的并且难以诊断。诀窍是检查原始文件内容,如果您再次在 Linux 系统上克隆存储库,这是最简单的,在所有行结尾都没有任何操作。然后,您可以使用行尾感知检查器来查看文件中的内容。
(您可以在其他系统上使用git cat-file -p 执行此操作,以提取特定文件而无需任何污迹或其他过滤器或文本转换,并使用行尾感知检查器检查生成的字节流。如何执行后者在 Windows 上,我不知道——我通常避免使用 Windows 系统。MacOS 有 cat -v 和 hexdump。)
2我们在这里说“包含”而不是“包含”,因为您可以编写自己的涂抹和清洁过滤器,这些过滤器在 CRLF 调整之外应用 .