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!