跳轉到內容

Git 縮減倉庫 優化 Git 倉庫大小 提高性能

Git Repository Optimization

隨著項目的不斷髮展,Git 倉庫可能會變得越來越大,導致克隆、推送和拉取操作變慢。本文將詳細介紹如何優化 Git 倉庫大小,提高性能和效率。

為什麼需要優化 Git 倉庫?

在以下情況下,你需要考慮優化 Git 倉庫:

  • 倉庫體積過大:超過幾百 MB 甚至 GB 級別
  • 克隆速度慢:新成員克隆倉庫需要很長時間
  • CI/CD 效率低:持續集成構建時間過長
  • 存儲成本高:託管平臺對大倉庫有限制或收費
  • 歷史遺留問題:曾誤提交大文件或敏感信息

方法一:使用 git filter-repo 清理大文件(推薦)

git filter-repo 是現代 Git 倉庫清理的首選工具,比舊的 git filter-branch 更快、更安全。

安裝 git filter-repo

bash
# 使用 pip 安裝
pip install git-filter-repo

# 或使用 Homebrew(macOS)
brew install git-filter-repo

清除特定類型的文件

bash
# 清除垃圾文件 - 大量無用的 mp3 文件
git filter-repo --path-glob '*.mp3' --invert-paths --force

# 清除所有圖片文件
git filter-repo --path-glob '*.jpg' --path-glob '*.png' --invert-paths --force

# 清除特定目錄
git filter-repo --path node_modules/ --invert-paths --force

# 清除大於 100MB 的文件
git filter-repo --strip-blobs-bigger-than 100M --force

查找並刪除大文件

在實際操作前,先找出倉庫中的大文件:

bash
# 方法1:使用 git rev-list 查找大文件
git rev-list --objects --all | \
  git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' | \
  sed -n 's/^blob //p' | \
  sort --numeric-sort --key=2 | \
  tail -n 20 | \
  cut -c 1-12,41- | \
  $(command -v gnumfmt || echo numfmt) --field=2 --to=iec-i --suffix=B --padding=7 --round=nearest

# 方法2:使用 git ls-tree 遞歸查找
git ls-tree -r -t --long HEAD | sort -k 3 -n -r | head -20

# 方法3:查看整個歷史中的大文件
git verify-pack -v .git/objects/pack/*.idx | \
  sort -k 3 -n -r | \
  head -20

找到大文件後,將其從歷史中徹底刪除:

bash
# 假設發現 large-video.mp4 佔用了大量空間
git filter-repo --path large-video.mp4 --invert-paths --force

# 或者批量刪除多個文件
git filter-repo \
  --path video1.mp4 \
  --path video2.mp4 \
  --path dataset.csv \
  --invert-paths \
  --force

替換敏感信息

如果不小心提交了密碼、密鑰等敏感信息:

bash
# 替換所有歷史中的敏感字符串
git filter-repo --replace-text <(echo "OLD_PASSWORD==>NEW_PASSWORD") --force

# 創建 replacements.txt 文件
cat > replacements.txt << EOF
password123==>REDACTED
api_key_abc123==>REDACTED
secret_token==>REDACTED
EOF

git filter-repo --replace-text replacements.txt --force

方法二:清理標籤和分支

列出並刪除不需要的標籤

bash
# 查看所有本地標籤
git tag -l

# 查看遠程標籤
git ls-remote --tags origin

# 刪除本地標籤
git tag -d v1.0.0
git tag -d old-release

# 刪除遠程標籤
git push origin --delete v1.0.0
git push origin --delete old-release

# 批量刪除符合模式的標籤(謹慎使用!)
git tag -l 'v0.*' | xargs git tag -d
git push origin --delete $(git tag -l 'v0.*')

清理過時分支

bash
# 查看所有本地分支
git branch -a

# 查看已合併到主分支的分支
git branch --merged main

# 查看未合併的分支
git branch --no-merged main

# 刪除已合併的本地分支
git branch -d feature/old-feature
git branch -d bugfix/fix-123

# 強制刪除未合併的分支(謹慎!)
git branch -D abandoned-feature

# 刪除遠程分支
git push origin --delete feature/old-feature
git push origin --delete bugfix/fix-123

# 清理本地對遠程已刪除分支的引用
git remote prune origin

自動化清理腳本

bash
#!/bin/bash
# cleanup-branches.sh - 安全清理過時分支

echo "=== 開始清理過時分支 ==="

# 1. 更新遠程信息
git fetch --prune

# 2. 切換到主分支
git checkout main

# 3. 列出並刪除已合併的本地分支
echo "以下分支已合併到 main,將被刪除:"
git branch --merged main | grep -v "\* main" | grep -v "develop"

read -p "確認刪除?(y/n) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
    git branch --merged main | grep -v "\* main" | grep -v "develop" | xargs git branch -d
    echo "本地分支清理完成"
fi

# 4. 清理遠程已刪除的分支引用
git remote prune origin

echo "=== 清理完成 ==="

方法三:深度清理和垃圾回收

完整的清理流程

bash
# 1. 清理 reflog(引用日誌)
# reflog 記錄了 HEAD 的變化歷史,會佔用空間
git reflog expire --expire=now --all

# 2. 執行激進的垃圾回收
# --aggressive 選項會更徹底地優化對象存儲
git gc --prune=now --aggressive

# 3. 重新打包對象
# -A: 將所有對象打包
# -d: 刪除多餘的包文件
git repack -Ad

# 4. 修剪懸空對象
# 刪除不再被任何引用指向的對象
git prune

# 5. 清理 stashes(如果有)
git stash clear

# 6. 驗證倉庫完整性
git fsck --full

各命令詳解

git reflog expire

bash
# 默認情況下,reflog 條目保留 90 天
# 立即過期所有 reflog 條目
git reflog expire --expire=now --all

# 只過期特定分支的 reflog
git reflog expire --expire=now refs/heads/main

# 設置不同的過期時間
git reflog expire --expire=30.days.ago --all

git gc(Garbage Collection)

bash
# 基本垃圾回收
git gc

# 立即清理,不等待
git gc --prune=now

# 激進模式(更徹底但更慢)
git gc --aggressive

# 激進 + 立即清理(最徹底)
git gc --prune=now --aggressive

# 查看 gc 配置
git config --get gc.aggressiveDepth
git config --get gc.aggressiveWindow

gc 的參數說明:

  • --prune=now:立即刪除不可達對象,而不是等待默認的兩星期
  • --aggressive:更積極地優化打包,適合大倉庫
  • --auto:僅在必要時自動運行(默認行為)

git repack

bash
# 重新打包所有對象
git repack -Ad

# 參數說明:
# -A: 將所有 unreachable 對象也打包
# -d: 刪除舊的包文件
# -f: 強制重新打包,即使沒有變化
# -l: 僅處理本地對象

# 帶 delta 壓縮的重新打包
git repack -a -d -f --depth=250 --window=250

git prune

bash
# 刪除所有懸空對象
git prune

# 帶過期時間的修剪
git prune --expire=2.weeks.ago

# 顯示將要刪除的對象(dry run)
git prune --dry-run --verbose

一鍵清理腳本

bash
#!/bin/bash
# aggressive-cleanup.sh - 激進的倉庫清理

set -e

echo "⚠️  警告:此操作將永久刪除歷史數據!"
echo "建議先備份倉庫或創建裸克隆作為備份"
read -p "確認繼續?(yes/no) " -r
echo

if [[ ! $REPLY == "yes" ]]; then
    echo "操作已取消"
    exit 1
fi

echo "📊 清理前的倉庫大小:"
du -sh .git

echo ""
echo "🔧 開始清理..."

# 1. 清理 reflog
echo "  [1/6] 清理 reflog..."
git reflog expire --expire=now --all

# 2. 清理 stashes
echo "  [2/6] 清理 stashes..."
git stash clear

# 3. 刪除未跟蹤的文件(可選,謹慎使用)
# echo "  [3/6] 清理未跟蹤文件..."
# git clean -fdx

# 4. 垃圾回收
echo "  [3/6] 執行垃圾回收..."
git gc --prune=now --aggressive

# 5. 重新打包
echo "  [4/6] 重新打包對象..."
git repack -Ad

# 6. 修剪
echo "  [5/6] 修剪懸空對象..."
git prune

# 7. 驗證
echo "  [6/6] 驗證倉庫完整性..."
git fsck --full --no-dangling

echo ""
echo "✅ 清理完成!"
echo "📊 清理後的倉庫大小:"
du -sh .git

echo ""
echo "💡 提示:如果需要推送到遠程,請執行:"
echo "   git push origin --force --all"
echo "   git push origin --force --tags"

方法四:推送修改後的歷史到遠程

⚠️ 重要警告: 重寫歷史後推送到遠程是破壞性操作,會影響所有協作者!

推送策略

bash
# 1. 添加遠程倉庫(如果還沒有)
git remote add origin https://github.com/username/repository.git

# 2. 強制推送所有分支
git push origin --force --all

# 3. 強制推送所有標籤
git push origin --force --tags

協作團隊的最佳實踐

如果你的項目有多個協作者,請按以下步驟操作:

bash
# 步驟 1:通知所有團隊成員
echo "重要通知:我們將重寫 Git 歷史以優化倉庫大小。
請在今天下班前:
1. 提交併推送所有未完成的工作
2. 備份本地的重要分支
3. 明天早上重新克隆倉庫"

# 步驟 2:在維護窗口期間執行清理
# (按照前面的步驟進行清理)

# 步驟 3:強制推送
git push origin --force --all
git push origin --force --tags

# 步驟 4:通知團隊成員重新克隆
echo "清理完成!請執行以下操作:
1. 刪除本地舊倉庫
2. 重新克隆:git clone <repository-url>
3. 恢復你備份的本地分支"

替代方案:創建新的乾淨倉庫

如果擔心影響協作者,可以創建新倉庫:

bash
# 1. 克隆為裸倉庫
git clone --bare https://github.com/username/old-repo.git
cd old-repo.git

# 2. 執行清理操作
git reflog expire --expire=now --all
git gc --prune=now --aggressive
git repack -Ad
git prune

# 3. 創建新的遠程倉庫
# 在 GitHub/GitLab 上創建新倉庫 new-repo

# 4. 推送到新倉庫
git push --mirror https://github.com/username/new-repo.git

# 5. 歸檔舊倉庫,將新倉庫設為主要倉庫

方法五:預防倉庫膨脹的最佳實踐

1. 使用 .gitignore

bash
# 完善的 .gitignore 示例
# 依賴目錄
node_modules/
vendor/
.pnp/

# 構建輸出
dist/
build/
*.exe
*.dll
*.so
*.dylib

# 日誌文件
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# 環境變量
.env
.env.local
.env.*.local

# IDE 配置
.vscode/
.idea/
*.swp
*.swo

# 操作系統文件
.DS_Store
Thumbs.db

# 臨時文件
tmp/
temp/
*.tmp
*.bak

# 大型媒體文件(建議使用 Git LFS)
*.mp4
*.avi
*.mov
*.psd
*.ai

2. 使用 Git LFS 管理大文件

bash
# 安裝 Git LFS
git lfs install

# 追蹤大文件類型
git lfs track "*.psd"
git lfs track "*.mp4"
git lfs track "*.zip"

# 查看追蹤的文件類型
git lfs track

# 提交 .gitattributes 文件
git add .gitattributes
git commit -m "Configure Git LFS for large files"

# 推送 LFS 對象
git lfs push --all origin

3. 定期維護計劃

bash
# 添加到 crontab,每月執行一次
# 編輯 crontab
crontab -e

# 添加以下行(每月1號凌晨2點執行)
0 2 1 * * cd /path/to/repo && git gc --auto

# 或者每季度執行一次深度清理
0 2 1 */3 * * cd /path/to/repo && bash /path/to/aggressive-cleanup.sh

4. 監控倉庫大小

bash
# 創建監控腳本 monitor-size.sh
#!/bin/bash

REPO_DIR="/path/to/repo"
MAX_SIZE_MB=500

cd $REPO_DIR

# 獲取 .git 目錄大小(MB)
SIZE_MB=$(du -sm .git | cut -f1)

echo "當前倉庫大小: ${SIZE_MB}MB"

if [ $SIZE_MB -gt $MAX_SIZE_MB ]; then
    echo "⚠️  警告:倉庫大小超過 ${MAX_SIZE_MB}MB!"
    echo "建議執行清理操作"
    # 可以在此處添加郵件通知
    # mail -s "Git Repo Size Alert" admin@example.com <<< "Size: ${SIZE_MB}MB"
else
    echo "✅ 倉庫大小正常"
fi

常見問題排查

問題 1:清理後倉庫大小沒有明顯減少

bash
# 原因:可能有其他引用仍指向這些對象

# 解決方案:
# 1. 確保清理了所有引用
git reflog expire --expire=now --all
git stash clear

# 2. 檢查是否有其他遠程引用
git remote -v
git fetch --all --prune

# 3. 執行最徹底的清理
git gc --prune=now --aggressive
git repack -Ad
git prune

# 4. 驗證效果
du -sh .git

問題 2:強制推送後被拒絕

bash
# 錯誤:protected branch update failed

# 解決方案:
# 1. 在 GitHub/GitLab 上臨時解除分支保護
# 2. 執行強制推送
git push origin --force --all
# 3. 重新啟用分支保護

# 或者使用 --force-with-lease(更安全)
git push origin --force-with-lease --all

問題 3:清理後出現倉庫損壞

bash
# 檢查倉庫完整性
git fsck --full

# 如果有錯誤,從備份恢復
# 這就是為什麼要先備份的原因!

# 嘗試修復
git gc --prune=now
git reflog expire --expire=now --all

問題 4:想知道哪些文件佔用最多空間

bash
# 綜合查詢腳本
#!/bin/bash

echo "=== Top 20 Largest Files in Git History ==="
git rev-list --objects --all | \
  git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' | \
  sed -n 's/^blob //p' | \
  sort --numeric-sort --key=2 | \
  tail -n 20 | \
  awk '{printf "%-50s %s\n", $4, $3}' | \
  column -t

echo ""
echo "=== Largest Directories ==="
git ls-tree -r -t --long HEAD | \
  awk '{print $4}' | \
  sed 's|/[^/]*$||' | \
  sort | uniq -c | sort -rn | \
  head -10

實際案例:將一個 2GB 的倉庫縮減到 200MB

以下是一個真實案例的完整操作流程:

bash
# 初始狀態
$ du -sh .git
2.0G    .git

# 步驟 1:找出問題所在
$ git verify-pack -v .git/objects/pack/*.idx | \
  sort -k 3 -n -r | head -10

# 發現幾個大文件:
# - database-dump.sql (500MB)
# - video-demo.mp4 (800MB)
# - node_modules/ (多次提交,累計 400MB)

# 步驟 2:從歷史中刪除這些文件
$ git filter-repo \
  --path database-dump.sql \
  --path video-demo.mp4 \
  --path node_modules/ \
  --invert-paths \
  --force

# 步驟 3:刪除舊標籤
$ git tag -l 'v1.*' | xargs git tag -d
$ git push origin --delete $(git tag -l 'v1.*')

# 步驟 4:刪除廢棄分支
$ git branch -D old-experiment
$ git push origin --delete old-experiment

# 步驟 5:深度清理
$ git reflog expire --expire=now --all
$ git gc --prune=now --aggressive
$ git repack -Ad
$ git prune

# 最終結果
$ du -sh .git
200M    .git

# 步驟 6:推送到遠程
$ git push origin --force --all
$ git push origin --force --tags

# 縮減比例:90% 🎉

總結

優化 Git 倉庫大小是一個系統性的工作,需要根據具體情況選擇合適的方法:

方法對比

方法適用場景風險等級效果
git filter-repo刪除大文件、敏感信息高(重寫歷史)⭐⭐⭐⭐⭐
清理標籤分支刪除過時引用⭐⭐
垃圾回收日常維護極低⭐⭐⭐
Git LFS預防大文件⭐⭐⭐⭐
.gitignore預防不必要文件⭐⭐⭐⭐

最佳實踐建議

  1. 預防為主:配置好 .gitignore,使用 Git LFS
  2. 定期維護:設置自動化清理任務
  3. 謹慎操作:重寫歷史前務必備份
  4. 團隊協作:提前溝通,選擇合適的時機
  5. 監控預警:設置倉庫大小監控

下一步學習

通過合理運用這些技術,你可以保持 Git 倉庫的健康狀態,提高開發效率!

最後更新於: