在歷史紀錄中定位憑證

在重寫歷史紀錄之前,先找出所有包含機敏資料的 commit。可以用 git log 來找:

git log -p -S "SECRET_STRING"

清理歷史紀錄

有兩種常見的方法來改寫 Git 歷史紀錄。現代標準是 git-filter-repo,但標準的 git filter-branch 在所有 Git 安裝中都可以用。

使用 git filter-branch

Tree filter 允許在每個 commit 的 checkout 過程中執行 shell 腳本。為了防止 git filter-branch 因為警告而停下等待,請設定 FILTER_BRANCH_SQUELCH_WARNING=1 環境變數。

建立一個 bash 腳本來將機密取代為預位符:

!/bin/bash
if [ -f path/to/file ]; then
  sed -i 's/SECRET_STRING/PLACEHOLDER/g' path/to/file
fi

設定可執行:

chmod +x clean.sh

從引入洩漏之 commit 的 parent 開始進行重寫(例如 INTRODUCED_COMMIT~1):

FILTER_BRANCH_SQUELCH_WARNING=1 git filter-branch -f --tree-filter /absolute/path/to/clean.sh INTRODUCED_COMMIT~1..HEAD

使用 git-filter-repo

現代工具 git-filter-repogit filter-branch 更安全快速。如果已經安裝,執行簡單的搜尋取代指令即可:

git-filter-repo --replace-text <(echo "SECRET_STRING==>PLACEHOLDER")

這會自動更新儲存庫歷史紀錄中的所有參照。

更新已 checkout 的分支

如果重寫是在臨時分支上進行的,其他分支(像是 master)仍然包含舊的 commit。請藉由將其指標重設為重寫後的 HEAD 來更新它們:

git checkout target-branch
git reset --hard rewritten-branch-or-commit

如果本地端有未 commit 的變更,建議先不要這麼做,因為 hard reset 會捨棄這些變更。

管理鎖定的分支與工作區 (Worktree)

如果 Git 因為分支已在 worktree 中 checkout 而拒絕刪除它,請找出該 worktree:

git worktree list

使用其路徑來移除 worktree:

git worktree remove /path/to/worktree

一旦 worktree 被移除,該分支就會被解鎖,並可以安全地刪除:

git branch -d branch-name

更新遠端儲存庫

如果重寫後的 commit 已經推送到遠端伺服器,標準的 push 會因為歷史紀錄分歧而失敗。必須強制推送以更新伺服器:

git push origin branch-name --force

請通知其他協作者,因為他們需要 fetch 並將其本地分支重設為新的歷史紀錄。