開発ツール

git管理から外す:.gitignoreとgit rm --cachedの使い分け

読了 6分
開発ツール

git管理から外す:.gitignoreとgit rm --cachedの使い分け

.gitignore に書いたのに、なぜか変更が差分に出続ける」。これでしばらく困ったので書いておきます。Gitを使っているとほぼ必ず一度はハマる現象みたいです。

先に結論を書いておくと、原因は .gitignore の対象範囲にあります。仕組みが分かれば、もう迷わなくなりました。

.gitignoreは「未追跡ファイル」にしか効かない

Gitはファイルを大きく 追跡済み(tracked)未追跡(untracked) に分けて扱います。調べてみると、.gitignore が無視できるのは後者だけのようです。

つまり、一度 git add してコミットしてしまったファイルは、あとから .gitignore に書いても無視されません。Gitはすでに「このファイルを管理対象として知っている」状態だからです。

.envnode_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 statusUntracked files で確認できます。意図せず追跡しそうなファイルがないか、コミット前に一度見ておくと安心です。

まとめ

  • .gitignore は未追跡ファイルにしか効かない。追跡済みには無力
  • すでにコミットしたファイルは git rm --cached で追跡解除してから無視させる
  • --cached なら実ファイルは残る。付け忘れると削除されるので注意
  • 迷ったら git check-ignore -v でどのルールが効いているか確認する

なお、git rm --cached で外しても、過去のコミット履歴にはファイルが残ります。秘密情報を誤ってコミットした場合は、履歴の書き換えや鍵の失効も検討したほうがよさそうです(ここはちょっと怖い話です)。各パターンの詳細はGit公式のgitignoreドキュメントにまとまっています。これで差分の幽霊に悩まされなくなりました。以上です。