日本語 ▾ トピック ▾ 最新バージョン ▾ 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 の地雷について学ぶために 安全性 (および パフォーマンス) を注意深く読み、そこで挙げられている危険性を可能な限り回避するようにしてください。

説明

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

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

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

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

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

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

フィルター

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

の評価が非ゼロの終了ステータスを返した場合、操作全体が中断されます。

「元の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

一部のフィルターは、ツリーをそのままにした空のコミットを生成します。このオプションは、そのようなコミットが正確に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/ で始まる参照がある場合、強制されない限り起動を拒否します。

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

shift の魔法はまずツリー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 オプションは、コミッターや作者の身元情報を変更するために使用できます。例えば、設定ミスによって user.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 で、すべてのリフロッグを期限切れにします。

  • git gc --prune=now ですべての参照されていないオブジェクトをガベージコレクトします (または、お使いの git-gc が --prune の引数をサポートするほど新しくない場合は、代わりに 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 は必要な場合にのみファイル名を引用符で囲むため、ユーザーはファイルの1つが正規表現に一致しなかったことに気づかないかもしれません(少なくとも、手遅れになるまでは)。はい、core.quotePath について知っている人はこれを避けることができます(ただし、\t, \n, " などの特殊文字がある場合は除きます)。また、grep 以外のものと ls-files -z を使用する人はこれを避けることができますが、それが彼らがそうするとは限りません。

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

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

    • デフォルトでは履歴の部分的な書き換えのみが行われること(--all はデフォルトではなく、例も少ない)

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

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

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

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

    • 誰かが履歴の書き換えを行い、失敗したことに気づき、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 に誘導されることがありますが、これは作者とコミッターのみを更新し、tagger を見落とします。

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

また、git-filter-branch の劣悪なパフォーマンスが、しばしば安全性問題につながります。

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

  • さらに最悪なことに、ユーザーがついに動作するコマンドを見つけたとしても、当然それを共有したいと思うでしょう。しかし、彼らのリポジトリには、他の誰かのリポジトリにあるような特殊なケースがないことに気づいていないかもしれません。そのため、異なるリポジトリを持つ他の誰かが同じコマンドを実行すると、上記の問題にぶつかることになります。あるいは、ユーザーは特殊なケースのために実際に検証されたコマンドを実行するが、それが動作しない別のOSで実行してしまう、ということも上記で述べた通りです。

GIT

git[1] スイートの一部

scroll-to-top