Skip to content

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 --root

The 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~5

Before:

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

Change 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 validation

Result: Editor mở để bạn viết combined commit message.

FIXUP vs SQUASH

  • squash: Gộp commit VÀ mở editor để edit message
  • fixup: 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~3

Change:

reword abc1234 feat: add lgin form
pick def5678 fix: validation
pick ghi9012 feat: add logout

Result: 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~4

Change:

pick abc1234 feat: add feature
drop def5678 debug: console.log everywhere
pick ghi9012 fix: cleanup
pick jkl3456 feat: more features

Result: 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~3

Change:

edit abc1234 feat: add login and signup and profile
pick def5678 fix: something
pick ghi9012 feat: another

Khi 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~4

Before:

pick abc1234 feat: add UI
pick def5678 feat: add API
pick ghi9012 fix: UI bug
pick jkl3456 fix: API bug

Reorder to:

pick def5678 feat: add API
pick jkl3456 fix: API bug
pick abc1234 feat: add UI
pick ghi9012 fix: UI bug

Handling 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-cache

How 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

  1. Repeated rebases trên long-running feature branch
  2. Test merges - thử merge, resolve, abort, merge lại sau
  3. 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 -vH

Understanding 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 lookup

Benefits:

  • 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=now

Check 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

ActionKeywordEffect
Keep commitpick (p)Use commit as-is
Edit messagereword (r)Stop to edit message
Combinesquash (s)Meld into previous, edit combined message
Combine quietlyfixup (f)Meld into previous, discard message
Removedrop (d)Delete commit from history
Pauseedit (e)Stop for amending/splitting
Run commandexec (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."

  1. Golden Rule là absolute: KHÔNG rewrite public history
  2. Squash liberally: Feature = 1 commit (hoặc vài commits có ý nghĩa)
  3. Reword freely: Typo trong message? Fix it trước khi push
  4. Rerere saves time: Enable globally, forget about repeated conflicts
  5. 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-feature

FORCE 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!