我猜點(diǎn)進(jìn)來看的客官,至少都是使用過 Git 的衅胀。另外有些客官骄恶,可能知道琉苇,如果 Git 的換行符處理不當(dāng),就會(huì)產(chǎn)生某些問題娇钱。還有一些客官伤柄,也許正在忍受由于換行符處理不當(dāng)所帶來的各種問題。
究竟會(huì)有啥問題文搂,我咋不知道适刀?
在描述問題之前,先簡(jiǎn)要的說一些背景知識(shí)煤蹭。技術(shù)大牛大可直接略過笔喉。
由于歷史原因,不同的操作系統(tǒng)硝皂,在處理換行符時(shí)常挚,使用了不同的方案。Windows 操作系統(tǒng)使用了 CRLF
稽物,而 Unix 陣營的操作系統(tǒng)則使用了 LF
奄毡。Mac OS 最起初使用了 CR
,后來到了 Mac OS X 后贝或,改成了使用 LF
吼过,與 Unix 陣營保持了一致锐秦。雖然目前很多代碼編輯器都支持自動(dòng)識(shí)別和切換換行符風(fēng)格,然而那先,總有那么一些不合群的編輯器农猬,無法達(dá)到相應(yīng)的兼容性赡艰。
所以售淡,作為一個(gè)使用不限于 Git 的版本控制系統(tǒng)的程序猿,你很可能會(huì)遇到與換行符相關(guān)的尷尬局面慷垮。
第1層問題:被毀掉的 diff 工具
- 執(zhí)行
git status
命令揖闸,凡是打開過的文件,無論是否改過料身,其狀態(tài)都是已修改的狀態(tài)…… - 同事說他只改了一行代碼汤纸,然而我看 diff,發(fā)現(xiàn)整個(gè)文件都被改了……
以上兩個(gè)問題芹血,在跨平臺(tái)的項(xiàng)目開發(fā)過程中贮泞,會(huì)經(jīng)常出現(xiàn)。問題背后的原理幔烛,其實(shí)很簡(jiǎn)單啃擦。你的代碼編輯器兼容性不強(qiáng),在打開一個(gè)與自身默認(rèn)的換行符風(fēng)格不符的文件時(shí)饿悬,編輯器就自作主張令蛉,自動(dòng)地將換行符風(fēng)格轉(zhuǎn)換成自己的默認(rèn)風(fēng)格,并保存了下來狡恬。于是便出現(xiàn)了明明沒有改動(dòng)珠叔,卻被標(biāo)記為修改狀態(tài)的現(xiàn)象。
這個(gè)問題確實(shí)討厭弟劲。因?yàn)橐坏┏霈F(xiàn)該問題祷安,diff 工具的功效便瞬間大打折扣。當(dāng)然通過使用 git diff -w
兔乞,自動(dòng)忽略掉空白字符修改辆憔,可以很大程度減少問題帶來的影響,但是报嵌,Git 提交歷史中充斥著的大量毫無意義的換行符修改記錄虱咧,同樣會(huì)持續(xù)性的消耗開發(fā)人員有限而寶貴的精力。
所以锚国,有人想出了一個(gè)方案:只要我們?nèi)勘3纸y(tǒng)一腕巡,只用一種換行風(fēng)格就可以了。Git 其實(shí)也給出了解決方案血筑。在 Windows 平臺(tái)安裝 Git 時(shí)绘沉,肯定遇到過這樣一個(gè)選項(xiàng):
無論我們選擇第一種還是第二種選項(xiàng)煎楣,我們都可以保證,提交至版本庫的代碼都會(huì)被統(tǒng)一轉(zhuǎn)化成 LF
風(fēng)格车伞。背后的原理择懂,從圖中也可以看出來,其實(shí)就是把 core.autocrlf
選項(xiàng)設(shè)置成為了 true
或 input
另玖。當(dāng)然困曙,如果你是一個(gè)非常自律且注重細(xì)節(jié),或者不喜歡被條條框框的規(guī)則約束谦去,甚至有些潔癖的程序猿慷丽,你大可勇敢的選擇第三個(gè)選項(xiàng),然后優(yōu)雅的配置好你的代碼編輯器鳄哭,保證所有提交的風(fēng)格一致要糊。
然而,你以為這樣就萬事大吉的了嗎妆丘?不好意思锄俄,惡夢(mèng)可能剛剛開始……
第2層問題:惡魔般的已修改狀態(tài)
自從團(tuán)隊(duì)統(tǒng)一設(shè)置了 core.autocrlf
選項(xiàng)后,好長一段時(shí)間勺拣,我們的代碼提交奶赠,都顯得十分的干凈明了。在 commit 和 patch 中宣脉,再也沒有出現(xiàn)過大規(guī)模無意義的換行符修改车柠。然而,直到有一天塑猖,在我們團(tuán)隊(duì)來了一名新員工后竹祷,這種和諧便被打破了。
我們真的不知道他到底做了些什么羊苟,我們只知道塑陵,當(dāng)我們通過 git pull
命令與服務(wù)器同步后,有幾個(gè)文件蜡励,在剛剛 checkout
至工作區(qū)且沒有被任何編輯器打開或修改的狀態(tài)下令花,它們就已經(jīng)處于了 modified 狀態(tài)。更可怕的是凉倚,對(duì)該文件重新執(zhí)行 checkout
命令兼都,問題依然存在:
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: test.c
no changes added to commit (use "git add" and/or "git commit -a")
$ git checkout ./test.c
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: test.c
no changes added to commit (use "git add" and/or "git commit -a")
這種問題,可能不少人都遇到過稽寒。是不是覺得有些抓狂扮碧,甚至有些擔(dān)心,我們的版本庫是不是被那個(gè)毛孩子搞壞了?可是問題究竟是如何產(chǎn)生的呢慎王?
這種問題經(jīng)常會(huì)出現(xiàn)在跨平臺(tái)項(xiàng)目中的 Windows 開發(fā)環(huán)境中(喂蚓土,那些鄙視 Windows 的程序員不要鬧哈,這個(gè)鍋還真不能讓 Windows 背)赖淤。雖然團(tuán)隊(duì)規(guī)定了換行符的統(tǒng)一風(fēng)格蜀漆,甚至還要求每個(gè)人都對(duì) Git 做同樣的設(shè)置。然而咱旱,總會(huì)有一些狂奔不羈愛自由的野馬程序員确丢,或者是一些舉止粗曠的粗心程序員沒有按章行事。于是莽龟,就有這樣一位隊(duì)友蠕嫁,在某次提交中锨天,提交了一個(gè)混合了兩種換行符的文件毯盈。接下來,剩下的那些乖孩子病袄,由于之前設(shè)置過 core.autocrlf = true
搂赋,在從服務(wù)器更新這個(gè)文件時(shí),Git 會(huì)自動(dòng)將文件轉(zhuǎn)化為統(tǒng)一的 CRLF
風(fēng)格益缠。此時(shí)脑奠,如果對(duì)工作區(qū)里面的文件進(jìn)行 CRLF
向 LF
風(fēng)格的轉(zhuǎn)換,然后將轉(zhuǎn)換后的文件與版本庫中的對(duì)應(yīng)文件對(duì)比幅慌,Oops宋欺!就會(huì)發(fā)現(xiàn)這兩個(gè)文件竟然不一致!Git 就是通過這種方式胰伍,認(rèn)定一定是你自己把文件修改了……
如果你真的遇到了這個(gè)問題齿诞,也不要慌。通過先把 core.autocrlf
設(shè)置為 false
骂租,然后人工將該文件的換行符統(tǒng)一修改為 LF
并提交祷杈,最后再把 core.autocrlf
重新設(shè)置成 true
即可解決問題。
通過上面說的辦法渗饮,雖然問題解決了但汞。但是有人要問了:我這次雖然是解決了,但是團(tuán)隊(duì)這么多人互站,要是每天都有幾個(gè)豬隊(duì)友不小心私蕾,手一抖,再搞出幾個(gè)這樣的幺蛾子出來胡桃,我豈不是要被他們煩死踩叭?
不錯(cuò),對(duì)于這種情況标捺,Git 確實(shí)也提供了一個(gè)選項(xiàng)懊纳,叫做 core.safecrlf
揉抵。當(dāng)你同時(shí)設(shè)置了 core.autocrlf
與 core.safecrlf
參數(shù)后,如果你的提交混合了兩種換行符嗤疯,Git 就會(huì)根據(jù)你的設(shè)置發(fā)出警告冤今,或者拒絕提交。
所以茂缚,這樣戏罢,就又可以萬事大吉了?抱歉脚囊,還是不行龟糕!
第3層問題:被毀掉的數(shù)據(jù)和工程
上面提到的被毀掉的 diff ,以及偶爾冒出來的小惡魔可能并沒有那么嚴(yán)重悔耘。然而你要小心讲岁,換行符問題還可能毀掉你的代碼和工程。在 Git manpages 中衬以,有這樣一段描述缓艳。
CRLF conversion bears a slight chance of corrupting data. autocrlf=true will convert CRLF to LF during commit and LF to CRLF during checkout. A file that contains a mixture of LF and CRLF before the commit cannot be recreated by git. For text files this is the right thing to do: it corrects line endings such that we have only LF line endings in the repository. But for binary files that are accidentally classified as text the conversion can corrupt data.
簡(jiǎn)單來說,Git 的換行符自動(dòng)切換功能雖然好用看峻,但是是有一定風(fēng)險(xiǎn)的阶淘。因?yàn)椋创a文件之所以允許換行符的隨意切換互妓,是因?yàn)樵创a對(duì)換行符這一數(shù)據(jù)是不敏感的溪窒。對(duì)于某些特定的數(shù)據(jù),例如二進(jìn)制文件冯勉。如果該文件被意外的識(shí)別成了文本文件澈蚌,并執(zhí)行了換行符轉(zhuǎn)換,那么這個(gè)文件的數(shù)據(jù)可能就會(huì)被永久的損壞了珠闰。
什么惜浅,聽起來有些聳人聽聞?那我們就舉兩個(gè)口味稍微清淡點(diǎn)的例子:
- bash 腳本文件應(yīng)當(dāng)使用
LF
作為換行符伏嗜,如果使用CRLF
風(fēng)格的換行符坛悉,bash 解釋器可能無法正常工作;
$ ./crlf_bash.sh
-bash: ./crlf_bash.sh: /bin/sh^M: bad interpreter: No such file or directory
- Windows 批處理 bat 文件最好使用
CRLF
作為換行符承绸,如果使用LF風(fēng)格的換行符裸影,且代碼中包含了中文字符,那么解釋器可能無法正常工作军熏;
這兩個(gè)例子轩猩,影響都比較有限。最多就是面對(duì)剛剛 clone 的代碼無法運(yùn)行的事實(shí),略顯崩潰均践。之后晤锹,你仍可以使用類似于 unix2dos
和 dos2unix
之類的工具對(duì)文件的格式進(jìn)行轉(zhuǎn)換來解決問題。然而彤委,如果出問題的文件是一個(gè)二進(jìn)制數(shù)據(jù)鞭铆,那你只有哭了……
不過,幸好焦影,Git 專門為我們提供了 gitattributes
功能车遂,可以很好的解決這個(gè)問題。
優(yōu)雅處理換行符的終極方法
以上提到的問題斯辰,其實(shí)可以總結(jié)為兩類:
- Git 的換行符轉(zhuǎn)換策略配置需要與項(xiàng)目同步舶担,不能僅僅是依靠團(tuán)隊(duì)規(guī)范來約束;
- 文件是否需要做換行符轉(zhuǎn)換處理彬呻,是由文件本身的屬性決定的衣陶,需要對(duì)每個(gè)文件分別對(duì)待;
使用 .gitattributes
就完美解決了以上兩個(gè)問題:
-
.gitattributes
具有最高的優(yōu)先級(jí)废岂,無論你是否設(shè)置相關(guān)的換行符風(fēng)格轉(zhuǎn)化屬性祖搓,你都可以和團(tuán)隊(duì)保持一致狱意; - 使用
* text=auto
可以定義開啟全局的換行符轉(zhuǎn)換湖苞; - 使用
*.bat text eol=crlf
就可以保證 Windows 的批處理文件在checkout
至工作區(qū)時(shí),始終被轉(zhuǎn)換為CRLF
風(fēng)格的換行符详囤; - 使用
*.sh text eol=lf
就可以保證 Bash 腳本無論在哪個(gè)平臺(tái)上财骨,只要被checkout
至工作區(qū),始終被保持LF
風(fēng)格的換行符藏姐; - 使用
*.jpg -text
可以禁止 Git 將 jpg 文件識(shí)別為文本文件隆箩,從而避免由于換行符轉(zhuǎn)換引入的數(shù)據(jù)損壞;
關(guān)于 .gitattributes
的詳細(xì)使用方法羔杨,可以參考Git Documentation:gitattributes捌臊。
如果你覺得太麻煩,可以直接到這里兜材。鏈接中的網(wǎng)站理澎,提供了一系列針對(duì)各種開發(fā)環(huán)境,已經(jīng)寫好了的 .gitattributes
文件曙寡。
所以糠爬,趕緊給你的項(xiàng)目添加一個(gè) .gitattributes
文件吧!