はじめに
.gitignore に入れて git rm --cached すれば、そのファイルは安全圏に入ります。そう思っていたところ、ローカルにしかない設定ファイルが気づかないうちに消えていました。
原因は、追跡解除がコミット単位の操作でしかなく、その変更を含まないブランチでは、ファイルが依然として「追跡対象」のままだったことです。
今回は .claude/settings.local.json(Claude Codeのローカル許可設定)で発生させましたが、他にも .env やIDEの個人設定ファイルなど、**ローカル固有ファイルを後から .gitignore に入れようとした際に起こりうるミスです。
何が起きたか
経緯は以下の通りです。
- feature ブランチ上で
git rm --cachedしてファイルを追跡解除し、.gitignoreに追記してコミット - main にはその変更をまだマージしていない(=main では依然としてファイルが追跡されたまま)
- 作業のために main へ
checkout - しばらくして元のブランチに戻る
この間、次の2つが起きます。
1. 追跡ありのブランチに切り替えた瞬間 → 警告なく上書きされる
main はまだそのファイルを追跡しているため、checkout した瞬間にディスク上のファイルが、mainブランチが持っている古い内容で上書きされます。ローカルでしか更新していなかった内容はここで消えます。
2. 追跡なしのブランチに戻った瞬間 → worktreeから削除される
元のブランチ(追跡解除済み)に戻ると、gitは「このブランチにこのファイルは存在しないはず」と判断し、ディスクからファイルを削除します。
上書き→削除の順で、ローカル設定が失われます。
.gitignore されたファイルはgitの保護対象外
通常、追跡中のファイルをローカルで変更した状態で checkout しようとすると、gitは
error: Your local changes to the following files would be overwritten by checkout
と表示して処理を止めます。一方、.gitignore に入っているファイルはこの保護の対象外です。gitからすれば「無視してよいと明示されたファイル」であるため、上書きも削除も警告なしに実行されます。
つまり .gitignore に入れるという操作は、「gitに管理させない」だけでなく「gitの保護からも外す」ことを意味します。
根本原因:追跡解除は「全ブランチに行き渡って初めて有効」
git rm --cached はワーキングツリーではなくコミットに対する操作であり、そのコミットを含まないブランチには影響しません。長命ブランチ(main / develop など)がある構成では、フィーチャーブランチで追跡解除しただけでは事故は止まりません。mainにマージされてブランチ全体に行き渡るまでの間、mainを触るたびに同じ事故が起こりえます。
さらに、.gitignore の行を追加するだけでは解決しません。上書き・削除の原因は「そのブランチのindexにファイルが存在していること」であるため、.gitignore への追記と、indexからの削除(git rm --cached)はセットでなければ意味がありません。
対処法
応急処置:全ブランチで追跡解除を揃える
# 作業中の変更が無いか確認
git status
# main から hotfix ブランチを切る
git fetch origin
git switch -c hotfix/untrack-local-file origin/main
# 追跡だけ解除(--cached なのでディスク上のファイルは残る)
git rm --cached path/to/local-file
# .gitignore に追記
printf '
path/to/local-file
' >> .gitignore
git add .gitignore
git commit -m "chore: ローカル固有ファイルを追跡対象から除外する"
git push -u origin hotfix/untrack-local-file
gh pr create --base main
マージ後は main を develop や他の作業ブランチへも戻し入れる必要があります。これを怠ると、ブランチ間で .gitignore の内容が食い違ったままになり、次のマージで同じ行がコンフリクトします。
根本対策:最初から .gitignore に入れておく
最も確実な方法は、ローカル固有ファイルをリポジトリの最初のコミットの時点で .gitignore に入れておくことです。
後から外す場合、全ブランチに変更した .gitignore が行き渡るまでの間、事故が起こりうる状態が続きます。
消えたファイルは追えるのか
追跡対象外のファイルは履歴から追えないと思いがちですが、過去に一度でも追跡されていた期間があれば、消失時刻まで特定できる場合があります。
# 1. 追跡されていた期間があるかを確認する(--all が重要)
git log --oneline --all -- 'path/to/local-file'
# 2. どのコミットがファイルを持っているか(追跡あり/なしの食い違いを見る)
git cat-file -e <commit>:path/to/local-file && echo "追跡あり"
# 3. reflogでブランチ切替の時刻を突き合わせる
git reflog --date=iso -40
# 4. ディレクトリのmtimeで裏を取る(配下ファイルの増減時刻と一致するか)
ls -la path/to/
checkout: moving from A to B や pull origin X: Fast-forward の時刻が、そのままファイルの上書き・削除の時刻になります。
ただし、復元できるのは当然「最後に追跡されていた時点の内容」のみです。追跡解除後にローカルだけで積み上げた変更は残っていません。
まとめ
gitの仕様はきちんと理解しないと怖いですね。
コンフリクトばっかり気にしていましたが、こういう些細な部分にも気を配りたいものです。
