---
id: git-branching
title: "Branch, merge và rebase"
shortTitle: "Branch, merge, rebase"
track: git
trackTitle: "Git & VCS Hosting"
order: 2
level: core
status: published
estimatedMinutes: 45
prerequisites:
  - git-fundamentals
next: git-remote-pr
reviewedAgainst: 2026-10-15
sources:
  - title: "KodeKloud Engineer — 100 Days of DevOps"
    url: https://engineer.kodekloud.com/practice
  - title: "Pro Git — Branches in a Nutshell"
    url: https://git-scm.com/book/en/v2/Git-Branching-Branches-in-a-Nutshell
  - title: "Pro Git — Basic Branching and Merging"
    url: https://git-scm.com/book/en/v2/Git-Branching-Basic-Branching-and-Merging
  - title: "Pro Git — Rebasing"
    url: https://git-scm.com/book/en/v2/Git-Branching-Rebasing
  - title: "git-merge"
    url: https://git-scm.com/docs/git-merge
  - title: "git-rebase"
    url: https://git-scm.com/docs/git-rebase
---

# Branch, merge và rebase

Bạn cần một repo có vài commit. Dùng lại repo của bài trước, hoặc tạo mới theo mục Thực hành.

## Để làm gì

Bạn làm feature hay sửa config trên branch riêng, nên `main` đang chạy production không bị đụng tới. Làm xong thì gộp lại, tự xử lý conflict, và giữ history đủ sạch để người review và pipeline đọc được.

## Gồm những gì

- Branch là gì về mặt kỹ thuật: một con trỏ tới commit.
- `git switch`, `git switch -c`, `git checkout -b`.
- `git branch`, `git branch -m`, `git branch -d`, `git branch -D`.
- Merge: fast-forward và merge commit (three-way merge).
- Xử lý merge conflict: sửa file, `git add`, `git commit`. Bỏ dở: `git merge --abort`.
- Rebase, interactive rebase để dọn commit trước khi mở pull request.
- `git cherry-pick` để mang một commit sang branch khác, ví dụ hotfix.
- Quy tắc: không rebase branch người khác đang dùng.

Push branch và pull request nằm ở bài 3. Chiến lược branch cho cả team nằm ở bài 4.

## Dùng khi nào

- Bắt đầu mọi thay đổi: tạo branch từ `main` mới nhất.
- `main` có commit mới trong lúc bạn làm: merge hoặc rebase `main` vào branch của mình trước khi mở PR.
- Sửa lỗi production trên một branch release cũ: cherry-pick commit fix từ `main`.
- Branch có 10 commit kiểu `wip`, `fix typo`: interactive rebase gộp lại trước khi review.

## Kiến thức cốt lõi

### Branch chỉ là con trỏ

Pro Git mô tả branch là một con trỏ nhẹ, di chuyển được, trỏ tới một commit. Tạo branch chỉ là ghi thêm một file chứa hash, gần như tức thì. Mỗi lần commit trên branch, con trỏ nhích lên commit mới. `HEAD` cho biết bạn đang đứng ở branch nào.

`git switch -c feature/x` tạo nhánh rồi chuyển sang. `git checkout -b feature/x` làm cùng việc. `git branch -m moi` đổi tên nhánh đang đứng. `git branch -d feature/x` chỉ xóa nhánh đã merge. Nhánh chưa merge thì `-d` từ chối; `-D` là `--delete --force`.

```text
          C4  <- feature
         /
C1 - C2 - C3  <- main
```

### Fast-forward

Nếu `main` không có commit mới từ lúc tạo `feature`, merge chỉ việc dời con trỏ `main` lên đầu `feature`. Không có commit mới nào được tạo. Đó là fast-forward.

### Merge commit (three-way merge)

Nếu cả hai branch đều có commit mới, Git lấy ba điểm: đầu `main`, đầu `feature`, và commit chung gần nhất (merge base). Kết quả là một merge commit có hai parent.

```text
          C4 - C5      <- feature
         /       \
C1 - C2 - C3 - C6 - M  <- main
```

Cách này giữ đúng những gì đã xảy ra, nhưng đồ thị history sẽ rối hơn khi nhiều người cùng làm.

### Conflict

Conflict xảy ra khi hai branch sửa cùng một vùng của cùng một file. Git dừng lại và đánh dấu ngay trong file:

```text
<<<<<<< HEAD
replicas: 3
=======
replicas: 5
>>>>>>> feature/scale-api
```

Phần trên là của branch đang đứng, phần dưới là của branch đang merge vào. Bạn sửa file thành nội dung đúng, xoá các dấu, rồi `git add` và `git commit` (hoặc `git rebase --continue` nếu đang rebase). Muốn bỏ dở thì `git merge --abort`.

Với file config hay manifest, giải conflict thường không chỉ là chọn một bên. Ví dụ một bên đổi `replicas`, bên kia đổi `image` ở dòng kế bên: bạn phải giữ cả hai thay đổi.

### Rebase

Rebase lấy các commit trên branch của bạn và áp lại lần lượt lên đầu một branch khác. Kết quả là history thẳng, như thể bạn bắt đầu làm từ `main` mới nhất.

```text
Trước:                         Sau git rebase main:
          C4 - C5  <- feature                    C4' - C5'  <- feature
         /                                      /
C1 - C2 - C3 - C6  <- main     C1 - C2 - C3 - C6  <- main
```

`C4'` và `C5'` là commit mới, hash khác `C4`, `C5`. Vì vậy có quy tắc trong Pro Git: **đừng rebase commit đã nằm ngoài repo của bạn mà người khác có thể đã dựa vào**. Branch cá nhân chưa ai dùng thì rebase thoải mái. Branch chung như `main` thì không.

Nếu branch đã push lên remote thì sau khi rebase bạn phải force push. Dùng `git push --force-with-lease`: lệnh này từ chối ghi đè nếu remote có commit bạn chưa thấy.

### Merge hay rebase

| | Merge | Rebase |
| --- | --- | --- |
| History | Giữ nguyên, có merge commit | Thẳng, commit được viết lại |
| An toàn trên branch chung | Có | Không |
| Khi conflict | Giải một lần | Có thể giải nhiều lần, mỗi commit một lần |
| Hợp với | Gộp feature vào `main` | Cập nhật branch cá nhân, dọn commit trước PR |

Nhiều team kết hợp: rebase branch cá nhân lên `main` cho gọn, rồi merge vào `main` qua pull request.

### Interactive rebase

`git rebase -i HEAD~4` mở editor với 4 commit cuối. Đổi `pick` thành:

- `squash` hoặc `fixup`: gộp vào commit trước.
- `reword`: sửa message.
- `drop`: bỏ commit.
- Đổi thứ tự dòng: đổi thứ tự commit.

Chỉ làm việc này trên branch chưa merge.

### Cherry-pick

`git cherry-pick <hash>` tạo một commit mới trên branch hiện tại, mang cùng thay đổi với commit gốc. Bạn dùng nó khi cần đưa đúng một fix sang branch release mà không kéo theo các feature khác.

## Lệnh và code mẫu

Tạo branch và chuyển sang:

```bash
git switch main
git switch -c feature/healthcheck     # tạo và chuyển; git checkout -b làm cùng việc
git branch                            # liệt kê, dấu * là branch hiện tại
git branch -m feature/health          # đổi tên nhánh đang đứng
git branch -d feature/health          # xoá nhánh đã merge
git branch -D feature/wip             # xoá cả khi chưa merge
```

Merge:

```bash
git switch main
git merge feature/healthcheck              # fast-forward nếu được
git merge --no-ff feature/healthcheck      # luôn tạo merge commit
git merge --abort                          # bỏ dở khi đang conflict
```

Rebase branch cá nhân lên `main`:

```bash
git switch feature/healthcheck
git rebase main
# khi conflict: sửa file, rồi
git add k8s/deployment.yaml
git rebase --continue
# hoặc bỏ dở
git rebase --abort
```

Dọn commit trước PR:

```bash
git rebase -i main
```

Cherry-pick một fix sang branch release:

```bash
git switch release/1.4
git cherry-pick 3f2a9c1
```

## Thực hành

Đoạn lệnh dưới tạo sẵn một conflict trên file manifest:

```bash
mkdir -p "$HOME/devops-lab/git-branching"
cd "$HOME/devops-lab/git-branching"
git init -b main

printf 'replicas: 2\nimage: api:1.0.0\n' > deploy.yaml
git add deploy.yaml
git commit -m "Add deployment config"

git switch -c feature/scale
sed -i 's/replicas: 2/replicas: 5/' deploy.yaml
git commit -am "Scale API to 5 replicas"

git switch main
sed -i 's/replicas: 2/replicas: 3/' deploy.yaml
git commit -am "Scale API to 3 replicas"
```

macOS dùng `sed -i ''` thay cho `sed -i`.

1. Chạy `git log --oneline --graph --all`, thấy hai nhánh tách ra.
2. Trên `main`, chạy `git merge feature/scale`. Git báo conflict.
3. Mở `deploy.yaml`, chọn `replicas: 5`, xoá dấu conflict, `git add deploy.yaml`, `git commit`.
4. Chạy lại `git log --oneline --graph --all`, thấy merge commit có hai parent.
5. Tạo branch `feature/tag` từ `main`, làm 3 commit nhỏ, rồi `git rebase -i main` gộp thành 1.
6. Thử `git cherry-pick`: tạo branch `release/1.0` từ commit đầu tiên, cherry-pick commit "Scale API to 3 replicas".

## Kết quả mong đợi

- Bước 2 in `CONFLICT (content): Merge conflict in deploy.yaml`.
- Sau bước 3, `git show HEAD` cho thấy merge commit có dòng `Merge:` với hai hash.
- Sau interactive rebase, `git log --oneline main..feature/tag` chỉ còn 1 commit.
- Commit cherry-pick có hash khác commit gốc nhưng cùng nội dung thay đổi.

## Checklist

- [ ] Giải thích branch là con trỏ tới commit.
- [ ] Phân biệt fast-forward với merge commit.
- [ ] Giải conflict và hoàn tất merge hoặc rebase.
- [ ] Nói được vì sao không rebase branch chung.
- [ ] Dùng `--force-with-lease` thay cho `--force`.
- [ ] Gộp commit bằng `git rebase -i`.
- [ ] Cherry-pick một fix sang branch release.
- [ ] Đổi tên nhánh bằng `git branch -m`. Xóa nhánh đã merge bằng `-d`. Chưa merge thì `-D`.

## Thẻ ôn

### git-branching-main
kind: trap
cue: Commit thẳng lên `main` thì không ai review, và CI chỉ chạy sau khi code đã vào nhánh dùng để deploy.

Q: Production deploy từ `main`. Bạn sửa một dòng config, commit thẳng lên `main` cho nhanh vì thay đổi nhỏ. CI và người review có thấy thay đổi đó trước khi nó vào nhánh production không?

A: Không. Push thẳng thì không có pull request để ai đọc, và job CI gắn với nhánh đó chỉ chạy sau khi commit đã nằm trên `main`. Nếu `main` chính là thứ được deploy, code đã vào đường production trước khi có check. Tạo branch kể cả khi chỉ sửa một dòng, mở pull request, để CI chạy trên nhánh đó trước khi merge.

### git-branching-rebase
kind: trap
cue: Rebase một nhánh nhiều người đang dùng rồi force push làm history lệch và commit bị nhân đôi. Chỉ rebase nhánh của riêng mình.

Q: `main` đã có commit của nhiều người. Bạn rebase `main` cho history thẳng rồi force push. Đồng nghiệp pull về sẽ thấy gì?

A: Rebase viết lại commit, hash đổi. Force push thay history trên remote bằng history mới. Người đã có commit cũ sẽ thấy history lệch. Lúc họ pull, Git không ghép được thành một đường, và commit có thể xuất hiện hai lần: bản cũ họ đang có và bản đã viết lại. Chỉ rebase nhánh chưa ai khác dựa vào. Nhánh chung như `main` thì merge hoặc revert, không viết lại.

### git-branching-lease
kind: command
cue: Sau khi rebase nhánh của mình, push bằng `--force-with-lease`. `--force` ghi đè luôn commit đồng nghiệp vừa đẩy lên.

Q: Bạn vừa rebase nhánh feature của mình, cần đẩy lại lên remote. `--force` và `--force-with-lease` khác nhau thế nào nếu đồng nghiệp cũng vừa push một commit lên đúng nhánh đó?

A: Cả hai đều cập nhật nhánh remote sang commit mới của bạn. `--force` làm vậy dù remote đã có commit bạn chưa thấy, nên commit đồng nghiệp vừa push bị ghi đè. `--force-with-lease` chỉ đẩy khi remote vẫn đúng commit bạn đã thấy lúc fetch. Nếu có commit lạ, lệnh từ chối, bạn fetch và xử lý trước. Sau rebase, dùng `--force-with-lease`.

### git-branching-conflict
kind: trap
cue: Conflict trong YAML mà lấy trọn một phía sẽ làm mất thay đổi phía kia. Đọc cả hai, rồi chạy test hoặc dry-run trước khi commit.

Q: Merge bị conflict trong `deploy.yaml`. Để xong cho nhanh, bạn giữ nguyên phía mình và xóa phía kia rồi commit. Trong file manifest, thiếu gì?

A: Những dòng phía kia đã thêm, thường là một key nằm sát dòng của bạn, biến mất dù conflict trông chỉ là vài dòng. YAML không báo rằng ý nghĩa của file đã đổi. Đọc cả hai phần, giữ những key còn đúng, rồi kiểm tra file vẫn hợp lệ. Với manifest, chạy test hoặc `kubectl apply --dry-run=client` trước khi commit, để một lần resolve không đẩy một bản deploy hỏng.

### git-branching-cherry
kind: command
cue: Một fix đã nằm trên `main` mà nhánh release chỉ cần đúng thay đổi đó thì `git cherry-pick`. Commit mới có hash khác, cùng nội dung.

Q: Một lỗi production được sửa bằng một commit trên `main`. Nhánh `release/1.0` đang chạy bản cũ và cần đúng fix đó, không cần các commit tính năng mới hơn trên `main`. Làm gì, và commit trên nhánh release có cùng hash không?

A: Đứng trên `release/1.0` và `git cherry-pick` đúng commit fix. Git áp cùng phần thay đổi lên nhánh release và tạo một commit mới. Hash khác commit gốc vì commit cha và ngữ cảnh khác, nhưng diff của fix thì cùng nội dung. Đừng merge cả `main` vào release chỉ để lấy một commit.

### git-branching-delete
kind: distinguish
cue: `git branch -d` chỉ xóa nhánh đã merge. Nhánh còn commit chưa vào nhánh kia thì `-d` từ chối. `-D` xóa luôn.

Q: `git branch -d feature/wip` bị từ chối. Nhánh đó còn vài commit chưa merge. `-d` và `-D` khác nhau chỗ nào?

A: `-d` là xóa khi Git thấy nhánh đã được merge vào nhánh hiện tại hoặc vào remote đã theo dõi. Còn commit chưa gộp thì lệnh dừng, để bạn không mất việc chưa đưa vào `main`. `-D` là `--delete --force`: xóa dù chưa merge. Chỉ dùng `-D` khi bạn chắc những commit đó không cần nữa.

## Lỗi thường gặp

**Làm thẳng trên `main`.** Không ai review được, và CI chỉ chạy sau khi code đã vào branch production. Luôn tạo branch, kể cả khi chỉ sửa một dòng.

**Rebase `main` hoặc branch nhiều người dùng rồi force push.** Người khác pull về sẽ thấy history lệch và commit bị nhân đôi. Chỉ rebase branch của riêng mình.

**`git push --force` sau khi rebase.** Lệnh này ghi đè luôn commit đồng nghiệp vừa push lên branch đó. Dùng `git push --force-with-lease`.

**Giải conflict bằng cách lấy trọn một bên.** Thay đổi của bên kia sẽ mất, nhất là với YAML có nhiều dòng sát nhau. Đọc kỹ cả hai phần, chạy test hoặc `kubectl apply --dry-run=client` trước khi commit.

**Branch sống quá lâu.** Branch càng sống lâu càng lệch `main`, nên conflict càng lớn. Hãy merge từng phần nhỏ và thường xuyên, xem phần trunk-based ở bài 4.

**`git branch -D` cho mọi nhánh vừa xong.** `-D` bỏ cả commit chưa merge. Thử `-d` trước. Lệnh từ chối thì xem còn commit nào chưa vào `main`.

## Nguồn

1. [KodeKloud Engineer — 100 Days of DevOps](https://engineer.kodekloud.com/practice) — đề cương ngày tạo nhánh, merge, rebase, conflict, cherry-pick. Trang practice cần đăng nhập. Bài này viết lại lệnh, không chép lab.
2. [KodeKloud — GIT for Beginners](https://learn.kodekloud.com/learn/courses/git-for-beginners) — phạm vi module Git Branches và Rebasing: checkout, merge, rebase, interactive rebase, cherry-pick.
3. [Pro Git — Branches in a Nutshell](https://git-scm.com/book/en/v2/Git-Branching-Branches-in-a-Nutshell) — branch là con trỏ, `HEAD`.
4. [Pro Git — Basic Branching and Merging](https://git-scm.com/book/en/v2/Git-Branching-Basic-Branching-and-Merging) — fast-forward, three-way merge, conflict.
5. [Pro Git — Rebasing](https://git-scm.com/book/en/v2/Git-Branching-Rebasing) — rebase và quy tắc không rebase commit đã chia sẻ.
6. [git-merge](https://git-scm.com/docs/git-merge), [git-rebase](https://git-scm.com/docs/git-rebase), [git-cherry-pick](https://git-scm.com/docs/git-cherry-pick), [git-switch](https://git-scm.com/docs/git-switch), [git-branch](https://git-scm.com/docs/git-branch) — `-m`, `-d`, `-D`.
7. [git-push](https://git-scm.com/docs/git-push) — `--force-with-lease`.

## Tiếp theo

[Remote, pull request và code review](./03-remote-pr.md)
