注意:经过检查,这实际上是文件名大小写问题,请参见下面的“编辑”。
……在克隆完整个仓库后,它仍然有相同的修改文件
这意味着两种情况之一:
- 文件确实被更改了,或者
- Git对于哪个文件是哪个文件的想法与您的机器上的现实不符。
如果我手动更改文件,我现在可以在修改列表中看到它出现了两次。
这没什么道理。如果您包含您的意思的剪切和粘贴内容会更有帮助。
[编辑:根据评论,类似于以下内容:我会使用实际文本,但由于我没有这个问题,所以必须重新创建它 - ]
(boilerplate snipped)
modified: CRM-RestAPI/Web.config
modified: CRM-RestAPI/web.config
虽然Git将这些视为两个单独的文件(在区分大小写的文件系统上是如此),但如果您的操作系统执行大小写折叠(Windows或MacOS默认情况下会执行此操作),则只会有一个文件,命名为大写或小写W,而不是两者,两个字母都包含。这是我在下面描述的一般问题的具体示例,其中Git将文件名存储为几乎任意的字节字符串,但并非所有操作系统都这样做。
需要了解和解决问题的背景
由于目前还不清楚实际问题是什么,因此还无法解决它。可能是行尾问题,也可能是其他问题。您需要以下信息。
每个文件存在三个版本
在大多数情况下 - 包括任何新克隆的情况下 - 您可以看到的每个文件都存在三个版本。 所有三个版本通常应该相同,但它们可以有所不同(有意或无意的情况下)。
无论如何,您都有一个当前提交,您可以使用以下命令找到其哈希ID:
git rev-parse HEAD
随着您检出不同的提交或进行新的提交,当前提交的哈希ID会发生变化,但是始终存在一些当前提交(这里没有发生例外情况)。
每个提交都列出了一堆文件,如果您git checkout
该特定提交,则应该检出这些文件。如果您想要查看这些文件,可以使用以下命令:
git ls-tree -r <commit-hash>
这将向您详细展示与该提交相关的每个文件。
每个提交都是只读的——存储在此哈希 ID 下的此提交中的文件将永久存储1,并且它们永远不会更改。
每个文件的第二个副本保存在 Git 的索引中。这是您使用 git update-index --assume-unchanged
操作的内容。索引是 Git 用于许多事情的中央数据结构,但最好的描述可能是“您(和 Git)构建下一个要提交的提交的位置”。因此,索引通常从当前提交开始,与当前提交完全匹配。当前提交中的每个文件也都以相同的特殊的 Git-only 压缩格式存在于索引中。(从技术上讲,索引只是共享提交文件的副本。)索引副本与提交副本之间的重要区别在于,索引副本可以被覆盖,此后索引不再共享提交文件的版本。索引副本仍然是特殊的 Git-only 压缩格式,但与提交副本不同,您可以覆盖索引副本。
每个文件的最后一个副本是您实际使用的副本。此文件以计算机上的正常日常形式存在,而不是以特殊的 Git-only 格式存在。因为它以正常形式存在,所以它受到您的系统施加的任何限制,这就是我们进入有趣部分的地方。
1只要提交本身一样永久。如果让 Git 忘记提交,则文件本身将消失,除非其他提交共享它们。
HEAD、索引和工作树
我们可以通过对它们进行标记来说明三个副本:
HEAD index work-tree
--------- --------- ---------
README.md README.md README.md
somefile somefile somefile
等等。Git会在各个版本之间复制文件,但
HEAD
(已提交)版本始终是只读的,所以要“更改”已提交的版本,Git会从索引中构建一个新的提交,该提交包含当前索引中的所有内容。
git status
命令通过比较每个文件的HEAD
版本和索引版本来告诉您这些信息。如果这里有任何不同,git status
将打印文件名,并告诉您这是准备提交的更改。然后,它将比较每个文件的索引版本和工作区版本。如果这里有任何不同,git status
将打印文件名,并告诉您这是尚未暂存的更改。
git checkout
命令将文件从提交复制到索引和工作区,或从索引复制到工作区。(这些应该是单独的命令,一度也确实如此。)git reset
命令将文件从提交复制到索引,但不复制到工作区。git add
命令将文件从工作区复制到索引。git commit
命令将从索引中的所有内容创建一个新的提交,然后安排事情,使得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-only)行尾,而要在Windows系统上编辑的文件可能需要回车换行序列(CR-LF或CRLF行尾)。为了实现跨系统工作,Git提供了一些偷偷摸摸的行尾更改能力,但不是必须的。
通常的工作方式是Git将某些文件称为“干净”的文件,将另一些文件称为“弄脏”的文件。存储在提交和索引中-即在压缩的Git-only格式中-总是被认为是“干净的”。每当Git从索引复制文件到工作树时,它会“弄脏”该文件,每当它将相同的文件从工作树复制回索引时,它会“清理”该文件。
如果启用了CRLF行尾,那么弄脏过程包括将仅有LF的更改为CRLF,而清理过程包括将CRLF更改为仅有LF。
2这意味着只要存储库中的所有文件都是真正干净的,它们就会在你的Windows系统上正确地被弄脏,并在你在
git commit
之前
git add
它们之前重新清理文件。
但是,这里的关键点是,
所有这些过程都是可选的。 Linux用户不需要支付任何费用,因为他们关闭了所有这些功能,然后Linux端的Git存储库将工作树中的内容存储到索引版本中,
即使工作树文件具有CRLF行结束符。 然后可以提交这些内容,以便提交的内容包括CRLF结尾。如果您在Windows机器上提取此类文件并打开CRLF清理,则索引- >工作树转换会保留CRLF。但现在,所有工作树文件的CRLF都被更改为仅LF,因此它们不再匹配!它们都会立即更改(但还没有准备好提交)。
这种情况也非常棘手,因为Git通过各种方法尝试知道工作树文件何时被污染或清理,而无需运行它们通过所有污染和清理的过程。(在某些情况下,它相当慢 - 非常慢,因此通常很重要。)但这意味着文件的“更改”有些不可预测且难以诊断。诀窍是检查原始文件内容,如果您再次在Linux系统上克隆存储库,则最容易使用支持换行符的检查器来查看文件中的内容。
(您可以使用
git cat-file -p
在其他系统上执行此操作,以提取特定文件而不经过任何污染或其他过滤器或文本转换,并使用支持换行符的检查器检查生成的字节流。如何在Windows上进行后者,我不知道-通常情况下,我避免使用Windows系统。 MacOS具有
cat -v
和
hexdump
。)
我们在这里说“包括”而不是“由…组成”,因为您可以编写自己的污染和清理过滤器,这些过滤器会
除了 CRLF调整之外应用。
git revert
。 - Christophgit diff
显示了更改吗? - max630git status
显示 "nothing to commit",git diff
没有显示任何内容。但是,git checkout [branch]
却表示我的本地更改将被覆盖。 - jrpharis