Gitの「LF will be replaced by CRLF」警告の意味と対処|改行コードの確認・.gitattributesでの統一・$’\r’: command not foundの直し方

Gitの「LF will be replaced by CRLF」警告の意味と対処

こんにちは、おうどんです🍜

Windows でシェルスクリプトを書く。 git add する。

warning: in the working copy of 'deploy.sh', LF will be replaced by CRLF the next time Git touches it

……warning。 英語で何か言われた。 でも error じゃないし、コミットもできた。

よし、見なかったことにしよう。

数日後。 そのスクリプトを Linux のサーバーで動かす。

deploy.sh: line 3: $'ls\r': command not found

……え。 ls がない? いや、あるでしょ。一番よく打つコマンドだよ?🤔

(説明用の架空の場面で、上の2つは未実行の出力例です。同じ形のメッセージは、あとで実際に再現します)

犯人は、行末にこっそり立っている見えない文字。 CR(キャリッジリターン)です。

改行には、コンビで出てくる流派と、ピンで出てくる流派があるんです。

  • Windows:CR さんと LF さんの2人組(CRLF)
  • Linux・macOS:LF さん1人(LF)

おうどんはこの2人を、漫才コンビ「改行ズ」と呼ぶことにしました。

CR さん「どうも〜、改行ズです」

LF さん「Linux の舞台では、僕1人で出る約束なんですけど」

CR さん「ついてきちゃった、てへぺろ♪」

……反省の色が、改行コードより見えない。

それで bash が混乱しているんです。

今回は、Git の改行コードの警告の意味と、.gitattributes で改行をそろえる方法、$'\r': command not found の直し方をまとめます。

目次

結論:まずはこれだけ

先に答えを言うと、あの warning はエラーではなく、Git が改行を書き換えるという予告なんです。 困るのは、書き換えのルールが人によってバラバラなとき。

  • LF will be replaced by CRLF は「次に Git がこのファイルを書き出すとき、改行を CRLF にします」という予告
  • 改行コードは git ls-files --eol で、Git の中と手元の両方を確認できる
  • シェルスクリプトは .gitattributes に *.sh text eol=lf と書いて、LF に固定する

warning なら無視していいってことですよね?

予告を無視するのは、天気予報で「夕方から雨」と言われて傘を置いていくのと同じです。 雨は、デプロイの日に降ります。

じゃあ警告が出ないように、改行を全部消せば……

1行のシェルスクリプトになります。 しかも、たぶん動きません。改行ズを解散させないでください。

警告の正体は「次に触ったときに変えます」

ここは Git の言い分を聞いてあげましょう。Git は今すぐ変えるとは言っていないんです。

改めて警告を読むと、the next time Git touches it(次に Git がそれに触ったとき)と書いてあります。 今回の検証環境(Windows 10 Pro / Git for Windows 2.45.1 / Git Bash の bash 5.2.26)で再現してみます。

ユーザーごとの Git 設定の影響を受けないよう、グローバル設定とシステム設定を読まない状態で実行しています。

git init -q demo && cd demo
git config core.autocrlf true
printf 'kitsune\ntanuki\n' > menu.txt
git add menu.txt
git ls-files --eol
warning: in the working copy of 'menu.txt', LF will be replaced by CRLF the next time Git touches it
i/lf    w/lf    attr/                 	menu.txt

git ls-files --eol は、ファイルの改行を3つの欄で見せてくれるコマンドです。 git-ls-files のドキュメントによると、i/ はインデックス(git add した内容を置いておく場所)、w/ は作業ツリー(手元のファイル)、attr/ は適用されている改行の属性です。

結果は i/lf w/lf。 つまり、git add した時点では、手元のファイルは LF のまま。何も変わっていません。

Git「今は触りません。次に書き出すとき CRLF にしますね」

おうどん「……それ、わざわざ今言う必要ある?」

Git「あとで言ったら、勝手に変えたって怒るでしょう」

……ぐうの音も出ない。

では、なぜ CRLF にするのか。 原因は core.autocrlf true という設定です。 Pro Git(Git の公式サイトで公開されている解説書)では、Windows で core.autocrlf を true にすると、git add のときに CRLF を LF にし、チェックアウト(Git の中身を手元に書き出すこと)のときに LF を CRLF にする、と説明されています。

つまり Git の中は LF、Windows の手元は CRLF。 改行ズが、舞台によって人数を変える仕組みです。

じゃあ、それでうまくいってるなら何が問題なの?

問題は、この設定が人ごとのパソコンの設定だということ。 Aさんは true、Bさんは false、Cさんは設定したことすら覚えていない。 同じリポジトリで、改行ズの人数が人によって変わるわけです。

Cさんって誰?

たぶん、半年後のおうどんです。 設定した本人が一番覚えていない。よくある話です。

そして、そのうち誰かの CRLF が、シェルスクリプトごと Linux の舞台に上がります。

シェルスクリプトが動かない理由:CR が単語にくっつく

ここは CR さんの言い分から。bash にとって CR は区切りではなく、ただの文字なんです。

bash のマニュアル(Definitions)では、単語を区切るメタ文字(シェルが特別扱いする記号)は、スペース・タブ・改行と | & ; ( ) < > だと定義されています。 ここに CR は入っていません。

つまり cd udon + CR + LF という行は、bash には「cd と udon + CR」の2語に見えるんです。 udon という名前のフォルダはあっても、udon + CR という名前のフォルダはありません。

CR を含む単語を、わざと作って確かめてみます。 $'\r' は、bash で CR を1文字書くための書き方です。

mkdir -p udon
printf '#!/bin/bash\r\ncd udon\r\necho "done"\r\n' > crlf.sh

cd udon$'\r'
$'ls\r'
name=$'udon\r'
if [ "$name" = "udon" ]; then echo same; else echo different; fi
echo "[$name]" | cat -A
h2.sh: line 4: cd: $'udon\r': No such file or directory
h2.sh: line 5: $'ls\r': command not found
different
[udon^M]$

(上のコードを h2.sh というファイルにして、Git Bash で実行した結果です)

  • cd udon + CR → そんなフォルダはない
  • ls + CR → そんなコマンドはない
  • 変数の中身に CR がつくと、"udon" と比べても一致しない

cat -A は見えない文字を記号で表示するオプションで、^M が CR、$ が行末です。 [udon^M] の ^M が、変数の中に紛れ込んだ CR さんです。

CR さん「僕も udon の一部です」

bash「じゃあ udon じゃないです」

……名前に1文字足しただけで、別人扱い。 うどんに天かすをのせたら、もうかけうどんとは呼ばない。それと同じ厳しさです。

では、CRLF のスクリプトをそのまま実行するとどうなるか。 今回は、Linux の bash として WSL(Windows で Linux を動かす仕組み)の bash 4.3.48 で試しました。

mkdir -p udon
printf '#!/bin/bash\r\ncd udon\r\necho "done"\r\n' > crlf.sh
bash crlf.sh
echo "exit=$?"

出力には CR がそのまま入っていて、端末では表示が崩れます。 そこで、CR を \r と書き直したものを載せます。

crlf.sh: line 2: cd: udon\r: No such file or directory
done\r
exit=0

cd は失敗しているのに、done が表示されて、終了コードは 0(成功)。 bash は、エラーが出ても次の行へ進むのが基本だからです。

bash「cd は失敗しました。でも done です!」

……何が done なのか、もう誰にもわからない。

失敗したのに成功扱い。どこかで聞いた話……

そう、pipefail の記事と同じ構図です。 しかも今回は、フォルダ移動に失敗したまま、次の処理が別のフォルダで動きます。 デプロイスクリプトなら、違う場所にファイルを置いたり消したりしかねない。笑えないやつです。

同じコマンドを Git Bash で動かしたら?

実は、今回の検証環境の Git Bash(bash 5.2.26)では、CRLF のスクリプトをそのまま実行しても、エラーにならずに動きました。 手元の Windows では動くのに、サーバーでだけ動かない。 これが「おうどんのパソコンでは動いたんですけど」問題の正体の1つです。

($'\r' のように CR をはっきり書いた場合は、Git Bash でも上の h2.sh のとおり別の文字として扱われます)

見えない CR を見つける

見えない相手は、見える道具で探すしかありません。たいていは file と cat -A の2つで見つかります。

printf '#!/bin/bash\r\ncd udon\r\necho "done"\r\n' > crlf.sh
printf '#!/bin/bash\ncd udon\necho "done"\n' > lf.sh

file crlf.sh lf.sh
cat -A crlf.sh
grep -c $'\r' crlf.sh
grep -U -c $'\r' crlf.sh
crlf.sh: Bourne-Again shell script, ASCII text executable, with CRLF line terminators
lf.sh:   Bourne-Again shell script, ASCII text executable
#!/bin/bash^M$
cd udon^M$
echo "done"^M$
0
3
  • file は with CRLF line terminators(行末が CRLF)と教えてくれる
  • cat -A は行末に ^M$ が並ぶ

注目は下の2行です。 今回の検証環境の Git Bash(GNU grep 3.0)では、grep -c $'\r' が 0 を返しました。 CR が3行ともあるのに、0。

-U を付けると 3 になりました。 (なぜ -U なしだと数えられないのかは、今回は確かめていません。Windows 向けの grep の読み方の違いだと思われます)

grep「CR さん? 見てないですね」

-U 付きの grep「3人いました」

……同じ人に聞いたのに、メガネをかけたら見えるようになった。 Windows の grep で CR を探すときは、-U を付けるか、file・cat -A を使うのが安心です。

じゃあ、エディタで見れば一発では?

エディタによっては、画面の下のほうに CRLF か LF かを表示してくれるものもあります。 ただ、100ファイルを1つずつ開いて確かめるのは、うどんを1本ずつ数えて茹でるようなものです。 リポジトリ全体なら、あとで出てくる git ls-files --eol が早いです。

1ファイルだけ急いで直す

今すぐ動かしたいなら、行末の CR を消すだけで直ります。

printf '#!/bin/bash\r\ncd udon\r\necho "done"\r\n' > crlf.sh
sed -i 's/\r$//' crlf.sh
file crlf.sh
cat -A crlf.sh
crlf.sh: Bourne-Again shell script, ASCII text executable
#!/bin/bash$
cd udon$
echo "done"$

with CRLF line terminators が消えて、^M もいなくなりました。 sed -i 's/\r$//' は「行末の CR を空文字に置き換える」という意味です(今回は Git Bash の GNU sed で確認しました)。

CR さん「え、僕だけ帰されるんですか」

LF さん「Linux の舞台は、僕のピンネタなんで」

……コンビ解散の瞬間を見てしまった。

ただし、これは応急処置です。 次に Windows の誰かがこのファイルを編集して、core.autocrlf true の環境でチェックアウトし直したら、CR さんはまた戻ってきます。 根本対策は、次の .gitattributes です。

毎回 sed すればよくない?

毎回 sed する運用は、毎回忘れる運用です。 未来のおうどんが、金曜の夜に $'\r' を見る未来まで見えます。

🔰 ここまで読めば今日から困らない

  • LF will be replaced by CRLF は、エラーではなく「次に書き出すとき改行を変えます」という予告
  • シェルスクリプトが $'\r': command not found や cd: ... No such file で落ちたら、行末の CR を疑う
  • file か cat -A で CR(^M)を探す。今すぐ直すなら sed -i 's/\r$//' ファイル名
  • 根本対策は、リポジトリに .gitattributes を置いて *.sh text eol=lf と書く

4行か。最後の1行だけコピペしておけばいいよね?

最後の1行だけだと、すでに CRLF でコミットされたファイルは直りません(中級編で説明します)。 改行ズは、ルールを書いただけでは解散してくれないんです。

じゃあ4行全部、付箋に書いておく。

付箋の改行コードは、たぶん気にしなくて大丈夫です。

ここから先は中級者向け。読み飛ばしてもOKです。

中級:.gitattributes でリポジトリごとに決める

人ごとの設定に任せず、リポジトリにルールを書いてしまうのが根本対策です。

.gitattributes は、ファイルの種類ごとに Git の扱いを決めるファイルで、リポジトリの一番上に置いてコミットします。 GitHub のドキュメントには、このファイルをコミットすると、リポジトリのすべての参加者について core.autocrlf の設定を上書きする、と書かれています。

わざと core.autocrlf true にした状態で、.gitattributes が勝つか試します。

git init -q demo && cd demo
git config core.autocrlf true
cat > .gitattributes <<'EOF'
* text=auto
*.sh text eol=lf
*.bat text eol=crlf
*.png binary
EOF
printf 'echo hi\n' > run.sh
printf '@echo off\r\necho hi\r\n' > run.bat
printf 'kitsune\n' > menu.txt
printf '\x89PNG\r\n\x1a\n\x00\x00' > logo.png
git add . && git commit -qm init
rm run.sh run.bat menu.txt logo.png
git checkout -- .
git ls-files --eol
warning: in the working copy of '.gitattributes', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'menu.txt', LF will be replaced by CRLF the next time Git touches it
i/lf    w/lf    attr/text=auto        	.gitattributes
i/-text w/-text attr/-text            	logo.png
i/lf    w/crlf  attr/text=auto        	menu.txt
i/lf    w/crlf  attr/text eol=crlf    	run.bat
i/lf    w/lf    attr/text eol=lf      	run.sh

ファイルを消して git checkout で書き出し直したあとの状態です。 (.gitattributes 自体は消していないので、w/lf のままです)

  • run.sh:core.autocrlf true なのに w/lf。eol=lf が勝った
  • run.bat:w/crlf。バッチファイルは Windows 向けなので CRLF で書き出す
  • menu.txt:text=auto なので、この環境の設定どおり w/crlf
  • logo.png:-text(テキスト扱いしない)。画像の中の 0x0D 0x0A を勝手に書き換えられたら、画像が壊れます
  • Git の中(i/)は、テキストファイルがすべて LF にそろった

gitattributes のドキュメントによると、binary は -diff -merge -text をまとめて指定する組み込みのマクロ属性です。

.gitattributes「この舞台は LF さんのピン、あっちの舞台はコンビで」

おうどん「香盤表(出演順と出演者を書いた表)だ……」

……芸人さんの出演表を、Git が持っている。 これで、誰のパソコンで checkout しても、シェルスクリプトの舞台に CR さんは上がりません。

*.sh だけじゃなくて、全部 eol=lf にすれば?

Windows のバッチファイル(.bat・.cmd)は、CRLF のほうが安全に動かせます。 全部を LF にすると、今度は Windows 側のコンビ芸が成立しなくなります。 舞台に合わせて人数を決めましょう。

中級:ファイル全体が差分になったとき

誰かのエディタが改行を CRLF で保存し直すと、1文字も変えていないのに、全行が差分になります。

git init -q demo && cd demo
git config core.autocrlf false
printf 'kitsune\ntanuki\ntsukimi\n' > menu.txt
git add menu.txt && git commit -qm init
printf 'kitsune\r\ntanuki\r\ntsukimi\r\n' > menu.txt
git diff --stat
echo "--- ignore-cr-at-eol"
git diff --ignore-cr-at-eol --stat
git ls-files --eol
 menu.txt | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)
--- ignore-cr-at-eol
i/lf    w/crlf  attr/                 	menu.txt

3行とも書き換わった扱いです。 レビューする人から見ると、全行に赤と緑。

git diff --ignore-cr-at-eol は、git-diff のドキュメントのとおり、行末の CR を無視して比べるオプションです。 付けると、差分は何も出ませんでした。 そして git ls-files --eol で i/lf w/crlf を見れば、原因は改行だと確定です。

レビュアー「この PR(プルリクエスト:変更を取り込んでもらう依頼)、全行変わってるけど何したの?」

おうどん「保存しただけです」

レビュアー「保存しただけで全行変わるわけ……」

おうどん「改行ズが、2人組になって戻ってきました」

……説明としては正しいのに、何ひとつ伝わっていない。

じゃあ、ずっと –ignore-cr-at-eol を付けて見ればいいのでは?

見なかったことにする作戦、冒頭で一度失敗しています。 CR さんは、見えないだけで、ずっとそこにいます。

--ignore-cr-at-eol はあくまで「見るとき」の話です。 コミットする前に、.gitattributes か改行の設定をそろえて、本当に変えた行だけが差分になる状態にしましょう。

中級:途中から .gitattributes を入れたら renormalize

すでに CRLF でコミットされたファイルがあるリポジトリでは、.gitattributes を置いただけでは中身がそろいません。

git init -q demo && cd demo
git config core.autocrlf false
printf 'kitsune\r\ntanuki\r\n' > menu.txt
printf 'echo hi\r\n' > run.sh
git add . && git commit -qm "crlf committed"

printf '* text=auto\n*.sh text eol=lf\n' > .gitattributes
git add .gitattributes && git commit -qm "add .gitattributes"
echo "--- before renormalize"
git ls-files --eol
git status --short
echo "--- after renormalize"
git add --renormalize .
git status --short
git ls-files --eol
warning: in the working copy of '.gitattributes', LF will be replaced by CRLF the next time Git touches it
--- before renormalize
i/lf    w/lf    attr/text=auto        	.gitattributes
i/crlf  w/crlf  attr/text=auto        	menu.txt
i/crlf  w/crlf  attr/text eol=lf      	run.sh
 M run.sh
--- after renormalize
M  menu.txt
M  run.sh
i/lf    w/lf    attr/text=auto        	.gitattributes
i/lf    w/crlf  attr/text=auto        	menu.txt
i/lf    w/crlf  attr/text eol=lf      	run.sh

.gitattributes を入れた直後、Git の中の menu.txt と run.sh は i/crlf のまま。 git add --renormalize . を実行すると、i/lf にそろいました。

gitattributes のドキュメントと GitHub のドキュメントが案内している手順は、だいたいこうです。

  1. 作業中の変更を先にコミットしておく(改行の変更と混ざらないように)
  2. .gitattributes を置いて git add --renormalize .
  3. git status で変わるファイルを確認する。変えたくないファイルが出たら、そのファイルの text 属性を外す
  4. 改行だけの変更として、単独でコミットする

注意点が1つ。 上の結果の w/crlf を見てください。renormalize が直すのは Git の中(インデックス)だけで、手元のファイルは CRLF のままです。 手元もそろえたいときは、コミットしたあとに、前の節と同じようにファイルを消して git checkout し直すと、属性どおりに書き出されます。

renormalize って、ノーマルに戻すってこと? 何がノーマル?

Git の中を LF にそろえる、が Git にとってのノーマルです。 改行ズにとっては、事務所の方針で急にピン活動を命じられた感じです。

1コミットで全ファイル変わったら、git blame(行ごとに最後に変えた人を表示する機能)が全部自分になる……

なります。 一晩で、リポジトリでいちばん働いた人になれます。中身は改行だけですが。

だからこそ、改行だけのコミットを単独で作っておくんです。 あとから「この行を書いたのは誰?」と調べるとき、そのコミットだけ飛ばして考えられます。

中級:CRLF 事故の切り分け手順

改行が怪しいと思ったら、次の順で見ていきます。

  1. エラーに $'...\r' や、表示が崩れたメッセージがないか見る(CR が混ざっているサイン)
  2. file ファイル名 か cat -A ファイル名 で CR を確かめる
  3. git ls-files --eol ファイル名 で、Git の中(i/)と手元(w/)のどちらが CRLF か見る
  4. git config --show-origin core.autocrlf で、どこの設定ファイルに何が書かれているか見る
  5. .gitattributes に対象の拡張子のルールがあるか見る。なければ足して renormalize する

手順0は「とりあえずエディタで保存し直す」ですよね?

そのエディタが CRLF で保存する設定だったら、CR さんが増員されて戻ってきます。 保存ボタンは、改行ズの呼び込みボタンにもなるんです。

おうどん「わかった、全ファイルを一括で LF にするスクリプトを書く!」

……そのスクリプトが、画像ファイルの中の 0x0D も消します。 バイナリファイルを除外しない一括置換は、うどんと一緒にお箸まで茹でるようなものです。 .gitattributes の binary は Git 向けの指定なので、自作の置換スクリプトが自動で守ってくれるわけではありません。 一括でやるなら、.gitattributes と renormalize に任せましょう。

ここから先は上級者向け。読み飛ばしてもOKです。

上級:core.autocrlf と core.eol の関係

core.autocrlf の正体は、すべてのファイルに text=auto を付けて、作業ツリーの改行を CRLF にする設定の近道です。

Git の設定ドキュメント(core.autocrlf・core.eol)には、こう書かれています。

  • core.autocrlf=true は、すべてのファイルに text 属性を auto で付け、core.eol を crlf にするのと同じ
  • core.autocrlf=input は、出力側(チェックアウト時)の変換をしない
  • core.eol は、テキストと判定されたファイルの作業ツリーでの改行。lf・crlf・native(そのプラットフォーム本来の改行)を選べて、既定は native
  • core.autocrlf が true か input のときは、core.eol は無視される

ここで気になるのが native です。 core.autocrlf false にして、.gitattributes の * text=auto だけを置いたらどうなるか。

git init -q demo && cd demo
git config core.autocrlf false
printf '* text=auto\n' > .gitattributes
printf 'kitsune\n' > menu.txt
git add . && git commit -qm init
rm menu.txt && git checkout -- menu.txt
git ls-files --eol menu.txt
echo "--- core.eol=lf"
git config core.eol lf
rm menu.txt && git checkout -- menu.txt
git ls-files --eol menu.txt
warning: in the working copy of '.gitattributes', LF will be replaced by CRLF the next time Git touches it
warning: in the working copy of 'menu.txt', LF will be replaced by CRLF the next time Git touches it
i/lf    w/crlf  attr/text=auto        	menu.txt
--- core.eol=lf
i/lf    w/lf    attr/text=auto        	menu.txt

core.autocrlf false なのに、今回の検証環境(Git for Windows)では w/crlf で書き出されました。 core.eol の既定値 native が、Windows では CRLF として働いた結果です。 core.eol lf にすると w/lf になりました。

autocrlf を false にしたのに CRLF になるの、詐欺では?

* text=auto で「テキストは変換していい」と許可したのは、リポジトリのほうです。 そして、変換先の改行を決めるのが core.eol。 autocrlf を false にしても、別の係が仕事をしていたわけです。

autocrlf「僕は休みました」

core.eol「代わりに出勤しました。native で」

……シフトの引き継ぎだけは完璧。

つまり、Windows でも作業ツリーを LF にそろえたいなら、core.autocrlf ではなく .gitattributes の eol=lf(または core.eol lf)で決めることになります。

input も試しておきます。

git init -q demo && cd demo
git config core.autocrlf input
printf 'kitsune\r\ntanuki\r\n' > menu.txt
git add menu.txt
git ls-files --eol
warning: in the working copy of 'menu.txt', CRLF will be replaced by LF the next time Git touches it
i/lf    w/crlf  attr/                 	menu.txt

add のときに Git の中は LF にし、手元の CRLF には触りません。 Pro Git が Linux・macOS 向けに input をすすめているのは、この「入れるときだけ直す」動きのためです。

上級:text と text=auto の違いは「すでに CRLF のファイル」に出る

さっきの renormalize の結果、よく見ると差がありました。

.gitattributes を入れた直後の git status --short では、run.sh だけが M(変更あり)で、menu.txt は出ていません。

  • run.sh は text eol=lf(text を明示)
  • menu.txt は text=auto

gitattributes のドキュメントによると、

  • text を設定したファイルは、以前 CRLF で Git に入っていても、チェックインのたびに LF に正規化される
  • text=auto では、テキストと判定されても、すでに CRLF で Git に入っていたファイルは変換しない

つまり、text を明示した run.sh は「次に add したら LF になる」ので、今の CRLF の中身と食い違って変更扱い。 text=auto の menu.txt は「すでに CRLF で入っているので触らない」ので、変更なし。

なんで auto だけ遠慮するの?

text=auto は「Git が判断していいよ」という委任です。 すでに CRLF で入っているファイルを勝手に変えると、ある日突然、全行の差分が生まれます。 だから委任された範囲では、今あるものは動かさない。

text=auto「前からいた CR さんは、そのまま置いておきます」

text「前から? 関係ないです。規則なので」

……同じ部署なのに、仕事の進め方が真逆。 だからこそ、途中から導入したら renormalize で一度そろえる、が必要になるわけです。

上級:safecrlf と「元に戻せない変換」

Git には、変換したら元に戻らないファイルを止める設定もあります。

git init -q demo && cd demo
git config core.autocrlf true
git config core.safecrlf true
printf 'kitsune\ntanuki\n' > menu.txt
printf 'kitsune\r\ntanuki\n' > mixed.txt
git add menu.txt; echo "exit=$?"
git add mixed.txt; echo "exit=$?"
fatal: LF would be replaced by CRLF in menu.txt
exit=128
fatal: LF would be replaced by CRLF in mixed.txt
exit=128

Git の設定ドキュメント(core.safecrlf)には、true にすると、改行の変換が元に戻せるかを確かめ、コミットしてからチェックアウトしたときに元のファイルに戻らないなら拒否する、と書かれています。 warn にすると、警告だけ出して続行します。

core.autocrlf true の環境で LF のファイルを add すると、次のチェックアウトで CRLF になって帰ってきます。 元の LF には戻らないので、拒否。

CRLF と LF が混ざった mixed.txt も同じです。 入れるときに LF にそろい、出すときは全部 CRLF になるので、「混ざっていた」という元の状態には戻りません。

safecrlf「このファイル、預かったら元の形で返せません。お断りします」

おうどん「クリーニング屋さんだ……」

……しかも、シワを伸ばして返すのが嫌いなタイプのクリーニング屋さん。

safecrlf を設定していない冒頭の検証でも、同じ変換で警告だけが出ていました。既定では warn に近い動きをしているようです(既定値そのものは、今回ドキュメントでは確認していません)。 既存のリポジトリで true にすると、今回のように普通の LF ファイルの add まで止まることがあるので、入れるなら .gitattributes の整備とセットにしましょう。

全部 true にしておけば安全なのでは?

安全ですが、たぶん1日中 add が止まります。 安全すぎて、うどんが1杯も出てこないお店です。

チェックリスト

  • リポジトリの一番上に .gitattributes を置いて、* text=auto を書いた
  • シェルスクリプトは *.sh text eol=lf、バッチファイルは *.bat text eol=crlf にした
  • 画像などのバイナリは binary を指定した
  • 途中から入れたときは、作業中の変更をコミットしてから git add --renormalize . し、改行だけのコミットを単独で作った
  • renormalize のあと、手元のファイルも checkout し直してそろえた
  • git ls-files --eol で、i/ がテキストはすべて lf になっているのを確かめた
  • サーバーで動かすスクリプトは、デプロイ前に file で CRLF になっていないか確かめた

7個。改行ズより多い。

1個目と2個目だけでも、今日の $'\r' は防げます。 残りは、週明けのおうどんがやります。

……週明けのおうどん「聞いてない」

まとめ

冒頭では、LF will be replaced by CRLF を見なかったことにして、数日後に $'\r': command not found を浴びていました。

あの warning は、ちゃんと予告してくれていたんです。 「次に触ったとき、改行ズを2人組にしますよ」と。

悪いのは CR さんでもありません。 Windows の舞台では、ちゃんと必要な相方です。 問題は、どの舞台に何人で上がるかを、人ごとのパソコンに任せていたことでした。

CR さん、いい人だったんだ……

いい人です。 ただ、Linux の舞台で空気が読めないだけです。

改行コードは、人の設定ではなくリポジトリの .gitattributes で決めましょう。

おうどん「よし、今夜は全リポジトリに .gitattributes を入れて回る!」

……それ、明日の朝に全リポジトリで全行の差分が出るやつです。 1つずつ、renormalize のコミットを分けながら。

まずは、いま触っているリポジトリで git ls-files --eol を1回だけ打ってみてください。 サーバーで動かすスクリプトが i/lf w/lf になっているのを確かめたら、今夜は安心してきつねうどんです🍜

参考資料

確認日:2026-09-27。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

おうどん|癒しと創作をたのしむ雑食クリエイター
写経アプリやクレイセラピー、優しい和風デザインがすき。
ZARDと刀剣と文字に癒されて、今はアプリ作ってます。

コメント

コメントする

目次