git add và git commit: cách lưu thay đổi đúng
Lưu thay đổi trong Git gồm hai bước: git add đưa các chỉnh sửa cần thiết vào index (staging area), còn git commit -m "thông điệp" ghi chúng vào lịch sử thành một ảnh chụp. git status cho biết những gì đã thay đổi và những gì đã được chuẩn bị. Có thể sửa commit cuối bằng git commit --amend.
Trong Git không có nút «lưu». Thay đổi được ghi lại qua hai bước: đầu tiên git add chọn những chỉnh sửa sẽ nằm trong ảnh chụp tiếp theo, sau đó git commit ghi ảnh chụp đó vào lịch sử. Vùng trung gian giữa hai bước gọi là index, hay staging area. Nhiều người dùng Git nhiều năm mà vẫn chưa hiểu rõ nó — và chính vùng này làm cho các commit có ý nghĩa.
Ba trạng thái của tệp: thư mục làm việc, index, lịch sử
Mỗi thay đổi đi qua ba khu vực:
- Thư mục làm việc — bạn chỉnh sửa tệp trong trình soạn thảo.
- Index (stage) —
git addđưa phiên bản hiện tại của tệp vào bản nháp của commit tiếp theo. - Lịch sử —
git commitbiến nội dung của index thành một commit với tác giả, thời gian và thông điệp.
Có thể hình dung bằng việc gửi bưu kiện. Thư mục làm việc là căn phòng chứa đồ, index là chiếc hộp đang mở nơi bạn bỏ những thứ muốn gửi, còn commit là bưu kiện đã niêm phong và ký tên. Bạn tự quyết định bỏ gì vào hộp và không bắt buộc phải gửi tất cả cùng lúc.
Vì sao cần index
Hãy tưởng tượng: trong một giờ, bạn sửa lỗi xác thực, tiện tay sửa lỗi chính tả trong README và bắt đầu một tính năng mới. Nếu không có index, tất cả sẽ nằm trong một commit «linh tinh». Với index, bạn tạo ba commit riêng biệt, mỗi commit dễ hiểu, dễ review và có thể hoàn tác khi cần mà không ảnh hưởng phần còn lại.
git status: đã thay đổi gì và đã chuẩn bị gì
Trước mỗi lần add và commit, nên xem git status. Đây là lệnh an toàn nhất của Git — nó không thay đổi gì cả.
$ git status
On branch main
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: src/auth.js
Changes not staged for commit:
(use "git add <file>..." to include in what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: README.md
Untracked files:
(use "git add <file>..." to include in what will be committed)
src/profile.js
- Changes to be committed — đã nằm trong index, sẽ vào commit;
- Changes not staged — đã thay đổi nhưng chưa được add;
- Untracked files — tệp mới mà Git chưa biết tới.
Khi có nhiều tệp, định dạng ngắn tiện hơn:
$ git status -s
M src/auth.js
M README.md
?? src/profile.js
Cột đầu tiên là trạng thái trong index, cột thứ hai là trạng thái trong thư mục làm việc. M bên trái nghĩa là «đã thay đổi và đã add», bên phải là «đã thay đổi, chưa add», còn ?? là tệp chưa được theo dõi.
git add: đưa thay đổi vào index
| Lệnh | Thêm vào index |
|---|---|
git add file.js | Một tệp cụ thể |
git add src/ | Mọi thay đổi trong thư mục |
git add . | Mọi thay đổi trong thư mục hiện tại và bên dưới, kể cả tệp mới và tệp đã xóa |
git add -A | Mọi thay đổi trong toàn bộ kho lưu trữ |
git add -u | Chỉ các tệp đã được theo dõi, không đụng tới tệp mới |
git add -p | Tương tác, theo từng đoạn (hunk) |
git add '*.css' | Các tệp theo mẫu |
Một tên khác của git add là git stage: đây là cùng một lệnh, và từ đó có thuật ngữ «stage», «đưa vào stage».
git add -p: thêm một phần của tệp
Đây là mẹo ít được chú ý. Nếu trong một tệp có hai chỉnh sửa khác nhau, git add -p sẽ hiển thị từng đoạn và hỏi có thêm hay không:
$ git add -p src/auth.js
@@ -12,7 +12,7 @@ function login(user) {
- if (user.password = input) {
+ if (user.password === input) {
(1/2) Stage this hunk [y,n,q,a,d,s,e,?]?
y — thêm, n — bỏ qua, s — tách đoạn nhỏ hơn, q — thoát. Nhờ vậy, từ một tệp bạn có được hai commit gọn gàng.
Cách kiểm tra điều gì sẽ vào commit
git diff # thay đổi CHƯA được add vào index
git diff --staged # những gì ĐÃ có trong index và sẽ vào commit
Nhầm lẫn thường gặp: sau git add, lệnh git diff thông thường không hiển thị gì. Điều đó không có nghĩa là thay đổi đã mất — chúng đang nằm trong index, hãy xem git diff --staged (cũng có thể viết là --cached).
Cách gỡ tệp khỏi index
git restore --staged file.js
Khi đó nội dung chỉnh sửa trong tệp vẫn còn, chỉ là tệp không còn được chuẩn bị cho commit. Trong các phiên bản Git cũ, người ta dùng git reset HEAD file.js cho việc này — lệnh đó vẫn hoạt động.
git commit: ghi lại thay đổi
git commit -m "Sửa kiểm tra mật khẩu khi đăng nhập"
[main 3f2a9c1] Sửa kiểm tra mật khẩu khi đăng nhập
1 file changed, 1 insertion(+), 1 deletion(-)
Kết quả hiển thị nhánh, mã hash ngắn của commit mới và thống kê. Vào commit chỉ có nội dung của index. Nếu sau git add bạn sửa tệp thêm lần nữa, phần chỉnh sửa mới sẽ không vào commit cho đến khi bạn add lại.
Các tùy chọn hữu ích của git commit
| Lệnh | Tác dụng |
|---|---|
git commit | Mở trình soạn thảo để viết thông điệp nhiều dòng |
git commit -m "..." | Thông điệp ngay trong lệnh |
git commit -am "..." | Thêm mọi tệp đã theo dõi và có thay đổi rồi commit; tệp mới không được đưa vào |
git commit -v | Hiển thị diff trong trình soạn thảo khi bạn viết thông điệp |
git commit --amend | Ghi đè commit cuối cùng |
git commit --allow-empty -m "..." | Commit rỗng, ví dụ để chạy lại CI |
Nếu trình soạn thảo mặc định là Vim và bạn bị kẹt trong đó: gõ thông điệp, nhấn Esc, rồi gõ :wq và Enter. Để đổi trình soạn thảo: git config --global core.editor "code --wait" hoặc nano.
Cách viết thông điệp commit tốt
Thông điệp sẽ được đọc sau nửa năm, khi người ta tìm nguyên nhân của một lỗi. Nó phải trả lời câu hỏi «thay đổi gì và vì sao», chứ không phải «hôm nay tôi đã làm gì».
- dòng đầu tiên — khoảng 50–72 ký tự, nêu bản chất thay đổi;
- ở thể mệnh lệnh hoặc trung tính: «Thêm kiểm tra email», «Fix login redirect»;
- khi cần — để một dòng trống rồi viết chi tiết: vì sao làm vậy, có hạn chế gì;
- một commit — một thay đổi logic.
Không tốt: fix, sửa lung tung, wip, asdf. Tốt: Sửa lỗi giỏ hàng khi danh sách sản phẩm trống. Nhiều nhóm dùng định dạng Conventional Commits: feat: ..., fix: ..., docs: ....
git commit --amend: sửa commit cuối cùng
Sai chính tả trong thông điệp hay quên thêm tệp — không cần tạo commit mới kiểu «quên tệp». Hãy dùng amend.
Sửa thông điệp
git commit --amend -m "Sửa kiểm tra mật khẩu khi đăng nhập"
Thêm tệp bị quên
git add forgotten.js
git commit --amend --no-edit
Tùy chọn --no-edit giữ nguyên thông điệp cũ.
Điều quan trọng về amend
--amend không «sửa» commit mà tạo một commit mới với hash khác và thay thế commit cũ. Khi commit chỉ có ở máy bạn, thao tác này an toàn. Nếu bạn đã git push, sau amend lệnh push thông thường sẽ bị từ chối, và bạn phải dùng git push --force-with-lease. Trong nhánh chung, nơi người khác cũng làm việc, đừng làm vậy: bạn sẽ viết lại lịch sử mà họ đang dựa vào.
Nếu amend nhầm, commit cũ vẫn có thể tìm trong git reflog, và bạn quay lại bằng git reset --soft HEAD@{1}.
Lỗi thường gặp
«nothing to commit, working tree clean»
Không có thay đổi nào — mọi thứ đã được commit, hoặc bạn đang sửa tệp ở thư mục khác. Hãy kiểm tra git status và đường dẫn tới dự án.
«no changes added to commit»
Tệp đã thay đổi nhưng chưa được add vào index. Hãy chạy git add, hoặc dùng git commit -am cho các tệp đã được theo dõi.
«Please tell me who you are»
Chưa đặt tên và email. Chỉ cần chạy một lần git config --global user.name "Tên" và git config --global user.email "email".
Commit nhầm thứ không nên có
Nếu commit chưa được gửi đi, hãy hủy nó nhưng giữ thay đổi trong index: git reset --soft HEAD~1. Sau đó gỡ phần thừa bằng git restore --staged và commit lại. Nếu một bí mật (secret) đã lọt vào commit, hãy coi nó là bị lộ và cấp lại.
git add . thêm vào những thứ rác
Hãy cấu hình .gitignore từ trước và xem git status trước khi commit. Một tệp đã được theo dõi chỉ ngừng được theo dõi sau khi chạy git rm --cached tệp.
Commit thực sự là gì
Commit không lưu phần khác biệt so với phiên bản trước, mà lưu ảnh chụp toàn bộ dự án: tham chiếu tới cây tệp, tham chiếu tới commit cha, tác giả, thời gian và thông điệp. Tệp không thay đổi không được sao chép — Git chỉ tham chiếu tới các đối tượng đã lưu, nên lịch sử chiếm ít dung lượng.
Hash của commit (ví dụ 3f2a9c1) được tính từ toàn bộ nội dung đó. Chỉ cần đổi một chữ trong thông điệp là hash sẽ khác. Vì vậy --amend và rebase tạo commit mới chứ không sửa commit cũ, và lịch sử bị viết lại sẽ xung đột với các bản sao của đồng nghiệp.
Có thể xem lịch sử ngắn gọn như sau:
git log --oneline --graph -10
Mỗi dòng là một commit; nếu thông điệp trong lịch sử đủ rõ để hiểu mà không cần mở diff, bạn đang làm đúng.
Quy trình làm việc hằng ngày
git status # đã thay đổi gì
git diff # xem các chỉnh sửa
git add -p # chọn phần cần thiết
git diff --staged # kiểm tra điều gì sẽ vào commit
git commit -m "Mô tả rõ ràng"
git log --oneline -5 # kiểm tra commit đã có mặt
git push # gửi lên máy chủ
Commit chỉ lưu thay đổi cục bộ. Để chúng xuất hiện trên GitHub hoặc máy chủ khác, cần chạy git push.
Tóm lại
git add gom ảnh chụp tiếp theo vào index, git status và git diff --staged cho biết điều gì sẽ vào đó, còn git commit ghi ảnh chụp vào lịch sử. Hãy tạo các commit nhỏ, hợp lý với thông điệp rõ ràng, và dùng git commit --amend để sửa commit cuối khi có lỗi nhỏ — làm việc này trước khi push. Index và quy trình hai bước add → commit được giảng giải bằng ví dụ thực tế trong khóa học Git.
Câu hỏi thường gặp
Nên commit bao lâu một lần?
Mỗi khi hoàn thành một thay đổi logic và dự án ở trạng thái hoạt động. Tốt hơn là nhiều commit nhỏ thay vì một commit khổng lồ vào cuối ngày.
Git có lệnh git save không?
Không có lệnh đó. Lưu thay đổi là git add và git commit, còn để tạm cất công việc chưa xong có thể dùng git stash.
Làm sao xem một commit đã có những gì?
Lệnh git show hash sẽ hiển thị tác giả, thông điệp và toàn bộ thay đổi trong commit. Nếu không truyền hash, lệnh hiển thị commit cuối cùng.
git add . khác git add -A ở điểm nào?
Ở các phiên bản Git hiện đại, cả hai đều thêm tệp mới, tệp đã sửa và tệp đã xóa. Khác biệt nằm ở phạm vi: git add . làm việc với thư mục hiện tại và các thư mục con, còn git add -A làm việc với toàn bộ kho lưu trữ.
Có thể sửa một commit cũ hơn commit cuối không?
Có, bằng interactive rebase: git rebase -i hash-cha, sau đó đánh dấu commit cần sửa là edit hoặc reword. Chỉ làm vậy với các commit chưa được gửi lên nhánh chung.
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