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

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

![深入探討 Git 中的 Unreachable (無法到達的) 物件與清理方法](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/4c08fb1fc1d7f72833e7-2a7f6b82-fd9a-4241-9071-f7dbd1d7e75b.webp)

### TL;DR

廢話不多說，先說解決方案，有空就繼續看下去，可以多了解一些 Git 內部的技術細節。

```bash
# 確認哪些物件是 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` 物件是最重要的，因為它們記錄了提交的完整內容，並指向一個到多個 `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 物件的步驟：

1.  使用 `git fsck` 命令查找 Unreachable 物件
    
    `git fsck` 是 Git 中用來檢查 repository 的完整性並報告任何潛在問題的工具，它可以檢查所有的物件並標記出無法到達的物件。
    
    ```bash
    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`, `tree` 或 `blob` 物件類型，以及這些物件的 `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 的方法：
    
    ```bash
    git reflog expire --expire=now --all
    ```
    
    執行完之後，你用 `git reflog` 就什麼歷史提交紀錄都看不到了！
    
    你也可以僅刪除 365 天前的 Reflog 記錄，這樣可以保留最近一年的歷史紀錄：
    
    ```bash
    git reflog expire --expire=365.days.ago --all --verbose
    ```
    
    加上 `--verbose` 可以讓你看出刪除了哪些 reflog 記錄。若加上 `--dry-run` 參數，則不會真的刪除，只會顯示哪些 reflog 記錄會被刪除。
    
2.  執行垃圾回收 (GC)
    
    在清理了 reflog 之後，我們需要進行垃圾回收以實際移除那些無法到達的物件。
    
    ```bash
    git gc --prune=now
    ```
    
    `git gc` 是 Git 的垃圾回收命令，它會清理並最佳化 repository，`--prune=now` 選項表示立即移除未被參考的物件。
    
3.  使用更積極的垃圾回收選項
    
    如果希望進行更徹底的清理，可以使用 `--aggressive` 選項：
    
    ```bash
    git gc --aggressive --prune=now
    ```
    
    雖然說是「更徹底」的清理，但其實 `git gc --prune=now` 就能夠清除所有的 unreachable 物件了，只是 `--aggressive` 會進行更深層次的最佳化，他會使用更耗費資源的方式對這些物件進行壓縮與合併，因此會耗用更多的 CPU 資源，也會花費更多時間進行清理。
    
4.  再次檢查
    
    在完成上述步驟後，建議再次檢查 repository 中是否還有 unreachable 物件，以確保清理的徹底性。
    
    ```bash
    git fsck --unreachable
    ```
    
    確認輸出中是否仍存在 unreachable 物件。如果沒有任何輸出，表示所有 unreachable 物件都已成功清理！👍
    

### 相關連結

-   [Git - git-gc Documentation](https://git-scm.com/docs/git-gc)
-   [Git - git-fsck Documentation](https://git-scm.com/docs/git-fsck)
-   [Git - git-reflog Documentation](https://git-scm.com/docs/git-reflog)
