Tạo, chuyển và dọn branch cho từng feature
Mục tiêu bài học
- Giải thích được branch trong Git thực chất là gì, và vì sao tạo branch gần như miễn phí.
- Tạo nhánh mới và chuyển sang nhánh đó bằng
git switch -c, chuyển qua lại bằnggit switch. - Đặt tên nhánh feature theo quy ước có nghĩa để cả team đọc là hiểu.
- Xác định đúng thời điểm tách nhánh: trước khi viết dòng code đầu tiên, và luôn từ
mainmới nhất. - Dọn nhánh local và nhánh remote sau khi merge, không để rác lại trong repo.
Ôn tập nhanh
Bài trước em đã nối project blog lên GitHub bằng git remote add origin, push lần đầu với -u để đặt upstream, và git pull khi remote có thay đổi. Bài này dùng lại đúng hai thứ đó: git pull trên main trước khi tách nhánh, và upstream để biết nhánh nào đã đẩy lên remote.
Lý thuyết
Branch chỉ là một con trỏ
Người mới hay nghĩ tạo branch là copy toàn bộ thư mục project. Không phải. Branch trong Git chỉ là một con trỏ nhẹ tên tới một commit. Tạo một branch tốn vài byte, nên anh muốn em tạo nhánh thoải mái, mỗi việc một nhánh, đừng tiếc.
HEAD là con trỏ cho biết em đang đứng ở đâu. Khi em git switch, Git chỉ di chuyển HEAD và cập nhật working tree cho khớp commit đó.
Tạo và chuyển nhánh
git branch feature/trang-about # chỉ tạo, vẫn đứng ở nhánh cũ
git switch -c feature/trang-about # tạo và chuyển sang luôn
git switch main # chuyển về main
git switch - # quay lại nhánh vừa đứng
git branch # xem danh sách nhánh local
git branch -vv # kèm upstream và commit gần nhất
git switch là lệnh hiện đại; git checkout -b cũ vẫn chạy nhưng làm nhiều việc quá nên dễ nhầm. Từ giờ em dùng switch cho việc chuyển nhánh, restore cho việc khôi phục file.
Đặt tên nhánh có nghĩa
| Tiền tố | Dùng cho | Ví dụ |
|---|---|---|
feature/ |
tính năng mới | feature/trang-about |
fix/ |
sửa bug | fix/loi-hien-thi-ngay-thang |
chore/ |
việc lặt vặt, cấu hình | chore/cap-nhat-gitignore |
docs/ |
tài liệu | docs/huong-dan-cai-dat |
Quy tắc: chữ thường, kebab-case, ngắn nhưng đọc hiểu, gắn số issue nếu team có (feature/12-trang-about). Tránh test, abc, branch1, vì ba tuần sau chính em cũng không biết đó là gì.
Tách nhánh đúng lúc
Thời điểm đúng là trước khi em viết dòng code đầu tiên, và nhánh phải xuất phát từ main mới nhất:
git switch main
git pull
git switch -c feature/trang-about
Nếu tách từ một nhánh feature khác đang dở, em đang vay nợ: mọi commit của nhánh kia sẽ đi theo vào PR của em.
[!WARNING] Đừng tạo nhánh khi working tree còn thay đổi chưa commit. Git sẽ mang thay đổi đó sang nhánh mới và em rất dễ commit nhầm file vào sai nhánh. Chạy
git statustrước, sạch rồi hãyswitch.
Dọn nhánh sau khi merge
git branch -d feature/trang-about # chỉ xóa khi đã merge
git branch -D feature/trang-about # xóa cưỡng bức, mất commit chưa merge
git push origin --delete feature/trang-about
git fetch --prune # dọn remote-tracking branch đã bị xóa
Nhánh remote bị xóa trên GitHub thì máy em vẫn còn origin/feature/... trong git branch -a cho tới khi prune. Đây là lý do nhiều bạn thấy "remote còn đầy nhánh" dù đã xóa.
Ví dụ từng bước
Ví dụ 1: bắt đầu feature "trang About" cho blog
1. `git switch main` để về nhánh chính.
2. `git pull` để lấy commit mới nhất từ GitHub.
3. `git switch -c feature/trang-about` để tạo và chuyển nhánh.
4. `git status` để xác nhận đang ở nhánh mới, working tree sạch.
5. Code, commit như bình thường trên nhánh này.
Ví dụ 2: đang làm dở thì sếp báo lỗi gấp trên production
Em đang ở feature/trang-about, có file sửa dở. Cách an toàn: commit tạm hoặc stash, rồi tách nhánh fix từ main.
git commit -am "wip: dang lam trang about"
git switch main
git pull
git switch -c fix/loi-ngay-thang
Làm xong, merge nhánh fix (bài sau), rồi quay lại: git switch feature/trang-about.
Ví dụ 3: dọn sau khi PR đã merge
git switch main
git pull
git branch -d feature/trang-about
git push origin --delete feature/trang-about
git fetch --prune
git branch -a
Kết quả: chỉ còn main và remotes/origin/main. Repo sạch, danh sách nhánh trên GitHub chỉ còn nhánh đang làm.
Luyện tập có hướng dẫn
Bài 1. Em cần thêm trang "Liên hệ" cho blog. Viết đúng ba lệnh đầu tiên.
Gợi ý: git switch main → git pull → git switch -c feature/trang-lien-he.
Bài 2. git branch -d feature/trang-lien-he báo lỗi not fully merged. Em hiểu gì và xử lý sao?
Gợi ý: nhánh còn commit chưa merge vào main. Đừng vội dùng -D. Kiểm tra bằng git log main..feature/trang-lien-he, nếu commit đó thật sự cần thì merge trước, nếu là rác thì mới -D.
Bài 3. Trên GitHub đã xóa nhánh feature/cu, nhưng git branch -a ở máy em vẫn hiện remotes/origin/feature/cu. Sửa thế nào?
Gợi ý: git fetch --prune. Remote-tracking branch chỉ được cập nhật khi em fetch.
Góc chuyên gia
- Tách nhánh từ nhánh bẩn. Lỗi phổ biến nhất của intern. Luôn
git switch mainvàgit pulltrước khi tạo nhánh mới. - Nhánh sống quá lâu. Nhánh feature nên sống vài ngày, không phải vài tuần. Càng lâu, merge càng đau và review càng mệt.
- Tên nhánh không mang thông tin.
feature/abclà món nợ cho người review. - Xóa nhầm bằng
-D.-Dkhông hỏi lại. Nếu lỡ xóa,git reflogvẫn cứu được trong khoảng 30 ngày, nhưng đừng dựa vào nó. - Quên dọn remote. Mỗi PR merge xong là một nhánh remote cần xóa. Bật setting "Automatically delete head branches" trên GitHub để đỡ quên.
Tóm tắt và bước tiếp theo
Bốn câu chốt: branch chỉ là con trỏ nhẹ tên, git switch -c để tạo và chuyển, tách nhánh từ main mới nhất trước khi code, và dọn cả local lẫn remote sau khi merge.
Bài sau chúng ta sẽ merge nhánh feature/trang-about vào main, và học cách đọc dấu conflict khi hai nhánh cùng sửa một file. Chuẩn bị sẵn một nhánh feature đang có commit, em sẽ cần nó để thực hành.
Trong Git, branch chỉ là một {{c1::con trỏ}} nhẹ tên tới một commit, nên tạo nhánh gần như {{c2::miễn phí}}.
Lệnh tạo nhánh mới và chuyển sang ngay là {{c3::git switch -c}} theo sau là tên nhánh.
Trước khi tách nhánh feature, luôn chạy {{c4::git pull}} trên main để nhánh xuất phát từ commit mới nhất.
git branch -d chỉ xóa nhánh khi nó đã {{c5::merge}}, còn git branch -D thì xóa {{c6::cưỡng bức}}.
Sau khi nhánh remote bị xóa, dùng {{c7::git fetch --prune}} để dọn remote-tracking branch ở máy local.
[
{ "front": "Branch trong Git thực chất là gì?", "back": "Một con trỏ nhẹ tên tới một commit, không phải bản copy thư mục." },
{ "front": "Lệnh tạo nhánh mới và chuyển sang nhánh đó ngay?", "back": "git switch -c ten-nhanh (tương đương git checkout -b ten-nhanh)." },
{ "front": "Lệnh chỉ tạo nhánh mà không chuyển sang?", "back": "git branch ten-nhanh." },
{ "front": "Lệnh quay lại nhánh vừa đứng trước đó?", "back": "git switch -" },
{ "front": "HEAD là gì?", "back": "Con trỏ cho biết em đang đứng ở commit hoặc nhánh nào." },
{ "front": "Trước khi tách nhánh feature nên làm gì với main?", "back": "git switch main rồi git pull để nhánh xuất phát từ commit mới nhất." },
{ "front": "Quy ước đặt tên nhánh feature?", "back": "feature/<mô tả ngắn> dạng chữ thường kebab-case, ví dụ feature/trang-about." },
{ "front": "git branch -d khác git branch -D thế nào?", "back": "-d chỉ xóa khi nhánh đã merge, -D xóa cưỡng bức kể cả chưa merge." },
{ "front": "Xóa một nhánh trên remote GitHub bằng lệnh nào?", "back": "git push origin --delete ten-nhanh." },
{ "front": "Lệnh dọn các remote-tracking branch đã bị xóa trên remote?", "back": "git fetch --prune, hoặc viết tắt git fetch -p." },
{ "front": "git branch -vv hiển thị gì?", "back": "Danh sách nhánh local kèm upstream và commit gần nhất của mỗi nhánh." },
{ "front": "Vì sao không nên để nhánh feature sống quá lâu?", "back": "Conflict và khối lượng review phình to, merge hoặc rebase ngày càng đau." },
{ "front": "Nhánh feature nên được tách ra từ đâu?", "back": "Từ main mới nhất, không tách từ nhánh feature khác đang làm dở." }
]