Git 換行警告 LF will be replaced by CRLF:Windows 與 Mac 共用 repo 時統一換行字元

摘要

Windows 與 Mac 的文字檔換行字元不同,Windows 版 Git 預設會在取出檔案時自動轉換,因此 git add 時會出現 LF will be replaced by CRLF 的警告。只要在 repo 加上一份設定檔指定換行字元,不論每個人的 Git 設定為何,提交進 repo 與取出到電腦上的檔案都會是同一種換行,Mac 上也不會再出現整份檔案都被改過的差異。

文章目錄

文字檔每一行的結尾都有換行字元,Windows 慣用 CRLF(\r\n),Mac 與 Linux 則是 LF(\n)。Windows 版 Git 預設會在取出檔案時把 LF 轉成 CRLF,所以在 Windows 上 git add 一個 LF 的檔案時,會看到 LF will be replaced by CRLF the next time Git touches it 的警告。

只有自己在 Windows 上開發時,這個警告可以不理會。和 Mac 共用 repo 時,只要有人關掉了自動轉換,用 CRLF 提交檔案,Mac 上的編輯器用 LF 存檔後,整份檔案的每一行都會變成修改。在 repo 加上一份 .gitattributes 指定換行字元,不論每個人的 Git 設定為何,提交進 repo 與取出到電腦上的檔案都會是同一種換行。

測試環境是 Windows 11 的 Git 2.55.0(Git for Windows)與 macOS 的 Git 2.54.0(Apple Git)。

警告的意思

Git for Windows 的安裝程式預設選擇自動轉換換行,會在系統設定檔寫入 core.autocrlf=true,用 git config --show-origin core.autocrlf 可以看到這個值與它來自哪個設定檔。在這個設定下 git add 一個 LF 的檔案,就會出現警告:

Windows PowerShell 顯示 core.autocrlf 為 true 且來自 C:/Program Files/Git/etc/gitconfig,git add app.js 時出現 LF will be replaced by CRLF 的警告,git ls-files –eol 顯示 i/lf w/lf

git ls-files --eol 會列出每個檔案在 repo 與電腦上的換行字元,判斷換行問題時就看這三欄:

欄位 意思 例子
i/ 提交進 repo(index)的換行 i/lf 代表 repo 裡是 LF
w/ 電腦上這份檔案(working tree)的換行 w/crlf 代表電腦上是 CRLF
attr/ .gitattributes 對這個檔案的設定 沒有設定時是空的

截圖裡的 i/lf 代表 repo 裡存的仍然是 LF,警告只是預告下次取出時會改成 CRLF。我把檔案刪掉再用 git checkout 取出,電腦上的檔案就變成 w/crlf。

換行不一致造成的差異

Mac 的 Git 預設不轉換換行。我在 Mac 上模擬關掉自動轉換的 Windows 同事,用 CRLF 提交一個兩行的檔案,再用 LF 存檔,Git 就把兩行都當成修改:

步驟 指令 結果
用 CRLF 提交後 git ls-files --eol i/crlf w/crlf,repo 裡存的是 CRLF
編輯器用 LF 存檔後 git diff --stat app.js | 4 ++--,2 行刪除、2 行新增

用 git diff | cat -v 看差異時,刪掉的每一行結尾都多了 ^M,也就是 CRLF 裡的 \r,文字內容其實沒有改:

1-const a = 1;^M
2-const b = 2;^M
3+const a = 1;
4+const b = 2;

檔案一多,真正的修改就會淹沒在這些差異裡。

用 .gitattributes 統一換行

.gitattributes 的設定優先於每個人電腦上的 core.autocrlf,依 Git 的官方說明,text 屬性沒有設定的檔案才會照 core.autocrlf 轉換,所以不需要請每個人改自己的 Git 設定。整理換行會改到大量檔案,開始前先提交手上的修改,再依序完成下面四步:

  1. 在 repo 根目錄建立 .gitattributes,寫入下面這一行。text=auto 讓 Git 自動判斷哪些是文字檔,eol=lf 讓文字檔在 Windows 與 Mac 上取出時都是 LF:

    1* text=auto eol=lf
    
  2. 把 .gitattributes 加進 repo,再用 git add --renormalize . 讓已經提交的檔案依新規則重新整理,然後提交:

    1git add .gitattributes
    2git add --renormalize .
    3git commit -m "統一換行為 LF"
    
  3. 電腦上已經存在的檔案要重新取出才會換成 LF。下面兩行會依 repo 的內容重新產生所有檔案,git reset --hard 會丟掉還沒提交的修改,所以一定要在提交後才執行:

    1git rm -rq --cached .
    2git reset -q --hard
    
  4. 其他已經 clone 這個 repo 的人,git pull 之後同樣要執行第 3 步的兩行指令。

在 Windows 上照著做,提交後 git ls-files --eol 仍然顯示 w/crlf,執行第 3 步後 .gitattributes 與 app.js 才變成 i/lf w/lf,attr/ 欄位也顯示新的設定。畫面裡 git add .gitattributes 出現的 CRLF will be replaced by LF,是因為 Windows PowerShell 建立的 .gitattributes 本身用了 CRLF,Git 會在提交時轉成 LF,不用理會:

依序執行 git add .gitattributes、git add –renormalize .、git commit 後,git ls-files –eol 顯示 w/crlf,執行 git rm –cached 與 git reset –hard 後變成 i/lf w/lf attr/text=auto eol=lf

我另外用兩份 clone 模擬同事,同事 git pull 之後 app.js 仍然是 w/crlf,執行第 3 步後才變成 w/lf。之後在 Windows 新增 LF 的檔案並 git add,也不會再出現 LF will be replaced by CRLF 的警告。Mac 上用同一份設定測試,原本用 CRLF 提交的檔案在重新整理後變成 i/lf,重新取出後電腦上的檔案也是 LF。

VS Code 視窗右下角的狀態列會顯示目前檔案的換行字元,可以用來確認結果。同一個 app.js 在加上 .gitattributes 之前取出時顯示 CRLF,加上之後重新取出就是 LF:

VS Code 狀態列的換行顯示,設定前是 CRLF,設定後是 LF

總結

Windows 與 Mac 共用的 repo,在根目錄加上一份 .gitattributes 指定文字檔一律使用 LF,再把已經提交的檔案與電腦上的檔案都重新整理一次。實測 Windows 與 Mac 在設定後,repo 與電腦上的檔案都是 LF,Windows 上 git add 也不再出現 LF will be replaced by CRLF 的警告。

Ted Liou

Ted Liou

現役軟體工程師,分享 Unity 與 AI 的技術開發經驗,也提供諮詢。

留言