我猜點(diǎn)進(jìn)來看的客官,至少都是使用過 Git 的。另外有些客官,可能知道,如果 Git 的換行符處理不當(dāng),就會(huì)產(chǎn)生某些問題。還有一些客官,也許正在忍受由于換行符處理不當(dāng)所帶來的各種問題。
究竟會(huì)有啥問題,我咋不知道?
在描述問題之前,先簡要的說一些背景知識(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í)很簡單。你的代碼編輯器兼容性不強(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)格一致。
然而,你以為這樣就萬事大吉的了嗎?不好意思,惡夢可能剛剛開始……
第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.
簡單來說,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
文件吧!