首页 > 解决方案 > 为什么我的 .gitattributes 文件没有阻止在 Windows 上签出文件时添加“\r”?

问题描述

我在 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”(或者可能是另一个文件?)来防止这些行尾出现?

标签: gitwindows-10line-endingsgitattributes

解决方案


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

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

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

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

  • 要将提交中的文件获取到您的工作树中,Git 必须将它们复制出来。这很明显;不明显的是每个文件都有第三个“副本”。这第三个(实际上是中间的)副本存在于 Git 所谓的indexstaging area中,或者(现在很少见)cache中。所有三个名称都指同一个实体。

也就是说,假设HEAD附加到分支名称master,并且master当前表示其哈希 ID 为a123456.... 换句话说,这个带有丑陋的哈希 ID 的提交就是你当前的提交。在此提交中,我们有名为README.mdandmain.py和 - 在您的情况下 - <code>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.pymain.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获取索引中的任何内容并使用它进行新提交。然后新提交成为当前提交,因此name HEAD和当前分支名称现在指的是提交,而不是 commit a123456...。但是我们还不需要走那么远。)


1什么,有点复杂,但大致而言,您可以将索引视为保存您提议的下一次提交。每次你签出一些提交时,Git 必须设置索引以准备下一次提交:通常,通过从你刚刚签出的提交中填写它。


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

Git 索引中的文件副本采用压缩的、仅限 Git 的去重格式。工作树中文件的副本是普通的日常计算机格式。所以任何时候 Git从Git 的索引复制你的工作树,它都必须扩展文件;并且任何时候 Git你的工作树复制它的索引,它必须压缩和去重复文件。

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

如果 Git在退出索引的过程中变为进入,并在\n进入索引的过程变为进入索引,这就实现了你的目标。这就是将要做的。\r\n\r\n\n* text eol=crlf

但是,如果你不想那样呢?如果你希望\n结局仍然是\n结局怎么办?这就是* text eol=lf将要做的。结局如何\n保持\n结局?通过不进行任何更改

所以* text eol=lf意思是不做改动。但是,如果存储库中的文件(因此被复制到索引中)具有\r\n(CRLF) 行结尾怎么办?那么,您的工作树文件也是如此。

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

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

然后可以将此新提交分发到此存储库的所有其他副本,并用于代替\r\n这些文件具有 (CRLF) 结尾的现有(错误)提交。

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

现在,如果没有其他人拥有此存储库的副本,或者没有错误的提交,那么我们处于特殊情况。在这种情况下,我们可以放弃错误的提交以支持新的和改进的提交。(究竟如何在 Git 中做到这一点是另一个答案的主题。)但一般来说,我们只是添加一个修复,并保留原来的。


推荐阅读