【Git】巨大ファイルを誤ってコミットしたときの削除方法|BFG Repo-Cleanerとfilter-branch

【Git】巨大ファイルを誤ってコミットしたときの削除方法|BFG Repo-Cleanerとfilter-branch Git

誤って巨大ファイルをコミットしてしまうと、履歴全体が肥大化してクローンやフェッチが極端に遅くなり、ホスティングのサイズ制限にも引っかかる恐れがあります。
本記事では履歴から完全に痕跡を消すことを目的に、BFG Repo-Cleanerとgit filter-branchを使ったクリーンアップ手順を解説します。
操作は履歴書き換えを伴うため、強制プッシュが必要になります。実行前にバックアップを取り、関係者への周知と再クローンの案内までを一連の作業として捉えることが重要です。

事前準備と安全策

実作業に入る前に現在の状態を記録し、巻き戻し可能な退路を確保します。まずは状況の把握としてステータスと簡易ログを確認します。

git status
git log --oneline --decorate --graph -n 50

作業前の保険としてミラークローンを作成し、そこを作業用の舞台にします。元リポジトリを直接書き換えないことで、失敗時の復旧が容易になります。

# リモートURLは適宜置き換える
git clone --mirror https://example.com/owner/repo.git repo.git
cd repo.git

万一に備えてミラーのディレクトリ自体をコピーしておくと、やり直しが必要になったときに短時間で復元できます。作業の最後には強制プッシュが発生するため、事前にチームへアナウンスし、ローカルの再クローンを依頼できる準備を整えます。

巨大ファイルや対象パスの特定

まずは大きなブロブや問題のファイルパスを特定します。履歴全体を走査してオブジェクトとサイズを洗い出すと原因が見つけやすくなります。

# すべてのブロブを列挙し、サイズ順に並べる例
git rev-list --objects --all | \
  git cat-file --batch-check='%(objectname) %(objecttype) %(objectsize) %(rest)' | \
  grep ' blob ' | sort -k3 -n | tail -n 20

直近で混入したファイルに心当たりがある場合は、ファイル単位の履歴をたどって侵入ポイントを突き止めます。

# 特定ファイルの履歴を確認
git log --stat -- path/to/huge_file.bin

BFG Repo-Cleanerでサイズ超過ブロブを一括除去する

BFG Repo-Cleanerはフィルタリングの多くを高速に自動化してくれるため、大容量ブロブの除去には最優先で検討する価値があります。ミラークローン上で実行し、完了後にメンテナンスと強制プッシュを行います。

# 例: 100MB超のブロブを履歴から削除する
java -jar bfg.jar --strip-blobs-bigger-than 100M repo.git

# 例: 特定パスを履歴から完全削除する
java -jar bfg.jar --delete-files "huge_file.bin" repo.git
java -jar bfg.jar --delete-folders "large_assets" --no-blob-protection repo.git

BFGの実行後は書き換えられていない参照を掃除し、パックの再構築を行います。これにより実ディスクサイズも縮小されます。

cd repo.git
git reflog expire --expire=now --all
git gc --prune=now --aggressive

変更をリモートに反映する際は強制プッシュが必要です。他者の作業を誤って消さないよう、必ず--force-with-leaseを使います。

git push --force-with-lease --mirror

強制プッシュ後は既存のクローンが古い履歴を保持しているため、利用者には再クローンか厳密なリセットを依頼します。依存するCIやキャッシュも履歴に結びついている場合があり、必要に応じてワークスペースのフルクリーンを合わせて案内します。

git filter-branchで対象パスを除去する

BFGを使えない環境ではgit filter-branchでも履歴の改変は可能です。対象パスが明確なときはインデックスフィルターを用いて該当ファイルを全コミットから取り除きます。

# 例: 履歴から特定ファイルを完全削除
git filter-branch --index-filter \
  'git rm -r --cached --ignore-unmatch path/to/huge_file.bin' \
  --prune-empty --tag-name-filter cat -- --all

フォルダー単位の削除も同様に実行できます。大量の履歴を処理するため時間はかかりますが、完了後はBFGと同様に参照の掃除とガーベジコレクションを行います。

git reflog expire --expire=now --all
git gc --prune=now --aggressive
git push --force-with-lease --all
git push --force-with-lease --tags

サイズしきい値での一括削除はfilter-branch単独だと記述が複雑になりがちです。ファイルパスが不明瞭でサイズ超過ブロブを横断的に消したい場合は、パフォーマンスと簡潔さの面でBFGの利用が現実的です。

強制プッシュ後の運用と既存クローンの対処

履歴書き換え後は、各開発者のローカルに古い履歴が残っているため、再クローンか完全リセットを依頼します。再クローンが最も安全ですが、作業ブランチを保持したい場合はフェッチの後に強制的に履歴を合わせます。

# 既存クローンでの履歴同期例(注意して実行)
git fetch --all --prune
git checkout main
git reset --hard origin/main
git gc --prune=now

タグやリリースアーティファクトが古いブロブを指しているとサイズが残存するため、必要に応じてタグの付け直しやリリースアセットの再発行も検討します。サードパーティのミラーやフォークがある場合は、それらも独自に古い履歴を保持し続けることがあるため、合意形成と告知が重要です。

再発防止のポイント

次回以降の事故を防ぐために、巨大ファイルはGit LFSで管理し、通常の履歴には乗せない運用に切り替えます。トラッキングの設定は最初に済ませておくと安全です。

# 例: 大きな拡張子をLFSで管理
git lfs install
git lfs track "*.psd"
git add .gitattributes
git commit -m "Track large assets with Git LFS"

チームには. gitignoreの整備と、pre-commitまたはpre-receiveフックでのサイズチェックを導入しておくことを推奨します。一定サイズ以上の追加を検知した際にコミットやプッシュをブロックすることで、事故の早期検知が可能になります。ホスティング側のサイズ上限に近づいた場合のアラート設定も合わせて有効です。

まとめ

巨大ファイルが混入した歴史を綺麗にするには、まず対象を特定し、ミラークローン上でBFG Repo-Cleanerを優先的に適用するのが効率的です。環境的にBFGが使えない場合はgit filter-branchでもパス指定の削除が可能です。いずれの方法でも履歴書き換え後は強制プッシュと周知、既存クローンの再クローンや強制同期、CIやキャッシュのクリアまでを一連の作業として完了させることで、本当にサイズ問題を解消できます。再発防止としてGit LFSの活用とフックによるサイズチェックを取り入れれば、同様のトラブルを未然に防げます。