把密碼、API 金鑰或一個幾百 MB 的檔案不小心 commit 進 Git、又推上遠端——這時候光 git rm 沒用,它還躺在歷史裡,任何人 clone 下來都翻得到。要真正清掉,得重寫 Git 歷史。

Git 內建的 filter-branch 能做,但它慢、容易出錯,官方文件自己都標示不建議用了。現在的標準工具是 git-filter-repo

什麼是 git-filter-repo

用 Python 寫的開源工具,專門重寫 Git 歷史,是 filter-branch 的官方推薦替代品。比起 filter-branch,它快上一個量級、命令更直覺,還會自動做安全檢查(例如要求你在乾淨的 clone 上操作)。

安裝

1
2
3
pip install git-filter-repo
# 或 Ubuntu/Debian
sudo apt install git-filter-repo

情境一:移除意外推送的敏感資料

1
2
3
4
# 移除符合 glob 的檔案
git filter-repo --path-glob '*config.json' --invert-paths
# 或移除特定一個檔案
git filter-repo --path 'src/config/secret.js' --invert-paths

--invert-paths 是「保留以外的全部」,配合 --path 就是把這些路徑從整個歷史刪掉。

但有件事要先講清楚:把檔案從歷史移除,不等於敏感資料安全了。 只要它曾經推上遠端,就要當作已經外洩——

  • 立刻撤銷並更換所有外洩的密碼、金鑰
  • 檢查其他分支、fork、CI 日誌是否也有
  • 之後用 .gitignore + pre-commit hook(如 git-secrets)擋住下一次

清歷史是止血,換金鑰才是真正解決。

情境二:移除大型檔案

1
git filter-repo --path-glob '*.zip' --invert-paths

情境三:修改作者 email

這裡是最多人踩的坑。git-filter-repo 的 callback 拿到的 emailnamemessage 全都是 bytes,不是字串——因為 Git 的內容本質是位元組。所以字串字面量一定要加 b 前綴:

1
2
3
4
5
# ✅ 正確:email 是 bytes,用 b"..."
git filter-repo --email-callback 'return email.replace(b"old@email.com", b"new@email.com")'

# ❌ 錯誤:用一般字串會 TypeError: a bytes-like object is required
git filter-repo --email-callback 'return email.replace("old@email.com", "new@email.com")'

改提交訊息的 --message-callback 同樣是 bytes:

1
git filter-repo --message-callback 'return message.replace(b"[WIP]", b"[DONE]")'

記住「全部加 b」就對了,這是 callback 最常見的報錯來源。

情境四:把子目錄變成根目錄

1
git filter-repo --subdirectory-filter folder_name

常用在從 monorepo 拆出一個獨立專案,連歷史一起搬走。

用之前要知道的幾件事

  1. 在複製出來的 repo 操作,原始 repo 留著當保險。
  2. 重寫不可逆,確定真的要做。
  3. 之後要 git push --force 覆蓋遠端,先跟團隊講好,不然會把別人的本地搞亂。
  4. 不用手動下 git gc。git-filter-repo 跑完會自己 expire reflog、repack 清掉舊物件。

進階:用 callback 改檔案內容

要動的不只是路徑、還包括檔案內容時,用 --blob-callback,body 直接操作 blob(一樣是 bytes,而且是 in-place 修改、不用 return):

1
2
3
4
git filter-repo --blob-callback '
if b"old_text" in blob.data:
blob.data = blob.data.replace(b"old_text", b"new_text")
'

整個 git-filter-repo 的世界都是位元組——路徑、email、訊息、檔案內容全是 bytes,這點一致。

跟 filter-branch 比

特性 git-filter-repo git filter-branch
速度
上手難度 簡單 複雜
安全檢查
官方態度 推薦 不建議使用

收尾

git-filter-repo 把「清掉 Git 歷史裡的東西」變得又快又相對安全。真正要記住的就兩點:callback 全用 bytes(加 b)、清完敏感資料一定要換金鑰。其他照情境套就行。