Git filter-branch完全ガイド:Gitの歴史を書き換える強力なツール

git filter-branchの使い方を実例を交えて解説。機密情報の削除、著者名の変更、ディレクトリ構造の変更など、Gitの履歴を書き換える様々なテクニックを紹介します。

はじめに

Gitを使っていて「あ、パスワードをコミットしてしまった!」「過去のコミットの著者名を全部変更したい」といった経験はありませんか?

今回は、そんな時に役立つgit filter-branchという強力なコマンドについて解説します。このコマンドは「Gitの歴史改変ツール」とも呼べる、知る人ぞ知る上級コマンドです。

git filter-branchとは?

git filter-branchは、Gitリポジトリの履歴を書き換えるためのコマンドです。単一のコミットだけでなく、全履歴を対象に一括で変更を適用できるのが特徴です。

基本構文

1
git filter-branch [--option] '<command>' [revision-range]

主なオプション:

  • --tree-filter: 各コミットのファイルツリーを直接編集
  • --index-filter: インデックス(ステージング)を編集
  • --env-filter: 環境変数(著者情報など)を編集
  • --msg-filter: コミットメッセージを編集

実践例1:機密情報の削除

最も一般的な使用例は、誤ってコミットしてしまった機密情報の削除です。

パスワードファイルを全履歴から削除

1
2
3
4
# passwords.txtを全履歴から削除
git filter-branch --force --index-filter \
  'git rm --cached --ignore-unmatch passwords.txt' \
  --prune-empty --tag-name-filter cat -- --all

.envファイルを削除(より安全な方法)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# バックアップを作成
git branch backup-before-cleanup

# .envファイルを全履歴から削除
git filter-branch --force --index-filter \
  'git rm --cached --ignore-unmatch .env' \
  --prune-empty --tag-name-filter cat -- --all

# 削除を確認
git log --all --full-history -- .env

実践例2:著者情報の変更

GitHubのユーザー名を変更した場合など、過去のコミットの著者名を変更したい場合があります。

特定の著者名を一括変更

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#!/bin/bash
git filter-branch --env-filter '
OLD_EMAIL="old@example.com"
CORRECT_NAME="新しい名前"
CORRECT_EMAIL="new@example.com"

if [ "$GIT_COMMITTER_EMAIL" = "$OLD_EMAIL" ]
then
    export GIT_COMMITTER_NAME="$CORRECT_NAME"
    export GIT_COMMITTER_EMAIL="$CORRECT_EMAIL"
fi
if [ "$GIT_AUTHOR_EMAIL" = "$OLD_EMAIL" ]
then
    export GIT_AUTHOR_NAME="$CORRECT_NAME"
    export GIT_AUTHOR_EMAIL="$CORRECT_EMAIL"
fi
' --tag-name-filter cat -- --branches --tags

実際の使用例:tmy-ss-310 → tmy-310

私が実際に使用したケースです。GitHubのユーザー名を変更したため、過去のコミット履歴も更新しました:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
git filter-branch -f --env-filter '
if [ "$GIT_AUTHOR_NAME" = "tmy-ss-310" ]; then
    export GIT_AUTHOR_NAME="tmy-310"
    export GIT_AUTHOR_EMAIL="mito.motohiro@gmail.com"
fi
if [ "$GIT_COMMITTER_NAME" = "tmy-ss-310" ]; then
    export GIT_COMMITTER_NAME="tmy-310"
    export GIT_COMMITTER_EMAIL="mito.motohiro@gmail.com"
fi
' -- --all

実践例3:ディレクトリ構造の変更

サブディレクトリを新しいリポジトリとして切り出す

1
2
3
4
# frontendディレクトリだけを抽出して新しいリポジトリに
git filter-branch --subdirectory-filter frontend HEAD

# これにより、frontendディレクトリがルートディレクトリになる

全ファイルをサブディレクトリに移動

1
2
3
4
5
git filter-branch --index-filter \
  'git ls-files -s | sed "s-\t\"*-&newsubdir/-" |
   GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
   git update-index --index-info &&
   mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"' HEAD

実践例4:大きなファイルの削除

リポジトリサイズを削減するため、大きなファイルを履歴から削除します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 100MB以上のファイルを検出
git rev-list --objects --all |
  git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' |
  sed -n 's/^blob //p' |
  sort --numeric-sort --key=2 |
  tail -10

# 大きなファイル(例:huge-video.mp4)を削除
git filter-branch --force --index-filter \
  'git rm --cached --ignore-unmatch huge-video.mp4' \
  --prune-empty --tag-name-filter cat -- --all

filter-branchの注意点

⚠️ 破壊的操作

git filter-branchは履歴を完全に書き換えるため:

  1. 必ずバックアップを作成
1
git branch backup-$(date +%Y%m%d)
  1. 強制プッシュが必要
1
git push --force origin main
  1. チームメンバーへの影響
    • 他の開発者は再クローンが必要
    • 進行中の作業がある場合は要調整

パフォーマンスの問題

大規模なリポジトリでは処理に時間がかかります:

  • 1000コミット: 数分
  • 10000コミット: 30分以上

代替ツール:git filter-repo

Git 2.24以降では、git filter-repoが推奨されています:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# インストール
pip install git-filter-repo

# 使用例:メールアドレスの変更
git filter-repo --email-callback '
  return email.replace(b"old@example.com", b"new@example.com")
'

# パスの削除
git filter-repo --path passwords.txt --invert-paths

filter-repoの利点

  • 高速: filter-branchの10-720倍高速
  • 安全: デフォルトで新しいリポジトリに出力
  • 使いやすい: より直感的なオプション

実用的な使い分け

git filter-branchを使うべき場合

  • Gitの標準機能のみで完結したい
  • 簡単な著者名変更や単一ファイル削除
  • 追加ツールのインストールが困難な環境

git rebaseやresetで十分な場合

  • 最近のコミット(10個程度)の修正
  • 個人プロジェクトで履歴の詳細が不要
  • クリーンな履歴を優先

git filter-repoを使うべき場合

  • 大規模なリポジトリ
  • 複雑なフィルタリング条件
  • パフォーマンスが重要

トラブルシューティング

エラー: “Cannot create a new backup”

1
2
# 既存のバックアップを削除
rm -rf .git/refs/original/

リモートとの同期問題

1
2
3
4
5
# ローカルの変更を強制的に反映
git push --force-with-lease origin main

# より安全:新しいブランチにプッシュ
git push origin main:main-filtered

容量が減らない場合

1
2
3
# ガベージコレクションを実行
git reflog expire --expire=now --all
git gc --prune=now --aggressive

まとめ

git filter-branchは強力なツールですが、使用には注意が必要です:

使うべき時

  • 機密情報の緊急削除
  • 著者情報の一括修正
  • リポジトリの大規模なリファクタリング

避けるべき時

  • 公開済みの大規模プロジェクト
  • チーム開発中のアクティブなリポジトリ
  • 簡単な修正で済む場合

適切に使えば、Gitの履歴をクリーンに保ち、セキュリティ問題を解決できる強力な味方になります。ただし、「大いなる力には大いなる責任が伴う」ことを忘れずに!

参考リンク


この記事が役に立ったら、ぜひシェアしてください!Gitの困った時の救世主として、多くの開発者の助けになれば幸いです。

技術ネタ、趣味や備忘録などを書いているブログです
Hugo で構築されています。
テーマ StackJimmy によって設計されています。