Git Filter-Repo 使用說明
Git Filter-Repo 使用說明
git filter-repo 是一個強大的工具,用於重寫 Git 歷史記錄。它比 git filter-branch 更快、更安全,是官方推薦的替代方案。
目錄
安裝
macOS
1# 使用 Homebrew
2brew install git-filter-repo
3
4# 或使用 pip
5pip3 install git-filter-repoLinux
1# Ubuntu/Debian
2sudo apt install git-filter-repo
3
4# 或使用 pip
5pip3 install git-filter-repoWindows
1# 使用 pip
2pip install git-filter-repo基本概念
什麼時候使用 git filter-repo?
- 從歷史記錄中移除敏感資訊(密碼、API 金鑰等)
- 移除不小心 commit 的大型檔案
- 將子目錄拆分成獨立的 repository
- 合併多個 repository
- 批量修改 commit 作者資訊
重要提醒
注意:git filter-repo 會重寫 Git 歷史,這意味著:
- 所有 commit hash 都會改變
- 需要 force push 到遠端
- 其他協作者需要重新 clone 或 reset
常用操作
移除檔案或資料夾
移除單一檔案
1git filter-repo --invert-paths --path path/to/file.txt --force移除資料夾
1git filter-repo --invert-paths --path path/to/folder --force移除多個路徑
1git filter-repo --invert-paths --path .env --path secrets/ --path config/credentials.json --force使用 glob 模式移除
1# 移除所有 .log 檔案
2git filter-repo --invert-paths --path-glob '*.log' --force
3
4# 移除所有 node_modules 資料夾
5git filter-repo --invert-paths --path-glob '**/node_modules/*' --force使用正規表達式移除
1# 移除所有 .env 開頭的檔案
2git filter-repo --invert-paths --path-regex '^\.env.*' --force只保留特定路徑
將 repository 縮減為只包含特定資料夾(適用於拆分專案):
1# 只保留 src 資料夾
2git filter-repo --path src --force
3
4# 保留多個路徑
5git filter-repo --path src --path docs --path README.md --force重新命名檔案或資料夾
1# 將 old-name 重新命名為 new-name
2git filter-repo --path-rename old-name:new-name --force
3
4# 將資料夾移動到子目錄
5git filter-repo --path-rename src:app/src --force
6
7# 將所有檔案移到子目錄
8git filter-repo --to-subdirectory-filter my-subdir --force修改 commit 訊息
建立一個 Python 腳本 message-callback.py:
1# message-callback.py
2import re
3
4def message_callback(message):
5 # 將 "bug" 替換為 "fix"
6 return re.sub(b'bug', b'fix', message)執行:
1git filter-repo --message-callback '
2 return message.replace(b"bug", b"fix")
3' --force或使用外部檔案:
1git filter-repo --message-callback "$(cat message-callback.py)" --force修改作者資訊
使用 mailmap 檔案
建立 .mailmap 檔案:
1New Name <new@email.com> Old Name <old@email.com>執行:
1git filter-repo --mailmap .mailmap --force使用 callback 函數
1git filter-repo --name-callback '
2 return name.replace(b"OldName", b"NewName")
3' --email-callback '
4 return email.replace(b"old@email.com", b"new@email.com")
5' --force進階操作
移除大型檔案
找出大型檔案:
1git filter-repo --analyze
2cat .git/filter-repo/analysis/blob-shas-and-paths.txt | head -20移除超過特定大小的檔案:
1git filter-repo --strip-blobs-bigger-than 10M --force替換敏感內容
建立替換規則檔案 replacements.txt:
1regex:password=.*==>password=REDACTED
2literal:my-secret-api-key==>API_KEY_REMOVED執行:
1git filter-repo --replace-text replacements.txt --force只處理部分歷史
1# 只處理最近的 commit
2git filter-repo --refs HEAD~10..HEAD --path sensitive-file --invert-paths --force
3
4# 處理特定分支
5git filter-repo --refs main --path old-folder --invert-paths --force保留備份
1# 在操作前建立備份分支
2git branch backup-before-filter
3
4# 或 clone 一份完整備份
5git clone --mirror original-repo backup-repo注意事項
操作前檢查清單
- 確保工作目錄是乾淨的(
git status無未 commit 的變更) - 建立備份(branch 或完整 clone)
- 確認沒有其他人正在使用這個 repository
- 了解 force push 的影響
Remote 會被移除
git filter-repo 執行後會自動移除 origin remote。執行時你會看到以下提示:
1NOTICE: Removing 'origin' remote; see 'Why is my origin removed?'
2 in the manual if you want to push back there.為什麼要移除 Remote?
這是 git filter-repo 的安全機制設計,目的是保護你和你的團隊:
防止意外推送:重寫歷史後,所有 commit hash 都會改變。如果你不小心直接執行
git push,會把重寫後的歷史推送到遠端,可能覆蓋其他人的工作,造成嚴重問題。強迫你停下來思考:移除 remote 後,你必須:
- 確認是否真的要推送重寫後的歷史
- 手動重新加入 remote
- 明確使用
--force參數推送
給你機會通知團隊:在重新加入 remote 和 force push 之前,你有機會先通知其他協作者,讓他們做好準備。
如何處理
執行完 git filter-repo 後,依序執行:
1# 1. 重新加入 remote
2git remote add origin <repository-url>
3
4# 2. 確認 remote 已加入
5git remote -v
6
7# 3. Force push(確認團隊已知情後再執行)
8git push origin --force --allForce Push
重寫歷史後需要 force push:
1# Push 所有分支
2git push origin --force --all
3
4# Push 所有 tags
5git push origin --force --tags通知協作者
其他協作者需要執行以下操作來同步:
1# 方法一:重新 clone(推薦)
2git clone <repository-url>
3
4# 方法二:強制重設(注意:會丟失本地未 push 的變更)
5git fetch --all
6git reset --hard origin/<branch-name>常見問題
Q: 執行時出現 “Refusing to run without fresh clone” 錯誤
這是安全機制,使用 --force 參數來覆蓋:
1git filter-repo --invert-paths --path file.txt --forceQ: 如何還原操作?
如果有備份分支:
1git checkout backup-before-filter
2git branch -D main
3git checkout -b main如果有備份 repository:
1git remote add backup /path/to/backup-repo
2git fetch backup
3git reset --hard backup/mainQ: 為什麼我的 repository 大小沒有變小?
執行以下命令來清理:
1git reflog expire --expire=now --all
2git gc --prune=now --aggressiveQ: 可以只影響特定分支嗎?
可以,使用 --refs 參數:
1git filter-repo --invert-paths --path file.txt --refs main --forceQ: 如何預覽變更而不實際執行?
使用 --dry-run 參數:
1git filter-repo --invert-paths --path file.txt --dry-run