日本語 ▾ トピック ▾ 最新バージョン ▾ git-filter-branch は 2.44.0 で最終更新されました

名前

git-filter-branch - ブランチを書き換える

概要

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>…​]

警告

git filter-branch には、意図した履歴の書き換えを非自明な方法で破損させる可能性のある落とし穴が多数あり(そしてそのパフォーマンスの悪さから、そのような問題を調査する時間がほとんど残されない可能性があります)。これらの安全性とパフォーマンスの問題は、後方互換性を保ったまま修正することはできないため、使用は推奨されません。代わりに git filter-repo のような代替の履歴フィルタリングツールを使用してください。それでも git filter-branch を使用する必要がある場合は、安全性(および パフォーマンス)を注意深く読み、filter-branch の地雷について学び、そこにリストされている危険の可能な限り多くを慎重に避けてください。

説明

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

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

注記: このコマンドは .git/info/grafts ファイルと refs/replace/ 名前空間の参照を尊重します。グラフまたは置換参照が定義されている場合、このコマンドを実行するとそれらは永続化されます。

警告! 書き換えられた履歴はすべてのオブジェクトに対して異なるオブジェクト名を持ち、元のブランチとは統合されません。書き換えられたブランチを元のブランチの上に簡単にプッシュして配布することはできません。このコマンドの完全な意味を理解していない場合は使用しないでください。また、単一のコミットで問題を解決できる場合は、とにかく使用を避けてください。(公開された履歴の書き換えに関する詳細は、git-rebase[1] の「RECOVERING FROM UPSTREAM REBASE」セクションを参照してください。)

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

この操作は I/O 負荷が非常に高いため、-d オプションを使用して一時ディレクトリをディスク外 (例: tmpfs) にリダイレクトするのが良い考えかもしれません。報告によると、速度向上が非常に顕著です。

フィルタ

フィルターは以下にリストされている順序で適用されます。<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] によって作成される置換コミットの作者とコミッターのIDに影響を与えます。

<command> の評価がゼロ以外の終了ステータスを返すと、操作全体が中止されます。

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

オプション

--setup <command>

これは各コミットに対して実行される実際のフィルターではなく、ループの直前に行われる1回限りのセットアップです。したがって、コミット固有の変数はまだ定義されていません。ここで定義された関数や変数は、技術的な理由により、コミットフィルターを除いて、以下のフィルター手順で使用または変更できます。

--subdirectory-filter <directory>

指定されたサブディレクトリに影響する履歴のみを調べます。結果として、そのディレクトリ(およびそれのみ)がプロジェクトのルートになります。祖先への再マッピング を含みます。

--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>

これはコミットの親リストを書き換えるためのフィルターです。標準入力で親文字列を受け取り、標準出力で新しい親文字列を出力します。親文字列は 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>)…​]" の形式の引数と、標準入力からのログメッセージとともに呼び出されます。コミットIDは標準出力で出力されると期待されます。

特別な拡張として、コミットフィルターは複数のコミットIDを出力できます。その場合、元のコミットの書き換えられた子コミットは、それらすべてを親として持ちます。

このフィルターでは、便利な map 関数やその他の便利な関数も使用できます。例えば、skip_commit "$@" を呼び出すと現在のコミットをスキップします(ただし変更はスキップしません!変更もスキップしたい場合は、代わりに git rebase を使用してください)。

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

--tag-name-filter <command>

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

元のタグは削除されませんが、上書きできます。タグを単に更新するには、「--tag-name-filter cat」を使用します。この場合、非常に注意して、変換がうまくいかなかった場合に備えて古いタグをバックアップしておいてください。

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

--prune-empty

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

--original <namespace>

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

-d <directory>

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

-f
--force

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

--state-branch <branch>

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

<rev-list options>…​

git rev-list の引数。これらのオプションに含まれるすべての正の参照が書き換えられます。--all などのオプションも指定できますが、それらを git filter-branch オプションから分離するには -- を使用する必要があります。祖先への再マッピング を含みます。

祖先への再マッピング

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

終了ステータス

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

すべてのコミットからファイル(機密情報や著作権侵害を含む)を削除したいと仮定します。

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

ただし、一部のコミットのツリーにファイルがない場合、単純な rm filename はそのツリーとコミットに対して失敗します。したがって、代わりに rm -f filename をスクリプトとして使用したい場合があります。

git rm--index-filter を組み合わせると、著しく高速なバージョンが得られます。しかし、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を親とするマージコミットになります。

注意:コミットによって導入され、後続のコミットによって元に戻されていない変更は、書き換えられたブランチに残ります。コミットとともに変更を破棄したい場合は、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 を使用してください。

リポジトリを小さくするには2つの方法があります。より安全な方法は、元のリポジトリをそのまま残すクローンです。

  • git clone file:///path/to/repo でクローンします。クローンには削除されたオブジェクトは含まれません。git-clone[1] を参照してください。(プレーンなパスでクローンするとすべてがハードリンクされることに注意してください!)

何らかの理由でクローンを作成したくない場合は、代わりに次の点を(この順序で)確認してください。これは非常に破壊的なアプローチなので、バックアップを作成するか、クローンに戻してください。警告しました。

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

  • git reflog expire --expire=now --all ですべての参照ログを期限切れにします。

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

パフォーマンス

git-filter-branch のパフォーマンスは非常に遅く、その設計上、後方互換性のある実装では高速化することは不可能です。

  • ファイルを編集する際、git-filter-branchは設計上、元のリポジトリに存在した各コミットをすべてチェックアウトします。リポジトリに 10^5 個のファイルと 10^5 個のコミットがあり、各コミットが5個のファイルしか変更しない場合でも、git-filter-branchは(最大でも)5*10^5 個の一意なブロブしか存在しないにもかかわらず、10^10 回の変更を強制します。

  • git-filter-branch にコミットで変更されたファイルのみを処理させようとすると、次の2つの問題が発生します。

    • ユーザーが単純にファイルをリネームしようとしている場合、削除に関して問題に遭遇します(存在しないファイルを削除しようとすると何もしないように見えるため、任意のユーザーが提供するシェルを介してリネームが発生した場合、ファイルのリネームをまたいで削除を再マッピングするには多少の工夫が必要です)。

    • リネームのためのマッピングと削除のからくりに成功したとしても、技術的には後方互換性を侵害します。なぜなら、ユーザーはファイルの内容や名前のみに基づいてフィルタリングするのではなく、コミットのトポロジーに依存する方法でファイルをフィルタリングすることが許可されているからです(ただし、これは野外では観察されていません)。

  • ファイルを編集する必要がなく、名前の変更や削除など、ファイルのチェックアウトを避けられる場合(つまり、--index-filter を使用できる場合)でも、フィルターにはシェルスニペットを渡すことになります。これは、すべてのコミットに対して、それらのフィルターを実行できる準備された Git リポジトリが必要であることを意味します。これはかなりのセットアップです。

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

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

  • git-filter-branch 自体はシェルスクリプトで書かれており、それが多少遅い原因となっています。これは後方互換性を保ったまま修正できる唯一のパフォーマンス問題ですが、git-filter-branch の設計に内在する上記の問題に比べると、ツールの言語自体は比較的小さな問題です。

    • 余談:残念ながら、人々はシェルで書かれているという側面に固執しがちで、git-filter-branch を別の言語で書き直してパフォーマンス問題を解決できないかと定期的に質問します。しかし、これは設計におけるより大きな本質的な問題を無視しているだけでなく、期待されるほど効果がないでしょう。もし git-filter-branch 自体がシェルでなければ、便利な関数 (map()、skip_commit() など) や --setup 引数はプログラムの開始時に一度実行されるのではなく、すべてのユーザーフィルターの前に前置される必要があり (したがって、コミットごとに再実行されることになります)。

git filter-repo ツールは git-filter-branch の代替であり、これらのパフォーマンス問題や安全性問題 (下記参照) の影響を受けません。既存のツールが git-filter-branch に依存している場合、git filter-repo は、いくつかの注意点があるものの、ドロップインの git-filter-branch 代替である filter-lamely も提供します。filter-lamely は git-filter-branch と同じ安全性問題をすべて抱えていますが、少なくともパフォーマンス問題は少し改善されています。

安全性

git-filter-branch には多くの落とし穴があり、リポジトリを簡単に破損させたり、元の状態よりもひどい状態になったりする可能性があります。

  • 誰かが「機能しテストされたフィルター」のセットを持っていて、それを同僚に文書化したり提供したりした場合、同僚が異なるOSで同じコマンドを実行すると、それらが機能しなかったりテストされていなかったりすることがあります(git-filter-branch のmanページにあるいくつかの例もこれに影響されます)。BSDとGNUのユーザーランドの違いは本当に厄介です。運が良ければエラーメッセージが出力されます。しかし、同じくらい、コマンドは要求されたフィルタリングを行わないか、または望まない変更を加えて黙って破損させます。望まない変更は数個のコミットにしか影響しない場合があるため、必ずしも明らかではありません。(問題が必ずしも明らかではないという事実は、書き換えられた履歴がしばらく使用されるまで気づかれず、その時点で別の書き換えのための旗日を正当化することが本当に難しいことを意味します。)

  • ファイル名にスペースが含まれていると、シェルパイプラインで問題を引き起こすため、シェルスクリプトで誤って処理されることがよくあります。誰もが find -print0, xargs -0, git-ls-files -z などに精通しているわけではありません。これらの知識がある人でも、フィルタリングを行う人がプロジェクトに参加する前に、誰かがリポジトリ内のそのようなファイルをリネームしたため、そのようなフラグは関係ないと思い込むかもしれません。そして、多くの場合、スペースのある引数の処理に精通している人でも、あらゆる問題が発生する可能性を考えているわけではないため、そうしないことがあります。

  • 非ASCIIファイル名は、意図したディレクトリにあるにもかかわらず、黙って削除されることがあります。必要なパスのみを保持するには、通常、git ls-files | grep -v ^WANTED_DIR/ | xargs git rm のようなパイプラインが使用されます。ls-files は必要に応じてファイル名を引用符で囲むだけなので、人々はファイルの一つが正規表現に一致しなかったことに気づかないかもしれません (少なくとも手遅れになるまで)。そうです、core.quotePath を知っている人はこれを回避できます (ただし、\t、\n、または " のような他の特殊文字がある場合は別です)、grep 以外のものと ls-files -z を使用する人はこれを回避できますが、彼らがそうするとは限りません。

  • 同様に、ファイルを移動する際に、非ASCII文字や特殊文字を含むファイル名が、二重引用符文字を含む別のディレクトリに移動してしまうことがあります。(これは技術的には引用符に関する上記の問題と同じですが、問題として現れる可能性のある興味深い異なる方法かもしれません。)

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

    • 部分的な履歴の書き換えのみを行うというデフォルト設定 (--all はデフォルトではなく、例も少ない)

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

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

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

  • 次のいずれかの問題により、注釈付きタグが誤って軽量タグに変換される可能性があります。

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

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

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

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

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

  • --prune-empty が指定されていない場合、フィルタリング処理によって大量の混乱を招く空のコミットが作成される可能性があります。

  • --prune-empty を指定すると、フィルタリングによって空になったコミットだけでなく、フィルタリング操作前に意図的に配置された空のコミットも剪定されます。

  • --prune-empty が指定されていても、空のコミットが見落とされて残ることがあります(比較的まれなバグですが、発生します…​)

  • 些細な問題ですが、リポジトリ内のすべての名前とメールを更新することを目的とするユーザーは、--env-filter を使用すると、作成者とコミッターのみが更新され、タガーが抜けてしまう可能性があります。

  • ユーザーが複数のタグを同じ名前にマッピングする --tag-name-filter を提供した場合、警告やエラーは提供されません。git-filter-branch は、文書化されていない事前定義された順序で各タグを上書きし、最終的に1つのタグしか残りません。(git-filter-branch の回帰テストはこの驚くべき動作を必要とします。)

また、git-filter-branch のパフォーマンスの低さが、しばしば安全性問題を引き起こします。

  • いくつかのファイルを削除するなどの些細な変更を行う場合を除き、目的のフィルタリングを行う正しいシェルスクリプトを作成することは、時には困難です。残念ながら、人々はスクリプトが正しいか間違っているかを試行錯誤で学びますが、その正誤は特殊な状況(ファイル名にスペースが含まれているか、非ASCIIファイル名か、奇妙な作者名やメールアドレスか、無効なタイムゾーンか、グラフや置換オブジェクトの存在など)によって異なるため、長い時間を待ってエラーが発生し、再起動する必要があるかもしれません。git-filter-branch のパフォーマンスは非常に悪いため、このサイクルは苦痛であり、慎重に再確認するのに利用できる時間が減ります(書き換えを行う人の忍耐力に与える影響は言うまでもありません)。この問題は、壊れたフィルターからのエラーが長時間表示されないか、出力の海に埋もれてしまう可能性があるため、さらに悪化します。さらに悪いことに、壊れたフィルターはしばしば黙って間違った書き換えを引き起こします。

  • さらに、ユーザーがついに動作するコマンドを見つけても、当然、それを共有したいと考えます。しかし、彼らは自分のリポジトリにはなかった特殊なケースが他人のリポジトリにはあることを知らないかもしれません。そのため、異なるリポジトリを持つ他の人が同じコマンドを実行すると、上記の問題に遭遇します。あるいは、ユーザーは特殊なケースのために検証されたコマンドを実行するが、上記のように、動作しない異なるOSで実行するかもしれません。

GIT

git[1]スイートの一部

scroll-to-top