グローバル gitignore がディレクトリを巻き込む
~/.gitignore(core.excludesfile)に書いたパターンは、すべてのリポジトリに効く。ここに置きがちな tags — ctags が吐くタグファイルを無視するための1行 — が、tags という名前のディレクトリにもマッチする。gitignore のパターンは、スラッシュを含まない限りファイルとディレクトリを区別しないため。
実際にこれで src/pages/tags/ 配下が丸ごとコミットから漏れた。
手元では絶対に気づけない
厄介なのは、この状態がローカルでは完全に正常に見えること。
- ファイルは存在するので、ビルドもテストもローカルサーバーも通る
- 無視されているので
git statusに現れない git add -Aを実行しても追加されない- コミットのファイル一覧を眺めても、「無い」ことには気づきにくい
壊れるのは、クリーンなチェックアウトからビルドする場所だけ。CI のビルドログでページ数が減っていたことでようやく気づいた(手元 18、CI 6)。
原因の特定
git check-ignore -v は、どのファイルのどの行が効いたかまで教えてくれる。
$ git check-ignore -v src/pages/tags/index.astro
/Users/kfly8/.gitignore:19:tags src/pages/tags/index.astro
「なぜか add されない」ときは、まずこれを叩けばよい。
打ち消す
リポジトリ側の .gitignore で否定する。
!src/pages/tags/
ここで gitignore の仕様に注意が要る。親ディレクトリが除外されている場合、その中のファイルを再包含することはできない。 git は除外されたディレクトリを走査しないので、中のファイルにパターンを当てる機会がそもそもない。だから否定するのは個々のファイルではなく、巻き込まれたディレクトリそのものでなければならない。
git add -f で強制追加もできるが、それだと同じ罠が次のファイルで再発する。ディレクトリごと打ち消しておく方がよい。
教訓
- グローバルの gitignore に短い一般名詞を書くと、いつか何かのディレクトリ名と衝突する(
tags,build,dist,tmp,logあたりは要注意) - 新しいディレクトリを作ったら、コミット前に
git statusに出ているかを確認する - CI が「手元と同じ成果物」を作っていることを、ページ数やファイル数のような粗い数字でよいので照合する。ログに
18 page(s) builtと出ているなら、それは読む価値のある数字
理解度チェック
git add しても追加されないファイルがある。原因の gitignore とその行を特定するには。
git add しても追加されないファイルがある。原因の gitignore とその行を特定するには。git check-ignore -v <path>。効いたパターンのファイル名・行番号・パターン本体が出る。
除外されたディレクトリの中のファイルを、! で個別に再包含できないのはなぜか。
! で個別に再包含できないのはなぜか。git は除外されたディレクトリを走査しないので、中のファイルにパターンを当てる機会がそもそもない。打ち消すならディレクトリ自体を否定する。