git管理から外す:.gitignoreとgit rm --cachedの使い分け
git管理から外す:.gitignoreとgit rm --cachedの使い分け
「.gitignore に書いたのに、なぜか変更が差分に出続ける」。これでしばらく困ったので書いておきます。Gitを使っているとほぼ必ず一度はハマる現象みたいです。
先に結論を書いておくと、原因は .gitignore の対象範囲にあります。仕組みが分かれば、もう迷わなくなりました。
.gitignoreは「未追跡ファイル」にしか効かない
Gitはファイルを大きく 追跡済み(tracked) と 未追跡(untracked) に分けて扱います。調べてみると、.gitignore が無視できるのは後者だけのようです。
つまり、一度 git add してコミットしてしまったファイルは、あとから .gitignore に書いても無視されません。Gitはすでに「このファイルを管理対象として知っている」状態だからです。
.env や node_modules/ を最初に書き忘れてコミットしてしまい、後から .gitignore に追記した、という流れでこの問題が起きます。まさに自分がこれでした。
まずは追跡を解除する
解決には、いったん追跡対象から外す必要があります。ここで使うのが git rm --cached です。--cached を付けると ワーキングツリーのファイルは残したまま、Gitの管理からだけ外せます。
# 1ファイルを管理から外す(ファイル自体は消えない)
git rm --cached .env
# ディレクトリごと外すときは -r
git rm -r --cached node_modules
ここで注意が必要です。--cached を付け忘れて git rm .env とすると、実ファイルまで削除されてしまいます。--cached は「インデックス(ステージ)からだけ取り除く」という意味です。.env を消し飛ばすと泣くので、ここは気をつけてください。
その後 .gitignore に対象を書いて、コミットします。
git rm -r --cached node_modules
echo "node_modules/" >> .gitignore
git add .gitignore
git commit -m "node_modulesをGit管理から除外"
これ以降、node_modules/ は未追跡=無視対象になって、差分に現れなくなります。
.gitignoreの書き方の基本
パターンの書き方で挙動が変わります。代表的なものを押さえておくとよさそうです。
| 記述 | 意味 |
|---|---|
build/ | build ディレクトリ以下すべて |
*.log | 拡張子 .log のファイル |
/secret.txt | リポジトリ直下の secret.txt のみ |
!keep.log | 除外の中から例外的に追跡する |
**/tmp | 任意の階層の tmp |
末尾に / を付けるとディレクトリ限定、先頭に / を付けるとルート限定です。! による例外指定は、親ディレクトリ自体が無視されていると効かないので、ここは注意してください。自分はこれで一度ハマりました。
無視されているか確認する
「このファイルがどのルールで無視されているのか」は git check-ignore -v で確かめられます。原因のルールと行番号まで教えてくれます。
$ git check-ignore -v dist/app.js
.gitignore:3:dist/ dist/app.js
逆に「未追跡だが無視されていないファイル」は git status の Untracked files で確認できます。意図せず追跡しそうなファイルがないか、コミット前に一度見ておくと安心です。
まとめ
.gitignoreは未追跡ファイルにしか効かない。追跡済みには無力- すでにコミットしたファイルは
git rm --cachedで追跡解除してから無視させる --cachedなら実ファイルは残る。付け忘れると削除されるので注意- 迷ったら
git check-ignore -vでどのルールが効いているか確認する
なお、git rm --cached で外しても、過去のコミット履歴にはファイルが残ります。秘密情報を誤ってコミットした場合は、履歴の書き換えや鍵の失効も検討したほうがよさそうです(ここはちょっと怖い話です)。各パターンの詳細はGit公式のgitignoreドキュメントにまとまっています。これで差分の幽霊に悩まされなくなりました。以上です。