# git-filter-branch
> 原文: [https://git-scm.com/docs/git-filter-branch](https://git-scm.com/docs/git-filter-branch)
## 名稱
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>…?]
```
## 描述
允許您通過重寫< rev-list options>中提到的分支來重寫Git修訂歷史記錄,在每個修訂版上應用自定義過濾器。這些過濾器可以修改每個樹(例如,刪除文件或對所有文件運行perl重寫)或有關每個提交的信息。否則,將保留所有信息(包括原始提交時間或合并信息)。
該命令只會重寫命令行中提到的_正_ refs(例如,如果你傳遞 _a..b_ ,則只會重寫 _b_ )。如果您未指定過濾器,則將重新提交提交而不進行任何更改,這通常無效。然而,這在將來用于補償某些Git錯誤等方面可能是有用的,因此允許這樣的使用。
**注**:該命令用于表示`refs/replace/`命名空間中的`.git/info/grafts`文件和引用。如果您定義了任何移植物或替換引物,則運行此命令將使它們成為永久性的。
**警告**!重寫的歷史將具有所有對象的不同對象名稱,并且不會與原始分支會聚。您將無法在原始分支的頂部輕松推送和分發重寫的分支。如果您不知道完整的含義,請不要使用此命令,并且無論如何都要避免使用它,如果簡單的單個提交就足以解決您的問題。 (有關重寫已發布歷史記錄的詳細信息,請參閱 [git-rebase [1]](https://git-scm.com/docs/git-rebase) 中的“從上游重新恢復”部分。)
始終驗證重寫版本是否正確:原始引用(如果與重寫版本不同)將存儲在命名空間 _refs / original /_ 中。
請注意,由于此操作的I / O非常昂貴,因此使用`-d`選項重定向磁盤上的臨時目錄可能是個好主意。在tmpfs上。據說加速非常明顯。
### 過濾器
過濾器按以下列出的順序應用。 < command>始終使用 _eval_ 命令在shell上下文中評估參數(出于技術原因,提交過濾器有明顯的例外)。在此之前,`$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]過濾器運行后的](https://git-scm.com/docs/git-commit-tree)。
如果對< command>進行任何評估返回非零退出狀態,整個操作將被中止。
_map_ 函數可用于獲取“原始sha1 id”參數,如果已經重寫了提交,則輸出“重寫的sha1 id”,否則輸出“original sha1 id”;如果您的提交過濾器發出多次提交, _map_ 函數可以在單獨的行上返回多個ID。
## OPTIONS
```
--setup <command>
```
這不是為每次提交執行的實際過濾器,而是在循環之前進行一次設置。因此,尚未定義特定于提交的變量。由于技術原因,除了提交過濾器之外,可以在以下過濾器步驟中使用或修改此處定義的函數或變量。
```
--subdirectory-filter <directory>
```
只查看觸及給定子目錄的歷史記錄。結果將包含該目錄(并且僅包含該目錄)作為其項目根目錄。意味著[重新映射到祖先](#Remap_to_ancestor)。
```
--env-filter <command>
```
如果您只需要修改將在其中執行提交的環境,則可以使用此過濾器。具體來說,您可能想要重寫作者/提交者名稱/電子郵件/時間環境變量(有關詳細信息,請參閱 [git-commit-tree [1]](https://git-scm.com/docs/git-commit-tree) )。
```
--tree-filter <command>
```
這是用于重寫樹及其內容的過濾器。參數在shell中計算,工作目錄設置為簽出樹的根。然后按原樣使用新樹(自動添加新文件,自動刪除消失文件 - 既沒有.gitignore文件也沒有任何其他忽略規則**有任何影響**!)。
```
--index-filter <command>
```
這是重寫索引的過濾器。它類似于樹過濾器,但不檢查樹,這使得它更快。經常與`git rm --cached --ignore-unmatch ...`一起使用,請參見下面的示例。對于毛病例,請參見 [git-update-index [1]](https://git-scm.com/docs/git-update-index) 。
```
--parent-filter <command>
```
這是用于重寫提交的父列表的過濾器。它將在stdin上接收父字符串,并在stdout上輸出新的父字符串。父字符串采用 [git-commit-tree [1]](https://git-scm.com/docs/git-commit-tree) 中描述的格式:初始提交為空,正常提交為“-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;在這種情況下,原始提交的重寫子項將全部作為父項。
您也可以在此過濾器中使用_地圖_便利功能,以及其他便利功能。例如,調用 _skip_commit“$ @”_將省略當前提交(但不會更改!如果你想要,請改用 _git rebase_ )。
如果您不希望保持對單個父項的提交并且不對樹進行更改,也可以使用`git_commit_non_empty_tree "$@"`而不是`git commit-tree "$@"`。
```
--tag-name-filter <command>
```
這是用于重寫標記名稱的過濾器。傳遞時,將為每個指向重寫對象(或指向重寫對象的標記對象)的標記ref調用它。原始標記名稱通過標準輸入傳遞,新標記名稱在標準輸出上是預期的。
原始標簽不會被刪除,但可以被覆蓋;使用“--tag-name-filter cat”來簡單地更新標簽。在這種情況下,請務必小心并確保備份舊標簽,以防轉換發生沖突。
支持幾乎正確的標記對象重寫。如果標記附加了消息,則將使用相同的消息,作者和時間戳創建新的標記對象。如果標簽附有簽名,則簽名將被刪除。根據定義,不可能保留簽名。這是“幾乎”正確的原因,因為理想情況下,如果標簽沒有改變(指向同一個對象,具有相同的名稱等),它應該保留任何簽名。情況并非如此,簽名將永遠刪除,買家要小心。也不支持更改作者或時間戳(或標記消息)。指向其他標記的標記將被重寫以指向底層提交。
```
--prune-empty
```
某些過濾器將生成空提交,使樹保持不變。如果git-filter-branch只有一個或零個非修剪父項,則該選項指示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 /_ 開始refs時,除非被強制。
```
--state-branch <branch>
```
此選項將導致在啟動時從命名分支加載從舊對象到新對象的映射,并在退出時將其保存為該分支的新提交,從而實現大樹的增量。如果_< branch>_ 不存在它將被創建。
```
<rev-list options>…?
```
_git rev-list_ 的參數。這些選項包含的所有正面參考都被重寫。您也可以指定`--all`等選項,但必須使用`--`將它們與 _git filter-branch_ 選項分開。意味著[重新映射到祖先](#Remap_to_ancestor)。
### 重新映射到祖先
通過使用 [git-rev-list [1]](https://git-scm.com/docs/git-rev-list) 參數,例如路徑限制器,您可以限制被重寫的修訂集。但是,命令行上的正數引用是有區別的:我們不會讓它們被這些限制器排除在外。為此,它們被重寫為指向最近的未被排除的祖先。
## 退出狀態
成功時,退出狀態為`0`。如果過濾器找不到任何要重寫的提交,則退出狀態為`2`。在任何其他錯誤上,退出狀態可以是任何其他非零值。
## 例子
假設您要從所有提交中刪除文件(包含機密信息或侵犯版權):
```
git filter-branch --tree-filter 'rm filename' HEAD
```
但是,如果某個提交的樹中沒有該文件,則該樹和提交的簡單`rm filename`將失敗。因此,您可能希望使用`rm -f filename`作為腳本。
將`--index-filter`與 _git 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;
}
```
移位魔法首先拋棄樹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`可以幫助您找到重命名。
* 你真的過濾了所有的refs:在調用git-filter-branch時使用`--tag-name-filter cat -- --all`。
然后有兩種方法可以獲得更小的存儲庫。更安全的方法是克隆,保持原始原封不動。
* 用`git clone file:///path/to/repo`克隆它。克隆將沒有刪除的對象。參見 [git-clone [1]](https://git-scm.com/docs/git-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`使所有reflogs到期。
* 垃圾收集所有未引用的對象`git gc --prune=now`(或者如果你的git-gc不夠新,不支持`--prune`的參數,請改用`git repack -ad; git prune`)。
## 筆記
git-filter-branch允許您對Git歷史記錄進行復雜的shell腳本重寫,但如果您只是_刪除不需要的數據_(如大文件或密碼),則可能不需要這種靈活性。對于那些操作,您可能需要考慮 [BFG Repo-Cleaner](http://rtyley.github.io/bfg-repo-cleaner/) ,一種基于JVM的git-filter-branch替代方案,對于這些用例通常至少快10-50倍,并且具有完全不同的特性:
* 一旦,任何特定版本的文件都會被準確清除_。與git-filter-branch不同,BFG不會根據歷史記錄中提交的位置或時間以不同方式處理文件。這個約束給出了BFG的核心性能優勢,并且非常適合清理壞數據的任務 - 你不關心哪里有壞數據,你只是想讓它_消失_。_
* 默認情況下,BFG充分利用多核機器,并行清理提交文件樹。 git-filter-branch按順序清除提交(即以單線程方式),盡管可以在針對每個提交執行的腳本中編寫包含其自身并行性的過濾器。
* [命令選項](http://rtyley.github.io/bfg-repo-cleaner/#examples)比git-filter分支更具限制性,專門用于刪除不需要的數據的任務 - 例如:`--strip-blobs-bigger-than 1M`。
## GIT
部分 [git [1]](https://git-scm.com/docs/git) 套件
- git
- git-config
- git-help
- git-init
- git-clone
- git-add
- git-status
- git-diff
- git-commit
- git-reset
- git-rm
- git-mv
- git-branch
- git-checkout
- git-merge
- git-mergetool
- git-log
- git-stash
- git-tag
- git-worktree
- git-fetch
- git-pull
- git-push
- git-remote
- git-submodule
- git-show
- git-log
- git-shortlog
- git-describe
- git-apply
- git-cherry-pick
- git-rebase
- git-revert
- git-bisect
- git-blame
- git-grep
- gitattributes
- giteveryday
- gitglossary
- githooks
- gitignore
- gitmodules
- gitrevisions
- gittutorial
- gitworkflows
- git-am
- git-format-patch
- git-send-email
- git-request-pull
- git-svn
- git-fast-import
- git-clean
- git-gc
- git-fsck
- git-reflog
- git-filter-branch
- git-instaweb
- git-archive
- git-bundle
- git-daemon
- git-update-server-info
- git-cat-file
- git-check-ignore
- git-checkout-index
- git-commit-tree
- git-count-objects
- git-diff-index
- git-for-each-ref
- git-hash-object
- git-ls-files
- git-merge-base
- git-read-tree
- git-rev-list
- git-rev-parse
- git-show-ref
- git-symbolic-ref
- git-update-index
- git-update-ref
- git-verify-pack
- git-write-tree