Hướng dẫn

Nhánh trong Git: git branch, tạo, chuyển và xóa

Cập nhật: · Skillaria.Top

Nhánh trong Git là con trỏ nhẹ trỏ tới một commit, còn HEAD cho biết bạn đang ở nhánh nào. git branch liệt kê, tạo, đổi tên và xóa nhánh; git switch chuyển giữa các nhánh; git switch -c tên vừa tạo nhánh vừa chuyển sang nhánh đó.

Nhánh trong Git là gì

Nhánh trong Git không phải là bản sao của dự án, mà là một tệp nhỏ chứa hash của một commit. Tạo nhánh chỉ mất vài phần nghìn giây và hầu như không tốn dung lượng, vì Git không nhân bản gì cả: mọi nhánh đều trỏ tới cùng các đối tượng trong cơ sở dữ liệu của kho. Khi bạn commit, con trỏ của nhánh hiện tại chỉ đơn giản dịch chuyển sang commit mới.

Điều này thay đổi cách nhìn về nhánh. Không cần tiếc hay tiết kiệm chúng: một nhánh riêng cho mỗi công việc, mỗi lần sửa lỗi hay mỗi thử nghiệm là thực hành bình thường. Nếu thử nghiệm không thành công, nhánh bị xóa và lịch sử chính vẫn sạch.

Bạn có thể tự kiểm chứng rằng nhánh chỉ là một tệp:

$ cat .git/refs/heads/main
8b7d4e2a9f1c0d3e5b6a7c8d9e0f1a2b3c4d5e6f

(Trong các kho lớn, một phần tham chiếu được gom vào .git/packed-refs, nên tệp có thể không tồn tại — bản chất không thay đổi.)

HEAD trong Git: bạn đang ở đâu

HEAD là con trỏ tới thứ đang được «lấy ra» trong thư mục làm việc. Thông thường HEAD không trỏ trực tiếp tới commit mà trỏ tới một nhánh:

$ cat .git/HEAD
ref: refs/heads/main

Chuỗi tham chiếu là: HEAD → main → commit. Commit mới sẽ có cha là commit mà HEAD đang trỏ tới, sau đó Git dịch chuyển nhánh mà HEAD đang chỉ vào. Chính vì vậy các commit «rơi» vào nhánh hiện tại.

Nếu chuyển thẳng sang một commit hoặc thẻ (git switch --detach v1.0 hoặc git checkout 3f2a1c9), HEAD sẽ trỏ trực tiếp tới commit. Trạng thái này gọi là detached HEAD. Xem code trong đó thì an toàn, nhưng các commit mới không gắn với nhánh nào và dễ bị mất sau khi chuyển đi. Nếu bạn đã bắt đầu chỉnh sửa, hãy tạo nhánh ngay tại đó: git switch -c fix-from-old-version.

Trong các biểu thức, HEAD dùng làm điểm mốc: HEAD~1 — commit trước đó, HEAD~3 — ba commit trước, HEAD^2 — cha thứ hai của commit gộp.

Xem danh sách nhánh: git branch

git branch            # các nhánh cục bộ, nhánh hiện tại có dấu *
git branch -v         # kèm commit cuối của mỗi nhánh
git branch -vv        # kèm upstream và số commit lệch/vượt
git branch -r         # nhánh từ xa (origin/...)
git branch -a         # tất cả các nhánh
git branch --show-current   # chỉ tên nhánh hiện tại

Ví dụ kết quả của git branch -vv:

  feature/login 4c1e2d7 [origin/feature/login: ahead 2] Form đăng nhập
* main          8b7d4e2 [origin/main] Cập nhật README
  old-spike     1a9b3c0 Thử nghiệm cache

Ở đây thấy rằng feature/login có hai commit chưa được đẩy lên, main khớp với máy chủ, còn old-spike hoàn toàn không có nhánh tương ứng trên máy chủ.

Để lọc nhánh có thể dùng git branch --merged (đã gộp vào nhánh hiện tại — ứng viên để xóa), git branch --no-merged (chưa được gộp) và git branch --list 'feature/*' (theo mẫu).

Cách tạo nhánh

Có hai cách. git branch tạo nhánh nhưng bạn vẫn ở nhánh hiện tại:

git branch feature/login            # từ commit hiện tại
git branch hotfix v1.2.0            # từ thẻ
git branch review 3f2a1c9           # từ một commit cụ thể

Thường thì bạn muốn tạo nhánh và chuyển sang luôn. Dùng git switch -c (xuất hiện từ Git 2.23) hoặc cách cũ tương đương là git checkout -b:

git switch -c feature/login
git switch -c hotfix origin/main    # nhánh mới từ trạng thái mới nhất của máy chủ

Thói quen tốt: trước khi tạo nhánh cho công việc, hãy cập nhật nền: git fetch, rồi git switch -c feature/x origin/main. Như vậy nhánh bắt đầu từ mã mới nhất, không phải từ main cục bộ đã lâu không cập nhật.

Cách đặt tên nhánh

Trong nhóm thường thống nhất tiền tố theo loại công việc và mô tả ngắn bằng dấu gạch ngang: feature/user-avatar, fix/login-timeout, chore/update-deps, đôi khi kèm mã công việc: feature/SHOP-142-cart. Không được dùng khoảng trắng, ~ ^ : ? * [, hai dấu chấm liên tiếp và kết thúc bằng .lock.

Chuyển sang nhánh khác: git switch

git switch main             # chuyển sang nhánh đã có
git switch -                # quay lại nhánh trước đó
git switch feature/login    # nếu chưa có nhánh cục bộ nhưng có origin/feature/login,
                            # Git tạo nhánh cục bộ và liên kết với nhánh từ xa

Khi chuyển nhánh, Git thay đổi các tệp trong thư mục làm việc theo phiên bản của nhánh đích. Thay đổi chưa commit sẽ đi theo bạn nếu không xung đột với nhánh đích. Nếu xung đột, Git sẽ từ chối:

error: Your local changes to the following files would be overwritten by checkout:
        app.js
Please commit your changes or stash them before you switch branches.

Cách xử lý: commit, cất tạm bằng git stash (sau đó khôi phục bằng git stash pop), hoặc chủ động mang thay đổi sang với việc cố gắng gộp: git switch -m main. Cờ --discard-changes sẽ bỏ các chỉnh sửa — chỉ dùng khi chắc chắn không cần chúng.

Vì sao dùng git switch chứ không phải checkout? Checkout vừa chuyển nhánh, vừa ghi đè tệp, nên dễ nhầm lẫn. Switch chỉ làm một việc — làm việc với nhánh — nên an toàn hơn. So sánh chi tiết checkout, switch và restore có trong một bài riêng.

Đổi tên nhánh

git branch -m new-name              # đổi tên nhánh hiện tại
git branch -m old-name new-name     # đổi tên nhánh bất kỳ
git branch -M main                  # giống trên nhưng ghi đè nhánh đã tồn tại

Nếu nhánh đã có trên máy chủ, việc đổi tên chỉ diễn ra cục bộ. Hãy đẩy nhánh mới lên và xóa nhánh cũ:

git push -u origin new-name
git push origin --delete old-name

Xóa nhánh

git branch -d feature/login     # chỉ xóa nếu nhánh đã được gộp
git branch -D old-spike         # xóa bắt buộc
git push origin --delete feature/login   # xóa trên máy chủ

-d bảo vệ bạn khỏi mất công việc: nếu trong nhánh có commit mà nhánh hiện tại (hoặc upstream của nó) chưa có, Git sẽ từ chối với thông báo «The branch 'x' is not fully merged». -D xóa mà không hỏi. Không thể xóa nhánh đang dùng — hãy chuyển sang nhánh khác trước.

Lỡ tay xóa? Commit không biến mất ngay. Tìm hash commit cuối của nhánh trong git reflog và khôi phục: git branch old-spike 1a9b3c0.

Các nhánh theo dõi từ xa đã cũ, không còn trên máy chủ, được dọn bằng git fetch --prune.

Liên kết nhánh cục bộ với nhánh từ xa

Nhánh cục bộ có thể có upstream — nhánh từ xa mà nó được so sánh và đồng bộ với. Điều này giúp git push và git pull chạy không cần tham số, còn git status hiển thị «ahead/behind».

git push -u origin feature/login                 # lần gửi đầu tiên
git branch --set-upstream-to=origin/main main    # với nhánh đã có
git branch --unset-upstream                      # bỏ liên kết

Gộp nhánh: tóm tắt

Nhánh được tạo ra để sau này gộp lại. Khi công việc hoàn thành, hãy chuyển sang nhánh đích và gộp nhánh của bạn vào đó:

git switch main
git merge feature/login

Nếu main không tiến thêm kể từ khi tạo nhánh, Git thực hiện fast-forward: chỉ dịch chuyển con trỏ main tới commit cuối của nhánh, không tạo commit gộp. Nếu cả hai nhánh đều có commit mới, sẽ có phép gộp ba chiều: Git tìm tổ tiên chung, so sánh hai phía và tạo commit gộp với hai cha. Trong nhóm, việc gộp thường được thực hiện qua pull request trên máy chủ, còn cục bộ bạn chỉ kéo về main mới nhất.

Bảng tra cứu nhánh

Việc cần làmLệnh
Danh sách nhánhgit branch -vv
Tạo nhánhgit branch tên
Tạo và chuyển sanggit switch -c tên
Chuyển nhánhgit switch tên
Quay lại nhánh trướcgit switch -
Đổi têngit branch -m cũ mới
Xóa nhánh đã gộpgit branch -d tên
Xóa bắt buộcgit branch -D tên
Xóa trên máy chủgit push origin --delete tên
Nhánh có thể xóagit branch --merged
Khôi phục nhánh đã xóagit reflog, sau đó git branch tên hash

Lỗi thường gặp của người mới

  • Làm việc trực tiếp trên main. Nếu đã commit nhưng chưa đẩy, hãy tạo nhánh ngay tại vị trí hiện tại (git branch feature/x), rồi đưa main về trạng thái máy chủ: git reset --hard origin/main (trước đó kiểm tra mọi thứ đã commit).
  • Tạo nhánh từ main cũ. Chạy git fetch trước, và tạo nhánh từ origin/main.
  • Commit trong detached HEAD. Trước khi chuyển đi, hãy tạo nhánh; nếu đã chuyển rồi thì tìm commit trong git reflog.
  • Nhánh sống quá lâu. Nhánh tách biệt càng lâu thì gộp càng đau. Thường xuyên kéo main về và gộp công việc thành các phần nhỏ.

Nhánh trong làm việc nhóm

Quy trình trong các nhóm khác nhau, nhưng gần như luôn gặp một trong hai sơ đồ. Trong GitHub Flow có một nhánh main tồn tại lâu dài, luôn sẵn sàng để triển khai, và các nhánh tính năng ngắn hạn được gộp qua pull request sau khi review và CI kiểm tra. Trong Git Flow có thêm develop, nhánh phát hành và nhánh hotfix; sơ đồ này nặng hơn và phù hợp với sản phẩm có bản phát hành có phiên bản hiếm khi ra mắt.

Dù chọn sơ đồ nào, các quy tắc chung vẫn giống nhau: một nhánh cho một công việc, nhánh sống vài ngày chứ không phải vài tháng, trước khi tạo pull request nhánh phải được cập nhật với main mới nhất, và sau khi gộp thì xóa nhánh cả ở máy cục bộ lẫn máy chủ. Nhánh chính được bảo vệ bằng cài đặt của kho: cấm push trực tiếp và force push, việc gộp chỉ thực hiện sau review.

Xem lịch sử nhánh

Cách dễ nhất để hiểu quan hệ giữa các nhánh là xem đồ thị: git log --oneline --graph --all vẽ mọi nhánh và điểm rẽ nhánh ngay trong terminal. Để xem các commit có trong nhánh nhưng chưa vào main, dùng git log main..feature/login; còn để xem khác biệt trong tệp, dùng git diff main...feature/login (ba dấu chấm so sánh với tổ tiên chung, tức là chỉ hiển thị thay đổi của chính nhánh đó).

Tóm lại

Nhánh trong Git là con trỏ rẻ tiền tới một commit, còn HEAD là con trỏ tới nhánh hiện tại. git branch quản lý nhánh (liệt kê, tạo, đổi tên, xóa), git switch chuyển giữa các nhánh, còn git switch -c làm cả hai việc cùng lúc. Nhánh, HEAD, fast-forward và gộp ba chiều được giải thích qua các ví dụ thực tế trong khóa học Git.

Câu hỏi thường gặp

Có thể tạo bao nhiêu nhánh trong Git?

Hầu như không giới hạn: mỗi nhánh là một tham chiếu chỉ vài chục byte, nên không có giới hạn về dung lượng.

Xóa nhánh có xóa luôn commit không?

Không ngay. Các commit không còn được tham chiếu vẫn được lưu một thời gian và có thể thấy trong git reflog, cho tới khi bộ thu gom rác xóa chúng (mặc định các mục reflog không đến được phải chờ tối thiểu 30 ngày).

Làm sao biết nhánh hiện tại được tạo từ nhánh nào?

Git không lưu thông tin này: nhánh chỉ nhớ commit của nó. Có thể tìm tổ tiên chung với main bằng lệnh git merge-base HEAD main.

Nhánh khác thẻ (tag) thế nào?

Nhánh di chuyển tới trước mỗi khi có commit mới, còn thẻ được gắn vĩnh viễn với một commit và dùng để đánh dấu bản phát hành.

Vì sao là main chứ không phải master?

Đó chỉ là tên mặc định của nhánh. Nhiều dịch vụ đã chuyển sang main; có thể đặt tên cho kho mới bằng cấu hình git config --global init.defaultBranch main.

Học chủ đề này qua video

Chủ đề này được giảng chi tiết kèm thực hành trong khóa học “Git с нуля до уверенного разработчика”.

Đến khóa học