Git
English ▾ トピック ▾ 最新バージョン ▾ 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による書き換えの実行)したり、各コミットに関する情報を変更したりすることができます。それ以外の情報(元のコミット時刻やマージ情報など)はすべて保存されます。

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

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

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

書き換えられたバージョンが正しいことを常に確認してください。元のrefは、書き換えられたrefと異なる場合、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>

これは、コミットの親リストを書き換えるためのフィルターです。stdinで親文字列を受け取り、stdoutに新しい親文字列を出力します。親文字列の形式は、git-commit-tree[1]で説明されているとおりです。最初のコミットの場合は空、通常のコミットの場合は「-p 親」、マージコミットの場合は「-p 親1 -p 親2 -p 親3 …」です。

--msg-filter <command>

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

--commit-filter <command>

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

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

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

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

--tag-name-filter <command>

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

元のタグは削除されませんが、上書きできます。「--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になります。その他のエラーが発生した場合、終了ステータスは他の非ゼロ値になります。

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

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

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

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

--env-filterオプションを使用して、コミッタと/または作成者の識別情報を変更できます。たとえば、ユーザー.emailの設定ミスによりコミットの識別情報が間違っていることがわかった場合、プロジェクトを公開する前に、次のように修正できます。

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で、すべてのreflogの有効期限を切らせます。

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

パフォーマンス

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

  • ファイルの編集では、git-filter-branchは設計上、元のrepoに存在した状態の各コミットをチェックアウトします。repoに10^5個のファイルと10^5個のコミットがあるが、各コミットは5個のファイルのみを変更する場合、git-filter-branchは10^10個の変更を行うことになります。(多くても)5*10^5個の一意のBLOBしかないにもかかわらずです。

  • git-filter-branchをコミットで変更されたファイルのみに動作させるようにしようとしても、2つのことが起こります。

    • ユーザーが単にファイルの名前を変更しようとしている場合、削除時に問題が発生します(存在しないファイルを削除しようとすると、no-opのように見えます。名前の変更が任意のユーザー提供のシェルを介して行われる場合、名前の変更にわたって削除を再マップするには、いくつかの策略が必要です)。

    • map-deletes-for-renamesの策略に成功しても、技術的には後方互換性を違反します。なぜなら、ユーザーはファイルの内容や名前に基づいてのみフィルタリングするのではなく、コミットのトポロジに依存するファイルのフィルタリングが許可されているためです(ただし、これは実際には観察されていません)。

  • ファイルの編集が必要なく、一部の名前変更や削除のみを行う場合(つまり、--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 は、filter-lamely も提供しています。これは、いくつかの注意点はあるものの、git-filter-branch の代替としてそのまま使用できます。filter-lamely は git-filter-branch と同じ安全上の問題を抱えているものの、パフォーマンスの問題はいくらか軽減されます。

安全性

git-filter-branch は、様々な方法でリポジトリを簡単に破損させたり、開始時よりも悪い状態にしたりする落とし穴だらけです。

  • ドキュメント化または同僚に提供された「動作確認済みのフィルター」のセットを使用している人がいるとします。その同僚が異なるOSで実行すると、同じコマンドが動作しない/テストされていない場合があります(git-filter-branch のマニュアルページにある例も、これの影響を受けています)。BSD と GNU のユーザランドの違いは大きな問題になります。運が良ければ、エラーメッセージが表示されます。しかし、同様に、コマンドが要求されたフィルタリングを実行しなかったり、意図しない変更を加えることで、サイレントに破損することがあります。意図しない変更は、少数のコミットのみに影響を与える可能性があるため、必ずしも明らかではありません。(問題が必ずしも明らかにならないということは、書き換えられた履歴がかなり長い間使用されるまで気付かれずにいる可能性が高く、そうなると、別の書き換えのための別のフラグデーを正当化するのは非常に困難になります。)

  • スペースを含むファイル名は、シェルパイプラインに問題を引き起こすため、シェル スニペットで誤って処理されることがよくあります。find -print0、xargs -0、git-ls-files -z などに精通している人は全員ではありません。これらに精通している人でも、そのようなフラグは関連しないと仮定するかもしれません。なぜなら、フィルタリングを行う人がプロジェクトに参加する前に、他の誰かがリポジトリ内のそのようなファイルの名前を変更したからです。そして多くの場合、スペースを含む引数の処理に精通している人でも、起こりうるすべての問題を考慮するという精神状態にないため、そうしないかもしれません。

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

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

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

    • 部分的な履歴の書き換えのみを行うデフォルト(--all はデフォルトではなく、例もほとんどありません)

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

    • --tag-name-filter(タグの名前を変更するために使用する場合)は、古いタグを削除するのではなく、新しい名前で新しいタグを追加するだけです。

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

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

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

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