Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GitHub 第一坑:换行符自动转换 #22

Open
cssmagic opened this issue Aug 16, 2013 · 53 comments
Open

GitHub 第一坑:换行符自动转换 #22

cssmagic opened this issue Aug 16, 2013 · 53 comments
Labels

Comments

@cssmagic
Copy link
Owner

cssmagic commented Aug 16, 2013

GitHub 第一坑:换行符自动转换

源起

一直想在 GitHub 上发布项目、参与项目,但 Git 这货比较难学啊。买了一本《Git 权威指南》,翻了几页,妈呀,那叫一个复杂,又是 Cygwin 又是命令行的,吓得我不敢学了。

终于某天发现 GitHub 还有一个 Windows 客户端,试了一下还挺好用。不需要掌握太多的 Git 原理和命令,也可以在 GitHub 上麻溜建项目了,甚是欢喜。可是好景不长,第一次参与开源项目就出洋相了。

经过

小心翼翼地 Fork 了朴灵大大 (@JacksonTian) 的 EventProxy 项目,本地改好提交,同步到服务器,怀着激动的心情发出 Pull Request……这时发现问题了。我发现 diff 图表显示的更新并不仅是我修改的那几行,而是整个文件都显示为已修改。(下图为示意图)

first-trap-on-github-autocrlf-diff

这看起来很奇怪啊,于是赶紧撤回 Pull Request,自己闷头找原因。

初步定位是文件的换行符问题,因为我发现本地的文件是 Windows 换行符,但很显然大家现在做项目都是用 UNIX 换行符啊。这是一大疑点,于是在反复对比 Web 端和本地的各个文件、各个版本之后,基本定位到了问题所在。

背景

在各操作系统下,文本文件所使用的换行符是不一样的。UNIX/Linux 使用的是 0x0A(LF),早期的 Mac OS 使用的是 0x0D(CR),后来的 OS X 在更换内核后与 UNIX 保持一致了。但 DOS/Windows 一直使用 0x0D0A(CRLF)作为换行符。(不知道 Bill Gates 是怎么想的,双向兼容?)

这种不统一确实对跨平台的文件交换带来麻烦。虽然靠谱的文本编辑器和 IDE 都支持这几种换行符,但文件在保存时总要有一个固定的标准啊,比如跨平台协作的项目源码,到底保存为哪种风格的换行符呢?

Git 作为一个源码版本控制系统,以一种(我看起来)有点越俎代庖、自作聪明的态度,对这个问题提供了一个“解决方案”。

Git 由大名鼎鼎的 Linus 开发,最初只可运行于 *nix 系统,因此推荐只将 UNIX 风格的换行符保存入库。但它也考虑到了跨平台协作的场景,并且提供了一个“换行符自动转换”功能。

安装好 GitHub 的 Windows 客户端之后,这个功能默认处于“自动模式”。当你在签出文件时,Git 试图将 UNIX 换行符(LF)替换为 Windows 的换行符(CRLF);当你在提交文件时,它又试图将 CRLF 替换为 LF。

(看明白了吗?一个版本控制系统会在你不知不觉的情况下修改你的文件。这 TM 简直酷毙了,对吧?)

缺陷

Git 的“换行符自动转换”功能听起来似乎很智能、很贴心,因为它试图一方面保持仓库内文件的一致性(UNIX 风格),一方面又保证本地文件的兼容性(Windows 风格)。但遗憾的是,这个功能是有 bug 的,而且在短期内都不太可能会修正。

问题具体表现在,如果你手头的这个文件是一个 包含中文字符的 UTF-8 文件,那么这个“换行符自动转换”功能 在提交时是不工作的(但签出时的转换处理没有问题)。我猜测可能这个功能模块在处理中文字符 + CRLF 这对组合时直接崩溃返回了。

这可能还不是唯一的触发场景(毕竟我没有太多精力陪它玩),但光这一个坑就已经足够了。

踩坑

这是一个相当大的坑,Windows 下的中文开发者几乎都会中招。举个例子,你在 Windows 下用默认状态的 Git 签出一个文件,写了一行中文注释(或者这个文件本来就包含中文),然后存盘提交……不经意间,你的文件就被毁掉了。

因为你提交到仓库的文件已经完全变成了 Windows 风格(签出时把 UNIX 风格转成了 Windows 风格但提交时并没有转换),每一行都有修改(参见本文开头的示意图),而这个修改又不可见(大多数 diff 工具很难清楚地显示出换行符),这最终导致谁也看不出你这次提交到底修改了什么。

这还没完。如果其他小伙伴发现了这个问题、又好心地把换行符改了回来,然后你又再次重演上面的悲剧,那么这个文件的编辑历史基本上就成为一个谜团了。

由于老外几乎不可能踩到这个坑,使得这个 bug 一直隐秘地存在着。但在网上随便搜一下,就会发现受害者绝对不止我一个,比如 这位大哥的遭遇 就要比我惨痛得多。

防范

首先,不要着急去整 Git,先整好自己。你的团队需要确立一个统一的换行符标准(推荐使用 UNIX 风格)。然后,团队的成员们需要分头做好准备工作——配置好自己的代码编辑器和 IDE,达到这两项要求:

  • 在新建文件时默认使用团队统一的换行符标准
  • 在打开文件时保持现有换行符格式不变(即不做自动转换)

这样一方面可以最大程度保证项目代码的规范一致,另一方面,即使现有代码中遗留了一些不规范的情况,也不会因为反复转换而导致混乱。(当然,作为一个强迫症患者,我还是祝愿所有的项目从一开始就步入严谨有序的轨道。)

接下来,我们就可以开始调教 Git 了。我的建议是, 完全关掉这个自作聪明的“换行符自动转换”功能。关闭之后,Git 就不会对你的换行符做任何手脚了,你可以完全自主地、可预期地控制自己的换行符风格。

下面主要针对不同的 Git 客户端,分别介绍一下操作方法。

Git for Windows

这货由 Git 官方出品,在安装时就会向你兜售“换行符自动转换”功能,估计大多数人在看完华丽丽的功能介绍之后会毫不犹豫地选择第一项(自动转换)。请千万抵挡住诱惑,选择最后一项(不做任何手脚)。

first-trap-on-github-autocrlf-git-install

如果你已经做出了错误的选择,也不需要重新安装,可以直接使用命令行来修改设置。很简单,直接打开这货自带的命令行工具 Git Bash,输入以下命令,再敲回车即可:

git config --global core.autocrlf false

first-trap-on-github-autocrlf-bash

TortoiseGit

很多从 TortoiseSVN 走过来的同学很可能会选用 TortoiseGit 作为主力客户端,那么也需要配置一下。在 Windows 资源管理器窗口中点击右键,选择“TortoiseGit → Settings → Git”,做如下设置。

first-trap-on-github-autocrlf-tortoisegit

(由于 TortoiseGit 实际上是基于 Git for Windows 的一个 GUI 外壳,你在上一节所做的设置会影响到上图这些选项的状态,它们可能直接就是你所需要的样子了。)

GitHub 的 Windows 客户端

它是今天的第二被告。这货很容易上手,很适合小白,我主要用它来一键克隆项目到本地。可能正是为了维护简洁易用的亲切形象,这货并没有像 TortoiseGit 那样提供丰富的选项(对“换行符自动转换”这样的细节功能完全讳莫如深啊,我这样的小白死了都不知道怎么死的……)。因此,我们需要手动修改一下它的配置。

GitHub 的 Windows 客户端实际上也是一个壳,它自带了一个便携版的 Git for Windows。这个便携版和你自己安装的 Git for Windows 是相互独立的,不过它们都会使用同一个配置文件(实际上就是当前用户主目录下的 .gitconfig 文件)。

所以如果你已经配置好了自己安装的 Git for Windows,那就不用操心什么了。但如果你的机器上只装过 GitHub 的 Windows 客户端,那么最简单的配置方法就是手工修改配置文件了。

修改 Git 的全局配置文件

进入当前用户的主目录(通常 XP 的用户目录是 C:\Documents and Settings\yourname,在 Vista 和 Win7 下是 C:\Users\yourname),用你最顺手的文本编辑器打开 .gitconfig 文件。

[core] 区段找到 autocrlf,将它的值改为 false。如果没找到,就在 [core] 区段中新增一行:(最终效果见图)

    autocrlf = false

first-trap-on-github-autocrlf-gitconfig

事实上上面介绍的所有命令行或图形界面的配置方法,最终效果都是一样的,因为本质上都是在修改这个配置文件。

还有

关掉了 Git 的“换行符自动转换”功能就万事大吉了吗?失去了它的“保护”,你心里会有点不踏实。你可能会问:如果我不小心在文件中混入了几个 Windows 回车该怎么办?这种意外可以防范吗?

事实上 Git 还真能帮你阻止这种失误。它提供了一个换行符检查功能(core.safecrlf),可以在提交时检查文件是否混用了不同风格的换行符。这个功能的选项如下:

  • false - 不做任何检查
  • warn - 在提交时检查并警告
  • true - 在提交时检查,如果发现混用则拒绝提交

我建议使用最严格的 true 选项。

core.autocrlf 一样,你可以通过命令行、图形界面、配置文件三种方法来修改这个选项。具体操作就不赘述了,大家自己举一反三吧。

最后

你可能还会问,如果我自己一不小心用编辑器把整个文件的换行符都转换成了另一种格式怎么办?还能预防吗?

这……我就真帮不了你了。所以还是建议大家在提交文件之前多留心文件状态:

first-trap-on-github-autocrlf-commit

如果发现变更行数过多,而且增减行数相同,就要警惕是不是出了意外状况。被图形界面惯坏的孩子往往缺乏耐心,对系统信息视而不见,看到按钮就点,容易让小疏忽酿成大事故。所以高手们青睐命令行,并不是没有道理的。

好了,小伙伴们,今天的《踩坑历险记》就到这儿,我们下集再见!祝大家编码愉快!


© Creative Commons BY-NC-ND 4.0   |   我要订阅   |   我要打赏

@perryyeh
Copy link

试了下 找到之前没这个提示的原因了。 之前的git客户端是默认配置,没autocrlf = false 和safecrlf = true 的选项,所以提交的时候,git是自己的转换来提交的,倒也一直没处问题。 刚设置了下这2个选项,果然一下子就提示整个文件被替换了。

@antife-yinyue
Copy link

结论:不要用 windows 😄

@liuweifeng
Copy link

结论:不要用 windows 😄 哈哈~~

@JacksonTian
Copy link

结论:不要用 windows 😄 +1

@cssmagic
Copy link
Owner Author

感谢朴灵 (@JacksonTian) 和米粽粽 (@myst729) 两位老师对本文的技术校审。

@myst729
Copy link

myst729 commented Aug 19, 2013

弄死楼上几个果粉 [挖鼻屎] Windows 用官方的命令行客户端 msysgit 毫无压力 XD

@SunLn
Copy link

SunLn commented Aug 19, 2013

同事用 window写代码、linux虚拟机提交代码曾经碰到过这种问题,看来这篇文章可以解决。

@cssmagic
Copy link
Owner Author

转发转载记录:

  • [微博] 伯乐在线官方微博: //weibo.com/1670481425/A5uRqg6an
  • 伯乐在线: //blog.jobbole.com/46200/

@cssmagic
Copy link
Owner Author

转自《Git 权威指南》作者蒋鑫 (@jiangxin) 老师的微博: //weibo.com/2080831604/A5v5Rhehj

靠程序员设置 core.autocrlf 就是在以德治国,不靠谱。对于有跨平台需要的库,要提交一个 .gitattributes 文件,详见《Git权威指南》第40章40.3小节。第40章40.1小节字符集部分不适用于1.7.10版本后的msysGit,不要再设置 i18n.commitEncoding和i18n.logOutputEncoding了。

我回头翻一下书,补补课。

@lexdene
Copy link

lexdene commented Aug 20, 2013

Windows ?
23333333

@devan5
Copy link

devan5 commented Aug 22, 2013

你好, 我尝试了下, 并没有发现此问题.

我试着重现这个故事原委, 可是没有发现 autocrlf 自动转换失败

具体步骤是 :

git init 
git config core.autocrlf true
touch Test.js
notepad++ Test.js // 设定为 UTF8 编码, 档案格式为 window(CRLF), 并写下几行代码和注释, 包含中文, 按照楼主所说
git add .
git ci -m "init"

git config core.autocrlf false
rm Test.js
git co .
notepad++ Test.js // 发现文档格式已经是 LF, 说明自动转换成功

若有纰漏, 请批判纠正补充.

另外, 附注下我的环境:
git 客户端版本: 1.8.3
window7 64

@cssmagic
Copy link
Owner Author

@devan5 谢谢回复。有空我再重现一下事故现场。

@yisibl
Copy link

yisibl commented Mar 4, 2014

弄死楼上几个果粉 [挖鼻屎] Windows 用官方的命令行客户端 msysgit 毫无压力 XD

@sunshuyang
Copy link

原来问题出在git的客户端配置上!

@wy-ei
Copy link

wy-ei commented Oct 24, 2014

其实命令行挺好用的 用一段时间就习惯了

@markyun
Copy link

markyun commented Oct 24, 2014

结论:最好不要用 windows 😄 +1

1 similar comment
@Yannyezixin
Copy link

结论:最好不要用 windows 😄 +1

@qianyuxiang01
Copy link

结论:最好不要用windows😄+1

@chenyongze
Copy link

摒弃windows

@hax
Copy link

hax commented Sep 11, 2015

现在这个bug估计已经修好了。。。

话说用\r\n的也不止windows,比如按照http规范,协议里的换行都是\r\n

@windqyoung
Copy link

真是搞不懂, 无缘无故的为什么要改我的文件.
我把这选项全试过了, 都没用. 还是被改了.
这功能真恶心.

@momognu
Copy link

momognu commented Feb 1, 2016

不要用 windows...
我还不要用中文注释呢

@cssmagic
Copy link
Owner Author

cssmagic commented Feb 1, 2016

不要用 windows...
我还不要用中文注释呢

在公司项目中要求用纯英文写注释,估计坑会大得多。 😂

@anguiao
Copy link

anguiao commented Apr 15, 2016

额,事实上CR-LF才是正统。。打字机就是这么设计的。。不过纠结于这些也没什么意义了。。

@momognu
Copy link

momognu commented Apr 18, 2016

@anguiao 我还以为是微软一直坚持要用^M$来代表自己呢

@myst729
Copy link

myst729 commented Apr 18, 2016

@momognu

回想你看过的老电影里面使用打字机的场景,一行敲满后要做两件事:换行,回车。

这是两个不同的行为:

  • LF(换行,line feed):将光标下移一行;
  • CR(回车,carriage return):将光标移动到当前行的开头。

Typewriter

每输入一个字符,纸张托架会左移一小截(一个字符的宽度 + 字符间距,好在那时候打字机都是等宽字体 😃 )。当托架移动到最左侧,即纸张右侧已经和敲针对齐时,会有响铃提示已经抵达行末,该另起一行了。

这时候拉动换行杆就会把纸往上卷一小截(行高,line height),然后把托架推到最右侧,使纸张左侧重新和敲针对齐(这个动作就叫回车,不知道“回车”译法出自何处,我猜测跟 carriage 另一个意思“马车”有关)。如果只换行不回车,那么第一行敲满以后,敲针始终在纸张右侧,没法继续输入;只回车不换行,所有的内容都敲到同一行里了。

后来一些改良的产品已经能让这两个动作连贯起来了,比如这个视频(Youtube),注意看视频快结束时他是怎么输入连续空行的。这个视频(Youtube)讲解更详细。

然而,不论设计怎样改进,在机械设备上操作再无缝,换行和回车仍然是两个不同的行为。

@momognu
Copy link

momognu commented Apr 18, 2016

@myst729 哦!我算是知道这段历史了,不过微软为什么在linux和mac都统一用单一$的今天,还一直坚持这个“仿古”行为?

@hax
Copy link

hax commented Apr 18, 2016

@momognu 为了保持兼容性。我前面也说过,实际上许多互联网协议里都要求换行是 CR LF。

@tcnf2008
Copy link

我代码中包含中文注释,但没有遇到这个问题,一直ok。是不是新版本git修复了这个问题?

@cssmagic
Copy link
Owner Author

有可能,也可能是没有触发。总之大家只要清楚原因就可以做好预防或补救了。

@mingjiu
Copy link

mingjiu commented Jul 24, 2017

终于找到了。妈的垃圾百度,搜了一个下午没搜出来什么问题。换google两分钟

@noisyui
Copy link

noisyui commented Sep 28, 2017

Thanks, 今天刚被坑过。编译器报各种error,最后定位到一行,只要在这一行随便写点注释就能通过,但直接删除这一行就不行。最后还是在vi下把多余的几个^M删掉才算最终解决。

@mzhiyuan
Copy link

gerrit+git也有这个问题,我说怎么windows提交的代码每一行都改变了,谢谢楼主。

@liuyangnwpu
Copy link

初学Git,在Git目前版本2.15.1上,如果设置core.autocrlf=false,那么core.safecrlf设置不起任何作用,官方文档说明,safecrlf也是只在autocrlf激活时才起作用(core.safecrlf: If true, makes Git check if converting CRLF is reversible when end-of-line conversion is active)。经过验证,在windows上创建LF和CRLF混合的文本文件,提交和签出时设置safecrlf=true都无任何提示。

@jiangbingo
Copy link

mark

@jiangbingo
Copy link

jiangbingo commented Mar 20, 2018

when you want to regulate the files which may be changed the characters of end lines,try this before you commit the files.

class RegulateEndCharOfLines(object):
    def __init__(self, path):
        self.path = path

    def _get_files(self):
        for root, _, files in os.walk(self.path):
            for f in files:
                if os.path.splitext(f)[-1] in ['.txt', '.py', '.md']:   # filename extension
                    yield os.path.join(root, f)

    def _delete_whitspase(self, lines):
        new_content = ''
        for line in lines:
            if line.endswith((' \n', '\t\n', ' ', '\r\n')):   #windows-like and uncessary characters  
                line = line.rstrip() + '\n'            # unix-like line feed character
            new_content += line
        return new_content

    def _read_file(self, file):
        with open(file, 'rb') as fp:
            return fp.readlines()
    def _write_file(self, content, file):
        with open(file, 'wb') as fp:
            fp.write(content)

    def _adapt_one_file(self, file):
        content = self._read_file(file)
        new_content = self._delete_whitspase(content)
        self._write_file(new_content, file)

    def adapt_files(self):
        files = self._get_files()
        for file in files:
            self._adapt_one_file(file)
if __name__ == '__main__':
    path = os.path.abspath(os.path.dirname(sys.argv[1]))
    RegulateEndCharOfLines(path).adapt_files()

@wyrhero
Copy link

wyrhero commented Apr 9, 2018

总结得不错,thanks

@caixinning
Copy link

万分感谢,解决了我的问题!

@leozhiyu
Copy link

leozhiyu commented Sep 9, 2018

非常感谢

@FancyKings
Copy link

以后这种事儿再也不百度了,谷歌一下接着就出来了。
谢谢前辈教导,已经完美解决问题,十分感谢

@pimgeek
Copy link

pimgeek commented Mar 27, 2019

现在, 我主要使用基于 Win10 的 Debian Linux Sub System, 感觉体验上明显比 Git for Windows 好很多, 毕竟不需要再依赖 cmd.exe 了. 而且, 在这个系统中, 可以直接创建指向 Win10 的符号链接 (/mnt/d/path-to-git-repo/), 比以前架虚拟机, 设置 samba 服务访问要方便而且稳定得多. 而且 Linux 命令行实在不方便的, 在 bash 下面使用 > explorer.exe . 命令就可以在当前位置调出资源管理器.

@azurefx
Copy link

azurefx commented Apr 15, 2019

这么多说不要用Windows的人是什么思路…
是纯Windows平台的开源项目不配用Git,还是不配开源?
明明是Git的锅好不好 一个合格的文本编辑器应当能正确处理换行符

↑刚被SourceTree for Windows的内嵌Git客户端坑过的抱怨

@Yangfan2016
Copy link

大神,求助
全局 设置 git config 和 新建一个 .gitattriubutes 那个优先级高

@cssmagic

@azurefx
Copy link

azurefx commented May 15, 2019

大神,求助
全局 设置 git config 和 新建一个 .gitattriubutes 那个优先级高

When deciding what attributes are assigned to a path, Git consults $GIT_DIR/info/attributes file (which has the highest precedence), .gitattributes file in the same directory as the path in question, and its parent directories up to the toplevel of the work tree (the further the directory that contains .gitattributes is from the path in question, the lower its precedence). Finally global and system-wide files are considered (they have the lowest precedence).

@Yangfan2016
Copy link

thx @azurefx

@xxsong5
Copy link

xxsong5 commented Jun 21, 2019

linux 虚拟机 + window 混合操作, linux checkout 后, 在window中看被莫名修改。 方法:git config --global --unset core.autocrlf 可以保持一致性

@azbh111
Copy link

azbh111 commented Dec 10, 2020

新买的mac遇到问题返厂换新了, 要一个月左右, 不得不用公司配的windows电脑进行开发
我在wni10上装了linux子系统, 把idea终端配置成linux的终端
于是问题就来了
在linux终端上git status 发现所有文件都有修改, git commit之后, linux终端里git status终于干净了
但是idea里显示所有文件都有修改..... cmd里运行git status也显示所有文件都有修改
在cmd里commit掉, linux终端里又显示所有文件有修改
core.autocrlf不管设置啥都没用
oooo fuck......

有大神能解决这个问题吗

@azbh111
Copy link

azbh111 commented Dec 10, 2020

@pimgeek 你有遇到我说的这个问题吗

@ghost
Copy link

ghost commented Mar 15, 2021

Mark:

cr ascII字符-> \r 回车 早期macOS
lf ascII字符-> \n 换行 Linux/unix/macOS
crlf ascII字符 -> \r\n 回车换行 windows

如何在vscode中查看换行符类型:在搜索中用正则表达式匹配

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests