为什么我的 .gitattributes 文件在 Windows 上签出文件时不阻止添加"\r"(CRLF)?



我在windows 10上的Cygwin shell中使用Git版本2.28.0.windows.1。在我克隆我的存储库后,我可以看到这个

$ cat .gitattributes 
* text=auto
*.sh text eol=lf

我设置它时认为它可以纠正错误的行结尾(我想消除自动包含的"\r"行结尾)。然而,在我克隆之后

git clone https://github.com/chicommons/maps.git
cd maps

我仍然可以看到我不想要的行尾。。。

$ grep 'r' web/entrypoint.sh
python manage.py migrate
python manage.py migrate directory
python manage.py docker_init_db_data

我能用我的"做什么;。gitattributes";(或者可能是另一个文件?)以防止出现这些行尾?

eol=lf指令将阻止Git添加回车,但不会阻止Git保留现有回车

真正理解这里发生的事情需要一点关于Git如何在提交中存储文件的知识。关键是:

  • 每次提交都以只读、压缩、仅Git和消除重复的格式存储每个文件的完整快照。这意味着您实际看到和使用的文件不在存储库中:您使用的文件在工作树的工作树

  • 任何提交的所有部分,包括其所有文件,都是不可更改的。如果你从存储库中取出一个Git内部对象(包括提交),以某种方式对其进行修改,并将其放回,那么你就没有更改原始对象相反,您刚刚添加了另一个,这个新的得到了不同的哈希ID。

  • 要将提交中的文件复制到工作树中,Git必须将它们复制出来。这是显而易见的;不明显的是;复制";每个文件的。第三个副本,或者说中间的副本,实际上存在于Git所称的索引,或者暂存区中,或者——现在很少——缓存。这三个名字都指同一个实体。

也就是说,假设HEAD附加到分支名称master,并且master当前表示哈希ID为a123456...的提交。换句话说,这个提交(带有丑陋的哈希ID)就是您当前的提交。在这个提交中,我们有名为README.mdmain.py的文件,在您的情况下,还有名为web/migrate.sh的文件。有三个";副本";此文件的"副本";这里是引号,因为其中两个是自动消除重复的格式,所以实际上只有一个底层副本。

我们可以在一个表中说明这三个副本,使用特殊名称HEAD来表示提交a123456...(当前提交):

HEAD              index           work-tree
--------------    --------------    --------------
README.md         README.md         README.md
main.py           main.py           main.py
web/migrate.sh    web/migrate.sh    web/migrate.sh

这些文件是从哪里来的?好吧,当你第一次克隆存储库时,你的Git会从其他Git获得所有提交。这些提交在每个Git中都完全相同,并且在每个Git中具有相同的哈希ID。然后,您的Git将其中一个提交(即您正在签出的提交)复制到Git的索引中,并将文件从其索引复制到您的工作树中。这就是每个文件的三份副本。

工作树文件是普通的日常文件,你可以用电脑上的任何程序读写。其他文件是而不是。当(或之后)您对其中一个文件的工作树副本做了一些工作时,您会在其上运行git add。原因是git add告诉Git:使索引副本与工作树副本匹配。因此,如果您更改了main.py,例如,索引中main.py版本现在与存储库中main.py版本不同:

HEAD              index           work-tree
--------------    --------------    --------------
README.md(1)      README.md(1)      README.md
main.py(1)        main.py(2)        main.py
web/migrate.sh(1) web/migrate.sh(1) web/migrate.sh

提交中的副本实际上是不可更改的,因此HEAD(目前是commit a123456...的缩写)将始终包含这三个版本的文件。但是,虽然索引使用内部格式,但它不是提交1,并且不是只读的。因此git add可以替换索引副本

(运行git commit会获取索引中的任何内容,并使用它来进行新的提交。然后,新的提交将成为当前提交,因此名称HEAD和当前分支名称现在指的是提交,而不是提交a123456...。但我们还不需要走那么远。)


1是什么有点复杂,但在第一个近似值中,您可以将索引视为保存建议的下一个提交。每次签出某个提交时,Git都必须设置索引,以便为下一次提交做好准备:通常,通过从刚签出的提交中填充索引。


当Git调整行尾时,从索引复制或复制到索引

Git索引中文件的副本采用压缩的、仅限Git的、消除重复的格式。工作树中文件的副本是普通的日常计算机格式。因此,每当Git将Git的索引复制到你的工作树时,它都必须扩展文件;每当Git将您的工作树复制到的索引时,它都必须压缩和消除文件的重复。

此复制过程是对文件进行任何更改的理想时间。所以这就是.gitattributes和行尾的东西发挥作用的地方。假设索引中的文件通过存储库中的方式到达那里,它有换行的行,只有n。不过,假设您希望文件的工作树副本具有rn或CRLF行结尾。

如果Git在出索引的过程中将n转换为rn,并在in到进行索引的过程中将rn转换为n,则可以实现您的目标。这就是* text eol=crlf的作用。

但如果你不想那样呢?如果您希望n结尾保持为n结尾,该怎么办?这就是* text eol=lf的作用。n的结尾如何保持n的结尾?通过不进行任何更改

因此* text eol=lf意味着不进行更改。但是,如果存储库中的文件(因此被复制到索引中)具有rn(CRLF)行结尾,该怎么办?那么,你的工作树文件也是如此。

要使存储库中的一些文件只有n行结尾,您需要:

  1. 从工作树副本中删除r
  2. git add生成的文件;以及
  3. git commit进行新的提交

然后可以将此新提交分发到此存储库的所有其他副本,并使用它来代替那些文件以rn(CRLF)结尾的现有(坏)提交。

请注意,错误提交将继续存在:这就是修订控制的全部内容。我们不会消除坏的,因为其他人也有,我们会记住他们使用的是坏的。

现在,如果其他没有这个存储库的副本,或者提交错误,那么我们就处于特殊情况。在这种情况下,我们可以放弃错误的提交,转而使用新的改进的提交。(确切地说,如何在Git中做到这一点是另一个答案。)但通常情况下,我们只是添加一个修复程序,并保留原始版本。

grep在其搜索模式中不执行C语言转义,它有一个非常有限的集合。你在这些行中找到了字面意义上的r

试试grep $'r' web/entrypoint.sh看看怎么回事,或者试试grep --color=always 'r' web/entrypoint.sh

全面披露:我已经很久没有被这个绊倒了,我也忘了,我不得不在灯泡亮起之前敲打一下。

最新更新