Git处理换行符

发布时间 2023-12-29 09:59:10作者: euv

处理文本文件的换行符

当我们按下回车键盘在文本文件中换行时,Linux和MacOS添加的换行符是LF,Windows是CRLF。这会导致至少以下3个问题:
① 假设使用Windows的开发者将代码提交到仓库,MacOS开发者拉取代码换行符是CRLF,换行符跨平台不兼容。
② MacOS即使未对代码进行任何改动,但是在提交时,产生大量不必要的冲突,因为同一行代码在仓库中的换行符是CRLF,但是MacOS本地的工作区是LF。
③ git diff 比较暂存区或工作区与版本库的差异时,会导致仅因为换行符不同出现大量差异,干扰我们比对。

Git的解决上述问题的策略是,提交到仓库的文本文件的换行符全部自动转换成LF(Windows平台提交时存在转换,MacOS和Linux则不存在转换,仓库中的文本文件的换行符一定是LF),检出时,Git根据主机的Git配置决定是否将文本文件的LF再转换成CRLF.
综上,Git做了一件事情:文本文件在Commit时,可能发生CRLF被替换成LF,在Checkout时可能发生LF被替换成CRLF,它避免了换行符跨平台的问题,同时避免了因为换行符导致的不同。Git diff时,工作区和暂存区的换行符始终相同,同时为CRLF或者同时为LF,因为检出时,版本库替换换行符后流入暂存区再流入工作区。工作区(暂存区)和版本库在比较时,会忽略换行符不同,不认为是不同点或变更点。Git Merge时,版本库中的任意版本的换行符都是LF,所以不会发生某一行仅因为换行符不同造成冲突。二进制文件(非文本文件)Git不进行换行符替换,不对文件进行任何变更。

上述策略的配置方式是:

git config --global core.autocrlf true   # commit时,转换成LF,检出到暂存区和工作区时,再转换成CRLF,适合Windows
git config --global core.autocrlf input # commit时,转换成LF,检出到暂存区和工作区时,仍旧保持LF,适合MacOS和Linux
git config --global core.autocrlf false # Commit和检出时,不对换行符进行任何处理

--global 任何仓库 --local 具体某个仓库,这和配置commit作者信息十分相似,配置信息同样也存储在保存commit作者信息的那个文件中。

但是上述策略是不完美,它有以下几个缺陷:
一、Git在提交和检出时依靠自己的判断一个文件是文本文件还是二进制文件的算法对文件进行处理,但是该判断算法并不是100%严谨。如果判断错误,一个二进制文件发生0x0D和0x0D0A替换造成文件损坏。
二、有些文本文件的换行符与操作系统无关,必须保持原样,如bat脚本换行符必须是CRLF,shell脚本换行符必须是LF。
三、如果开发者未配置或正确配置Git,仍旧会发生问题,我们无法约束所有开发者。

所以,Git还有另外一种策略,在仓库根目录添加.gitattributes文件,在文件中精确告诉Git哪些文件是文本文件哪些是二进制文件,哪些文本文件是特例不进行换行符替换是必须的。使用该策略时,git config core.autocrlf策略就不再起作用。

小结一下

  1. 如果大家都在Windows或MacOS或Linux其中一种平台上开发,不存在仓库代码在不同操作系统被检出或提交,那么我们可以关闭Git的一切策略 git config --global core.autocrlf false,因为不存在换行符相关的问题,同时避免Git识别二进制和文本文件时出现错误,导致一些二进制文件被损坏。
  2. 如果仓库允许开发人员在不同的操作系统上检出和提交,那么我们必须配置Git策略处理换行符。但是不建议通过core.autocrlf控制,因为不能保证每个开发者配置了或者正确配置了,强烈建议使用.gitattributes策略进行管控换行符。

在提交和检出时禁止Git对仓库内的任何文件的换行符进行处理

如果我们接手一个文件数量庞大类型繁多的项目仓库,由于我们对仓库的文件种类,数量和目录结构不够了解,一时无法通过.gitattributes准确配置出哪些文件是文本文件,哪些文件是二进制文件。如果强行配置,一旦疏忽,则会导致某些二进制文件损坏。
但是如果我们确定此代码仓库的所有开发人员都是使用Windows系统,即仓库的代码不可能出现在Linux或MacOS的电脑上,那我们可以禁止Git对仓库的任何文件进行换行符处理。如果这样,则工作区,暂存区,版本库的换行符完全一样,不存在这样或那样的问题。


处理换行符的终极配置流程

第一步

Windows:

git config --system core.autocrlf true # 提交LF,检出CRLF
git config --global core.autocrlf true # 提交LF,检出CRLF

MacOS和Linux

git config --system core.autocrlf input # 提交LF,检出LF
git config --global core.autocrlf input # 提交LF,检出LF
第二步

如果项目的开发者使用Linux,MacOS,Windows的都有(项目仓库存在在不同操作系统上被检出或被提交的可能性),需要做3件事:
1.
Windows:

git config --local core.autocrlf true

MacOS和Linux

git config --local core.autocrlf input

在仓库根目录添加.gitattributes文件
3.
提交.gitattributes在内的工作区改动,清空暂存区和工作区,将版本库最新版本重新检出到暂存区和工作区,

如果项目的开发者都仅使用Windows系统或Linux系统或MacOS系统(项目仓库不存在在不同操作系统上被检出或被提交的可能性),我们仍旧可以像上述那样配置,但是这是不必要的,我们可以偷懒,只需要做2件事情:
1.
无论Windows,MacOS还是Linux,全部执行

git config --local core.autocrlf false

.gitattributes的规则细节

.gitattributes 文件必须在存储库的根目录下创建,且像任何其他文件一样提交。

文件格式:要匹配的文件格式 属性1 属性2 ...

先定义的配置会被后定义的覆盖。

# Git如果认为某个文件是文本文件,提交到仓库时,所有的CRLF会被转换成LF;检出到暂存区和工作区时,Git会自动检测当前的开发操作系统,是Windows,则再次将LF替换成CRLF,是MacOS和Linux,不替换。Git如果认为某个文件是二进制文件,无论是提交还是检出,都不会发生任何换行符替换。
* text=auto

# 告诉Git后缀名是.txt的文件都是文本文件,提交时CRLF被替换成LF,检出时,Git自动检测当前的开发操作系统,是Windows,则再次将LF替换成CRLF,是MacOS和Linux,不替换。
*.txt text

# 告诉Git后缀名是.bat和.cmd的文件都是文本文件,提交时CRLF被替换成LF,检出时,无论当前是什么操作系统,LF都被替换成CRLF.
*.bat text eol=crlf
*.cmd text eol=crlf

# 告诉Git后缀名是.bash的文件都是文本文件,提交时CRLF被替换成LF,检出时,无论当前是什么操作系统,LF都保持不变仍旧是LF.
*.bash text eol=lf

# 告诉Git后缀名是.jpg的文件是二进制文件,不进行任何处理
# binary Git会理解指定文件不是文本,并且不应尝试更改这些文件,相当于-text -diff(不是文本文件,不要更改,git diff时,也忽略此文件)。
*.jpg binary
*.pdf binary

新增.gitattributes文件或发生了变动的.gitattributes生效的步骤

# 把当前工作区,暂存区的所有变动以及.gitattributes文件全部提交到版本库。
# git add . 暂存已追踪的变更文件和工作区中未被追踪的新增文件,不包括工作区被删除的已追踪文件;git add -u 除了工作区新增的未被追踪的文件,其他所有已经被追踪的更改或删除的文件都暂存到暂存区;git add --all 会把工作区的所有变动都暂存起来。
git add --all

# 提交到版本库
git commit -m "Saving files before refreshing line endings"

# 清空暂存区。这个步骤必须要做。执行完毕后,可以认为暂存区和工作区全部是空的,没有任何文件,消灭了暂存区或工作区包含不应该出现的换行符形式的文件;而版本库中全部是LF版本。
git rm -rf --cached .

# 清空暂存区包括暂存未提交的文件,清空工作区包括未暂存的任何改动,然后将版本库中最新版本检出到暂存区和工作区。
git reset --hard HEAD