</>Học Dev
Bài học

Tuần 2 - Ngày 2: Pull Request, Code Review và Interactive Rebase

Tuần 2 – Ngày 2

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 --fixup--autosquash workflow
  • 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--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

  1. 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.

  2. Sự khác biệt giữa squashfixup trong interactive rebase?

    Xem đáp án

    Cả hai đều kết hợp commit vào commit trước. Khác biệt: squash mở editor để bạn chỉnh sửa commit message kết hợp (merge cả hai message lại). fixup bỏ 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).

  3. 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 prefix fixup! này để git rebase -i --autosquash tự động sắp xếp và squash đúng vị trí.

  4. Khi nào dùng reword thay vì squash trong interactive rebase?

    Xem đáp án

    Dùng reword khi 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. squash kết hợp nội dung của nhiều commits; reword chỉ edit message của một commit duy nhất.

  5. Tại sao sau git rebase -i, bạn phải dùng --force-with-lease khi 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