動画で読む
まず結論 — Git の「無視」は 5 つの層に分かれている
最初に結論を書きます。ファイルを Git の管理から外す方法は .gitignore だけではありません。「まだ追跡していないファイルを無視する」のか「すでに追跡しているファイルの変更を無視する」のかで道具がまるごと変わり、前者には .gitignore / .git/info/exclude / グローバル ignore の 3 層、後者には git rm --cached / assume-unchanged / skip-worktree があります。元ネタは nelson.cloud の記事 で、私はこれを worktree ベースのソロ開発の運用目線で読み直しました。
.gitignore は便利ですが、万能ではありません。チームで共有したくない自分専用のメモ、マシン固有の .DS_Store、コミットしたくないローカル設定。これらを全部 .gitignore に押し込むと、リポジトリの ignore ルールが「自分の事情」で汚れていきます。Git は無視の対象と範囲ごとに別の置き場所を用意していて、それを知っているかどうかで、共有ファイルの綺麗さが変わります。順番に棚卸しします。
.git/info/exclude — 自分のマシンだけで効くローカル無視
結論から。「このリポジトリの、自分の環境でだけ無視したい」ファイルは、.gitignore ではなく .git/info/exclude に書きます。このファイルは各リポジトリの .git ディレクトリの中にあり、その内容自体は Git の管理対象にならない(コミットされない)からです。
書き方は .gitignore と同じで、無視したいパターンを 1 行ずつ並べるだけです。
# .git/info/exclude に追記する(このファイルはコミットされない)
echo "notes.txt" >> .git/info/exclude
echo "scratch/" >> .git/info/exclude
私の使い方はこうです。リポジトリ直下に notes.txt を置いて作業中の思考メモを書き散らすのですが、これをチームの .gitignore に足すと「なぜ他人のメモ用ファイルがうちの ignore に?」という話になる。だから自分の手元だけで黙らせたいものは .git/info/exclude に入れます。Issue 駆動で復旧手順を仕組みに組み込む話を書いたときの下書きメモも、最初はこの exclude で隠していました。
注意は 1 点だけ。.git ディレクトリの中にあるので、リポジトリを clone し直すとこの設定は消えます。共有されないのが利点であり、引き継がれないのが弱点。この非対称を分かって使えば事故りません。
グローバル ignore — マシン全体で一律に無視する
ここも結論を先に。OS が勝手に作る .DS_Store や Thumbs.db、エディタの作業ファイルのように「どのリポジトリでも一律に無視したい」ものは、グローバル ignore に書きます。git config で 1 度設定すれば、そのマシンの全リポジトリに効きます。
設定はこうです。
# グローバル ignore ファイルの場所を指定する
git config --global core.excludesFile ~/.gitignore_global
# あとは中身を書くだけ
echo ".DS_Store" >> ~/.gitignore_global
echo "*.swp" >> ~/.gitignore_global
core.excludesFile を明示しない場合でも、Git は ~/.config/git/ignore を既定のグローバル ignore として読みます。だから設定コマンドを打たずに、このパスへ直接書く手もあります。私は Mac の .DS_Store をここで一括して黙らせていて、おかげで個々のプロジェクトの .gitignore に .DS_Store を書く必要が無くなりました。.DS_Store を 10 個のリポジトリの .gitignore に律儀にコピペしていた過去の自分に、これを教えてやりたい。
外したくなったら unset します。
git config --global --unset core.excludesFile
すでに追跡しているファイルは ignore では消えない
大事な落とし穴を先に。.gitignore も exclude もグローバル ignore も、効くのは「まだ追跡していないファイル」だけです。一度 git add してコミットしてしまったファイルは、後から ignore に書いても追跡され続けます。ここを知らないと「ignore に書いたのに差分に出続ける」で時間を溶かします。
すでに追跡済みのファイルを追跡から外すには、インデックス(Git が追跡しているファイル一覧)から抜きます。
# ディスク上のファイルは残し、Git の追跡だけを外す
git rm --cached config/secret.yml
--cached を付けると、ワーキングツリーのファイルはそのままで、インデックスからだけ削除されます。これをコミットすれば、次から ignore が効くようになります。--cached を付け忘れると、ディスクのファイルごと消えます。だからここは、指が覚えるまで毎回少し緊張する所です。
assume-unchanged と skip-worktree — 似て非なる二つ
結論から言うと、この二つは名前が紛らわしいだけで目的がまったく違います。assume-unchanged は「変更チェックを省いて速くする」性能ヒント、skip-worktree は「追跡対象のローカル変更を保持してコミットさせない」運用フラグです。混同すると、私のように痛い目に遭います。
# 性能ヒント: 大きくて変わらない前提のファイルの stat を省く
git update-index --assume-unchanged path/to/bigfile
# ローカル変更を保持: 追跡中の設定ファイルを自分用に書き換えたまま隠す
git update-index --skip-worktree config/database.yml
失敗談を 1 つ。追跡済みの config/database.yml をローカルだけ書き換えて使いたくて、当時の私は assume-unchanged を立てました。しばらくは平和でしたが、別ブランチを checkout した拍子に Git が平然と私の変更を上書きし、ローカルの接続先設定が吹き飛びました。assume-unchanged はあくまで「変わっていないと仮定して速くする」だけで、変更を守る約束はしていません。ローカル変更を保持したいなら skip-worktree が正解でした。名前から受ける印象と挙動が半歩ずれていて、原因に気付くまで 30 分ほど首をかしげました。git に「黙っていろ」と頼んだつもりが、git は黙って私の上書きを進めていた、というオチです。
どれが効いているか分からなくなったら check-ignore
最後は診断です。あるファイルが無視される理由が分からなくなったら、git check-ignore -v を使うと、どの ignore ファイルの何行目のパターンが効いているのかを教えてくれます。
$ git check-ignore -v build/output.log
.gitignore:12:build/ build/output.log
出力は左から、効いているファイル名・行番号・パターン・対象ファイルの順です。.gitignore なのか .git/info/exclude なのかグローバル ignore なのかが一目で分かるので、「なぜか無視される / なぜか無視されない」の犯人探しが一瞬で終わります。再現性を気にして古い道具でビルドし直す話を書いたときも、最終的に頼ったのはこういう「いまの状態を問い合わせるコマンド」でした。推測ではなく問い合わせる。これは ignore に限らず Git 全般の鉄則です。
運用者としての使い分け
棚卸しすると、選択は「対象」と「範囲」の二軸で決まります。共有したいなら .gitignore、自分のリポジトリだけなら .git/info/exclude、マシン全体なら グローバル ignore。そして相手が「追跡済み」なら、追跡をやめる git rm --cached、性能のための assume-unchanged、ローカル変更を守る skip-worktree。
| やりたいこと | 道具 | 範囲 |
|---|---|---|
| まだ追跡していないファイルを無視 | .gitignore | リポジトリで共有 |
| 自分の環境だけで無視 | .git/info/exclude | このリポジトリ・ローカル |
| マシン全体で一律に無視 | グローバル ignore (core.excludesFile) | マシン全体 |
| 追跡済みファイルの追跡をやめる | git rm --cached | リポジトリで共有 |
| 変更チェックを省いて速くする | git update-index --assume-unchanged | ローカル |
| 追跡中ファイルのローカル変更を保持 | git update-index --skip-worktree | ローカル |
私はソロ開発でも、この区別を意識してから .gitignore の差分が静かになりました。共有すべきルールだけが .gitignore に残り、自分の事情は手元に隠れる。24/7 で自動化を回す運用でも、ローカル設定の取り違えは地味に事故の温床になるので、最初に道具を揃えておく価値はあります。無視は 1 種類ではなく、対象と範囲で選ぶもの。それだけ覚えて帰ってもらえれば十分です。
よくある質問
- .gitignore と .git/info/exclude はどう使い分けますか?
- チームで共有したい無視ルールは .gitignore に、自分の環境だけで無視したいファイルは .git/info/exclude に書きます。後者は .git ディレクトリ内にありコミットされないため共有されず、clone し直すと消えます。
- ignore に書いたのに差分に出続けるのはなぜですか?
- ignore が効くのは「まだ追跡していないファイル」だけだからです。一度 git add してコミットしたファイルは ignore に書いても追跡され続けます。git rm --cached でインデックスから外し、それをコミットすれば次から ignore が効きます。
- assume-unchanged と skip-worktree はどちらを使うべきですか?
- 追跡中ファイルのローカル変更を保持したいなら skip-worktree です。assume-unchanged は変更チェックを省く性能ヒントにすぎず、変更を守る保証はなく、checkout などで上書きされることがあります。
- あるファイルがなぜ無視されるのか調べるには?
- git check-ignore -v <ファイル> を実行すると、効いている ignore ファイル名・行番号・パターン・対象ファイルが表示されます。.gitignore か .git/info/exclude かグローバル ignore かを一目で特定できます。