誤って commit した重いファイルを git 履歴から除去してリポジトリを軽量化する
状況
ビルド成果物(数 MB の Go バイナリ)を誤って commit して push してしまった。.gitignore で追跡解除しても 履歴には残り続ける ためリポジトリサイズが膨らむ。
クリーンに除去するには履歴の書き換えが必要で、これは破壊的操作なので force-push と各クライアントの再 clone(または reset)が伴う。
主な選択肢
1. git filter-repo(推奨)
git filter-branch の後継として Git 公式が推奨するツール。高速・安全・パスベースの除去が簡単。
# pip / brew でインストール
pip install git-filter-repo
# 特定ファイルを全履歴から削除
git filter-repo --invert-paths \
--path path/to/big-file-1 \
--path path/to/big-file-2
# remote が自動で外れるので再追加
git remote add origin git@github.com:org/repo.git
# 強制 push
git push --force-with-lease origin mainメリット: シンプル、高速、複雑な書き換えにも対応。 デメリット: 別途インストール必要、デフォルトで origin remote を消す(再追加が必要)。
2. BFG Repo-Cleaner
巨大リポジトリ向けの高速ツール(Java 製)。サイズ閾値での一括削除が得意。
# 100KB 以上のファイルを全部除去
bfg --strip-blobs-bigger-than 100K
git reflog expire --expire=now --all
git gc --prune=now --aggressive
git push --forceメリット: 100MB のリポでも数秒、サイズ指定が便利。 デメリット: Java 必要、細かいパス指定は filter-repo の方が柔軟。
3. git filter-branch(非推奨)
Git 標準だが遅くてバグが多く、Git 公式も filter-repo を推奨している。今は使う理由がない。
4. 直近 commit のみなら git rebase
push 済みでも自分しか触っていないなら、interactive rebase で当該 commit を drop → force-push でも可。1 コミットだけ消したい場合これが一番軽い。ただし上に commit が積まれていると、衝突リスクが上がる。
実行手順(filter-repo)
1. バックアップを取る
履歴書き換えは取り返しがつかないので必ずミラークローンを取る。
git clone --mirror /path/to/repo /tmp/repo-backup-$(date +%s).git2. remote URL を控える
filter-repo は実行後に origin を消すので、URL を保存しておく。
git remote get-url origin > /tmp/origin-url.txt3. filter-repo 実行
git filter-repo --invert-paths \
--path path/to/file-a \
--path path/to/file-b \
--path path/to/file-c過去に同じリポで filter-repo を実行したことがある場合、already_ran チェックに引っかかって実行が止まる。--force でも対話プロンプトが出るので、echo "Y" | でパイプするか stdin を渡す。
echo "Y" | git filter-repo --force --invert-paths --path ...4. remote 再追加して force-push
git remote add origin "$(cat /tmp/origin-url.txt)"
git push --force-with-lease origin main--force-with-lease は通常の --force よりも安全で、リモートが自分の認識する状態と異なる場合は push を拒否する。
5. ローカル GC で容量回収
git reflog expire --expire=now --all
git gc --prune=now --aggressiveこれで dangling object(参照されなくなった古いオブジェクト)が物理的に削除される。du -sh .git で前後比較できる。
副作用と注意点
全 commit の SHA が変わる
filter-repo は安全のため全 commit を rewrite する(対象ファイルが含まれない commit も含めて)。これにより:
- ローカルの全ブランチが remote と SHA レベルで divergence する(内容は同じ)
- push 済みの古い SHA への参照(PR 上のリンクなど)は壊れる可能性がある
- 共同作業者は再 clone が必要になる
他のブランチの扱い
force-push するのは履歴を整理したいブランチ(多くの場合 main)のみで OK。他の feature ブランチに対象ファイルが含まれていなければ、それらは触らない方が安全。
ただしローカル側では filter-repo が全 ref を rewrite してしまうので、再 fetch すると [behind] 表示が出る。必要なブランチだけ git reset --hard origin/<branch> で揃え直すか、リポジトリを再 clone する。
リモート側の物理削除
force-push しても、削除した古い commit は GitHub などのホスティング側の pack に残る。即時削除したい場合はサポートに連絡が必要だが、通常はホスティング側の自動 GC でいずれ消えるので待てばよい。
セキュリティ事故(誤って secret を commit)の場合は即削除を依頼すること。
確認コマンド
履歴書き換え前後で、対象ファイルが reachable な commit に含まれていないか確認する。
# 全 ref から到達可能な範囲で、ファイル追加 commit を探す
git log --all --diff-filter=A --name-only --oneline \
| grep -E "target-file-name"
# 結果が空なら除去成功
# 特定 commit が到達可能か
git branch -a --contains <old-sha>
# 何も出なければ unreachableまとめ
- 重いファイルを履歴から消すには
git filter-repoが現代の標準 - 必ずバックアップ → 実行 → force-push → gc の順
- 全 commit の SHA が変わるので共同作業者・PR 参照に注意
- private リポ・単独作業なら影響範囲が限定的で実行しやすい
- 完全削除はホスティング側の GC を待つかサポート依頼