1. 複雑なリポジトリで直面する現場の課題
以前、私が担当した会社のモノレポプロジェクトでは、15以上のマイクロサービスと何十ものネストされたディレクトリが混在し、規模が肥大化していました。その結果、ファイルの管理はまさに悪夢のような状態でした。ある日、ジュニアエンジニアが .env.staging ファイルを誤ってコミットに含め、そのままリモートリポジトリにプッシュしてしまうというトラブルが発生しました。チーム全員が、このファイルは当然除外設定(ignore)されていると思い込んでいたのです。
また別の機会には、packages/ui/assets/icons/logo.svg に新規追加したアイコンファイルが git status を実行しても表示されない現象が起きました。チーム全体で原因が分からず頭を抱え、締め切りに追われていたメンバーは手っ取り早い解決策として git add -f で強制的にGitに認識させる道を選んでしまいました。
その場しのぎの対応のツケは、後から大きなトラブルとなって返ってきました。リポジトリの容量は急増し、プルリクエストごとにWindowsとmacOS間での改行コード(CRLF/LF)の不整合・コンフリクトが頻発する事態に陥ったのです。ignoreやattributesの設定ミスは、目視だけでは発見が極めて難しい落とし穴です。
2. なぜGitは期待通りにファイルを除外してくれないのか?
Gitはルートディレクトリにある1つの .gitignore ファイルだけを読み込んでいるわけではありません。あるファイルを除外するか、あるいは属性を付与するかを判定する際、Gitは複数の階層と優先順位を走査します。
- ローカルの
.gitignoreファイル: 各サブディレクトリに散らばるファイルは、親ディレクトリのルールを上書きします。 - リポジトリ固有の設定ファイル:
.git/info/excludeファイルは個人のローカル環境でのみ有効で、リモートにはコミットされません。 - グローバル設定ファイル:
~/.gitconfig内のcore.excludesFile設定で定義されるグローバルな除外ファイルです。 - 否定ルール(Negation
!): 親ディレクトリ全体が除外されている場合(例:dist/)、Gitはそのディレクトリ配下の走査を完全にスキップします。そのため、内部で!dist/bundle.jsのような否定ルールを記述しても一切効果がありません。
.gitattributes の仕組みも同様です。改行コード変換設定(eol=lf)、Git LFSのフィルター、diffドライバーなどはすべてディレクトリ階層に応じて上書きされます。リポジトリ内に何十もの設定ファイルがネストしている場合、手作業で調査するのは事実上不可能です。
3. よくある間違った対処法
方法1: Ctrl + F や grep での手動検索
ファイルがGitに認識されないとき、開発者はルートの .gitignore を開いて Ctrl + F で検索しがちです。見つからなければ、さらに各サブディレクトリの設定ファイルを1つずつ開いて確認していくことになります。
デメリット: 時間がかかる上に、**/*.log や build/*/temp といった複雑なglobパターンを見落としやすくなります。また、各自のPC上のグローバル設定から適用されているルールには一切気づくことができません。
方法2: git add -f による場当たり的な対応
急いでコミットしたいのにGitがファイルを無視している場合、つい手軽に以下を実行してしまう人がいます。
git add -f src/services/mailer/templates/welcome.html
デメリット: 強制追加コマンドはその場しのぎに過ぎません。設定ファイルの誤ったルールはそのまま残るため、他のメンバーがコードをプルして同じファイルを編集した際には再びignoreされてしまいます。さらに危険なのは、この習慣によって機密情報や認証情報(シークレット)を含むファイルが誤ってリポジトリに流出しやすくなる点です。
方法3: git check-ignore と git check-attr の活用
Gitには専用のコマンドとして git check-ignore と git check-attr が標準で用意されています。これらはデバッグツールとして機能し、どの設定ファイルのどの行が対象ファイルに影響を与えているのかをピンポイントで教えてくれます。
4. 実務で役立つ確実なデバッグ手順
私のチームのトラブルシューティング手順にこの2つのコマンドを取り入れて以来、ignoreや改行コードに関するあらゆる疑問は数秒で解決できるようになりました。
git check-ignore で除外ルールをデバッグする
最も効果的なのは -v(verbose)フラグを付ける方法です。Gitは 設定ファイル名 : 該当の行番号 : マッチしたパターン を詳細に出力してくれます。
# local.json がどの行によって ignore されているか確認する
git check-ignore -v config/environments/local.json
ターミナルに出力される結果:
.gitignore:14:local.* config/environments/local.json
ルートの .gitignore の14行目にある local.* ルールが原因であることが一目瞭然です。
複数のファイルを同時に確認したい場合は、--non-matching(または -n)フラグを追加することで、ignoreされていないファイルも含めて確認できます。
# ファイルリストを一括確認する
git check-ignore -v -n src/index.ts .env.local docs/setup.pdf
出力例:
:: src/index.ts
.gitignore:3:.env* .env.local
packages/docs/.gitignore:2:*.pdf docs/setup.pdf
先頭行の :: という記号は、src/index.ts がどのignoreルールにもマッチしておらず、正常に追跡対象になっていることを示しています。
よくある否定パターン(!)の罠と解決法
よくあるシナリオとして、logs/ ディレクトリ全体は除外したいが、logs/important.log だけは保持したいというケースがあります。
# .gitignore の誤った設定例:
logs/
!logs/important.log
この設定では、git status を実行しても important.log は表示されません。git check-ignore で確認してみましょう。
git check-ignore -v logs/important.log
# 出力結果: .gitignore:1:logs/ logs/important.log
正しい修正方法: logs/ ディレクトリ自体を除外するのではなく、ディレクトリ内のコンテンツのみを除外することで、Gitがディレクトリ内を走査できるようにします。
# .gitignore の正しい設定例:
logs/*
!logs/important.log
git check-attr でファイル属性を確認する
クロスプラットフォーム開発環境やGit LFSを使用している環境では、.gitattributes がテキスト/バイナリの判別やCRLF/LFの変換ルールを制御します。
ファイルに適用されているすべての属性を一覧表示するには、以下を実行します。
git check-attr -a src/scripts/deploy.sh
実行結果の表示例:
src/scripts/deploy.sh: text: set
src/scripts/deploy.sh: eol: lf
src/scripts/deploy.sh: diff: unspecified
例えば、50MBのPhotoshopデザインファイルにGit LFSの設定が正しく適用されているか確認したい場合:
git check-attr filter diff merge assets/banner.psd
ターミナルに filter: lfs と返ってくれば、正常に設定されています。もし unspecified と表示される場合は、.gitattributes に *.psd に対するルールが不足しています。
覚えておくべきコマンドチートシート
git check-ignore -v <path>: 対象をignoreしている設定ファイル名と行番号を特定する。git check-ignore -v -n <paths...>: 複数ファイルのignore状態をまとめて確認する。git check-attr -a <path>: ファイルに割り当てられているEOL、diff、LFSなどの全属性を表示する。
ファイルが表示されない、あるいは改行コードがおかしいと感じたとき、安易に git add -f を使うのは避けましょう。上記2つのコマンドを実行すれば、根本原因を素早く突き止め、適切に対処できます。

