Ở Bài 2, mỗi lần git commit chúng ta chỉ nói chung
chung "HEAD cập nhật". Bài này mở đúng chỗ đó ra: branch và
HEAD thực chất chỉ là những file văn bản bé xíu trỏ tới hash commit — và hiểu rõ cơ
chế con trỏ này giải thích được vì sao Git khuyến khích tạo hàng chục nhánh mỗi ngày mà không lo tốn
kém gì.
1. Branch Chỉ Là Một Con Trỏ 41 Byte
Một branch trong Git, ví dụ main, thực chất là 1 file tại
.git/refs/heads/main chứa đúng 1 dòng: hash 40 ký tự hex của commit mà
nhánh đó đang trỏ tới (cộng 1 ký tự xuống dòng = 41 byte). Không có gì khác. Tạo 1 branch mới nghĩa là
tạo 1 file bé xíu như vậy — cực rẻ, bất kể repo có bao nhiêu file hay lịch sử dài tới đâu:
$ cat .git/refs/heads/main
a1b2c3d4e5f6789012345678901234567890abcd
$ git branch feature # tạo file .git/refs/heads/feature với CÙNG hash trên
$ cat .git/refs/heads/feature
a1b2c3d4e5f6789012345678901234567890abcd
2. HEAD: Con Trỏ Của Con Trỏ
HEAD trả lời câu hỏi "tôi đang đứng ở đâu?" — nhưng nó thường không trỏ
thẳng vào 1 commit. Nó trỏ vào tên của 1 branch, và branch đó mới trỏ vào commit. Đây
gọi là symbolic ref:
$ cat .git/HEAD
ref: refs/heads/main # HEAD trỏ tới TÊN branch, không phải hash trực tiếp
Vì HEAD trỏ gián tiếp qua tên branch, khi bạn git commit, Git chỉ cần cập nhật file
refs/heads/main để trỏ tới commit mới — HEAD không cần đổi gì cả, nó tự
động "theo" branch main tới vị trí mới vì vẫn trỏ vào đúng cái tên đó.
3. git checkout / git switch — Di Chuyển HEAD
Chuyển nhánh nghĩa là ghi đè nội dung file .git/HEAD để nó trỏ sang tên branch khác, rồi
cập nhật Working Directory + Staging Index (từ Bài 2) cho khớp
với tree của commit mà branch mới đang trỏ tới:
$ git checkout feature # .git/HEAD giờ chứa: ref: refs/heads/feature
$ git switch feature # lệnh mới hơn (Git 2.23+), làm đúng việc tương tự cho branch
$ git switch -c hotfix # tắt: tạo branch "hotfix" RỒI chuyển sang luôn
4. Detached HEAD: Khi HEAD Trỏ Thẳng Vào Commit
Nếu bạn checkout thẳng vào 1 hash commit (thay vì tên branch), .git/HEAD sẽ
chứa chính hash đó thay vì ref: refs/heads/... — trạng thái này gọi là
detached HEAD. Bạn vẫn commit được bình thường, nhưng
không branch nào trỏ tới những commit mới này:
checkout sang 1 branch khác, những
commit đó không còn nằm trên nhánh nào — chúng vẫn tồn tại trong
.git/objects (bất biến, không tự xoá ngay) nhưng không có con trỏ nào dẫn tới. Nếu
không tạo branch mới trỏ vào chúng trước khi rời đi, chúng sẽ dần bị garbage-collect. Luôn
git branch <tên> ngay khi nhận ra mình cần giữ lại các commit lỡ tạo ở trạng thái
detached.
5. Fast-Forward: Khi Không Cần Tạo Commit Mới
Xem trước một khái niệm mà Bài 4 sẽ đào sâu: nếu branch
main chưa có commit nào mới kể từ khi bạn tách nhánh feature ra, thì "merge"
feature vào main không cần tạo commit hợp nhất nào cả — Git chỉ đơn giản
di chuyển con trỏ main tới thẳng vị trí commit mới nhất của
feature. Đây gọi là fast-forward, và nó chỉ khả thi chính xác vì branch
chỉ là 1 con trỏ có thể di chuyển tự do dọc theo 1 đường thẳng lịch sử.
6. So Sánh Các Lệnh Di Chuyển Nhánh
| Lệnh | Tạo branch mới? | Di chuyển HEAD? |
|---|---|---|
git branch <tên> |
Có | Không |
git checkout <branch> / git switch <branch> |
Không | Có — trỏ sang branch đó |
git checkout -b <tên> / git switch -c <tên> |
Có | Có — trỏ sang branch vừa tạo |
git checkout <hash-commit> |
Không | Có — detached, trỏ thẳng vào commit |
Sân chơi tương tác: Interactive Git Graph Simulator
Gõ lệnh Git giả lập vào ô bên dưới và xem đồ thị commit, con trỏ branch, và HEAD cập nhật ngay lập tức. Thử: tạo 1 branch mới, chuyển qua lại, commit trên từng nhánh, rồi thử checkout thẳng vào 1 hash commit để xem detached HEAD trông như thế nào.
Terminal giả lập
git commit -m "..."git branch <tên>git checkout <tên|hash>git checkout -b <tên>git switch <tên>git log
Nhật ký
Trắc nghiệm ôn tập
Câu 1: Khi bạn git commit trên branch main, vì sao HEAD "tự động đi theo"
tới commit mới mà không cần cập nhật gì thêm?
Trắc nghiệm ôn tập
Câu 2: Bạn git checkout thẳng vào 1 hash commit rồi tạo 3 commit mới. Sau đó bạn
git checkout main. Chuyện gì xảy ra với 3 commit vừa tạo?
Trắc nghiệm ôn tập
Câu 3: Điều kiện nào khiến một merge trở thành fast-forward (không tạo commit hợp nhất)?