这是极其基本的问题“为什么 Git 告诉我文件已更改但 diff 显示没有更改?”的无数个版本。类似的问题已发布在这里和这里,但这些答案都没有帮助。
我的场景如下:
我.gitattributes向现有的 Git 存储库添加了一个文件,其中包含多个已存在的提交。该文件的内容.gitattributes如下所示:
* text=auto
*.bat text eol=crlf
*.cmd text eol=crlf
*.ps1 text eol=crlf
*.sh text eol=lf
*.csproj text eol=crlf
*.filters text eol=crlf
*.props text eol=crlf
*.sqlproj text eol=crlf
*.sln text eol=crlf
*.vcxitems text eol=crlf
*.vcxproj text eol=crlf
*.cs text
*.config text
*.jmx text
*.json text
*.sql text
*.tt text
*.ttinclude text
*.wxi text
*.wxl text
*.wxs text
*.xaml text
*.xml text
*.bmp binary
*.gif binary
*.ico binary
*.jpg binary
*.pdf binary
*.png binary
Run Code Online (Sandbox Code Playgroud)
添加该文件后,我执行了以下命令:
git rm --cached -r .
git reset --hard
Run Code Online (Sandbox Code Playgroud)
结果是,Gitgit status现在将 Git 存储库中的大多数文件显示为modified. 但是,我看不到这些文件中的任何更改。diff 工具在文本视图和十六进制视图中均未显示任何更改。
该存储库已在 Windows 计算机上创建,我目前正在 Windows 计算机上使用它。命令输出git config --list如下:
http.sslbackend=schannel
diff.astextplain.textconv=astextplain
credential.helper=manager-core
core.autocrlf=true
core.fscache=true
core.symlinks=false
core.editor="C:\\Program Files\\Notepad++\\notepad++.exe" -multiInst -notabbar -nosession -noPlugin
pull.rebase=false
credential.https://dev.azure.com.usehttppath=true
init.defaultbranch=master
user.name=My Name
user.email=my@email.whatever
core.autocrlf=true
core.eol=crlf
diff.tool=bc
difftool.bc.path=C:/Program Files/Beyond Compare 4/bcomp.exe
difftool.bc.cmd="C:/Program Files/Beyond Compare 4/bcomp.exe" "$LOCAL" "$REMOTE"
difftool.bc.prompt=false
merge.tool=bc
mergetool.bc.path=C:/Program Files/Beyond Compare 4/bcomp.exe
mergetool.bc.cmd="C:/Program Files/Beyond Compare 4/bcomp.exe" "$LOCAL" "$REMOTE" "$BASE" "$MERGED"
mergetool.bc.keepbackup=false
mergetool.bc.trustexitcode=true
core.repositoryformatversion=0
core.filemode=false
core.bare=false
core.logallrefupdates=true
core.symlinks=false
core.ignorecase=true
Run Code Online (Sandbox Code Playgroud)
因此,据我从文档中可以解密的内容来看,魔术开关core.autocrlf和core.eol是 Windows 中应该有的开关。
有谁知道我在这里踩到了什么 Git 地雷吗?
这里有多种可能性,但迄今为止最常见的与这些 CRLF 行结尾有关。它很复杂,要真正理解它,我们首先需要一些背景知识。
\n从高层次的角度来看,Git 基本上有两个选择:
\n第一个非常简单,并且是所有类 Unix 系统上的默认设置。这可能也是 Windows 上的默认设置,但我不使用 Windows,因此我必须遵从其他人的说法。在此设置中,如果您创建一个文件并在该文件中存储字节序列:
\nh e l l o CTRL-M CTRL-J w o r l d CTRL-M CTRL-J
然后git add文件并运行git commit,Git 将在存储库中存储一个新的提交,其中该文件包含这 14 个字节。Blob 哈希 ID 将为:
$ printf \'blob 14\\0hello\\r\\nworld\\r\\n\' | shasum\n23eb407b644b0e362fa224168ecd0adfa02b022a\nRun Code Online (Sandbox Code Playgroud)\n该文件有 CRLF 行结尾。提取提交将生成一个以 CRLF 行结尾的文件。存储库中的文件现在是只读的,永久冻结;它有 blob hash ID 23eb407b644b0e362fa224168ecd0adfa02b022a,就像任何文件中的每个文件一样一样,只要该文件恰好包含该文本。
现在假设创建了这个文件(或没有),我们打开“混乱行结尾”选项。我们现在有许多子选项,指定Git 将如何、何时、在哪些文件上弄乱行结尾。其中包括eol=crlf、eol=lf、text、binary等:
*.bat text eol=crlf\n*.sh text eol=lf\n*.jpg binary\nRun Code Online (Sandbox Code Playgroud)\n这个片段告诉 Git,如果文件名以 结尾.bat,Git 应该以一种特定的方式打乱行结尾;如果它以 结尾.sh,Git 应该以另一种特定的方式打乱行结尾;如果它以 结尾.jpg,Git 不应该弄乱行结尾。
我们知道binary规范意味着对于此类文件,Git不会弄乱行结尾。这很好,因为,例如,.jpg文件实际上一开始就没有行,因此任何类似于行结尾的东西都只是巧合。当 Git不搞乱任何东西时,一切都很简单:Git 存储那里的内容并向您显示存储的内容。
但对于其他文件来说,情况不再如此。由于 Git 现在正在搞乱他们的行结尾,因此提出和回答更多问题变得很重要:
\n这就是事情变得复杂的地方。理解这里的关键是了解 Git 的索引。这个东西\xe2\x80\x94这个“索引”\xe2\x80\x94是Git的核心,你确实必须了解它才能正确使用Git,所以让我们来浏览一下索引。
\nGit 的索引要么太重要,要么太糟糕(或两者兼而有之),以至于它实际上有三个名称。它也称为暂存区,指的是您通常如何使用它,有时也称为缓存。如今,这个姓氏非常罕见:您通常会在诸如 之类的标志中看到它git rm --cached。(有些命令,例如git diff,同时具有 --staged和--cached,具有相同的含义。出于某种原因,没有人抽出时间来添加git rm --staged。我认为现在就会发生这种情况,而且我仍然认为有一天它会发生。)
索引为Git做了很多事情,但在这里我们真正关心的是它为\xe2\x80\x94 和\xe2\x80\x94你做了什么。它为您所做的就是保留您提议的下一次提交。从根本上来说,Git 不是关于文件,而是关于提交。每个提交都保存文件:事实上,每个提交都有每个文件的完整快照。(每个提交也有一些元数据,例如提交作者的姓名和电子邮件地址,但我们将在此处跳过它。)
\n然而,关于提交的问题是它们纯粹是只读的。您可以创建新的提交,但您永远无法更改任何现有的提交。git commit --amend例如,该命令会伪造它:它不会更改现有的提交,而是创建一个新的提交并停止使用旧的提交,转而使用新的提交。当您无法区分\xe2\x80\x94,有时甚至无法\xe2\x80\x94时,这也一样好。当您能够区分\xe2\x80\x94并且有时您可以\xe2\x80\x94时,裂缝就会显现出来。
但是,如果您无法更改提交\xe2\x80\x94,并且您可以\'t\xe2\x80\x94,并且如果提交内的文件位于特殊的、压缩的、去重的、仅限 Git 的形式,除了 Git 本身之外,其他任何程序都无法读取,那么如何使用提交中的文件呢?答案很简单: 为了使用提交,您必须首先让 Git提取该提交。 我们跑步git checkout还是git switch为了达到这个目的。Git 从提交中提取文件,将它们的可用版本放置在我们的工作树或工作树中,我们可以在其中看到它们并完成我们的工作。
Git 可以在这里停止,在当前提交中提交的文件\xe2\x80\x94只读,并永久冻结\xe2\x80\x94和工作文件。其他版本控制系统确实到此为止。但 Git 没有。相反,在提取提交时,Git 将每个文件的“副本”放入 Git 的索引中。
\n我在这里将“副本”放在引号中,因为 Git 索引中的文件以内部、压缩、去重复的格式存储。由于它们只是从某些提交中提取的,因此它们不占用空间:它们已被删除重复项。它们在索引中保存的数据与它们在提交内时保存的数据相同:该数据始终被冻结。
\n文件索引“副本”的特殊之处在于,与提交的副本不同,您可以替换它们。该git add命令告诉 Git:压缩并消除重复的工作树文件。Git 读取工作树副本,对其进行压缩,并检查压缩结果是否与任何现有提交中的某些现有文件重复。(这就是 blob 哈希 ID 技巧的用武之地:这就是为什么任何完全由 组成的文件都hello\\r\\nworld\\r\\n具有哈希 ID 23eb407b644b0e362fa224168ecd0adfa02b022a。)如果这是重复的,Git 会将重复的哈希 ID 放入索引中。如果不是重复的,Git 会安排在对象数据库中存储一个新的 blob , 1并将新 blob 的哈希 ID 存储在索引中。
不管怎样,在更新索引步骤之后,建议的下一次提交现在已更新。您编辑的文件git add现已暂存,并将git status暂存的哈希 ID 与当前提交的哈希 ID 进行比较,并判断staged for commit这些哈希 ID 是否不匹配。(这意味着,git add对一个已被转回以匹配提交副本的文件会删除该staged for commit消息,即使该文件实际上将在下一次提交中。只是哈希 ID 现在匹配了! )
因此,Git 的索引保存了提议的下一次提交。要进行新的提交,您:
\ngit add它们以将它们复制回 Git 的索引中;和git commit以打包 Git 索引中的所有内容。这就是为什么每次更改文件时都必须保留git add文件的原因:Git 不会自动将工作树文件复制回索引中。Git 仅在您要求执行此操作时将其复制回来。2
最终效果\xe2\x80\x94 以及下一节中应该考虑的内容\xe2\x80\x94 是,Git 在任何时候都拥有每个文件的三个副本:
\n HEAD index work-tree\n--------- --------- ---------\nREADME.md README.md README.md\nimg.jpg img.jpg img.jpg\nmain.py main.py main.py\nRun Code Online (Sandbox Code Playgroud)\n例如。工作树版本是您可以查看、读取、写入、提供给 JPG 查看器、使用 Python 程序运行等的版本。另外两个用于 Git:版本是当前提交的HEAD永久冻结副本,索引版本是可延展但冻结格式的副本,准备进入下一次提交。
git checkout命令git switch切换到某个提交,将文件从提交复制到 Git\ 的索引,然后复制到您的工作树。git restore命令从某处读取文件\xe2\x80\x94a提交或索引\xe2\x80\x94,并根据(-S写入暂存)和-W(写入工作树)将其写入索引和/或工作树选项。git reset -- file命令从 Git 的索引中读取文件并将其写入您的工作树。(--这里是一个预防措施,以防文件名是,master或者dev类似分支名称的东西)。git add file命令从工作树中读取文件并将其写入索引。因此,所有这些不同的命令都是操作索引和/或工作树副本的技巧,为下一次提交做准备(因为 Git 主要是进行新的提交,同时保留所有旧的提交)。
\n1 Git 实际上会立即存储新的压缩 blob 对象,即使它在您进行新提交之前最终被替换。这没关系(如果在某些特殊情况下可能不是最佳的),因为 Git 会git gc时不时地为你运行。某些较旧的 Git 版本有一个错误,运行得git gc 不够频繁,这实际上可能是一个问题,但这个问题已经修复多年了。
2使用git add -u告诉 Git 查找修改后的工作树文件并添加它们,从而自动执行该作业。使用git commit -a很像运行git add -u && git commit:它git add -u在提交之前运行一个步骤。然而,-a这会让事情变得更加复杂,并且与编写得不好的预提交挂钩交互得很糟糕,所以这是一个坏主意。尽量不要依赖它:使用它git add -u,以防万一您有这些错误的提交挂钩之一。或者,学会喜欢索引,它可以让你玩一些聪明的技巧git add -p,比如 ,尽管这也会与编写得不好的预提交钩子相互作用很差。
如果:
\ntext,这样 Git就会弄乱这个文件,或者text=auto正在使用该设置,Git猜测这个文件是文本然后:
\ncheckout在从索引到工作树(或switch、restore、各种类型等reset)的过程中弄乱文件的字节,并且add主要是 )。Git 会搞什么鬼?这取决于eol=设置:
eol=crlf:在退出时, Git 会将 LF-only 更改为 CRLF。如果一行读hello\\n入索引,Git 将写入hello\\r\\n工作树副本。在 中, Git 会将 CRLF 更改为仅 LF。hello\\r\\n如果在工作树副本中读取一行,Git 将写入hello\\n,Git 将写入索引副本。
eol=lf:在退出时, Git 不会对该文件执行任何操作。在 中, Git 会将 CRLF 更改为仅 LF。
这就是 \xe2\x80\x94 这就是 Git 要做的一切!例如,它永远不会在in 的过程中将 LF 更改为 CRLF。从这个意义上说,我们可以说 Git“更喜欢”仅使用 LF 的行结尾。(如果你想要一些更奇特的东西,你可以编写clean和smudge过滤器,它们也分别对“传入”和“传出”的数据进行操作,在这里你可以做任何你喜欢的事情。但是里面内置的东西Git 仅限于这几个 CRLF 选项。)
\n还有一个更棘手的地方:Git 努力优化而不是在索引和工作树的内部或外部进行复制。这种尝试通常是正确的,但如果您切换 Git 是否以及如何打乱行结尾,它就会失败(在应该复制时不进行复制)。例如,您链接到的技巧rm .git/index主要是解决此问题的方法。这会强制 Git 复制数据,即使在 Git 认为不需要复制数据的情况下,即使文件状态的更改(从-textto text、eol=lftoeol=crlf或其他)意味着 Git确实必须复制。
这就是您需要记住的全部内容。剩下的细节可以解决。
\n假设您有一个存储库,其中在每个包含文本文件的提交中,所有提交的副本都具有仅 LF 行结尾。由于这实际上是 Git 的“首选”格式,因此文件已经全部“正常”。如果您选择让 Git 处理文件,则所有未来的提交也将具有仅 LF 的行结尾,并且未来的提交将与现有的提交匹配。
\n但假设您有一个存储库,其中部分或所有文本文件均以 CRLF 行结尾提交。这些提交将被永远冻结!你确实无法改变它们。他们将继续使用 CRLF 行结尾。如果您现在开始选择让 Git 处理文件,则未来的提交将逐渐或突然突然出现部分或全部文件仅以 LF 行结尾(存储在存储库中)。
\n无论上述关于现有存储库的哪一条陈述正确,您的设置(如果您设置它们)都会影响您在工作树中查看文件的方式,因为要进入工作树,Git 必须从提交中提取文件。但是您的文件查看器可能不会向您显示行尾的样子。也就是说,如果您首选的文件查看器将CRLF 行和仅 LF 行显示为相同,那么它们看起来会会相同,即使它们不同。
\n事实上,行尾“改变”可以产生 Git 认为是改变的改变。如果存储库中的现有提交具有 CRLF 行结尾,并且您开始让 Git 弄乱行结尾,那么最好进行一次“规范化”提交。 您将成为以这种方式更改的每个文件的每一行的所有者,但是git blame,如果您需要找出某些代码的来源,那么至少有一种方法可以“跳过”特定提交。由于此“修复所有文件,但没有真正的更改”提交除了规范化这些行之外不执行任何操作,因此您可以git blame跳过它。
请注意,Git(和git diff)确实认为这些行不同,除非您告诉git diff忽略某些空白更改:
--ignore-cr-at-eol:比较时忽略行尾的回车符。-w, --ignore-all-space:比较行时忽略空格。(还有其他的;这只是部分列表。)
\n当 Git 提交文件时,它会存储文件的数据及其“模式”。Git 有两种文件模式,它调用100644和100755显示它们,但对于其中git update-index有一个--chmod选项,它分别拼写-x为 和+x。这告诉 Git,在类 Unix 系统或任何其他具有等效系统的系统上,100755或+x文件应在签出时标记为可执行。
目前,大多数 Windows 文件系统都没有等效的文件系统。在这种情况下,Git 尝试保留chmod现有签出中的设置。该rm .git/index技巧击败了“保留旧设置”的技巧。因此,在修复行尾问题时可以更改文件模式。这就是为什么最好git add --renormalize在更改 CRLF 行结尾设置后使用(如果您的 Git 支持的话)。
一般认为,文件的某些变化或特征是不可见或难以看到的,这有点奇怪,但我们有非计算示例:例如,在精细排版中,我们有连字符(-),短破折号 (\xe2\x80\x93) 和长破折号 (\xe2\x80\x94)。这些可能会也可能不会在您的计算机上显示为不同宽度的破折号。我们还有其他计算机示例,例如空白编程语言或 makefile 语法的可怕错误(制表符很重要)。并且,在spycraft\xe2\x80\x94中,无论我们是否使用计算机\xe2\x80\x94,我们都有隐写术。
\n| 归档时间: |
|
| 查看次数: |
5624 次 |
| 最近记录: |