我在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.md
和main.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
行结尾,您需要:
- 从工作树副本中删除
r
git add
生成的文件;以及git commit
进行新的提交
然后可以将此新提交分发到此存储库的所有其他副本,并使用它来代替那些文件以rn
(CRLF)结尾的现有(坏)提交。
请注意,错误提交将继续存在:这就是修订控制的全部内容。我们不会消除坏的,因为其他人也有,我们会记住他们使用的是坏的。
现在,如果其他没有这个存储库的副本,或者提交错误,那么我们就处于特殊情况。在这种情况下,我们可以放弃错误的提交,转而使用新的改进的提交。(确切地说,如何在Git中做到这一点是另一个答案。)但通常情况下,我们只是添加一个修复程序,并保留原始版本。
grep在其搜索模式中不执行C语言转义,它有一个非常有限的集合。你在这些行中找到了字面意义上的r
。
试试grep $'r' web/entrypoint.sh
看看怎么回事,或者试试grep --color=always 'r' web/entrypoint.sh
。
全面披露:我已经很久没有被这个绊倒了,我也忘了,我不得不在灯泡亮起之前敲打一下。