在Windows上签出文件时,为什么我的.gitattributes文件不能阻止添加“ \ r”? 从索引复制或复制到索引是在Git调整行尾时

问题描述

我正在Windows 10的Cygwin外壳中使用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和重复数据删除格式存储每个文件的完整快照。这意味着您实际查看和使用的文件不在存储库中:您使用的文件位于工作树 work-tree 。

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

  • 要使文件从提交进入您的工作树,Git必须将其复制出来。那是显而易见的。不明显的是每个文件都有第三个“副本”。第三个副本或中间副本实际上位于Git称为 index staging区域的副本中,或者(通常在最近几天)位于 cache 的副本中>。这三个名称都引用相同的实体。

也就是说,假设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的索引中,并将文件从其索引复制到您的工作树中。这就是每个文件的三个副本的地方。

工作树文件是日常的普通文件,您可以使用计算机上的任何程序进行读写。其他文件是 not 。在其中一个文件的工作树副本上完成某项工作后(或之后),您可以在其上运行git add。原因是git add告诉Git:使索引副本与工作树副本匹配。因此,例如,如果您更改了main.py,则索引中main.py version 现在与{{1}的 version 不同}在存储库中:

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的缩写)将始终包含文件的这三个版本。但是,尽管索引使用内部 format ,但它不是commit 1 ,并且是 not 只读的。因此commit a123456...可以替换索引副本。

(运行git add会获取索引中的所有内容,并使用它进行新的提交。新的提交将成为当前的提交,因此 name git commit ,以及当前的分支名称,现在引用的是 new 提交,而不是提交HEAD。但是我们还不需要走那么远。)


1 它的有点复杂,但是从一个近似值来看,您可以将索引视为保存您的建议的下一次提交。每次您检出一些提交时,Git必须设置索引以准备进行 next 提交:通常,通过从刚检出的提交中填写索引即可。


从索引复制或复制到索引是在Git调整行尾时

Git索引中文件的副本采用压缩的,仅限Git的,重复数据删除的格式。工作树中文件的副本是普通的日常计算机格式。因此,只要Git每当从Git的索引复制您的工作树时,都必须扩展该文件。每当Git从工作树复制到其索引时,它都必须对文件进行压缩和重复数据删除。

此复制过程是对文件进行任何更改的理想时间。因此,这就是a123456...和行尾内容起作用的地方。假设在存储库中到达索引的文件 in 中具有换行符结尾的行,且仅带有.gitattributes。假设您希望文件的工作树副本具有\n或CRLF行结尾。

如果Git在索引的途中将\r\n变成\n,并在\r\n变成\r\n >进入进行索引,即可实现您的目标。这就是\n要做的。

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

因此\n表示请勿进行更改。但是,如果存储库中的文件(因此被复制到索引中)具有* text eol=lf(CRLF)行结尾怎么办?好吧,您的工作树文件也是如此。

要使存储库中的某些文件具有仅\r\n行尾,您需要:

  1. 从工作树副本中删除\n
  2. \r生成的文件;和
  3. git add进行新的提交。

然后,可以将新提交提交到该存储库的所有其他副本中,并代替那些文件的git commit(CRLF)结尾的现有(不良)提交。

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

现在,如果没有其他人拥有该存储库的副本或提交错误,那么我们处于一种特殊情况。在这种情况下,我们可以放弃错误的提交,而使用新的改进的提交。 (精确地在Git中执行此操作是另一个答案的主题。)但通常,我们只是添加一个修复程序,并保留原始版本。

,

grep不会在其搜索模式中进行C语言转义,它的设置非常受限制。您会在这些行中找到文字r

尝试grep $'\r' web/entrypoint.sh来查看最新消息,或grep --color=always '\r' web/entrypoint.sh

全面披露:这已经使我绊倒了很长时间,我也忘记了它,在灯泡点亮之前,我不得不稍作调整。