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ả:.gitnhỏ 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.gcgọiprunebê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

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

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: 0loose,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~1bỏ c3,git fsck --no-reflogsbáodangling 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 -ttrả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-reflogsbá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ồigit 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ề
git gcnén loose object thành packfile và dọn: đo thật, 9 loose (36 KiB) → 1 packfile (1.86 KiB);git fsckkiểm toàn vẹn và liệt kê object mồ côi (dangling commit).- 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-fileracommit) — đây chính là lưới an toàn cứu commit tưởng đã mất; chỉreflog expire+gcmới xóa thật. - Để Git tự lo:
gc --auto/git maintenancechạ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/objectsbằng tay hay gc khi có git khác đang chạy.
Nguồn
- Pro Git — Git Internals: Maintenance and Data Recovery: https://git-scm.com/book/en/v2/Git-Internals-Maintenance-and-Data-Recovery
- Git Documentation — git-gc / git-fsck / git-maintenance: https://git-scm.com/docs/git-gc
Đế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ó.