Git说有更改,但实际上没有。

4

首先,我无法放弃对文件的更改,我使用git reset --hard命令,没有错误提示,但更改仍然存在。我尝试了其他 StackOverflow 文章中的一些建议。

git rm .gitattributes
git add -A
git reset --hard

git rm --cached [fileName]

我甚至删除了我的仓库,但是在克隆新的仓库时,它立即拥有相同的修改文件。如果我手动更改文件(使用编辑器将GIT认为不同的内容移除),现在可以在修改列表中看到两个(也就是说,当我运行git status时,我会在未暂存的文件下看到同一路径下列出相同的文件两次)。最后我找到了这个方法来重置文件并将其从修改列表中移除。

git ls-files -m | xargs -i git update-index --assume-unchanged "{}"

现在,我试图切换分支,但它说更改将被覆盖,我必须提交或隐藏我的更改,然而,当我运行git stash时,它告诉我没有更改。我尝试了几个建议,包括尝试更新行结尾,但没有让我检出分支。该分支名称中有“/”,这可能会导致问题吗?

也许更改已经提交了?看一下 git revert - Christoph
1
那么,git diff显示了更改吗? - max630
“--assume-unchanged”听起来不太好,它只是通过忽略磁盘上的文件来推迟问题,但并没有解决其根本原因。 - max630
@max630 git status 显示 "nothing to commit",git diff 没有显示任何内容。但是,git checkout [branch] 却表示我的本地更改将被覆盖。 - jrpharis
我在 Mac 上遇到了同样的问题。 - cd491415
2个回答

9
注意:经过检查,这实际上是文件名大小写问题,请参见下面的“编辑”。

……在克隆完整个仓库后,它仍然有相同的修改文件

这意味着两种情况之一:

  • 文件确实被更改了,或者
  • 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,后者又与两个不同的文件ReadmeReadMe不同。目录名称也是如此。

与此同时,你自己的计算机可能有一个不区分大小写的文件系统:这里只有一个文件,它的名称是你选择的那些文件中的第一个。如果你有一个名为 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 -vhexdump。)
我们在这里说“包括”而不是“由…组成”,因为您可以编写自己的污染和清理过滤器,这些过滤器会除了 CRLF调整之外应用。

1
Git的文件名不区分大小写 - 我想你的意思是这里应该是“区分大小写” :) - ash
@jrpharis 一旦你设置了 --assume-unchanged(这是 Git 在索引条目上设置的标志),git status 就会跳过将文件的索引副本与其他任何内容进行比较。但是,该文件仍然不同(来自工作树版本)。您必须清除标志(git update-index --no-assume-unchanged)才能回到让 Git 比较索引副本的状态。同时,git checkout 继续警告您工作树副本未保存在任何地方 - 即它不查看“假定”位。 - torek
@torek 啊,谢谢,我原以为这个问题会在拉取或切换分支时被解决。我对 Git 还算熟练,但我们的情况已经超出了我的知识范畴。 - jrpharis
不知道这是否有所不同,但我通过检查几个提交的随机提交来解决了这个问题,这些提交不在分支的头部。将我们的开发分支合并到我的工作分支中,解决冲突并提交。然后再次检出开发分支(没有任何修改),那个文件仍然显示为已修改,我又回到了原点。 - jrpharis
1
发现问题了,Git看到两个不同的文件(web.config和Web.config),但我的Windows系统只看到一个。当我提到它在未暂存文件中显示了两次时,我没有注意到大小写不一致的问题。感谢您提供的所有深入背景信息。 - jrpharis
显示剩余5条评论

0

我遇到了类似的问题 -

在开发分支中,Shell文件的行结尾是CRLF,在特性分支中是LR行结尾

当我从特性分支切换到开发分支时,我遇到了文件被修改的问题。

以下步骤对我起作用 -


git ls-files -m | xargs -i git update-index --assume-unchanged "{}"

git reset --hard "LATEST COMMIT ID"

git checkout to feature_branch```

This issue will continue to appear until I merge my branches and have shell script with one line ending LF

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接