git - 为什么我的 .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”(或者可能是另一个文件?)来防止这些行尾出现?
解决方案
该eol=lf
指令将阻止 Git 添加回车,但不会阻止 Git保留现有的回车。
真正理解这里发生了什么需要一点关于 Git 如何在提交中存储文件的知识。关键是:
每个提交都以只读、压缩、仅 Git 和重复数据删除格式存储每个文件的完整快照。这意味着您实际看到和处理/使用的文件不在存储库中:您使用的文件在您的工作树或工作树中。
任何提交的所有部分,包括它的所有文件,实际上都是不可更改的。如果您从存储库中取出一个 Git 内部对象(包括提交),以某种方式对其进行修改,然后将其放回原处,您并没有更改原始对象;相反,您刚刚添加了另一个,而这个新的得到了不同的哈希 ID。
要将提交中的文件获取到您的工作树中,Git 必须将它们复制出来。这很明显;不明显的是每个文件都有第三个“副本”。这第三个(实际上是中间的)副本存在于 Git 所谓的index或staging area中,或者(现在很少见)cache中。所有三个名称都指同一个实体。
也就是说,假设HEAD
附加到分支名称master
,并且master
当前表示其哈希 ID 为a123456...
. 换句话说,这个带有丑陋的哈希 ID 的提交就是你当前的提交。在此提交中,我们有名为README.md
andmain.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.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
获取索引中的任何内容并使用它进行新提交。然后新提交成为当前提交,因此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 行结尾,您需要:
\r
从工作树副本中删除;git add
结果文件;和git commit
进行新的提交。
然后可以将此新提交分发到此存储库的所有其他副本,并用于代替\r\n
这些文件具有 (CRLF) 结尾的现有(错误)提交。
请注意,错误的提交将继续存在:这就是修订控制的全部内容。我们不会消除坏的,因为其他人也有它们,我们会记住他们使用的是坏的。
现在,如果没有其他人拥有此存储库的副本,或者没有错误的提交,那么我们处于特殊情况。在这种情况下,我们可以放弃错误的提交以支持新的和改进的提交。(究竟如何在 Git 中做到这一点是另一个答案的主题。)但一般来说,我们只是添加一个修复,并保留原来的。
推荐阅读
- julia - 在 Julia 的直方图中标记和指定 bin
- python - 如何使用 Python3 安全地连接到 SQL 服务器
- reactjs - 赛普拉斯测试库作为开源
- python - Jenkins 构建中日志构建的 Logreduce
- rest - Connectwise 自动化 Rest API
- python - 如何在不上传 favicon 文件的情况下将 favicon 添加到 Google colab 上的 Flask 路由?
- r - 基于固定组和高值对列进行分组
- go - GoLang goSpace Get 功能不起作用
- c++ - std::regex_search 反向搜索
- php - 如何使用 mysql 为基于功能的登录系统建立访问级别?