在使用 Git 進行版本控制時,我們只要使用 git rebase
、git commit --amend
或 git reset
多多少少都會殘留一些無法到達的物件 (unreachable blob or commits)。這些無法到達的物件雖然不會直接影響我們的日常工作,但它們會佔用儲存空間,並可能在某些情況下引起混淆,例如明明檔案不多,但是卻佔用大量磁碟空間。今天我將深入探討什麼是 unreachable 物件,並詳細介紹如何有效地清理它們。
TL;DR
廢話不多說,先說解決方案,有空就繼續看下去,可以多了解一些 Git 內部的技術細節。
# 確認哪些物件是 unreachable 的
git fsck --unreachable
# 清理 reflog 參考記錄 (執行前請三思)
git reflog expire --expire=now --all
# 清理沒有參考到的物件
git gc --prune=now
# 更積極的清理與壓縮所有的物件
git gc --aggressive --prune=now
# 再次確認是否還有 unreachable 物件
git fsck --unreachable
需求說明
其實 Git 本身會自動處理這些 unreachable (無法到達的) 物件,但是它不會立即清理,而是等到一定時間後才會進行清理。
因為我們有時候會特別拿 Git Repo 來當作臨時的傳檔工具,所以並沒有「版本控制」的需求,所以每次都會用 git commit --amend
搭配 git push -f
來更新檔案,但是這樣的操作方式會造成很多 unreachable 物件殘留在 .git
本地儲存庫之中,且在短時間內就會累積到好幾個 GB 的儲存空間佔用,我不太想等 Git 自動清理這些物件,因此需要找個方法來提前清理這些無效的物件。
什麼是 Unreachable 物件
Git 總共有以下四種物件類型:
-
blob
儲存檔案內容的物件。
-
tree
儲存目錄結構的物件。
-
commit
表示一次提交記錄的物件。
-
tag
用於標記特定 commit 的物件。
在 Git 的儲存庫中,commit
物件是最重要的,因為它們記錄了提交的完整內容,並指向一個到多個 blob
或 tree
物件,而 tree
物件又會指向 tree
或 blob
物件,還會指向上一個 commit
物件,最終組成一個完整的 Git Graph (整個版本歷史)。
頻繁使用 git commit --amend
、git rebase
或 git reset
等操作,可能會產生許多 unreachable 的 blob
與 tree
與 commit
物件。除此之外,刪除分支或 git filter-branch
也會導致產生無法到達的物件。這些物件由於不再被任何有效的 branch
(分支) 或 tag
(標籤) 所參考,因此可以進行手動的清理。
檢查 Unreachable 物件
在了解了什麼是 unreachable 物件之後,我們需要檢查 repository 中是否存在這些無法到達的物件。Git 提供了一個名為 git fsck
的命令來檢查和報告 repository 中的問題,包括 unreachable 物件。以下是檢查 unreachable 物件的步驟:
-
使用 git fsck
命令查找 Unreachable 物件
git fsck
是 Git 中用來檢查 repository 的完整性並報告任何潛在問題的工具,它可以檢查所有的物件並標記出無法到達的物件。
git fsck --unreachable
-
解讀 git fsck --unreachable
的輸出
當你執行 git fsck --unreachable
後,你會看到類似以下的輸出:
Checking object directories: 100% (256/256), done.
Checking objects: 100% (20/20), done.
unreachable commit 023c24a6ddfb6c7524509905aa6475f2c3fd8413
unreachable commit 430d34251c930246557d478eb93ec9d6e4fe1c55
unreachable commit 05403e80a1ddbea7f13c0acff3b10be2e414f97b
unreachable tree 0a81ae9b7013f7ce5846ce63ef1a42931ff192d5
unreachable tree 0e4b1eda0d0967b8e8b7a4f55ae09e82f063c768
unreachable blob 4f16e92b0629617b5e32cc943d7f0454fd25c7a1
unreachable blob 521e271987c5d6514bc95765e24ef22d54cfb8e1
在這個輸出中,每一行代表一個 unreachable 物件,包含 commit
, tree
或 blob
物件類型,以及這些物件的 SHA-1
值。
清理 Unreachable 物件的步驟
在確認了 Git repository 中存在的 unreachable 物件後,我們需要進行清理以移除這些不再需要的物件。以下是詳細的清理步驟:
-
清理 reflog 參考記錄
git reflog
記錄了所有分支和 HEAD 的移動歷史,包括那些曾經 commit 過的記錄,全部都會被記錄在 Reflog 之中。
我們在執行 git gc --prune=now
的時候,經常會發現無論怎樣都刪除不掉所有的 unreachable 物件,就是因為 Reflog 記錄了這些物件,所以我們需要先清理掉這些過期的 Reflog 記錄,才有機會真正刪除所有的 unreachable 物件。
不過,刪除 reflog 之後,你就真的無法回頭了,所以在執行這個步驟之前,請確保你真的不需要這些物件了。
以下是使用 git reflog expire
刪除所有 Reflog 的方法:
git reflog expire --expire=now --all
執行完之後,你用 git reflog
就什麼歷史提交紀錄都看不到了!
你也可以僅刪除 365 天前的 Reflog 記錄,這樣可以保留最近一年的歷史紀錄:
git reflog expire --expire=365.days.ago --all --verbose
加上 --verbose
可以讓你看出刪除了哪些 reflog 記錄。若加上 --dry-run
參數,則不會真的刪除,只會顯示哪些 reflog 記錄會被刪除。
-
執行垃圾回收 (GC)
在清理了 reflog 之後,我們需要進行垃圾回收以實際移除那些無法到達的物件。
git gc --prune=now
git gc
是 Git 的垃圾回收命令,它會清理並最佳化 repository,--prune=now
選項表示立即移除未被參考的物件。
-
使用更積極的垃圾回收選項
如果希望進行更徹底的清理,可以使用 --aggressive
選項:
git gc --aggressive --prune=now
雖然說是「更徹底」的清理,但其實 git gc --prune=now
就能夠清除所有的 unreachable 物件了,只是 --aggressive
會進行更深層次的最佳化,他會使用更耗費資源的方式對這些物件進行壓縮與合併,因此會耗用更多的 CPU 資源,也會花費更多時間進行清理。
-
再次檢查
在完成上述步驟後,建議再次檢查 repository 中是否還有 unreachable 物件,以確保清理的徹底性。
git fsck --unreachable
確認輸出中是否仍存在 unreachable 物件。如果沒有任何輸出,表示所有 unreachable 物件都已成功清理!👍
相關連結