Tuần 2 - Ngày 2: Pull Request, Code Review và Interactive Rebase
Mục tiêu học tập
- Hiểu văn hóa Pull Request (PR) và best practices code review
- Thực hành
git rebase -i(interactive rebase): squash, fixup, edit, reword - Dùng
--fixupvà--autosquashworkflow - Tạo atomic PR dễ review
1. Pull Request — Hơn cả merge request
PR (hoặc MR trong GitLab) là cơ chế đề xuất merge code. Nó không chỉ là kỹ thuật — mà là giao tiếp.
Anatomy của một PR tốt
Title: feat(auth): add JWT refresh token endpoint
Description:
## Vấn đề
Token hết hạn sau 1 giờ khiến user bị logout đột ngột.
## Giải pháp
Thêm endpoint POST /auth/refresh nhận refresh token,
trả về access token mới. Refresh token có TTL 7 ngày.
## Thay đổi
- src/auth/refresh.ts: new endpoint
- src/auth/middleware.ts: update token validation
- tests/auth.test.ts: add tests
## Test
- [ ] Unit tests pass (npm test)
- [ ] Manual test: login → wait > 1h → request với expired token
→ gọi /auth/refresh → nhận token mới → request lại thành công
- [ ] Test case: expired refresh token → 401
## Screenshots (nếu có UI change)
Closes #123
Atomic PR — Nguyên tắc số 1
Mỗi PR nên:
- Giải quyết một vấn đề hoặc một feature
- Reviewer có thể review trong < 30 phút
- Có thể deploy độc lập (không phụ thuộc PR khác chưa merge)
BAD — một PR quá lớn:
"feat: redesign entire user module"
→ 50 files changed, 2000 lines
GOOD — chia nhỏ:
PR 1: "feat(user): add avatar upload endpoint"
PR 2: "feat(user): update profile page to show avatar"
PR 3: "feat(user): add avatar crop UI"
2. Code Review — Văn hóa
Reviewer: Tư duy khi review
Hỏi, không phán xét:
BAD: "Code này sai"
GOOD: "Tôi hiểu mục đích này, nhưng với edge case X thì sao?"
Suggest, không command:
BAD: "Đổi sang for loop"
GOOD: "Có thể dùng .map() ở đây không? Trông idiomatic hơn — nit"
Label severity:
[blocker]: Security issue, logic bug — must fix
[major]: Code quality, maintainability — should fix
[minor]: Style, naming — nice to have
[nit]: Nitpick — author's call
[question]: Tôi không hiểu, giải thích giúp
Approve khi:
- Không có blocker/major chưa resolved
- Code làm đúng điều PR title nói
Author: Mindset khi nhận review
- Comment là về code, không phải về bạn
- Giải thích lý do (trong comment hoặc commit message) khi không đồng ý
- Không defensive — reviewer thường thấy điều mình bỏ sót
- Đánh dấu "resolved" khi đã sửa hoặc đã giải thích
3. Interactive Rebase — Dọn dẹp lịch sử
git rebase -i (interactive) cho phép chỉnh sửa, kết hợp, xoá, đổi thứ tự commits trước khi push hoặc tạo PR.
Cú pháp cơ bản
# Rebase interactive N commits gần nhất
git rebase -i HEAD~3 # chỉnh 3 commits gần nhất
git rebase -i HEAD~5 # chỉnh 5 commits gần nhất
# Rebase từ điểm phân kỳ với main
git rebase -i main
Editor mở ra
# Sau git rebase -i HEAD~4, editor hiển thị:
pick abc1234 feat: add login form
pick def5678 fix: typo in button text
pick 9a8b7c6 WIP: still working on validation
pick 1f2e3d4 feat: complete login validation
# Commands:
# p, pick = dùng commit này
# r, reword = dùng commit nhưng đổi message
# e, edit = dùng commit nhưng dừng để amend
# s, squash = kết hợp vào commit trước, giữ message
# f, fixup = kết hợp vào commit trước, bỏ message này
# d, drop = xoá commit này
# Thứ tự từ trên xuống = từ cũ đến mới
Các thao tác phổ biến
squash — Kết hợp commits, merge messages
Trước:
pick abc1234 feat: add login form
squash def5678 fix: typo in button text
squash 9a8b7c6 WIP: still working
pick 1f2e3d4 feat: complete validation
→ Git kết hợp abc+def+9a8 thành một commit
→ Mở editor để bạn viết message mới
fixup — Kết hợp commits, bỏ message phụ
pick abc1234 feat: add login form
fixup def5678 fix: typo in button text ← bỏ message này
fixup 9a8b7c6 WIP ← bỏ message này
→ Một commit duy nhất với message "feat: add login form"
reword — Chỉ đổi commit message
reword abc1234 feat: add login form ← đổi message
pick def5678 feat: add logout
→ Git dừng tại abc1234, mở editor để đổi message
→ Tiếp tục rebase tự động
drop — Xoá commit
pick abc1234 feat: add feature A
drop def5678 WIP: debug logging ← xoá hẳn
pick 9a8b7c6 feat: add feature B
→ commit def5678 không tồn tại trong lịch sử nữa
edit — Tách một commit thành nhiều commits
# Trong editor
edit abc1234 feat: add authentication and authorization
# Git dừng tại commit đó
git reset HEAD~1 # unstage nhưng giữ file changes
# Giờ bạn có 2 nhóm thay đổi trong working dir
git add src/auth/login.ts
git commit -m "feat(auth): add login logic"
git add src/auth/rbac.ts
git commit -m "feat(auth): add role-based access control"
git rebase --continue # tiếp tục rebase
4. --fixup và --autosquash Workflow
Đây là workflow rất hiệu quả: thay vì rebase -i thủ công, bạn tạo fixup commits và --autosquash tự sắp xếp.
# Lịch sử hiện tại:
git log --oneline
# abc1234 feat: add login form
# def5678 feat: add user profile page
# Phát hiện typo trong login form, tạo fixup commit
git add src/auth/login.ts
git commit --fixup abc1234
# → Tự tạo message "fixup! feat: add login form"
# Thêm một sửa nữa cho profile
git add src/profile/profile.tsx
git commit --fixup def5678
# Xem lịch sử giờ:
git log --oneline
# 7g8h9i0 fixup! feat: add user profile page
# 5e6f7g8 fixup! feat: add login form
# def5678 feat: add user profile page
# abc1234 feat: add login form
# Autosquash: tự động sắp xếp và squash fixup commits
git rebase -i --autosquash main
# Editor mở ra với thứ tự đã được sắp xếp:
# pick abc1234 feat: add login form
# fixup 5e6f7g8 fixup! feat: add login form
# pick def5678 feat: add user profile page
# fixup 7g8h9i0 fixup! feat: add user profile page
# Chấp nhận và rebase xong — lịch sử clean!
# Cấu hình autosquash mặc định (không cần gõ --autosquash mỗi lần)
git config --global rebase.autoSquash true
5. Workflow thực tế: Feature PR với clean history
# 1. Tạo feature branch từ main mới nhất
git switch main && git pull
git switch -c feature/user-notifications
# 2. Làm việc, commit freely (không cần perfect)
git commit -m "WIP: notification component"
git commit -m "feat: add notification bell"
git commit -m "fix: typo"
git commit -m "feat: add notification count badge"
git commit -m "WIP: still need to handle click"
git commit -m "feat: complete notification dropdown"
git commit -m "test: add tests for notification"
# 3. Trước khi tạo PR, dọn dẹp lịch sử
git rebase -i main
# Squash WIP commits vào commit liên quan
# Kết quả mong muốn:
# pick a1 feat: add notification component with bell and badge
# pick a2 feat: add notification dropdown with click handler
# pick a3 test: add tests for notification component
# 4. Push (force-with-lease vì đã rebase)
git push -u origin feature/user-notifications
# Lần sau nếu có thêm fixup:
git push --force-with-lease
# 5. Tạo PR → Review → Merge
Câu hỏi ôn tập
-
Atomic PR nghĩa là gì? Tại sao quan trọng?
Xem đáp án
Atomic PR giải quyết một vấn đề duy nhất, reviewer có thể review trong dưới 30 phút, và có thể deploy độc lập. Quan trọng vì: (1) reviewer dễ hiểu context, review chất lượng hơn, (2) nếu cần revert, chỉ revert một tính năng rõ ràng, (3) tránh bottleneck — PR nhỏ merge nhanh hơn, không block đồng nghiệp.
-
Sự khác biệt giữa
squashvàfixuptrong interactive rebase?Xem đáp án
Cả hai đều kết hợp commit vào commit trước. Khác biệt:
squashmở editor để bạn chỉnh sửa commit message kết hợp (merge cả hai message lại).fixupbỏ message của commit này và chỉ giữ message của commit trước — phù hợp khi commit là minor fix không cần giải thích riêng (ví dụ sửa typo). -
Lệnh
git commit --fixup <hash>tạo ra commit với message như thế nào?Xem đáp án
Tự động tạo message có format
fixup! <message-của-commit-gốc>. Ví dụ commit gốc có message"feat: add login form"→ fixup commit sẽ có message"fixup! feat: add login form". Git dùng prefixfixup!này đểgit rebase -i --autosquashtự động sắp xếp và squash đúng vị trí. -
Khi nào dùng
rewordthay vìsquashtrong interactive rebase?Xem đáp án
Dùng
rewordkhi muốn chỉ đổi commit message mà không thay đổi nội dung hay kết hợp với commit khác. Ví dụ: commit message có typo, hoặc cần thêm ticket ID, hoặc đổi từWIP: ...thành message chuẩn Conventional Commits.squashkết hợp nội dung của nhiều commits;rewordchỉ edit message của một commit duy nhất. -
Tại sao sau
git rebase -i, bạn phải dùng--force-with-leasekhi push?Xem đáp án
Interactive rebase viết lại lịch sử — tất cả commits bị chỉnh sửa có hash mới. Remote branch vẫn có commits với hash cũ. Git từ chối push thường vì local history "phân kỳ" với remote. Cần force push để ghi đè remote. Dùng
--force-with-lease(không phải--force) để đảm bảo không ghi đè commits của đồng nghiệp nếu họ push trong lúc bạn đang rebase.
Bài tập thực hành
mkdir pr-rebase-lab && cd pr-rebase-lab
git init
git config user.name "Test" && git config user.email "test@test.com"
# Setup
echo "app" > app.js && git add . && git commit -m "chore: init"
git switch -c feature/notifications
# Tạo "messy" commit history như thực tế
echo "// notification" > notification.js
git add . && git commit -m "WIP: start notifications"
echo "const bell = () => {}" >> notification.js
git add . && git commit -m "feat: add bell component"
echo "// typo fix" >> notification.js
git add . && git commit -m "fix typo"
echo "const badge = () => {}" >> notification.js
git add . && git commit -m "feat: add badge"
echo "const dropdown = () => {}" >> notification.js
git add . && git commit -m "WIP: dropdown"
echo "export { bell, badge, dropdown }" >> notification.js
git add . && git commit -m "feat: complete notification module"
git log --oneline
# 6 commits, messy
# Interactive rebase: squash WIP và fixups thành 2 clean commits
git rebase -i HEAD~6
# Cố gắng đạt:
# pick XXX feat: add notification bell and badge
# pick YYY feat: add notification dropdown and exports
git log --oneline
# → 2 commits gọn gàng
# Thực hành --fixup
echo "// additional comment" >> notification.js
git add . && git commit --fixup HEAD~1 # fixup cho commit gần nhất
git log --oneline
# thấy "fixup! feat: ..."
git rebase -i --autosquash HEAD~3
# Xem autosquash tự sắp xếp thế nào
Tài liệu tham khảo chính thức
Tiếp theo: Ngày 3 — Conflict Resolution Nâng Cao