背景:Gitリポジトリが「肥大化」し、含まれるべきでないデータが混入する問題
こんな経験、思い当たる方も多いのではないでしょうか。ある日、古いリポジトリをレビューしていたら、6ヶ月前のgit履歴に本物のAPIキーが入った.envファイルがそのまま残っているのを発見した——。あるいは、実際のコードは数十MBしかないのにリポジトリが200MBに膨れ上がっている——誰かが誤って動画ファイルやデータセット、SQLバックアップをコミットしてしまったせいで。
ファイルをworking treeから削除して再コミットしても、問題は解決しません。Gitはすべての履歴を保存しているため、そのファイルはオブジェクトデータベースに残り続け、古いコミットハッシュを使えばいつでもチェックアウトできます。git log --allとgit checkoutを知っている人なら、誰でもその内容を取り出すことができます。
なぜgit filter-branchは良い選択ではないのか?
古典的な解決策はgit filter-branchです——Gitの履歴を書き換えるためのビルトインコマンドです。問題は、非常に遅いことです。以前、約3,000コミットある500MBのリポジトリでgit filter-branchを実行したところ、2時間近くかかりました。数万コミットのリポジトリなら、何日もかかることになります。
BFG Repo-Cleanerは、まさにこの問題を解決するために生まれました。作者のベンチマークによると、BFGはgit filter-branchより10〜720倍高速です。Scalaで書かれ、マルチスレッドでオブジェクトグラフを並列処理するため、これだけの速度差が生まれます。
BFG Repo-Cleanerのインストール
必要条件:Java Runtime
BFGはJVM上で動作するため、Java 8以上が必要です:
java -version
# java version "17.0.8" または同様のバージョンが表示されればOK
Javaがインストールされていない場合:
# Ubuntu/Debian
sudo apt install default-jre
# macOS (Homebrew)
brew install openjdk
# Windows (Chocolatey)
choco install openjdk
BFG JARのダウンロード
BFGはJARファイル一つだけで、複雑なインストールは不要です:
# バージョン1.14.0をダウンロード(最新の安定版)
wget https://repo1.maven.org/maven2/com/madgag/bfg/1.14.0/bfg-1.14.0.jar -O bfg.jar
# 確認
java -jar bfg.jar --version
日常的に使いやすくするために、shellの設定にaliasを追加しておきましょう:
# ~/.bashrc または ~/.zshrc に追加する
alias bfg='java -jar /path/to/bfg.jar'
source ~/.bashrc
実際のユースケース別:詳細な使い方
準備:リポジトリをmirrorでクローン
BFGはbareリポジトリに直接作用します。何かを行う前に、まずmirrorとしてローカルにクローンします:
# bareとしてクローン(mirror)— 通常のcloneは使わないこと
git clone --mirror [email protected]:username/my-repo.git my-repo.git
# bareディレクトリを確認
ls my-repo.git/
bareクローン上で作業することで安全性が高まります——force pushするまで、リモートの元のリポジトリには影響しません。これは重要なセーフティネットです。
ケース1:誤ってコミットした機密ファイルを削除する
最も一般的なユースケースです。例えば.env、secrets.json、またはプライベートキーを誤ってコミットしてしまった場合:
cd my-repo.git
# 特定のファイルを履歴全体から削除
java -jar bfg.jar --delete-files .env
# 複数のファイルを削除(globパターン)
java -jar bfg.jar --delete-files '*.pem'
java -jar bfg.jar --delete-files '{.env,.env.local,.env.production}'
# BFG実行後、必ずこのステップを実行すること
git reflog expire --expire=now --all
git gc --prune=now --aggressive
git gcコマンドが実際にディスク容量を解放するステップです——BFGはオブジェクトをorphanとしてマークするだけで、gcが実際にディスクから削除します。
ケース2:大きなファイルを削除してリポジトリサイズを縮小する
機械学習プロジェクトのリポジトリで実際にあったケースです——誰かが1.5GBのdatasetフォルダを誤ってコミットし、リポジトリが50MBから1.6GBに膨れ上がってしまいました。BFGならこれをすっきり処理できます:
# 50MBを超えるblobをすべて履歴から削除
java -jar bfg.jar --strip-blobs-bigger-than 50M
# より厳しい制限を設ける場合
java -jar bfg.jar --strip-blobs-bigger-than 10M
重要な注意点:BFGは自動的に最新のコミット(HEAD)を保護します。大きなファイルがまだHEADに存在する場合は、先に手動で削除する必要があります:
# BFGを実行する前にHEADからファイルを削除
git rm --cached dataset.zip
git commit -m "chore: remove accidentally committed large file"
# その後、BFGを実行
java -jar bfg.jar --strip-blobs-bigger-than 50M
ケース3:コード内にハードコードされたパスワードやトークンを置換する
ファイル全体を削除するのではなく、機密文字列だけを置換したい場合もあります——例えばconfigファイルにハードコードされたAPIキーなど:
# 置換対象の文字列を含むファイルを作成(1行1文字列)
cat > passwords.txt << 'EOF'
sk-ant-api03-xxx-yyy-zzz
ghp_xxxxxxxxxxxxxxxxxxxx
AKIAIOSFODNN7EXAMPLE
EOF
# BFGが上記の文字列をすべて***REMOVED***に置換する
java -jar bfg.jar --replace-text passwords.txt
BFGは履歴全体をスキャンし、該当する文字列を正確に置換します。ファイルの構造やその他の内容はそのまま保持されます。
ケース4:ディレクトリ全体を履歴から削除する
# logs/とnode_modules/を履歴全体から削除
java -jar bfg.jar --delete-folders logs
java -jar bfg.jar --delete-folders node_modules
# 複数のディレクトリを一度に削除
java -jar bfg.jar --delete-folders '{logs,tmp,cache,__pycache__}'
確認とリモートへの反映
プッシュ前に結果を確認する
BFGは実行のたびにレポートを出力します——何件のコミットが書き換えられたか、どのファイルが処理されたかをしっかり確認しましょう。さらに手動でも確認します:
# 機密ファイルが履歴から消えているか確認
git log --all --full-history -- .env
# 出力がなければ削除成功
# 削除前後のサイズを比較
git count-objects -vH
# 履歴全体から機密文字列を検索
git log -p --all | grep -n "sk-ant-api03"
# 結果がなければクリーン
リモートへのforce push
確認が完了したら、リモートにプッシュします。このステップでリモートの履歴が書き換えられます——全チームメンバーのローカルリポジトリが同期エラーになるため、事前に周知が必要です:
cd my-repo.git
# すべてのbranchとtagをプッシュ
git push --force
チームメンバー全員が再同期する必要があります:
# 最も安全な方法:再クローン
git clone [email protected]:username/my-repo.git
# ローカルの変更を保持したい場合
git fetch --all
git reset --hard origin/main
認証情報を即座にローテーションする
見落とされがちな重要なポイントです:git履歴から削除しても、漏洩したすべての認証情報をrevokeして再作成する必要があります。GitHub、GitLab、その他ほとんどのプラットフォームはパブリックリポジトリを自動的にクロールしています——その認証情報が公開されてから削除するまでの間に収集されていないとは誰にも断言できません。まずrevokeし、その後クリーンアップする——この順序を守ってください:
- GitHub Token: Settings → Developer settings → Personal access tokens → Delete
- AWS Key: IAM → Access keys → Deactivate → Delete
- Anthropic API Key: console.anthropic.com → API Keys → Revoke
.gitignoreで再発を防ぐ
誰も再び誤ってコミットしないよう、.gitignoreに追加しておきましょう:
# .gitignore
.env
.env.local
.env.*.local
*.pem
*.key
*.p12
secrets/
config/database.yml
私が管理する8人のチームでは、.envファイルを誤ってコミットする事故が一度発生してから、機密パターンに一致するファイルのコミットをブロックするgit hookを追加し、pre-commitチェックと組み合わせました。それ以来、一度も同じ事故は起きていません。エラーをその場で自動的に防ぐ仕組みは、後からレビューするよりもはるかに効果的です。
BFGとgit filter-repo、どちらを選ぶ?
Git 2.24以降、git filter-repoが登場しました——Gitが公式にgit filter-branchの代替として推奨するPythonツールで、filter-branchより高速でBFGより柔軟性があります。しかし、BFGは多くの場面で依然として優位性があります:
- BFG: よく使われるユースケース(ファイル削除、テキスト置換、large blobの除去)に対してシンプルな構文。Pythonへの依存なし。JARファイル一つですぐ実行できる。
- git filter-repo: リポジトリの再構成、サブディレクトリを別リポジトリに分割、作者によるフィルタリングなど、複雑な操作に適している。
目的が機密ファイルの削除やリポジトリサイズの縮小だけであれば、BFGが最も速くリスクの少ない選択肢です——2分でセットアップでき、数秒で実行できます。

