從 filter-branch 轉(zhuǎn)換
本文檔面向熟悉 filter-branch 并希望學習如何轉(zhuǎn)換到使用 filter-repo 的人。
目錄
- 基本差異
- [filter-branch 示例的轉(zhuǎn)換](#filter-branch 示例的轉(zhuǎn)換)
- 速查表:額外的轉(zhuǎn)換示例
基本差異
使用 git filter-branch 時,你有一個 git 倉庫,其中每個提交(在你指定的分支或修訂版本內(nèi))都會被檢出,然后你運行一個或多個 shell 命令來將工作副本轉(zhuǎn)換為你想要的最終狀態(tài)。
使用 git filter-repo 時,你實際上獲得了一個編輯工具,可以操作倉庫的 fast-export 序列化。這意味著有一個包含倉庫所有內(nèi)容的輸入流,你通常不是以要運行的命令形式指定過濾器,而是使用許多常見的預定義過濾器,這些過濾器提供各種方式來基于倉庫的組件(如路徑名、文件內(nèi)容、用戶名或電子郵件等)對倉庫進行切片、切塊或修改。這使得常見操作更容易,即使它不如 shell 回調(diào)那么靈活。對于需要更復雜或特殊處理的情況,filter-repo 提供了 Python 回調(diào),可以對從 fast-export 流中填充的數(shù)據(jù)結構進行操作,幾乎可以做任何你想做的事。
filter-branch 默認在倉庫的一個子集上工作,并要求你指定一個或多個分支,這意味著你需要指定 -- --all 來修改所有提交。相比之下,filter-repo 默認重寫所有內(nèi)容,如果你想限制到某個特定的分支集或提交范圍,你需要指定 --refs <rev-list-args>。(但是,以連字符開頭的任何 <rev-list-args> 都不被 filter-repo 接受,因為它們看起來像是不同選項的開始。)
filter-repo 還自動處理額外的問題,比如重寫舊commit ID 的提交消息,使其引用重寫后的commit ID,刪除由于指定的過濾器而變?yōu)榭盏奶峤唬约霸谶^濾操作結束時自動縮小和 gc 倉庫。
filter-branch 示例的轉(zhuǎn)換
刪除文件
filter-branch 手冊提供了三個刪除單個文件的不同示例,基于不同級別的易用性與謹慎性和性能:
git filter-branch --tree-filter 'rm filename' HEAD
- 這個命令會檢出每個提交,運行 rm filename 命令,然后重新提交。
- 如果文件不存在,rm 命令會報錯,但過濾器會繼續(xù)執(zhí)行。
- 這種方法最慢,因為它需要在每個提交上實際檢出文件。
git filter-branch --tree-filter 'rm -f filename' HEAD
- 這個命令與第一個類似,但使用了 rm -f,這意味著"強制刪除"。
- 即使文件不存在,也不會報錯。
- 仍然會檢出每個提交,但比第一個命令稍微健壯一些。
git filter-branch --index-filter 'git rm --cached --ignore-unmatch filename' HEAD
- 這個命令使用 --index-filter,它只操作 Git 索引,不會檢出文件。
- git rm --cached 從 Git 索引中刪除文件,但不觸及工作目錄。
- --ignore-unmatch 選項使得即使文件不在某些提交中也不會報錯。
這是最快和最高效的方法,特別是對于大型倉庫。
所有這些在git filter-repo都變成了
git filter-repo --invert-paths --path filename
提取子目錄
通過以下方式提取子目錄:
git filter-branch --subdirectory-filter foodir -- --all
這是最容易轉(zhuǎn)換的命令之一;它只是變成了
git filter-repo --subdirectory-filter foodir
將整個樹移動到子目錄
保留所有文件但將它們放在新的子目錄中:
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-repo --to-subdirectory-filter newsubdir
刪除某個作者的提交
警告:這對于 filter-branch 和 filter-repo 都是一個糟糕的例子。它并不從倉庫中刪除用戶所做的更改,它只是刪除有問題的提交,同時將其更改壓縮到任何后續(xù)提交中,就好像后續(xù)作者也對這些更改負責一樣。如果你在看這個例子,git rebase 可能更適合你真正想要的。(另見這個解釋 rebase 和 filter-repo 之間差異的說明)
這個 filter-branch 例子
git filter-branch --commit-filter '
if [ "$GIT_AUTHOR_NAME" = "Darl McBribe" ];
then
skip_commit "$@";
else
git commit-tree "$@";
fi' HEAD
變成了
git filter-repo --commit-callback '
if commit.author_name == b"Darl McBribe":
commit.skip()
'
重寫提交消息 -- 刪除文本
通過以下方式從提交消息中刪除 git-svn-id: 行:
git filter-branch --msg-filter '
sed -e "/^git-svn-id:/d"
'
變成了
git filter-repo --message-callback '
return re.sub(b"^git-svn-id:.*\n", b"", message, flags=re.MULTILINE)
'
重寫提交消息 -- 添加文本
通過以下方式向最后十個提交添加 Acked-by 行:
git filter-branch --msg-filter '
cat &&
echo "Acked-by: Bugs Bunny <bunny@bugzilla.org>"
' master~10..master
變成了
git filter-repo --message-callback '
return message + b"Acked-by: Bugs Bunny <bunny@bugzilla.org>\n"
' --refs master~10..master
更改作者/提交者(/標簽者?)信息
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
變成了
# 確保 '<john@example.com> <root@localhost>' 是 .mailmap 中的一行,然后:
git filter-repo --use-mailmap
或
git filter-repo --email-callback '
return email if email != b"root@localhost" else b"john@example.com"
'
(作為額外的好處,這兩種 filter-repo 替代方案也會修復標簽者的電子郵件,而 filter-branch 示例不會)