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 重寫)或是每個提交的資訊。除此之外,所有資訊(包括原始提交時間或合併資訊)都將被保留。

此指令只會重寫命令列中提到的正向 (positive) 參考(例如,如果您傳入 a..b,則只有 b 會被重寫)。如果您未指定任何篩選器,提交將會被重新提交而不進行任何變更,這通常不會產生任何影響。儘管如此,這在未來對於彌補某些 Git 錯誤等情況可能會很有用,因此允許此種用法。

注意:此指令會遵循 .git/info/grafts 檔案以及 refs/replace/ 命名空間中的參考。如果您定義了任何嫁接 (grafts) 或取代參考,執行此指令將使它們永久化。

警告!重寫後的歷史對於所有物件都會有不同的物件名稱,且不會與原始分支合併。您將無法輕易地在原始分支之上推送並分發重寫後的分支。如果您不了解其完整含義,請勿使用此指令,且如果簡單的單一提交足以解決您的問題,也請避免使用它。(關於重寫已發布歷史的更多資訊,請參閱 git-rebase[1] 中的「從上游變基中復原」章節。)

請務必驗證重寫後的版本是否正確:如果原始參考與重寫後的參考不同,原始參考將會儲存在 refs/original/ 命名空間中。

請注意,由於此操作的 I/O 負擔非常大,使用 -d 選項將暫存目錄重新導向至磁碟外(例如 tmpfs)會是個好主意。據報導,這樣可以顯著提升速度。

篩選器

篩選器會依照下列順序列出並套用。<command> 引數總是在 shell 環境中使用 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] 建立的替換提交的作者和提交者身分。

如果任何 <command> 的求值傳回非零的退出狀態,整個操作將會中止。

有一個 map 函數可以使用,它接受一個「原始 sha1 id」引數,若提交已被重寫則會輸出「重寫後的 sha1 id」,否則輸出「原始 sha1 id」;如果您的提交篩選器發出了多個提交,map 函數可以在不同行傳回多個 id。

選項

--setup <command>

這不是為每個提交執行的實際篩選器,而是在迴圈開始前的一次性設定。因此,此時尚未定義任何與提交相關的變數。此處定義的函數或變數可以在接下來的篩選步驟中使用或修改(因技術原因,提交篩選器除外)。

--subdirectory-filter <directory>

僅查看涉及給定子目錄的歷史記錄。結果將包含該目錄(且僅該目錄)作為其專案根目錄。隱含了重映射至祖先

--env-filter <command>

如果您只需要修改執行提交的環境,可以使用此篩選器。具體來說,您可能想要重寫作者/提交者姓名/電子郵件/時間環境變數(詳情請參閱 git-commit-tree[1])。

--tree-filter <command>

這是用於重寫樹狀結構及其內容的篩選器。該引數會在 shell 中進行求值,且工作目錄設定為已簽出樹的根目錄。新的樹狀結構將按原樣使用(新檔案會自動新增,消失的檔案會自動移除 - .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>

這是用於重寫提交訊息的篩選器。引數會在 shell 中求值,並以原始提交訊息作為標準輸入;其標準輸出會被用作新的提交訊息。

--commit-filter <command>

這是執行提交的篩選器。如果指定了此篩選器,它將取代 git commit-tree 指令被呼叫,並使用格式為 "<TREE_ID> [(-p <PARENT_COMMIT_ID>)…​]" 的引數,且將記錄訊息透過 stdin 傳入。預期在 stdout 輸出提交 ID。

作為一項特殊擴充,提交篩選器可以發出多個提交 ID;在這種情況下,原始提交的重寫後子提交將會把這些 ID 全部作為父級。

您可以在此篩選器中使用 map 便利函數,也可以使用其他便利函數。例如,呼叫 skip_commit "$@" 將會跳過目前的提交(但不會跳過其變更!如果您想要跳過變更,請改用 git rebase)。

如果您不希望保留只有單一父級且對樹沒有變更的提交,您也可以使用 git_commit_non_empty_tree "$@" 來取代 git commit-tree "$@"

--tag-name-filter <command>

這是用於重寫標籤名稱的篩選器。當傳入時,它將針對每個指向已重寫物件(或指向已重寫物件的標籤物件)的標籤參考進行呼叫。原始標籤名稱會透過標準輸入傳入,並預期在標準輸出中得到新的標籤名稱。

原始標籤不會被刪除,但可以被覆寫;使用 "--tag-name-filter cat" 來單純更新標籤。在這種情況下,請務必非常小心,並確保已備份舊標籤,以防轉換過程出現問題。

支援近乎正確的標籤物件重寫。如果標籤附有訊息,將會建立一個包含相同訊息、作者和時間戳記的新標籤物件。如果標籤附有簽章,簽章將會被移除。根據定義,保留簽章是不可能的。之所以說是「近乎」正確,是因為理想情況下,如果標籤沒有變更(指向相同的物件、有相同的名稱等),它應該保留任何簽章。但事實並非如此,簽章總是會被移除,請買家自負。此外,不支援更改作者或時間戳記(標籤訊息也是如此)。指向其他標籤的標籤將會被重寫以指向底層的提交。

--prune-empty

某些篩選器會產生對樹沒有影響的空提交。此選項指示 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 作為腳本。

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

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

如果您需要將 Acked-by 行新增至(例如)最後 10 個提交(其中沒有合併),請使用此指令

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 在您告知它之前,會竭力避免遺失您的物件。首先確保

  • 您確實移除了檔案名稱的所有變體(如果 blob 在其生命週期內被移動過)。git log --name-only --follow --all -- filename 可以協助您找到重新命名。

  • 您確實篩選了所有參考:呼叫 git-filter-branch 時使用 --tag-name-filter cat -- --all

接下來有兩種方法可以獲得較小的儲存庫。較安全的方法是 clone,它能保持您的原始儲存庫完整。

  • 使用 git clone file:///path/to/repo 進行 clone。複製本將不會有被移除的物件。請參閱 git-clone[1]。(注意:使用純路徑進行 clone 會將所有內容進行硬連結!)

如果您因為任何原因真的不想 clone 它,請改為檢查以下幾點(順序如下)。這是一種破壞性極強的方法,所以請進行備份或回到 clone 的方法。已經警告過您了。

  • 移除由 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 的設計會檢查原始儲存庫中存在的每一個提交。如果您的儲存庫有 10^5 個檔案和 10^5 個提交,但每個提交只修改了 5 個檔案,那麼 git-filter-branch 會強迫您進行 10^10 次修改,儘管(最多)只有 5*10^5 個唯一 blob。

  • 如果您想走捷徑,試圖讓 git-filter-branch 只在提交中修改的檔案上工作,那麼會發生兩件事

    • 只要使用者只是試圖重新命名檔案,您就會遇到刪除問題(因為嘗試刪除不存在的檔案看起來就像沒有進行任何操作;當重新命名透過使用者提供的任意 shell 發生時,要跨檔案重新命名來映射刪除需要一些詭計)

    • 即使您成功處理了重新命名映射刪除的詭計,在技術上您仍然違反了向後相容性,因為使用者可以以取決於提交拓撲的方式過濾檔案,而不是單純基於檔案內容或名稱進行過濾(儘管這在現實中尚未發現)。

  • 即使您不需要編輯檔案,而只是想例如重新命名或移除某些檔案,從而可以避免簽出每個檔案(即您可以使用 --index-filter),您仍然會為您的篩選器傳入 shell 片段。這意味著對於每個提交,您都必須有一個準備好的 git 儲存庫,以便這些篩選器可以執行。這是一個重大的設定開銷。

  • 此外,git-filter-branch 每個提交會建立或更新幾個額外的檔案。其中一些是為了支援 git-filter-branch 提供的便利函數(如 map()),而另一些則是為了追蹤內部狀態(但也可以被使用者篩選器存取;git-filter-branch 的一個回歸測試就是這麼做的)。這實質上等於使用檔案系統作為 git-filter-branch 與使用者提供篩選器之間的 IPC 機制。磁碟往往是一種緩慢的 IPC 機制,寫入這些檔案也有效地代表了我們在每次提交時都會碰到的獨立行程之間的強制同步點。

  • 使用者提供的 shell 指令可能會涉及指令管線,導致每個提交產生許多行程。建立並執行另一個行程在不同作業系統間所需的時間差異很大,但在任何平台上,相較於呼叫函數,這都非常緩慢。

  • git-filter-branch 本身是用 shell 編寫的,這有點慢。這是唯一可以在保持向後相容的情況下修復的效能問題,但與上述 git-filter-branch 設計固有的問題相比,工具本身的語言是一個相對較小的問題。

    • 側記:不幸的是,人們傾向於專注於它是用 shell 編寫的這一點,並定期詢問 git-filter-branch 是否可以改寫為另一種語言來解決效能問題。這不僅忽略了設計中更大的內在問題,而且幫助比您預期的要少:如果 git-filter-branch 本身不是 shell,那麼便利函數(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 充滿了陷阱,容易導致儲存庫毀損,或最終弄得一團糟,情況比開始時更糟。

  • 有人可能擁有一組「經過測試且有效的篩選器」,並將其記錄下來或提供給同事,但同事在不同的作業系統上執行時,同樣的指令卻無法運作或未經測試(git-filter-branch 手冊頁中的一些範例也受到此影響)。BSD 與 GNU 使用者空間的差異真的會讓人吃虧。運氣好點,會噴出錯誤訊息。但同樣可能的是,指令要麼沒有執行要求的過濾,要麼透過進行某些非預期的變更而無聲地毀損資料。非預期的變更可能只影響少數提交,所以不一定顯而易見。(問題不一定會顯而易見,意味著它們很可能在重寫後的歷史被使用很久之後才被發現,到了那個時候,要為另一次重寫進行停機維護是非常困難的。)

  • 含有空格的檔案名稱經常被 shell 片段錯誤處理,因為它們會給 shell 管線造成問題。並非每個人都熟悉 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 或 "),而使用 ls-files -z 搭配 grep 以外工具的人也可以避免這種情況,但這並不代表他們會這麼做。

  • 同樣地,當移動檔案時,可能會發現含有非 ASCII 或特殊字元的檔案名稱最終落在了包含雙引號字元的不同目錄中。(這在技術上與上述引號問題相同,但也許是一種有趣的、不同的呈現問題的方式。)

  • 太容易意外地將舊歷史和新歷史混合在一起。任何工具都有可能發生這種情況,但 git-filter-branch 幾乎是引誘使用者犯錯。運氣好的話,唯一的缺點是使用者因為不知道如何縮減儲存庫並移除舊內容而感到沮喪。運氣不好的話,他們會將舊歷史和新歷史合併,並最終導致每個提交都有多個「副本」,其中一些包含不想要或敏感的檔案,而另一些則沒有。這透過多種不同方式發生

    • 預設只進行部分歷史重寫(--all 不是預設值,且範例中很少出現)

    • 執行後沒有自動清理機制

    • --tag-name-filter(當用於重新命名標籤時)不會移除舊標籤,而只是以新名稱新增標籤

    • 幾乎沒有提供教育資訊來告知使用者重寫的後果以及如何避免混合舊歷史和新歷史。例如,此手冊頁討論了使用者需要了解他們需要為所有分支在新的歷史基礎上變基(或刪除並重新 clone),但這只是眾多需要考慮的顧慮之一。請參閱 git filter-repo 手冊頁的「討論」一節以獲取更多詳細資訊。

  • 附註標籤可能會因以下兩個問題之一,意外地轉換為輕量標籤

    • 有人進行了歷史重寫,意識到弄錯了,從 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,這只會更新作者和提交者,而遺漏了打標籤者 (taggers)。

  • 如果使用者提供了一個將多個標籤映射到相同名稱的 --tag-name-filter,則不會提供任何警告或錯誤;git-filter-branch 只會以某種未記錄的預定義順序覆寫每個標籤,導致最後只剩下一個標籤。(git-filter-branch 的一個回歸測試要求這種令人驚訝的行為。)

此外,git-filter-branch 的糟糕效能經常導致安全問題

  • 想出正確的 shell 片段來進行您想要的過濾有時很困難,除非您只是進行瑣碎的修改,例如刪除幾個檔案。不幸的是,人們通常透過嘗試來了解片段是否正確,但正確與否可能會因特殊情況而異(檔案名稱中的空格、非 ASCII 檔案名稱、有趣的作者姓名或電子郵件、無效的時區、嫁接或取代物件的存在等),這意味著他們可能必須等待很長時間,遇到錯誤,然後重新啟動。git-filter-branch 的效能非常糟糕,以至於這個循環很痛苦,減少了仔細重新檢查的時間(更不用說即使他們在技術上確實有更多時間,這對進行重寫的人的耐心有什麼影響)。這個問題更加複雜,因為破碎篩選器產生的錯誤可能在很長一段時間內都不會顯示,或者在海量的輸出中丟失。更糟糕的是,破碎的篩選器通常只會導致無聲的錯誤重寫。

  • 最重要的是,即使使用者最終找到了可以運作的指令,他們自然也會想分享它們。但他們可能不知道他們的儲存庫沒有某些其他人有的特殊情況。因此,當其他擁有不同儲存庫的人執行相同的指令時,他們就會遭遇上述問題。或者,使用者只是執行了真正針對特殊情況進行過審核的指令,但他們在無法運作的作業系統上執行了它,如上所述。

GIT

git[1] 套件的一部分