guide

gitignore の優先順位と否定(!)パターン

最後のルールが勝つ仕組み、.gitignore・info/exclude・グローバル設定の間の優先順位、親ディレクトリが除外されると ! で戻せない理由と解決法を説明します。

gitignore のルールが期待どおりに動作しないとき、原因のほとんどは順序親ディレクトリにある。この記事では、git が複数のルールのうちどれに従うのかを整理する。

同じファイル内: 最後に一致したルールが勝つ

1 つの .gitignore の中では上から下へ読み進め、最後に一致したパターンが結果を決める。そのパターンが ! で始まっていれば無視せず、そうでなければ無視する。

*.log
!important.log

important.log は両方の行に一致するが、最後の行が否定なので追跡される。順序を逆にすると結果も逆になる。

!important.log
*.log

今度は important.log の最後の一致が *.log なので無視される。ジェネレーターで複数のテンプレートを結合するとき、選択の順序がそのままルールの順序になるのはこのためである。たとえば Java テンプレートの *.jar と Gradle テンプレートの !gradle-wrapper.jar を一緒に使うときは、Gradle が後ろに来なければならない。

複数ファイル間の優先順位

git は次の 4 か所からルールを読む。上にあるほど優先順位が高い。

  1. コマンドラインで受け取ったパターン(git ls-files --exclude など一部のコマンド)
  2. パスと同じディレクトリまたは親ディレクトリの .gitignore — より深い場所のファイルが親のファイルを上書きする
  3. $GIT_DIR/info/exclude (通常は .git/info/exclude)
  4. core.excludesFile 設定が指すファイル(グローバル gitignore)

たとえばルートの .gitignore*.tmp があり、sub/.gitignore!*.tmp があれば、sub/y.tmp は下位ファイルの否定ルールによって追跡される。逆に .git/info/exclude!x と書いても .gitignorex が優先されるため、x は引き続き無視される。

$ git check-ignore -v x y.tmp sub/y.tmp
.gitignore:1:x	x
.gitignore:2:*.tmp	y.tmp
sub/.gitignore:1:!*.tmp	sub/y.tmp

最も重要な例外: 親ディレクトリが除外されると戻せない

git のドキュメントにはこう書かれている。ファイルの親ディレクトリが除外されていると、そのファイルを再び含めることはできない。 パフォーマンスのため、git は除外されたディレクトリの中をまったく見ないので、その中のファイルに対する ! ルールは読まれすらしない。

logs/
!logs/keep.txt

このルールでは logs/keep.txt は引き続き無視される。git check-ignore -v を実行すると、決定したルールとして logs/ が表示される。

解決法は、ディレクトリではなくディレクトリの中身を除外することである。

logs/*
!logs/keep.txt

logs/*logs ディレクトリ自体には一致しないため、git が中に入って keep.txt に否定ルールを適用する。Rails テンプレートの /log/* + !/log/.keep、VS Code テンプレートの .vscode/* + !.vscode/settings.json は、いずれもこの原理を使っている。

より深いパスを戻すには、途中のディレクトリも 1 つずつ戻す必要がある。

config/*
!config/app/
config/app/*
!config/app/defaults.yml

許可リスト(allowlist)パターン

無視するものを列挙する代わりに、追跡するものだけを列挙したい場合も同じ原理を使う。

# ルートのすべてを無視して
/*
# 必要なものだけを再び含める
!/.gitignore
!/src/
!/README.md

/* はルートの各項目に一致するだけで、ルート自体は除外しないため !/src/ が機能する。この方式では .gitignore 自身も /* に一致するので、!/.gitignore を忘れてはならない。実際、この行なしで git status --ignored を実行すると、.gitignore が無視リストに現れる。

重複ルールと順序

同じルールが複数回現れる場合、前のものは結果に影響しない。後ろの同じルールが常により後で一致するためである。一方、後ろの重複を削除すると、間に否定ルールがある場合は結果が変わることがある。

*.log
!debug.log
*.log

ここで最後の *.log を削除すると、debug.log が追跡対象に変わる。このサイトのジェネレーターはこうしたケースを検出し、間に否定ルールがない場合にのみ後ろの重複を削除し、それ以外は残しておく。

参考

← 前へ.gitignore パターン構文まとめ