2017年7月2日 星期日

[git]為什麼常出現有修改但是比對不卻顯示不出差異?談談檔案斷行問題和如何達到不同平台正確一致化

用git之後,有時候遇到在檢查狀態的時候明明顯示有修改,但是實際比對的時候發現完全沒有差異

通常來說發生這種情況就是檔案的斷行跑掉了,如果沒有好好解決到時候log會常常出現修改,但是實際上並不是真的修改,未來查找log很辛苦。

在這篇將會對於如何一勞永逸處理掉這個斷行問題做個說明。

問題描述

有時候檢查檔案狀態,發現有變更,但是實際比對會發現其實什麼變更都沒有:

log有修改,但是比對發現沒有任何差異
log有修改,但是比對發現沒有任何差異

當然,如果各位有在每次commit的時候認真比對的話,那麼一定不會把這個加到commit裡面,但是有時候如果漏看的話,不小心把這個commit上去, 只會造成未來在看的時候有更多雜訊,浪費時間。

解決方式

首先要設定好讓git自動做斷行處理,再來如果專案裡面以前沒有設定過,很有可能斷行已經亂掉,這時候做一個一致化(Normalize line ending)的動作。

設定

要一次解決這個問題,可以透過兩個設定:

git config --global core.autocrlf
  1. 如果是windows設定為true

    git config --global core.autocrlf true

  2. 如果是Unix系列設定為 input

    git config --global core.autocrlf input

為專案加入 .gitattributes檔案

檔案裡面要加入 * text=auto

更多gitattribute關於斷行的設定可以參考這個github repo 一些不同語言的gitattributes範本

或者可以看我自己每一個專案都會放的gitattribute檔案:我的gitattribute

一致化

做好了以上設定,要把現行的斷行一致化就變得很簡單,只需要:

假設有任何修改尚未commit,先commit
git commit -am "儲存目前修改"
把所有檔案刪掉(不包含.git)
git rm --cached -r .
重新從repo把刪除的修改revert掉
git reset .
加入index
git add -u - 如果有看到很多warning提示會從crlf轉成lf是正常的。
commit上去
git commit -m "斷行一致化處理"

如果對於這兩個設定的目的和用途為何,請繼續往下看。

問題發生的原因

Windows 和 Unix(包含Linux和mac)代表斷行的字符是不一樣的。

Windows
crlf - 兩個字符代表斷行
Unix
lf - 字符代表斷行

還記得git是版控檔案內容,所以假設本來是crlf,如果被改成了lf那麼對於git來說就是一個修改。

但是因為斷行字符是看不到的內容,所以有些比對工具不會顯示出任何差異,造成混亂。

而本質上這些不應該算是修改,而是因為作業系統不同而導致的問題,所以git本身有考慮進去怎麼處理。

要解決這個問題其實很簡單,假設不管哪個作業系統都是用一樣的斷行作為儲存,那麼就不會有因為斷行不一樣的原因出現修改。

假設一開始沒有設定好,那麼很有可能有些檔案存成lf有些存成crlf,這個時候就需要做斷行一致化的動作(normalize line ending)

git 如何處理這個問題 - 自動轉換的時間點

在git裡面,其實內建的時候就有考慮到這個事情,所以他有允許做自動轉換。

這個自動轉換發生在兩個時間點:

當把檔案寫到repo的時候
發生的時間點如:git addgit commit
當把檔案從repo讀出來的時候
發生的時間點如:git checkout

所以我們可以透過設定告訴git在這兩個時間點的時候,應該要怎麼轉換。

git 斷行自動轉換相關設定

在git裡面有兩個設定是告訴git如何做這個斷行轉換:

  1. git config 的 core.autocrlf
  2. .gitattributes設定

這邊git config的設定對所有專案都有作用,但是只能夠對自己有起作用,換句話說如果在團隊裡面,有些人的電腦沒有設定好 那麼只靠這個設定是不夠的,因為很有可能就是有個別的電腦設定有問題導致斷行錯亂。

因此,.gitattributes的作用就出來了,這個是能夠跟著專案走的斷行處理設定,設定了就不用擔心有個別電腦沒有設定 core.autocrlf的問題。

git config core.autocrlf

這個其實就是在設定讀和寫的時候自動轉換斷行的處理,總共有三個設定:

  • auto
  • input
  • false

這三個設定分別為:

的時候轉換的時候轉換建議
autolf 轉 crlf crlf 轉 lfWindows作業系統使用
input不轉換crlf 轉 lfUnix作業系統使用
false不轉換不轉換不建議使用

.gitattributes

這個檔案放在同.git資料夾層級,裡面可以設定一些檔案屬性,其中一個就是轉換的部分

一個範例檔案可能長成如下:

  * text=auto
  *.md text
  *.csproj text eol=crlf

自動轉換的設定是* text=auto - 這個意思是只要是text類型的檔案git會自動轉換(類似於core.autocrlf但是更加智能)。

這個時候可能會好奇,到底哪些是屬於text檔案?git會自動判斷,不過也可以透過設定強制告訴git,例如:*.md text就是說md結尾的都是 屬於text類型 - 換句話說就是會做轉換斷行的處理。

也有可能有些檔案要保持某一個斷行,例如*.csrpoj(C# 專案檔案,要保持crlf),這個時候也可以透過設定:*.csproj text eol=crlf

如果想參考一些不同檔案的設定,可以參考這個repo: 一些不同語言的gitattributes範本

我自己也有一個常用的設定,也可以參考一下:我的gitattribute

要注意一下,假設今天你不是使用指令的方式在執行git - 那麼有些gui其實不一定會吃.gitattributes的設定(有些ide的git套件不會吃這個設定 - 不確定是否目前還是如此,不過曾經有過像egit不吃這個設定) 所以最好還是要搭配git config的設定。

結語

斷行的部分其實還有幾個相關設定,不過那些設定基本上不會碰到,因此我就沒有提。

希望透過這篇也讓大家了解到斷行在不同作業系統會帶來的問題,並且怎麼樣能夠一勞永逸的解決。

gitattributes其實還有別的進階用途,未來有機會在和大家介紹

參考資料

Mind the End of Your Line(英文)
一篇很詳細介紹關於斷行問題的文章
gitattributes的官方說明(英文)
介紹整個gitattributes的其他應用
github介紹一致化斷行的處理(英文)
標籤:

沒有留言 :

張貼留言