《公開 Repo 密鑰外洩清除實錄》之五:獵殺 autostash 與隱密分支殘留物
雲端與開發本機的歷史都已重寫,接下來 Icekimo 必須回到 Synology NAS 生產伺服器,對齊這段全新的 Git 歷史。然而,這是一台正在運行生產服務的機器,任何粗心的操作都可能引發災難。
我要準備在 NAS 執行 `git reset --hard` 來對齊新歷史了。從此大家就會忘記蜘蛛人,是吧,我剛說什麼來著?在動手之前,有什麼要注意的?
務必先備份那些在運行時產生變動的檔案(例如 Caddy portal 的 `users.json` 和 CrowdSec 的 hub index),因為 `git reset --hard` 會毫不留情地清空所有本地未 commit 的修改。而且注意,還原這些檔案時,絕對不能用 `cp` 覆寫!
單檔 Bind Mount 的 Inode 致命陷阱
Claude Fable 解釋了一個極度隱蔽的 Linux 系統底層行為:
「在 Docker 容器中,如果使用了單檔掛載(如 users.json),宿主機(NAS)上的檔案 Inode 是被 Docker daemon 所咬住的。如果你用 cp 去還原備份檔案,系統會為其分配一個新的 Inode。這會導致容器內的 Docker 進程依然咬著舊的 Inode,你的任何修改都不會同步進容器!正確的做法是使用 cat backup > file,以覆寫內容的方式保留原有的 Inode。」
Icekimo 牢記在心,立刻在 NAS 上安全操作:
# 1. 備份運行時變動檔案
$ cp -p caddy_config/portal/users.json $B/users.json
$ cp -p crowdsec_config/hub/.index.json $B/hub-index.json
# 2. 對齊新歷史(此時密鑰檔已是 untracked,不會被 reset 影響)
$ git fetch origin
$ git reset --hard origin/main
# 3. ⚠️ 以保留 inode 的方式還原運行時檔案
$ cat $B/users.json > caddy_config/portal/users.json
$ cat $B/hub-index.json > crowdsec_config/hub/.index.json
# 4. 告訴 git 忽略 users.json 的本地修改
$ git update-index --assume-unchanged caddy_config/portal/users.json
驚悚時刻:舊歷史竟然還活著!?
對齊結束後,Icekimo 執行了例行驗證:
$ git log --all --oneline -- .env
3a4d2b1 Add credentials in initial commit
「怎麼回事!?明明已經 reset 了,為什麼 git log --all 還看得到含有密鑰的舊 commit 歷史!?」Icekimo 額頭冒出冷汗,該不會這世界除了哈梅內伊,還有哈沒內二吧。
糟糕了!NAS 上的 `git log --all` 依然列出了包含舊 `.env` 的歷史提交!明明 origin/main 已經乾淨了啊!
別慌,這代表你的本地倉庫還指著某些保留了舊歷史的 ref。讓我們執行 `git for-each-ref` 列出所有參考指標,找出究竟是誰在偷偷藏匿舊時空的歷史!
藉由 git for-each-ref 的輸出,他們聯手揪出了兩個隱密的藏匿點:
1. stash@{0}: autostash:某次執行 git pull --rebase 時,Git 自動為其做了一份暫存(autostash)後沒有被清理,整包舊 .env 密鑰就藏在 stash 裡面。
2. origin/<已刪除的PR分支>:GitHub 上的 PR 分支雖然已經被刪除,但本地的 remote-tracking refs 還牢牢指著舊歷史,導致舊 commit 物件無法被回收。
終極獵殺:Reflog 與垃圾資源回收
找到了兇手,Icekimo 展開了無情的清理,是時間管理局TVA大展神威的時候了:
# 1. 確認 stash 內容可以捨棄,然後丟棄
$ git stash show --name-only 'stash@{0}'
$ git stash drop 'stash@{0}'
# 2. 刪除無效的 remote-tracking 分支
$ git branch -rd origin/stale-branch-name
# 3. 徹底讓所有 reflog(引用日誌)即刻過期失效
$ git reflog expire --expire=now --all
# 4. 強制執行垃圾回收(GC),立刻剪除無效物件
$ git gc --prune=now
清理完畢,Icekimo 抓起舊 commit 的 SHA,執行了最後的驗證:
$ git cat-file -e 3a4d2b1 && echo "還在" || echo "已徹底清除"
終端機冷冷地回傳:
已徹底清除
「成功了!」Icekimo 歡呼。隱藏在 NAS corners 中的最後一把歷史密鑰,終於落入末日火山的岩漿裡,順便讓我踹guru一腳,給他一點溫暖。