Fundamentals
Git, Phần 1: Commit là snapshot, branch là nhãn dán
Commit lưu snapshot đầy đủ, branch là tên có thể di chuyển, HEAD cho Git biết bạn đang đứng ở đâu.
- Gieo
- Chăm lần cuối

Bạn đã gõ git commit và git branch hàng ngày. Phần khó là các từ đó không giống nghĩa ở chỗ khác. Commit không phải diff đã lưu. Branch không phải thư mục chứa file. HEAD không phải một người. Sau bài này bạn có thể chỉ vào một đồ thị commit nhỏ và nói rõ ba thứ đó là gì, rồi đoán git commit, git branch, và git switch sẽ làm gì trước khi chạy.
Đây là Phần 1 trong loạt sáu phần. Phần 2 nói về branch và merge. Hoàn tác, remote, rebase và quy trình nhóm sẽ theo sau.
Nó là gì?
Git là công cụ lưu lịch sử dự án dưới dạng chuỗi snapshot. Mỗi snapshot là một commit. Commit trỏ tới một tree (toàn bộ nội dung thư mục tại thời điểm đó) và tới zero hoặc nhiều commit parent. Khi bạn xin Git so diff, nó so hai snapshot. Lịch sử chính là đồ thị commit, không phải chồng patch.
Nhiều người nói “Git lưu diff.” Cách đó hữu ích khi đọc lịch sử trong git log -p, và packfile của Git có thể nén object tương tự phía sau. Mô hình trong đầu vẫn là snapshot: mỗi commit đặt tên cho một tree hoàn chỉnh. Tôi nghĩ một bước chuyển đó xóa hầu hết rối cho người mới mà tôi thấy khi review.
Ba vùng quan trọng mỗi lần lưu việc:
- Working tree — file bạn sửa trong thư mục.
- Index (còn gọi staging area) — danh sách nội dung file Git sẽ đưa vào commit kế tiếp.
- Repository — cơ sở object dưới
.git, gồm commit, tree và con trỏ có tên.
git add copy nội dung hiện tại của path vào index. git commit ghi index thành commit mới. Chỉnh sửa chưa git add vẫn chỉ ở working tree. git commit -a cập nhật index từ file đang track trước, rồi commit; file untracked vẫn cần git add.
Branch là tên di chuyển lưu một commit id. Tạo feature khi main trỏ commit C2 thì feature cũng trỏ C2. Không file nào bị copy. HEAD là cách Git biết bạn ở đâu: thường lưu ref: refs/heads/main, nghĩa là “theo branch main.” Khi HEAD lưu commit id thô thay vì ref, bạn đang ở detached HEAD.
Một ẩn dụ là đủ: mỗi commit như ảnh chụp có nhãn của cả dự án, mỗi branch như nhãn dán dán lên ảnh mới nhất bạn quan tâm. HEAD là ngón tay chỉ vào nhãn bạn đang cầm — hoặc khi detached, chỉ vào ảnh không có nhãn.
Khi nào dùng?
Dùng mô hình này khi một lệnh làm bạn bất ngờ. Nếu git status hiện thay đổi ở hai phần, bạn đang nhìn khoảng cách giữa working tree và index, và giữa index và HEAD. Nếu đồng đội nói “tôi tạo branch,” giờ bạn biết họ tạo tên, không phải bản copy repo.
Bạn không cần hình đồ thị cho mọi chỉnh sửa nhỏ. Khi một mình trên branch sạch và chỉ muốn lưu tiến độ, add và commit là đủ. Với tới mô hình khi đổi branch, xem commit cũ, hoặc Git cảnh báo bạn sắp bỏ lại commit.
Trong team tôi hay bảo mọi người nói một câu trước khi switch rủi ro: “HEAD theo feature tại C3; main vẫn ở C2.” Nếu nói được to, lệnh tiếp theo thường đúng.
Demo tương tác
Bấm Watch demo một lần, hoặc tự bấm từng bước. Theo dõi ba điều: node commit mới nhấp nháy, đường nét đứt từ con tới parent, token màu hổ phách trượt dọc nhãn HEAD hoặc branch. Chú ý git branch feature thêm nhãn tím cùng tip mà không tạo commit mới. Khi detach tại C2 rồi commit lại, C4 xuất hiện chỉ HEAD giữ — đó là lý do Git cảnh báo trước khi rời đi.
Minh họa tương tác
A commit stores a full tree snapshot plus a parent pointer. The current branch tip, if any, and HEAD both move to the new commit.
A commit graph starts with node C1. Main and HEAD point at C1. Press git commit to add C2 and slide both labels forward. Press git branch feature to add a feature label on the same tip. Press git switch feature, then git commit, to grow C3 on feature while main stays on C2. Press git switch main, then git switch --detach C2, to point HEAD at C2 with no branch name. A final commit while detached adds C4 that only HEAD holds until you reset.
- C1 is the first snapshot. main and HEAD both name it.
- git commit adds C2 and slides main and HEAD forward. The parent edge points at C1.
- git branch feature adds a second label on C2. No files were copied.
- git switch feature moves only HEAD onto the feature label.
- A commit on feature grows C3. main stays on C2.
- git switch main returns HEAD to the main tip.
- git switch --detach C2 points HEAD at the commit itself. There is no current branch.
- A commit while detached adds C4 that only HEAD holds. Reset to start again.
current: main · 1 commits
Code
Phiên chạy trong thư mục throwaway. Line ending tắt (core.autocrlf false) để diff yên. Hash trên máy bạn sẽ khác; hình dạng thì không.
$ git init -b main
Initialized empty Git repository in .../git-mental-model-clean/.git/
$ git status
On branch main
No commits yet
nothing to commit (create/copy files and use "git add" to track)
$ # write note.txt with the line "hello"
$ git add note.txt
$ git status
On branch main
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: note.txt
$ git commit -m "Add note"
[main (root-commit) ed4cfdc] Add note
1 file changed, 1 insertion(+)
create mode 100644 note.txt
$ git log --oneline --decorate
ed4cfdc (HEAD -> main) Add note
git init -b main đặt tên branch đầu là main ngay từ đầu. Trên máy này, git init thuần vẫn tạo master trừ khi đặt init.defaultBranch. Sau commit đầu, HEAD -> main nghĩa HEAD là symbolic ref tới branch, và tip branch là ed4cfdc.
$ # change note.txt so it also contains "more"
$ git status --short
M note.txt
$ git diff --cached --stat
$ # empty: nothing is staged yet
$ git add note.txt
$ git status --short
M note.txt
$ git diff --cached --stat
note.txt | 1 +
1 file changed, 1 insertion(+)
$ git commit -m "Extend note"
[main ace29b3] Extend note
1 file changed, 1 insertion(+)
$ git log -1 --format=commit:%h%ntree:%T%nparent:%P
commit:ace29b3
tree:d45f2e4436aadbf278e2d86ed492c46af08387b9
parent:ed4cfdc76fcf3554795118d348257b08a9f8786a
Short status M (space rồi M) nghĩa working tree đổi, index chưa. Sau git add thành M (đã stage). Object commit lưu tree id và parent id. Cạnh parent đó là đường demo vẽ từ C2 tới C1.
$ git branch feature
$ git rev-parse --short main
ace29b3
$ git rev-parse --short feature
ace29b3
$ git rev-parse --short HEAD
ace29b3
$ git symbolic-ref HEAD
refs/heads/main
$ git switch feature
Switched to branch 'feature'
$ # add a "feature" line, then:
$ git add note.txt
$ git commit -m "Note on feature"
[feature b192102] Note on feature
1 file changed, 1 insertion(+)
$ git log --oneline --graph --decorate --all
* b192102 (HEAD -> feature) Note on feature
* ace29b3 (main) Extend note
* ed4cfdc Add note
git branch feature tạo tên thứ hai cho ace29b3. git switch feature chuyển HEAD theo tên đó. Commit kế chỉ đẩy feature. Git cũ dùng git checkout feature cho cùng việc; ưu tiên git switch nếu Git ≥ 2.23.
$ git switch main
Switched to branch 'main'
$ git switch --detach ed4cfdc
HEAD is now at ed4cfdc Add note
$ git status
HEAD detached at ed4cfdc
nothing to commit, working tree clean
$ # rewrite note.txt to "side", add, commit
$ git commit -m "Side note"
[detached HEAD 7f819df] Side note
1 file changed, 1 insertion(+), 1 deletion(-)
$ git log --oneline --graph --decorate --all
* b192102 (feature) Note on feature
* ace29b3 (main) Extend note
| * 7f819df (HEAD) Side note
|/
* ed4cfdc Add note
$ git switch main
Warning: you are leaving 1 commit behind, not connected to
any of your branches:
7f819df Side note
...
Switched to branch 'main'
Detached HEAD hữu ích để xem snapshot cũ. Commit tạo ở đó vẫn là object commit thật. Nếu không branch nào trỏ khi bạn rời đi, Git cảnh báo. Id thường còn trong git reflog một lúc, và git branch keep-it 7f819df (hoặc git switch -c keep-it trước khi rời) gắn tên để commit dễ tìm.
Đã kiểm tra với Git 2.43.0.windows.1.
Mã nguồn
Mã nguồn
Chỉ đọcTooling/Git/session.sh
Chỉ đọc
# Create a repo and the first snapshot
git init -b main
echo hello > note.txt
git add note.txt
git commit -m "Add note"
# Stage a second snapshot on main
echo more >> note.txt
git add note.txt
git commit -m "Extend note"
# A branch is a second name for the same tip
git branch feature
git switch feature
echo feature >> note.txt
git add note.txt
git commit -m "Note on feature"
# Detach HEAD to look at an older commit
git switch main
git switch --detach HEAD~1
Ưu và nhược điểm
+ Ưu điểm
- Snapshot khiến mỗi commit là trạng thái dự án phục hồi được, không phải chuỗi patch dễ vỡ phải replay tay.
- Branch là tên rẻ, thử ý tưởng chỉ tốn một nhãn và vài commit, không copy cả thư mục.
- HEAD làm vị trí hiện tại rõ ràng: branch bạn có thể đẩy tiếp, hoặc commit detached để inspect.
− Nhược điểm
- Ba vùng nghĩa ba chỗ thay đổi có thể ẩn — lý do có git status và người mới hay lạc.
- Commit ở detached HEAD dễ bỏ lại nếu switch đi trước khi đặt tên.
- Mô hình đồ thị cần một buổi luyện trước khi lệnh hàng ngày lại tự nhiên.
Mẹo
- 01Chạy git status trước hầu hết lệnh khác. Dạng ngắn cho biết thay đổi unstaged, staged hay đã commit.
- 02Ưu tiên commit nhỏ gắn một ý. Snapshot dễ suy luận khi thay đổi tree nhỏ.
- 03Tạo branch bằng git branch hoặc git switch -c. Tên mới bắt đầu tại tip hiện tại và không copy gì thêm.
- 04Dùng git switch cho branch và git restore cho nội dung file. checkout cũ làm cả hai và trộn mô hình.
- 05Nếu status báo HEAD detached, inspect rồi switch về, hoặc git switch -c <name> trước khi rời để commit mới giữ tip branch.
- 06Nếu đã bỏ commit detached, lấy id từ git reflog và trỏ branch tới đó trước khi nghĩ nó mất hẳn.
Series
- Phần 1: Git, Phần 1: Commit là snapshot, branch là nhãn dán
- Phần 2: Git, Phần 2: Merge nối hai nhánh snapshot
- Phần 3: Git, Phần 3: Restore, revert và reset đụng các lớp khác nhau
- Phần 4: Git, Phần 4: Remote là một repository khác
- Phần 5: Git, Phần 5: Rebase replay commit lên parent mới
- Phần 6: Git, Phần 6: Team thống nhất lịch sử thế nào
Tiếp theo
Bài kế trong đường đọc: Git, Phần 2: Merge nối hai nhánh snapshot