Suốt sê-ri này ta thấy Git liên tục tạo object: mỗi commit, mỗi reset, mỗi rebase để lại object mới, và bỏ lại object cũ "mồ côi". Vậy thư mục .git có phình mãi không? Không — Git tự bảo trì bằng git gc (garbage collection). Bài cuối sê-ri Git mổ xẻ cơ chế dọn dẹp: gc nén và dọn, fsck kiểm toàn vẹn, và một điểm nhiều người hiểu sai — vì sao gc --prune=now không xóa được commit bạn vừa reset bỏ. Câu trả lời (reflog) khép lại vòng tròn của cả sê-ri. Đo thật bằng git 2.39.

Ba công cụ bảo trì

  • git gc: gom các loose object rời rạc vào một packfile (nén delta — bài git-08), khử trùng lặp, và có thể dọn object không còn cần. Kết quả: .git nhỏ lại, thao tác nhanh hơn.
  • git fsck: kiểm tra toàn vẹn kho (object có hỏng không) và liệt kê object dangling (mồ côi — không nhánh nào với tới).
  • git prune: xóa object không với tới. gc gọi prune bên trong.
git gc                    # dọn định kỳ (thường Git tự gọi khi cần)
git gc --prune=now        # nén và dọn ngay
git fsck --no-reflogs     # kiểm toàn vẹn, liệt kê object mồ côi
git count-objects -vH     # xem loose vs in-pack, dung lượng

Ảnh chụp đoạn mã nền tối giải thích gc fsck và bảo trì kho dọn rác nén và kiểm toàn vẹn, Git tự sinh rác và tự dọn mỗi commit reset rebase để lại loose object object mồ côi git gc gom loose object thành packfile nén delta khử trùng dọn object không còn cần .git nhỏ lại git fsck kiểm toàn vẹn kho liệt kê object dangling mồ côi git prune xóa object không với tới gc gọi nó bên trong, điểm mấu chốt reflog bảo vệ object khỏi bị dọn gc không xóa object mà reflog còn trỏ tới reset rebase bỏ commit nó mồ côi với nhánh nhưng reflog vẫn giữ gc --prune=now cũng không xóa đây là lưới an toàn muốn xóa thật git reflog expire --expire=now --all rồi gc, thực tế để Git tự lo và git maintenance git gc --auto Git tự gọi khi loose object quá nhiều git maintenance start lịch bảo trì nền đừng xóa .git objects bằng tay đừng gc khi có tiến trình git khác chạy

Hình 1: git gc nén loose object thành packfile và dọn; git fsck kiểm toàn vẹn + liệt kê object mồ côi; git prune xóa object không với tới. Điểm mấu chốt: reflog bảo vệ object khỏi bị dọn — lưới an toàn của cả sê-ri.

Đo thật: nén, mồ côi, và reflog cứu object

Ảnh chụp bảng kết quả đo thật nền tối chạy git 2.39, phần một trước gc loose object reset bỏ c3 mồ côi count 9 loose size 36 KiB git reset --hard HEAD~1 c3 10e256c mồ côi git fsck --no-reflogs dangling commit 10e256c, phần hai git gc --prune=now nén loose thành packfile count 0 in-pack 9 packs 1 size-pack 1.86 KiB loose gom hết vào 1 packfile nén delta, phần ba c3 vẫn còn vì reflog giữ lưới an toàn git cat-file -t 10e256c commit chưa bị xóa reflog HEAD@1 còn trỏ c3 gc không dám dọn, phần bốn xóa thật hết hạn reflog rồi gc git reflog expire --expire=now --all git gc --prune=now git cat-file -t 10e256c đã bị xóa nhánh chính vẫn nguyên 0523e09 c2 aa301e0 c1

Hình 2: Trước gc có 9 loose object (36 KiB); reset --hard bỏ c3 (10e256c) → fsck báo dangling. gc --prune=now gom vào packfile (in-pack 9, 1.86 KiB). Nhưng c3 vẫn còn (cat-file -t ra commit) vì reflog giữ. Chỉ khi reflog expire --expire=now --all rồi gc mới xóa thật; nhánh chính (c1, c2) nguyên vẹn.

Kết quả kể trọn câu chuyện:

  • Nén thật: trước gc có 9 loose object (36 KiB). Sau gc --prune=now: count: 0 loose, in-pack: 9, packs: 1, size-pack: 1.86 KiB. Loose gom hết vào một packfile nén delta — nhỏ hơn nhiều.
  • fsck tìm mồ côi: sau reset --hard HEAD~1 bỏ c3, git fsck --no-reflogs báo dangling commit 10e256c — c3 không còn nhánh nào với tới.
  • Điểm quan trọng nhất — reflog bảo vệ: sau gc --prune=now, c3 (10e256c) vẫn tồn tại (git cat-file -t trả commit)! Vì sao? Vì reflog (HEAD@{1}) vẫn trỏ tới c3 — với gc, reflog cũng là một dạng tham chiếu, nên nó không dám xóa. fsck --no-reflogs báo "dangling" là xét reachability bỏ qua reflog, nhưng object chưa bị xóa. Đây là lý do vì sao ở bài reflog/rebase, commit "mất" vẫn cứu được — gc mặc định tôn trọng reflog.
  • Xóa thật cần hai bước: git reflog expire --expire=now --all (xóa mọi entry reflog) rồi git gc --prune=now — lúc đó c3 mới thực sự bị xóa. Nhánh chính (c1, c2) tất nhiên nguyên vẹn.

Đánh đổi và lưu ý

Để Git tự lo — gc --auto. Bình thường bạn không cần chạy git gc tay. Git tự gọi gc --auto khi số loose object vượt ngưỡng (sau nhiều thao tác), và các lệnh mạng (push/fetch) cũng kích hoạt dọn. Git hiện đại còn có git maintenance start để lên lịch bảo trì nền (gc, commit-graph, prefetch) — mượt hơn gc thủ công. Chỉ chạy gc tay khi repo phình bất thường hoặc muốn dọn ngay.

Đừng vội --prune=now hay reflog expire khi chưa chắc. Hai lệnh này phá lưới an toàn: sau chúng, commit lỡ tay reset/rebase bỏ sẽ mất vĩnh viễn, không cứu được nữa. Reflog mặc định giữ 90 ngày (reachable) / 30 ngày (unreachable) là có chủ đích — đủ để phát hiện và cứu tai nạn. Chỉ ép xóa khi thật sự cần thu hồi dung lượng và chắc chắn không cần lịch sử đó.

Đừng đụng .git/objects bằng tay, đừng gc khi có git khác chạy. Xóa file trong .git/objects thủ công là cách nhanh nhất làm hỏng kho. Và chạy gc trong khi một tiến trình git khác đang ghi (một script, một IDE) có thể gây đua tranh — Git có khóa nhưng đừng thử vận may. Để gc chạy khi repo "yên".

Ba ý mang về

  1. git gc nén loose object thành packfile và dọn: đo thật, 9 loose (36 KiB) → 1 packfile (1.86 KiB); git fsck kiểm toàn vẹn và liệt kê object mồ côi (dangling commit).
  2. reflog bảo vệ object khỏi bị dọn: sau reset --hard + gc --prune=now, commit "mất" vẫn còn vì reflog giữ (cat-file ra commit) — đây chính là lưới an toàn cứu commit tưởng đã mất; chỉ reflog expire + gc mới xóa thật.
  3. Để Git tự lo: gc --auto/git maintenance chạy nền — hiếm khi cần gc tay; đừng vội --prune=now/reflog expire (phá lưới an toàn), đừng xóa .git/objects bằng tay hay gc khi có git khác đang chạy.

Nguồn

Đến đây sê-ri Git nội bộ khép lại 18 phần: từ object địa chỉ hoá nội dung, ba vùng, ref, DAG, merge/rebase, reflog, packfile, status/diff, bisect, cherry-pick/detached HEAD, fetch/push, tới stash, worktree, submodule, hooks, rebase tương tác, và bảo trì kho. Sợi chỉ xuyên suốt: Git không phải phép màu — nó là vài cấu trúc dữ liệu đơn giản (object bất biến băm theo nội dung, con trỏ, packfile nén delta) cùng một lưới an toàn (reflog). Hiểu chúng thì mọi lệnh Git, kể cả lúc "hỏng", đều lý giải và sửa được — và gần như không gì thật sự mất cho tới khi bạn cố ý xóa nó.