Giao diện
History Rewriting - Interactive Rebase & Maintenance 🔄
ROLE: HPN (Git Mastery).
AUDIENCE: Engineers muốn có clean history và repository hiệu quả.
Git history không phải chỉ để đọc - nó có thể được viết lại. Module này dạy bạn cách reshape history để có commits sạch, meaningful, và repository được tối ưu.
🎯 Mục tiêu
Sau module này, bạn sẽ:
- Master Interactive Rebase - squash, reword, drop, split commits
- Hiểu The Golden Rule - khi nào KHÔNG được rewrite history
- Setup Rerere - Git tự nhớ cách bạn resolve conflicts
- Dùng Garbage Collection - dọn dẹp và nén repository
⚠️ The Golden Rule
NEVER REWRITE PUBLIC HISTORY
Không bao giờ rewrite history đã được push lên shared branch (main, develop).
Rewriting public history = Đồng nghiệp pull về và gặp:
- Conflict vô lý
- Lost commits
- "Why is my branch 50 commits behind AND ahead?"
Chỉ rewrite history trên LOCAL branches hoặc feature branches chưa ai pull.
🔧 Interactive Rebase (git rebase -i)
The Swiss Army Knife of Git
Interactive rebase cho phép bạn chỉnh sửa, gộp, xóa, sắp xếp lại các commits.
bash
# Rebase 5 commits gần nhất
git rebase -i HEAD~5
# Rebase từ một commit cụ thể
git rebase -i abc123
# Rebase từ root (toàn bộ history)
git rebase -i --rootThe Interactive Editor
Khi chạy git rebase -i, Git mở editor với danh sách commits:
pick abc1234 feat: add login form
pick def5678 fix: typo in button
pick ghi9012 wip: trying something
pick jkl3456 fix: actually working now
pick mno7890 feat: add validation
# Rebase abc1234..mno7890 onto xyz0000 (5 commands)
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# d, drop = remove commit
# x, exec = run command🔀 Technique 1: Squash (Gộp Commits)
Problem: 5 commits nhỏ lẻ cho 1 feature → messy history
Solution: Squash thành 1 commit meaningful
bash
git rebase -i HEAD~5Before:
pick abc1234 feat: add login form
pick def5678 fix: typo in button
pick ghi9012 wip: trying something
pick jkl3456 fix: actually working now
pick mno7890 feat: add validationChange to:
pick abc1234 feat: add login form
squash def5678 fix: typo in button
squash ghi9012 wip: trying something
squash jkl3456 fix: actually working now
squash mno7890 feat: add validationResult: Editor mở để bạn viết combined commit message.
FIXUP vs SQUASH
squash: Gộp commit VÀ mở editor để edit messagefixup: Gộp commit VÀ discard message của commit đó
Dùng fixup khi commit là "WIP" hoặc "typo fix" không cần giữ message.
✏️ Technique 2: Reword (Sửa Message Cũ)
Problem: Commit message typo hoặc không đúng format
bash
git rebase -i HEAD~3Change:
reword abc1234 feat: add lgin form
pick def5678 fix: validation
pick ghi9012 feat: add logoutResult: Git dừng lại để bạn edit message của commit đầu tiên.
🗑️ Technique 3: Drop (Xóa Commit)
Problem: Commit debug code nhầm, muốn xóa hoàn toàn
bash
git rebase -i HEAD~4Change:
pick abc1234 feat: add feature
drop def5678 debug: console.log everywhere
pick ghi9012 fix: cleanup
pick jkl3456 feat: more featuresResult: Commit def5678 bị xóa khỏi history như chưa từng tồn tại.
✂️ Technique 4: Split (Tách Commit)
Problem: Một commit làm quá nhiều thứ, muốn tách ra
bash
git rebase -i HEAD~3Change:
edit abc1234 feat: add login and signup and profile
pick def5678 fix: something
pick ghi9012 feat: anotherKhi Git dừng lại ở commit cần split:
bash
# 1. Undo commit nhưng giữ changes
git reset HEAD^
# 2. Stage và commit từng phần
git add src/login.js
git commit -m "feat: add login"
git add src/signup.js
git commit -m "feat: add signup"
git add src/profile.js
git commit -m "feat: add profile"
# 3. Continue rebase
git rebase --continue🔄 Technique 5: Reorder (Sắp Xếp Lại)
Problem: Commits không theo thứ tự logic
bash
git rebase -i HEAD~4Before:
pick abc1234 feat: add UI
pick def5678 feat: add API
pick ghi9012 fix: UI bug
pick jkl3456 fix: API bugReorder to:
pick def5678 feat: add API
pick jkl3456 fix: API bug
pick abc1234 feat: add UI
pick ghi9012 fix: UI bugHandling Conflicts During Rebase
bash
# Khi có conflict:
# 1. Resolve conflict trong files
# 2. Stage resolved files
git add <resolved-files>
# 3. Continue
git rebase --continue
# Hoặc abort nếu muốn hủy
git rebase --abort
# Skip commit hiện tại (nguy hiểm)
git rebase --skip🧠 Git Rerere - Remember Conflict Resolutions
The Problem
"Rebase feature branch lên main. Resolve 5 conflicts. Abort vì lý do nào đó. Rebase lại. Phải resolve 5 conflicts lại từ đầu."
The Solution: Rerere
Rerere = REuse REcorded REsolution
Git ghi nhớ cách bạn resolve conflicts, tự động apply lần sau.
bash
# Enable rerere (một lần)
git config --global rerere.enabled true
# Xem recorded resolutions
ls .git/rr-cache/
# Forget a resolution
git rerere forget <file>
# Clear all resolutions
rm -rf .git/rr-cacheHow It Works
Rerere in Action
bash
# 1. Enable rerere
git config rerere.enabled true
# 2. First time conflict
git merge feature
# CONFLICT in auth.py
# ... resolve manually ...
git add auth.py
git commit -m "merge: feature branch"
# rerere: Recorded resolution for 'auth.py'
# 3. Later, abort and retry
git reset --hard HEAD^
git merge feature
# CONFLICT in auth.py
# rerere: Resolved 'auth.py' using previous resolution. ✅
git add auth.py
git commit -m "merge: feature branch"RERERE USE CASES
- Repeated rebases trên long-running feature branch
- Test merges - thử merge, resolve, abort, merge lại sau
- CI/CD - pre-test merge conflicts
🗑️ Git Garbage Collection
What Gets Collected?
Unreachable objects:
- Commits sau
git reset --hard - Commits từ deleted branches
- Old versions của amended files
- Leftover từ failed rebases
When Does GC Run?
bash
# Git tự động chạy gc sau ~7000 loose objects
# Hoặc manual:
# Basic garbage collection
git gc
# Aggressive (slower, more thorough)
git gc --aggressive
# Prune unreachable objects NGAY (thay vì đợi 2 tuần)
git gc --prune=now
# Xem gc stats
git count-objects -vHUnderstanding Packfiles
bash
# Loose objects
.git/objects/
├── a1/b2c3d4... # Each object is a separate file
├── e5/f6g7h8...
└── i9/j0k1l2...
# After gc → Packfiles
.git/objects/pack/
├── pack-abc123.pack # All objects compressed into one file
└── pack-abc123.idx # Index for fast lookupBenefits:
- Delta compression (chỉ lưu diff giữa similar objects)
- Faster clones (ít files hơn)
- Smaller repo size
Manual Cleanup Commands
bash
# Remove old reflog entries
git reflog expire --expire=30.days --all
# Prune unreachable objects
git prune
# Repack objects
git repack -a -d --depth=250 --window=250
# The nuclear option (careful!)
git gc --aggressive --prune=nowCheck Repository Health
bash
# Verify integrity
git fsck
# Verify with more detail
git fsck --full
# Count objects
git count-objects -vH
# Output:
# count: 42 ← loose objects
# size: 168.00 KiB ← loose objects size
# in-pack: 12345 ← packed objects
# size-pack: 45.20 MiB
# prune-packable: 0
# garbage: 0
# size-garbage: 0 bytes📊 Quick Reference
Interactive Rebase Actions
| Action | Keyword | Effect |
|---|---|---|
| Keep commit | pick (p) | Use commit as-is |
| Edit message | reword (r) | Stop to edit message |
| Combine | squash (s) | Meld into previous, edit combined message |
| Combine quietly | fixup (f) | Meld into previous, discard message |
| Remove | drop (d) | Delete commit from history |
| Pause | edit (e) | Stop for amending/splitting |
| Run command | exec (x) | Run shell command |
Emergency Escape
bash
# Abort any rebase in progress
git rebase --abort
# Recover from bad rebase (use reflog)
git reflog
git reset --hard HEAD@{5} # Go back to safe state💡 Key Takeaways
HPN'S INSIGHT
"Clean history không phải vanity - nó là documentation. 'WIP commit 47' không giúp ai debug 6 tháng sau."
- Golden Rule là absolute: KHÔNG rewrite public history
- Squash liberally: Feature = 1 commit (hoặc vài commits có ý nghĩa)
- Reword freely: Typo trong message? Fix it trước khi push
- Rerere saves time: Enable globally, forget about repeated conflicts
- GC là automatic: Nhưng biết cách manual run khi cần
Workflow Recommendation
bash
# Trước khi push feature branch:
git rebase -i main # Clean up history
git push origin feature/my-feature
# Hoặc nếu đã push (branch chỉ mình bạn dùng):
git rebase -i main
git push --force-with-lease origin feature/my-featureFORCE PUSH SAFELY
Luôn dùng --force-with-lease thay vì --force:
--force: Overwrite bất kể--force-with-lease: Fail nếu remote có commits bạn chưa có
Safer, nhưng vẫn nguy hiểm trên shared branches!
🧠 Quiz
Câu 1: Tại sao nên dùng --force-with-lease thay vì --force khi push sau rebase?
- [ ] A) Vì
--force-with-leasenhanh hơn - [x] B) Vì
--force-with-leasefail nếu remote có commits mới mà bạn chưa pull, tránh overwrite code người khác - [ ] C) Vì
--forceđã bị deprecated - [ ] D) Vì
--force-with-leasetự động backup trước khi push
💡 Giải thích:
--forceghi đè bất kể tình trạng remote.--force-with-leasekiểm tra xem remote có commits mới không - nếu có, nó từ chối push. Đây là "safety net" tránh vô tình xóa code của đồng nghiệp.
Câu 2: git commit --amend thay đổi điều gì?
- [ ] A) Tạo một commit mới hoàn toàn độc lập
- [x] B) Thay thế commit cuối cùng bằng commit mới (SHA thay đổi)
- [ ] C) Chỉ sửa commit message mà không thay đổi SHA
- [ ] D) Merge commit cuối với commit trước đó
💡 Giải thích:
--amendtạo commit MỚI thay thế commit cuối (SHA thay đổi vì nội dung khác). Có thể sửa message, thêm/bớt file. Chỉ dùng cho commit chưa push, vì nó viết lại lịch sử.
Câu 3: Trong Interactive Rebase (git rebase -i), action "squash" làm gì?
- [ ] A) Xóa commit khỏi lịch sử
- [ ] B) Đổi thứ tự commit
- [x] C) Gộp commit vào commit trước đó, kết hợp cả hai messages
- [ ] D) Đổi tên commit message
💡 Giải thích:
squashgộp commit hiện tại vào commit phía trên, cho phép bạn edit combined message.fixuptương tự nhưng bỏ message của commit bị gộp. Rất hữu ích để dọn dẹp "WIP" commits trước khi merge.