SYNOPSIS

git filter-branch [--setup <command>] [--subdirectory-filter <directory>]
        [--env-filter <command>] [--tree-filter <command>]
        [--index-filter <command>] [--parent-filter <command>]
        [--msg-filter <command>] [--commit-filter <command>]
        [--tag-name-filter <command>] [--prune-empty]
        [--original <namespace>] [-d <directory>] [-f | --force]
        [--state-branch <branch>] [--] [<rev-list options>…]

WARNING

git filter-branch には、意図した履歴の書き換えに加えて明白でない変なものを生成することができる沢山の落とし穴があります(そして、酷い性能なので、そのような問題を調査する時間がほとんどありません)。 これらの安全性とパフォーマンスの問題は、下位互換性を持って修正することはできないため、 git filter-branch 使用はお勧めしません。 git filter-repo などの代わりの履歴フィルタリングツールを使用してください。 それでもあなたが git filter-branch を使用する必要がある場合は、 [SAFETY] (と [PERFORMANCE])を注意深く読んで、filter-branchの地雷について学び、リストされている危険の多くを注意深く可能な限り回避してください。

DESCRIPTION

あなたは <rev-list options> に記載されているブランチを書き換え、各リビジョンにカスタムフィルターを適用することで、Gitリビジョン履歴を書き換えることができます。 これらのフィルターは、各ツリー(ファイルの削除やすべてのファイルに対するperlリライトの実行など)または各コミットに関する情報を変更できます。 それ以外の場合は、すべての情報(元のコミット時間またはマージ情報を含む)が保持されます。

このコマンドは、コマンドラインに記載されている「明らかな」ref(positive ref)のみを書き換えます(たとえば、a..b を渡すと、 b のみが書き換えられます)。 フィルタを指定しない場合、コミットは変更なしで再コミットされますが、通常は効果がありません。 それでも、これは将来、Gitのバグなどを補うのに役立つ可能性があるため、このような使用は許可されています。

Note
このコマンドは、 .git/info/grafts ファイルと refs/replace/ 名前空間のrefを尊重します。 graftsまたは置換refが定義されている場合、このコマンドを実行するとそれらが永続的になります。
Warning
書き換えられた履歴は、すべてのオブジェクトに対して異なるオブジェクト名を持ち、元のブランチに収束しません。 書き直されたブランチを元のブランチの上に簡単にプッシュして配布することはできません。 あなたが完全な影響がわからない場合はこのコマンドを使用しないでください。 問題を解決するのに単純な単一のコミットで十分な場合は、とにかく使用しないでください。 (公開された履歴の書き換えの詳細については、 git-rebase(1) の「RECOVERING FROM UPSTREAM REBASE」セクションを参照してください。)

書き換えられたバージョンが正しいことを常に確認(verify)してください。書き換えられたものと異なる場合、元のrefは名前空間 refs /original/ に格納されます。

注意: この操作は入出力に非常にコストがかかるため、 -d オプションを使用して一時ディレクトリをディスク外にリダイレクト、たとえば tmpfs にすることをお勧めします。 聞いた限りでは、スピードアップは非常に顕著です。

Filters

フィルタは、以下リストされた順序で適用されます。 <command> 引数は、(技術的な理由より、コミットフィルターを除き、)常に eval コマンドを使用してシェルコンテキストで評価されます。 その前に $GIT_COMMIT 環境変数は、書き換えられるコミットのIDを含むように設定されます。 また、GIT_AUTHOR_NAME と GIT_AUTHOR_EMAIL と GIT_AUTHOR_DATE と GIT_COMMITTER_NAME と GIT_COMMITTER_EMAIL と GIT_COMMITTER_DATE 環境変数は現在のコミットから取得されて環境変数にエクスポートされ、フィルター実行後に git-commit-tree(1) が作成する代替コミット(replacement commit)の作者とコミッターに影響を及ぼします。

<command> のいずれかの評価がゼロ以外のexitステータスを返す場合、操作全体が中止(abort)されます。

「元のsha1 ID」引数を取り、コミットがすでに書き換えられている場合は「書き換えられたsha1 ID」を出力し、それ以外の場合は「元のsha1 ID」を出力する「map」関数を使用できます。 コミットフィルターが複数のコミットを発行した場合、「map」関数は別々の行に複数のIDを返すことができます。

OPTIONS

--setup <command>

これは、コミットごとに実行される実際のフィルターではなく、ループの直前に1回だけセットアップされます。 したがって、このフィルターコマンドではコミット固有の変数はまだ定義されていません。 ここで定義された関数または変数は、技術的な理由により、コミットフィルターを除く次のフィルターステップで使用または変更できます。

--subdirectory-filter <directory>

指定のサブディレクトリにアクセスする履歴のみを探してください。 結果には、そのディレクトリ(かつ、それのみ)がプロジェクトルートとして含まれます。 [Remap_to_ancestor] を含んでいます。

--env-filter <command>

このフィルターは、コミットが実行される環境変数を変更するだけでよい場合に使用できます。 具体的には、 作者/コミッター名/電子メールアドレス/時間 の環境変数を書き直したい場合です(詳細については、 git-commit-tree(1) 参照)。

--tree-filter <command>

これは、ツリーとその内容を書き換えるためのフィルターです。 フィルターコマンドの引数は、チェックアウトされたツリーのルートに設定された作業ディレクトリを使用してシェルで評価されます。 その後、新しいツリーがそのまま使用されます(新しいファイルは自動的に追加され、消えたファイルは自動的に削除されます。 .gitignore ファイルも他の無視ルールも「効果がありません!」)。

--index-filter <command>

インデックスを書き換えるためのフィルタです。 これはツリーフィルターに似ていますが、ツリーをチェックアウトしないため、はるかに高速になります。 git rm --cached --ignore-unmatch ... で頻繁に使用されます。以下の例を参照してください。 ぞっとするほど危険なケースについては、 git-update-index(1) を参照してください。

--parent-filter <command>

これは、コミットの親リストを書き換えるためのフィルターです。 フィルターコマンドは stdinで親の文字列を受け取り、stdoutで新しい親の文字列を出力します。 親の文字列は、 git-commit-tree(1) で説明されている形式です。最初のコミットの場合は空、通常のコミットの場合は -p parent 、 マージコミットの場合は -p parent1 -p parent2 -p parent3 ... です。

--msg-filter <command>

これは、コミットメッセージを書き換えるためのフィルターです。 フィルターへの引数は、標準入力の元のコミットメッセージを使用してシェルで評価されます。 フィルターの標準出力は、新しいコミットメッセージとして使用されます。

--commit-filter <command>

これは、コミットを実行するためのフィルターです。 このフィルターを指定すると、 git commit-tree コマンドの代わりに、フィルターコマンドは `<TREE_ID> [(-p <PARENT_COMMIT_ID>)…]` 形式の引数とstdinのログメッセージを使用して呼び出されます。 stdoutにはコミットIDを出力することが期待されています。

特別な拡張機能として、コミットフィルターは複数のコミットIDを発行する場合があります。 その場合、元のコミットの書き直された子は、それらすべてを親として持ちます。

あなたはこのフィルターで「map」便利関数やその他の便利関数を使用することもできます。 たとえば、 skip_commit "$@" を呼び出すと、現在のコミットが除外されます(ただし、変更は除外されます! 必要に応じて、代わりに git rebase を使用してください)。

単一の親でコミットを保持したくない場合は、 git commit-tree "$@" の代わりに git_commit_non_empty_tree "$@" を使用することもできます。これにより、ツリーは変更されることはありません。

--tag-name-filter <command>

これは、タグ名を書き換えるためのフィルターです。 このフィルタが渡されると、書き換えられたオブジェクト(または書き換えられたオブジェクトを指すタグオブジェクト)を指す全てのタグrefに対してこのフィルタが呼び出されます。 元のタグ名は標準入力を介して渡され、新しいタグ名を標準出力に出力することが期待されます。

元のタグは削除されませんが、上書きできます。 --tag-name-filter cat を使用して、タグを更新するだけです。 この場合、変換が失敗した場合に備えて、古いタグをバックアップしておくなど、十分な注意が必要です。

タグオブジェクトは、ほぼ適切な書き換えがサポートされています。 タグにメッセージが添付されている場合、同一のメッセージと作者とタイムスタンプを使用して新しいタグオブジェクトが作成されます。 タグに署名が付いている場合、署名は削除されます。 定義上、署名を保持することは不可能です。 これが「ほぼ」適切である理由は、理想的には、タグが変更されていない場合(同じオブジェクトを指している、同じ名前を持っているなど)、署名を保持する必要があるためです。しかし署名は保持されません。 署名は常に削除されます。 利用者は注意してください。 また、作者またはタイムスタンプ(またはそのことについてのタグメッセージ)を変更することもサポートされていません。 他のタグを指すタグは、基になるコミットを指すように書き直されます。

--prune-empty

一部のフィルターは、ツリーをそのままにしておく空のコミットを生成します。 このオプションは、刈り込みされていない親が1個または0個しかない場合に、そのようなコミットを削除するようにgit-filter-branchに指示します。 したがって、マージコミットはそのまま残ります。 このオプションは --commit-filter と一緒に使用することはできませんが、コミットフィルターで提供されている git_commit_non_empty_tree 関数を使用することで同じ効果を得ることができます。

--original <namespace>

このオプションを使用して、元のコミットが保存される名前空間を設定します。 デフォルト値は refs/original です。

-d <directory>

このオプションを使用して、書き換えに使用される一時ディレクトリへのパスを設定します。 ツリーフィルターを適用する場合、コマンドは一時的にツリーをあるディレクトリにチェックアウトする必要があります。これは、大規模なプロジェクトの場合、かなりのスペースを消費する可能性があります。 デフォルトでは、これは .git-rewrite/ ディレクトリで行われますが、このパラメータでその選択を上書きできます。

-f
--force

git filter-branch は、強制されない限り、既存の一時ディレクトリでの開始を拒否するか、 refs/original/ で始まるrefがすでに存在する場合に拒否します。

--state-branch <branch>

このオプションを使用すると、古いオブジェクトから新しいオブジェクトへのマッピングが、起動時に名前付きブランチから読み込まれ、終了時にそのブランチへの新しいコミットとして保存され、大きなツリーの増分が可能になります。 <branch> が存在しない場合は、作成されます。

<rev-list options>…

git rev-list の引数。 これらのオプションに含まれるすべての正のref(positive refs)は書き直されます。 --all などのオプションを指定することもできますが、それらを git filter-branch のオプションから分離するには -- を使用する必要があります。 [Remap_to_ancestor] を含んでいます。

Remap to ancestor

git-rev-list(1) の引数、たとえばパスリミッター、を使用すると、書き換えられるリビジョンのセットを制限できます。 ただし、コマンドラインの正のrefは区別されます。このようなリミッターによって除外されることはありません。 この目的のために、代わりに、除外されなかった最も近い祖先を指すように書き直されます。

EXIT STATUS

成功すると、終了ステータスは「0」になります。 フィルタが書き換えるコミットを見つけられない場合、終了ステータスは「2」です。 その他のエラーの場合、終了ステータスはその他のゼロ以外の値である可能性があります。

EXAMPLES

すべてのコミットからファイル(機密情報または著作権違反を含む)を削除するとすると:

git filter-branch --tree-filter 'rm filename' HEAD

しかしながら、ファイルがコミットのツリーに存在しない場合、単純な rm filename はそのツリーとコミットで失敗します。 したがって、代わりにスクリプトとして rm -f filename を使用することをお勧めします。

--index-filtergit rm と一緒に使用すると、非常に高速なバージョンが生成されます。 rm filename を使用する場合と同様に、ファイルがコミットのツリーに存在しない場合、 git rm --cached filename は失敗します。 ファイルを「完全に忘れる」場合は、ファイルがいつ履歴に入力されたかは関係ないため、 --ignore-unmatch :

git filter-branch --index-filter 'git rm --cached --ignore-unmatch filename' HEAD

これで、書き換えられた履歴がHEADに保存されます。

foodir/ がプロジェクトルートであるかのようにリポジトリを書き直し、他のすべての履歴を破棄するには:

git filter-branch --subdirectory-filter foodir -- --all

したがって、たとえば、ライブラリサブディレクトリを独自のリポジトリに変えることができます。 注意: filter-branch オプションをリビジョンオプションから分離する -- と、すべてのブランチとタグを書き換えるための --all に注意してください。

(通常は他の履歴の先端にある)あるコミットを現在の初期コミットの親に設定し、他の履歴を現在の履歴の後ろに貼り付ける:

git filter-branch --parent-filter 'sed "s/^\$/-p <graft-id>/"' HEAD

(親の文字列が空の場合 — それは最初のコミットを処理しているときに発生します — 親として graftcommit を追加します)。 注意: これは、単一のルートを持つ履歴を想定していることに注意してください(つまり、共通の祖先がないとマージは発生しません)。 そうでない場合は、以下を使用してください:

git filter-branch --parent-filter \
        'test $GIT_COMMIT = <commit-id> && echo "-p <graft-id>" || cat' HEAD

または、さらに簡単に:

git replace --graft $commit-id $graft-id
git filter-branch $graft-id..HEAD

「Darl McBribe」によって作成されたコミットを履歴から削除するには:

git filter-branch --commit-filter '
        if [ "$GIT_AUTHOR_NAME" = "Darl McBribe" ];
        then
                skip_commit "$@";
        else
                git commit-tree "$@";
        fi' HEAD

関数 skip_commit は以下のように定義されています:

skip_commit()
{
        shift;
        while [ -n "$1" ];
        do
                shift;
                map "$1";
                shift;
        done;
}

シフトの魔法により、最初にツリーIDを破棄し、次に -p パラメーターを破棄します。 注意: このハンドルは適切にマージされます! DarlがP1とP2の間のマージをコミットした場合、それは適切に伝播され、マージのすべての子は、マージコミットではなく、親としてP1、P2を持つマージコミットになります。

Note
コミットによって導入され、後続のコミットによって元に戻され無い変更は、引き続き書き換えられたブランチに残ります。 あなたがコミットと一緒に「変更」を破棄したい場合、 あなたは git rebase の対話モードを使用する必要があります。

--msg-filter を使用してコミットログメッセージを書き換えることができます。 たとえば、 git svn によって作成されたリポジトリ内の "git svn-id" 文字列は、以下の方法で削除できます:

git filter-branch --msg-filter '
        sed -e "/^git-svn-id:/d"
'

たとえば、最後の10個のコミット(いずれもマージではない)に Acked-by 行を追加する必要がある場合は、以下のコマンドを使用します:

git filter-branch --msg-filter '
        cat &&
        echo "Acked-by: Bugs Bunny <bunny@bugzilla.org>"
' HEAD~10..HEAD

--env-filter オプションを使用して、コミッターや作者のIDを変更できます。 たとえば、user.emailの設定が間違っているためにコミットのIDが間違っていることがわかった場合は、プロジェクトを公開する前に、以下のように修正できます:

git filter-branch --env-filter '
        if test "$GIT_AUTHOR_EMAIL" = "root@localhost"
        then
                GIT_AUTHOR_EMAIL=john@example.com
        fi
        if test "$GIT_COMMITTER_EMAIL" = "root@localhost"
        then
                GIT_COMMITTER_EMAIL=john@example.com
        fi
' -- --all

履歴の一部のみに書き換えを制限するには、新しいブランチ名に加えてリビジョン範囲を指定します。 新しいブランチ名は、この範囲の git rev-list が出力する最上位のリビジョンを指します。

以下の履歴について考えてみましょう:

     D--E--F--G--H
    /     /
A--B-----C

コミット D,E,F,G,H だけを書き換えて、 A,B,C はそのままにするには、以下のようにします:

git filter-branch ... C..H

コミット E,F,G,H を書き換えるには、以下のどちらかを使用します:

git filter-branch ... C..H --not D
git filter-branch ... D..H --not C

ツリー全体をサブディレクトリに移動する、またはそこから削除するには:

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

リポジトリ縮小チェックリスト

git-filter-branchは、ファイルのサブセットを取り除くために使用できます。通常は、 --index-filter--subdirectory-filter を組み合わせて使用します。 人々は結果のリポジトリが元のリポジトリよりも小さいことを期待していますが、Gitは指示があるまでオブジェクトを失わないように努力するため、実際にリポジトリを小さくするにはさらにいくつかの手順が必要です。 まずは以下のことを確認してください:

  • ブロブがその存続期間中に移動された場合、あなたはファイル名のすべての派生を本当に削除したことになります。 git log --name-only --follow --all -- filename は、名前の変更を見つけるのに役立ちます。

  • git-filter-branch を呼び出す際に --tag-name-filter cat -- --all を使用すると、本当にすべての refs をフィルタリングすることができます。

次に、より小さなリポジトリを取得する2つの方法があります。 より安全な方法は、クローンを作成することです。これにより、あなたの元のファイルがそのまま保持されます。

  • git clone file:///path/to/repo でクローンを作成します。 クローンには削除されたオブジェクトはありません。 git-clone(1) を参照してください。 (注意: 普通のパス指定でクローンを作成すると、すべてがハードリンクされてしまうことに注意してください!)

あなたがなんらかの理由でマヂでクローンを作成したくない場合は、代わりに以下の点を(この順序で)確認してください。 これは非常に破壊的なアプローチであるため、「バックアップを作成」するか、クローン作成に戻ってください。いいですね?我々はちゃんと警告しましたよ。

  • git-filter-branchによってバックアップされた元のrefを削除します。そのためには git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d とします。

  • git reflog expire --expire=now --all を使用してすべてのreflogを期限切れにします。

  • ガベージコレクションでは、参照されていないすべてのオブジェクトを git gc --prune=now で収集します(または、git-gcが --prune の引数をサポートするほど新しいバージョンでない場合は、代わりに git repack -ad; git prune を使用します)。

PERFORMANCE

git-filter-branch の性能は氷河の流れのように劇遅で、その設計上、後方互換性のある実装が高速になることはあり得ません:

  • ファイルの編集では、git-filter-branchは設計上、元のリポジトリに存在していたすべてのコミットをチェックアウトします。 リポジトリに 10^5(10万)個のファイルと 10^5(10万)個のコミットがあり、それぞれのコミットで変更されるのが5個のファイルしか変更していない場合、git-filter-branchを使用すると、(最大)5*10^5(50万)個のユニークなブロブしかないにもかかわらず、あなたは 10^10(百億)個の変更を行うハメになります。

  • あなたがズルしようとして、コミットで変更されたファイルに対してのみgit-filter-branchが機能するようにしようとすると、2つのことが起こります。

    • ユーザーが単にファイルの名前を変更しようとすると、削除の問題が発生します(存在しないファイルを削除しようとすると、何もしない(no-op)ように見えるためです。 ユーザーが提供する任意のシェルを介して名前の変更が行われる場合、ファイルの名前変更全体で削除を再マップするには、多少の手間がかかります)

    • あなたが map-deletes-for-renames という奇策によって成功した場合でも、技術的に下位互換性に違反します。なぜなら、ユーザーはファイルの内容や名前だけに基づいてフィルタリングするのではなく、コミットのトポロジに依存する方法でファイルをフィルタリングできるからです(ただし、実際にこれが観察されたわけではありません)。

  • あなたはファイルを編集する必要はないが、たとえば 一部の名前を変更または削除すると、各ファイルのチェックアウトを回避できます(つまり、 --index-filter を使用できます)が、あなたのフィルターのシェルスニペットは引き続き渡されます。つまり、コミットごとに、これらのフィルターを実行できるgitリポジトリを準備する必要があります。 これは重要な設定です。

  • さらに、git-filter-branchによって、コミットごとにいくつかの追加ファイルが作成または更新されます。 これらのいくつかは、git-filter-branchによって提供される便利な関数(map()など)をサポートするためのものであり、その他は内部状態を追跡するためのものです(ただし、ユーザーフィルターによってアクセスされる可能性もあります。 git-filter-branch の回帰(regression)テストはそうします)。 これは基本的に、ファイルシステムをgit-filter-branchとユーザー提供のフィルター間のIPCメカニズムとして使用することを意味します。 ディスクは遅いIPCメカニズムになりがちで、これらのファイルを書くことは、コミットするたびにぶつかる、別々のプロセス間の強制的な同期ポイントを効果的に表しています。

  • ユーザー提供のシェルコマンドには、コマンドのパイプラインが含まれる可能性が高く、コミットごとに多くのプロセスが作成されます。 別のプロセスを作成して実行するのにかかる時間はOSによって大きく異なりますが、どのプラットフォームでも関数を呼び出すのに比べると非常に遅くなります。

  • git-filter-branch自体はシェルで記述されているため、少し時間がかかります。 これは、下位互換性で修正できるパフォーマンスの問題の1つですが、git-filter-branchの設計に固有の上記の問題と比較すると、ツール自体の言語は比較的小さな問題です。

    • 補足: 残念ながら、人々はシェルで書かれた側面に固執し、パフォーマンスの問題を修正するためにgit-filter-branchを別の言語で書き直すことができるかどうかを定期的に尋ねる傾向があります。それは、設計に内在する大きな問題を無視することになるばかりか、期待するほどには役に立たないでしょう。 もし git-filter-branch 自体が shell でなかったら、便利な関数 (map(), skip_commit() など) と --setup 引数はプログラムの最初に一度実行するだけでよくなり、代わりにすべてのユーザーフィルターで前置する必要がありました(つまりコミットごとに再実行されることになります)。

git filter-repo ツールは、git-filter-branchの代替手段で、これらのパフォーマンスの問題や安全性の問題(後述)の影響を受けません。 git-filter-branchに依存する既存のツールを使用している場合、 git filter-repofilter-lamely も提供し、これは差し込み式の git-filter-branch の代替品です(いくつかの注意点があります)。 filter-lamelyは、git-filter-branchと同じ安全性の問題に悩まされていますが、少なくともパフォーマンスの問題を少し改善します。

SAFETY

git-filter-branch は、様々な方法で簡単にリポジトリを破損させたり、最初よりもひどい状態に陥らせたりするゴチャゴチャがたくさんあります:

  • 誰かが「動作してテストされたフィルター」のセットを持っている可能性があり、それを文書化するか、同僚に提供し、同僚は、同じコマンドが 動作/テスト されていない別のOSでそれらを実行したとします(git-filter-branchのmanページのいくつかの例もこの影響を受けます)。 BSDとGNUのユーザーランドの違いが本当に噛み付いてくる可能性があります(運が良ければ、エラーメッセージが表示されます)。 同様に、コマンドは要求されたフィルタリングを実行しないか、不要な変更を加えることで黙って破損させます。 不要な変更は、いくつかのコミットにのみ影響する可能性があるため、必ずしも明らかではありません。 (問題が明らかにならないということは、書き換えた履歴がしばらく使われていないと気づかない可能性が高く、気付いた時点で、もう一度書き換えるための正当な位置を探し出すのは本当に難しいのです。)

  • スペースを含むファイル名は、シェルパイプラインに問題を引き起こすため、シェルスニペットによって誤って処理されることがよくあります。 誰もが find -print0xargs -0git-ls-files -z などに精通しているわけではありません。 これらに精通している人でさえ、フィルタリングを行っている人がプロジェクトに参加する前に、他の誰かが既にリポジトリ内のそのようなファイルの名前を変更済であったため、そのようなフラグは関係ないと考えるかもしれません。 そして、しばしば、スペースを使用した引数の処理に精通している人でさえ、うまくいかない可能性のあるすべてのことを考えるという考え方を持っていないという理由でそうしない場合があります。

  • 非ASCIIファイル名は、目的のディレクトリにあるにもかかわらず、黙って削除できます。 必要なパスのみを保持することは、多くの場合、 git ls-files | grep -v ^WANTED_DIR/ | xargs git rm のようなパイプラインを使用して行われます。 ls-filesは必要な場合にのみファイル名を引用するため、ファイルの1つが正規表現と一致しなかったことに気付かない場合があります(少なくとも手遅れになるまでは)。 ええ、 core.quotePath を知っている人は(\t, \n, or " などの他の特殊文字がなければ、)これを避けることができるし、 ls-files -z を grep 以外のもので使う人はこれを避けることができますが、だからといって避けてくれるとは限りません。

  • 同様に、ファイルを移動すると、ASCII以外の文字または特殊文字を含むファイル名が、二重引用符を含む別のディレクトリに配置されることがあります。 (これは技術的には上記のクォートと同じ問題ですが、おそらく興味深いことに、この問題は別の形で現れる可能性がありますし、実際に問題として現れています。)

  • 実に簡単に古い履歴と新しい履歴を誤って混同してしまいます。それはどのツールでも起こりうるのですが、git-filter-branch その多くをやらかします。 運が良ければ、唯一の欠点は、リポジトリを縮小して古いものを削除する方法がわからないことにユーザーが不満を感じることです。 運が悪ければ、古い履歴と新しい履歴がマージされ、各コミットの複数の「コピー」が作成されます。その中には、不要なファイルや機密ファイルが含まれるものと、含まれないものがあります。 これが複数の異なる方法で発生します。

    • 履歴の部分的な書き換えのみを行うデフォルト(--all はデフォルトではない。そして、これを示す例もほとんどない)。

    • 実行後の自動クリーンアップがないという事実

    • --tag-name-filter (タグの名前を変更するために使用される場合)は古いタグを削除せず、新しい名前で新しいタグを追加するだけであるという事実

    • 書き換えの影響や、古い履歴と新しい履歴の混在を避ける方法をユーザーに知らせるための教育的な情報がほとんど提供されていないことです。 たとえば、この man ページでは、すべてのブランチの変更を新しい履歴の上にリベースする((あるいは削除して再クローンする)必要があることをユーザーが理解する必要があることを述べていますが、これは考慮すべき複数の懸念事項のうちのひとつにすぎません。 詳しくは、 git filter-repo マニュアルページの「DISCUSSION」セクションをご覧ください。

  • 以下の2つの問題のいずれかが原因で、注釈付きタグが誤って軽量タグに変換される可能性があります:

    • 誰かが履歴の書き換えを行い、混乱したことに気づき、 refs/original/ のバックアップから復元してから、git-filter-branchコマンドをやり直すことができます。 (refs/original/ のバックアップは実際のバックアップではありません。最初にタグを逆参照します。)

    • <rev-list options> で --tags または --all を指定してgit-filter-branchを実行します。 注釈付きタグ(annotated tags)を注釈(annotated)として保持するには、 --tag-name-filter を使用する必要があります(以前に失敗した書き換えで refs/original/ から復元してはいけません)。

  • エンコーディング指定のコミットメッセージは、書き換えによって破損します。 git-filter-branchはエンコーディングを無視し、元のバイトを取得して、適切なエンコーディングを通知せずにコミットツリーにフィードします。 (これは、 --msg-filter が使用されているかどうかに関係なく発生します。)

  • コミットメッセージ(それが全てUTF-8であっても)はデフォルトでは更新されないため破損します — コミットメッセージ内の他のコミットハッシュへの参照は、もはや存在しないコミットを参照するようになります。

  • ユーザーが削除すべき不純物を見つけるのを助ける機能はありません。つまり、不完全または部分的なクリーンアップが行われる可能性が高く、時には混乱を招き、人々は理解しようとして時間を浪費することになります。 (たとえば、ユーザーは大きなディレクトリや拡張子ではなく、削除すべき大きなファイルを探す傾向があり、いったんそうしてしまうと、後で新しいリポジトリを使って履歴を調べている人たちは、いくつかのファイルがあるのに他のファイルがないビルド成果物ディレクトリや、いくつかのファイルがないので機能しなかったはずの依存関係のキャッシュ((node_modules など)に気づくことになります)。

  • --prune-empty が指定されていない場合、フィルタリングプロセスにより、混乱を招く空のコミットが大量に発生する可能性があります

  • --prune-empty が指定されている場合、フィルタリングルールによって空になったコミットを刈り込みするだけでなく、フィルタリング操作の前から意図的に配置された空のコミットも刈り込みされます。

  • --prune-empty が指定されている場合、空のコミットが失われたり、とにかく全部残されることがあります(多少まれなバグですが、発生します…)

  • 些細なことですが、リポジトリ内のすべての名前と電子メールを更新するという目標を持っているユーザーは、著者とコミッターのみを更新し、タガー(tagger)を見逃す --env-filter に誘導されるかもしれません。

  • ユーザーが複数のタグを同じ名前にマップする --tag-name-filter を提供した場合、警告やエラーは提供されません。 git-filter-branchは、文書化されていない事前定義された順序で各タグを上書きするだけで、最後に1つのタグのみが生成されます。 (git-filter-branch回帰テストは、この驚くべき振る舞いがを要求します。)

また、 git-filter-branch のパフォーマンスが低いと、安全性の問題が発生することがよくあります:

  • あなたが望むフィルタリングを行うための正しいシェルスニペットを考え出すことは、いくつかのファイルを削除するような些細な修正を行うのでない限り、時に困難なことです。 しかし、その正否は特殊な状況((ファイル名にスペースがある、非ASCIIのファイル名、おかしな著者名やメール、無効なタイムゾーン、grafts や replace オブジェクトがあるなど)によって異なるため、長い間待ってエラーになり、再起動しなければならないことがあります。 git-filter-branch の性能は非常に悪いので、このサイクルは苦痛であり、慎重に再チェックする時間が減ってしまいます (たとえ技術的に余裕があったとしても、書き直す人の忍耐力に影響を与えることは言うまでもありません)。 壊れたフィルターによるエラーは長い間表示されなかったり、出力の海に紛れたりするので、この問題はさらに深刻になります。 さらに悪いことに、フィルターが壊れていると、ただ黙って間違った書き直しをすることになりがちです。

  • さらに言えば、ユーザーがようやく動作するコマンドを見つけたら、それを共有したいと思うのは自然なことです。 しかし、自分のリポジトリにはない特別なケースが他の人のリポジトリにはあることに気づいていないかもしれません。 そのため、異なるリポジトリを持つ他の人が同じコマンドを実行すると、上記のような問題に見舞われることになります。 あるいは、そのユーザーは、本当に特殊なケースを想定して吟味されたコマンドを実行しているだけなのですが、それを別のOSで実行すると、上に書いたように、うまくいかないのです。

GIT

Part of the git(1) suite