The Will Will Web

記載著 Will 在網路世界的學習心得與技術分享

深入探討 Git 中的 Unreachable (無法到達的) 物件與清理方法

在使用 Git 進行版本控制時,我們只要使用 git rebasegit commit --amendgit reset 多多少少都會殘留一些無法到達的物件 (unreachable blob or commits)。這些無法到達的物件雖然不會直接影響我們的日常工作,但它們會佔用儲存空間,並可能在某些情況下引起混淆,例如明明檔案不多,但是卻佔用大量磁碟空間。今天我將深入探討什麼是 unreachable 物件,並詳細介紹如何有效地清理它們。

深入探討 Git 中的 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 總共有以下四種物件類型:

  1. blob

    儲存檔案內容的物件。

  2. tree

    儲存目錄結構的物件。

  3. commit

    表示一次提交記錄的物件。

  4. tag

    用於標記特定 commit 的物件。

在 Git 的儲存庫中,commit 物件是最重要的,因為它們記錄了提交的完整內容,並指向一個到多個 blobtree 物件,而 tree 物件又會指向 treeblob 物件,還會指向上一個 commit 物件,最終組成一個完整的 Git Graph (整個版本歷史)。

頻繁使用 git commit --amendgit rebasegit reset 等操作,可能會產生許多 unreachable 的 blobtreecommit 物件。除此之外,刪除分支或 git filter-branch 也會導致產生無法到達的物件。這些物件由於不再被任何有效的 branch (分支) 或 tag (標籤) 所參考,因此可以進行手動的清理。

檢查 Unreachable 物件

在了解了什麼是 unreachable 物件之後,我們需要檢查 repository 中是否存在這些無法到達的物件。Git 提供了一個名為 git fsck 的命令來檢查和報告 repository 中的問題,包括 unreachable 物件。以下是檢查 unreachable 物件的步驟:

  1. 使用 git fsck 命令查找 Unreachable 物件

    git fsck 是 Git 中用來檢查 repository 的完整性並報告任何潛在問題的工具,它可以檢查所有的物件並標記出無法到達的物件。

    git fsck --unreachable
    
  2. 解讀 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, treeblob 物件類型,以及這些物件的 SHA-1 值。

清理 Unreachable 物件的步驟

在確認了 Git repository 中存在的 unreachable 物件後,我們需要進行清理以移除這些不再需要的物件。以下是詳細的清理步驟:

  1. 清理 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 記錄會被刪除。

  2. 執行垃圾回收 (GC)

    在清理了 reflog 之後,我們需要進行垃圾回收以實際移除那些無法到達的物件。

    git gc --prune=now
    

    git gc 是 Git 的垃圾回收命令,它會清理並最佳化 repository,--prune=now 選項表示立即移除未被參考的物件。

  3. 使用更積極的垃圾回收選項

    如果希望進行更徹底的清理,可以使用 --aggressive 選項:

    git gc --aggressive --prune=now
    

    雖然說是「更徹底」的清理,但其實 git gc --prune=now 就能夠清除所有的 unreachable 物件了,只是 --aggressive 會進行更深層次的最佳化,他會使用更耗費資源的方式對這些物件進行壓縮與合併,因此會耗用更多的 CPU 資源,也會花費更多時間進行清理。

  4. 再次檢查

    在完成上述步驟後,建議再次檢查 repository 中是否還有 unreachable 物件,以確保清理的徹底性。

    git fsck --unreachable
    

    確認輸出中是否仍存在 unreachable 物件。如果沒有任何輸出,表示所有 unreachable 物件都已成功清理!👍

相關連結

留言評論