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

Tuần 1 - Ngày 5: Merge vs Rebase — Quyết Định Quan Trọng Nhất

Tuần 1 – Ngày 5

Tuần 1 - Ngày 5: Merge vs Rebase — Quyết Định Quan Trọng Nhất

Mục tiêu học tập

  • Hiểu sâu sự khác biệt giữa merge commit và rebase
  • Nắm vững "golden rule of rebasing": không rebase published branches
  • Thực hành conflict resolution: cơ bản đến abort/continue
  • Biết khi nào chọn merge, khi nào chọn rebase

1. Merge — Bảo tồn lịch sử

Merge tạo một commit mới (merge commit) với 2 parents, bảo tồn đầy đủ ngữ cảnh của cả hai nhánh.

Trưcmerge:mainABCDEfeature/authSaugitmergefeature/auth:mainABCM(M=mergecommit,parents:CvàE)DE
git switch main
git merge feature/auth
# Tạo commit M với message "Merge branch 'feature/auth'"

Khi nào dùng merge:

  • Hợp nhất feature branch hoàn chỉnh vào main (tạo record rõ ràng)
  • Nhận thay đổi từ main vào long-running feature branch khi muốn giữ context
  • Branch shared (nhiều người cùng dùng)

2. Rebase — Viết lại lịch sử

Rebase "di chuyển" các commits của bạn lên đầu branch đích, tạo ra lịch sử tuyến tính. Thực chất Git tạo commits mới (hash khác) với cùng nội dung.

Trưcrebase:mainABCDEfeature/authSaugitrebasemain(tfeature/auth):mainABCD'E'feature/authD'vàE'làcommitsmi(kháchash)vicùngthayđinhưngparentlàCthayvìB
git switch feature/auth
git rebase main

# Sau đó merge vào main (sẽ fast-forward)
git switch main
git merge feature/auth   # fast-forward, không tạo merge commit

Khi nào dùng rebase:

  • Cập nhật feature branch với những thay đổi mới nhất từ main (thay vì merge main vào feature)
  • Trước khi tạo PR để lịch sử clean và dễ review
  • Interactive rebase để dọn dẹp commits trong personal branch

3. Golden Rule of Rebasing

Không bao giờ rebase branch đã được push và người khác đang dùng.

Tại sao?

Tình huống: feature/auth đã push, đồng nghiệp B đã checkout

Bạn (Dev A):
git rebase main         ← tạo D', E' với hash mới
git push --force-with-lease origin feature/auth

Đồng nghiệp B:
git pull origin feature/auth
# Git thấy: lịch sử remote và local không còn chung parent!
# D và D' là commits khác nhau (dù cùng content)
# → Merge conflict vô lý, lịch sử bị duplicate

Quy tắc an toàn

Được rebase:
- Commits chỉ có local, chưa push
- Branch personal (bạn là người duy nhất dùng)
- Sau khi đã thông báo team KHÔNG dùng branch đó trong lúc rebase

Không được rebase:
- main, master, develop (shared branches)
- Branch đã push và đồng nghiệp đang làm việc trên đó
- Bất kỳ branch nào bạn không chắc ai đang dùng

4. Conflict Resolution

Conflict trong merge

git switch main
git merge feature/auth
# Auto-merging src/config.js
# CONFLICT (content): Merge conflict in src/config.js
# Automatic merge failed; fix conflicts and then commit the result.

# Xem files có conflict
git status
# both modified: src/config.js

# Nội dung file conflict:
<<<<<<< HEAD                        ← nội dung của main (HEAD)
const timeout = 30 * 60 * 1000;
=======                             ← separator
const timeout = 60 * 60 * 1000;
>>>>>>> feature/auth               ← nội dung của feature/auth

# Resolve: chỉnh sửa file thủ công, xoá markers
const timeout = 60 * 60 * 1000;   // chọn version feature/auth

# Stage và complete merge
git add src/config.js
git commit                          # Git tự điền merge commit message

# HOẶC: bỏ merge hoàn toàn
git merge --abort

Conflict trong rebase

git switch feature/auth
git rebase main
# CONFLICT (content): Merge conflict in src/config.js
# error: could not apply abc1234... feat: update timeout

# Xem file conflict và resolve
git status
# Unmerged paths:
#   both modified: src/config.js

# ... chỉnh sửa file, xoá conflict markers ...

# Stage và tiếp tục rebase
git add src/config.js
git rebase --continue
# Git áp dụng commit tiếp theo, nếu có conflict nữa → resolve tiếp

# Bỏ rebase hoàn toàn
git rebase --abort   # đưa về trạng thái trước khi rebase

Conflict markers chi tiết

<<<<<<< HEAD
nội dung của branch đang ở (HEAD)
||||||| original                    ← chỉ xuất hiện nếu conflict3way bật
nội dung gốc trước cả hai branch thay đổi
=======
nội dung của branch đang merge/rebase
>>>>>>> tên-branch

# Bật conflict3way để thấy original base:
git config --global merge.conflictstyle diff3

5. Merge vs Rebase — So sánh đầy đủ

Tiêu chíMergeRebase
Lịch sửBảo tồn đầy đủTuyến tính, dễ đọc
Merge commitCó (2 parents)Không (fast-forward)
SafetyAn toàn với branch sharedNguy hiểm với published branch
TraceabilityRõ ngữ cảnh featureKhó biết commit thuộc feature nào
git bisectHoạt động nhưng phức tạpDễ dùng hơn với linear history
git revertCó thể revert merge commitĐơn giản hơn

Decision tree

Cncpnhtfeaturebranchvimainminht?BranchchmìnhdùnggitrebasemainBranchshared/đãpushchoteamgitmergemainCnmergefeaturevàomain?Munthyrõ"featurenàyđưcmergekhinào"--no-ffmergeMunlinearhistoryrebasefeaturetrưc,rimerge(fast-forward)GitHubPRsquashmergesquash+merge(1commitchocfeature)

6. Một số lệnh rebase nâng cao (preview Week 2)

# Rebase branch hiện tại lên main
git rebase main

# Chỉ rebase N commits gần nhất (interactive — sẽ học Week 2)
git rebase -i HEAD~3

# Rebase chỉ một số commits lên branch khác
git rebase --onto new-base old-base branch

# Ví dụ: chuyển feature/auth (đang base trên develop) sang base trên main
git rebase --onto main develop feature/auth

Câu hỏi ôn tập

  1. Rebase tạo ra commits mới (hash khác) hay di chuyển commits cũ?

    Xem đáp án

    Rebase tạo commits mới với hash khác. Git áp dụng lại từng commit lên branch đích với parent mới — do parent thay đổi, SHA-1 thay đổi hoàn toàn. Commits D và E sau rebase trở thành D' và E' — cùng nội dung thay đổi nhưng hash khác. Đây là lý do không được rebase published branches: người khác có D/E sẽ thấy lịch sử bị phân kỳ.

  2. "Golden rule of rebasing" là gì? Vi phạm gây hậu quả gì?

    Xem đáp án

    Không bao giờ rebase branch đã được push và người khác đang dùng. Vi phạm dẫn đến: đồng nghiệp có commits cũ (D, E) và remote có commits mới (D', E') với hash khác nhau — Git coi đây là hai lịch sử phân kỳ, gây "duplicate commits" sau khi pull, lịch sử lộn xộn, và conflict vô lý khó giải quyết.

  3. Khi git rebase gặp conflict, sau khi resolve bạn dùng lệnh gì để tiếp tục?

    Xem đáp án

    git rebase --continue. Workflow: (1) chỉnh sửa file để xoá conflict markers, (2) git add <file>, (3) git rebase --continue. Nếu có nhiều commits bị conflict, Git sẽ tiếp tục áp dụng từng commit và dừng lại nếu gặp conflict tiếp theo. Để hủy toàn bộ rebase và quay về trạng thái ban đầu: git rebase --abort.

  4. git merge --abortgit rebase --abort có tác dụng gì?

    Xem đáp án

    Cả hai đều hủy operation đang thực hiện và đưa repository về trạng thái trước đó — như thể bạn chưa bắt đầu merge/rebase. --abort an toàn để dùng bất kỳ lúc nào trong quá trình resolve conflict nếu bạn muốn tiếp cận khác. Chỉ hoạt động khi operation đang in-progress (có MERGE_HEAD hoặc rebase directory trong .git/).

  5. Tình huống nào nên dùng git merge --no-ff thay vì merge thường?

    Xem đáp án

    Dùng --no-ff khi muốn lưu lại dấu vết rõ ràng rằng một nhóm commits thuộc về một feature/branch cụ thể. Trong Git Flow, merge feature vào develop luôn dùng --no-ff để lịch sử thể hiện rõ "feature X được merge tại đây, gồm N commits". Với --no-ff, git log --graph hiện nhánh rẽ; nếu fast-forward, lịch sử linear và khó biết commits thuộc feature nào.

Bài tập thực hành

# Phần 1: Thực hành merge
mkdir rebase-lab && cd rebase-lab
git init
git config user.name "Test" && git config user.email "test@example.com"

# Tạo lịch sử ban đầu
echo "initial" > app.js && git add . && git commit -m "init"
echo "main v2" >> app.js && git add . && git commit -m "feat: main v2"

# Tạo feature branch
git switch -c feature/a
echo "feature a" >> app.js && git add . && git commit -m "feat: add feature a"

# Merge vào main
git switch main
echo "main v3" >> app.js && git add . && git commit -m "feat: main v3"
git merge feature/a
git log --oneline --graph --all   # thấy merge commit

# Phần 2: Thực hành rebase
git switch -c feature/b
echo "feature b" >> app.js && git add . && git commit -m "feat: add feature b"

git switch main
echo "main v4" >> app.js && git add . && git commit -m "feat: main v4"

git switch feature/b
git rebase main
git log --oneline --graph --all   # thấy lịch sử tuyến tính

# Phần 3: Tạo conflict và resolve
git switch -c conflict-test
echo "conflict version A" > conflict.txt
git add . && git commit -m "add conflict.txt A"

git switch main
echo "conflict version B" > conflict.txt
git add . && git commit -m "add conflict.txt B"

git merge conflict-test   # CONFLICT!
# Resolve: chọn "conflict version A\nconflict version B"
cat > conflict.txt << 'EOF'
conflict version A
conflict version B
EOF
git add conflict.txt && git commit
git log --oneline --graph

Tài liệu tham khảo chính thức


Tiếp theo: Ngày 6 — Undo và Recovery