Bạn đã quen thuộc với các lệnh cơ bản như `git add`, `git commit`, và `git push`? Chúc mừng bạn, bạn đang trên con đường trở thành một developer Git thuần thục. Tuy nhiên, khi dự án trở nên phức tạp hơn và làm việc trong một nhóm lớn, bạn sẽ cần đến những công cụ mạnh mẽ hơn. Bài viết này sẽ giới thiệu 10 lệnh Git trung cấp – những “vũ khí bí mật” mà bạn sẽ ước mình biết sớm hơn. Đây không phải là những mưu thuật nâng cao kiểu “gõ ba dòng lệnh và một con tàu vũ trụ xuất hiện”, mà là những lệnh thực tế, thường xuyên được sử dụng trong công việc hàng ngày.
Nếu bạn mới bắt đầu với Git, hãy tham khảo các hướng dẫn cơ bản trước. Nhưng nếu bạn đã nắm vững nền tảng, hãy cùng khám phá những lệnh tiếp theo.
Mục lục
1. & 2. `git merge` và `git rebase`: Sự khác biệt tinh tế nhưng quan trọng
Nhiều developer, ngay cả những người có kinh nghiệm, vẫn nhầm lẫn và sử dụng `merge` và `rebase` như nhau. Cả hai đều có mục đích tích hợp lịch sử từ nhánh này sang nhánh khác, nhưng phương thức hoạt động lại rất khác.
Trường hợp `git merge`
Khi bạn đang ở nhánh `feature` và chạy lệnh `git merge main`, Git sẽ kết hợp lịch sử của cả hai nhánh. Kết quả thường là một “merge commit” mới (ký hiệu `M` trong sơ đồ), giữ nguyên toàn bộ lịch sử phân nhánh và hợp nhất.
A—B—C——M
\ /
D—E—
“`
Ưu điểm: Giữ nguyên lịch sử thực tế, giúp bạn thấy rõ quá trình nhánh đã phát triển độc lập thế nào.
Nhược điểm: Nếu bạn thường xuyên merge `main` vào `feature`, lịch sử sẽ trở nên phức tạp, khó đọc, giống như một “mớ hỗn độn” (spaghetti).
Trường hợp `git rebase`
Khi bạn chạy `git rebase main` từ nhánh `feature`, Git sẽ thực hiện các bước sau: lấy các commit `D` và `E`, tạm thời xóa chúng đi, di chuyển nhánh `feature` lên trên commit mới nhất của `main`, và cuối cùng “phát lại” các commit `D` và `E` dưới dạng các commit mới (`D’` và `E’`) trên đầu.
“`
A—B—C—D’—E’
“`
Ưu điểm: Tạo ra một lịch sử tuyến tính, sạch sẽ, dễ theo dõi.
Nhược điểm quan trọng: Rebase đã t”viết lại lịch sử“. Các commit `D’` và `E’` là các commit mới hoàn toàn, có mã hash khác so với `D` và `E`. Đây là lý do bạn nên tuân thủ nguyên tắc vàng:
> Chỉ rebase công việc của riêng bạn. Hãy cẩn thận khi rebase lịch sử đã được chia sẻ.
Xử lý xung đột (Conflicts)
Cả hai lệnh đều có thể gây ra xung đột. Với `merge`, bạn giải quyết xung đột một lần. Với `rebase`, vì các commit được áp dụng lại từng cái một, bạn có thể phải giải quyết xung đột nhiều lần liên tiếp, điều này có thể khiến bạn… đặt câu hỏi về mọi lựa chọn nghề nghiệp đã đưa bạn đến đây. 😅
May mắn thay, cả hai lệnh đều có tùy chọn để hủy bỏ quá trình nếu mọi thứ diễn ra quá tệ:
“`bash
git merge –abort
# hoặc
git rebase –abort
“`
3. `git commit –amend`: Sửa đổi commit cuối cùng một cách thông minh
Bạn vừa tạo một commit hoàn hảo, nhưng sau đó phát hiện ra mình đã quên một file hoặc để lại một dòng `console.log(“Debug đây”);`. Bạn không muốn tạo thêm một commit nữa với thông báo “Xoá log debug” chỉ vì một lỗi nhỏ.
Lệnh `git commit –amend` đến để giải quyết vấn đề này. Nó cho phép bạn thêm các thay đổi mới vào commit cuối cùng và giữ nguyên thông báo commit nếu bạn dùng `–no-edit`.
“`bash
git add file-quen-kiem-tra.ts
git commit –amend –no-edit
“`
Hoặc nếu muốn sửa cả thông báo:
“`bash
git commit –amend
“`
Lưu ý: `amend` thực chất không sửa commit cũ mà tạo ra một commit mới với nội dung đã cập nhật. Do đó, nó cũng thay đổi mã hash. Nếu bạn đã push nhánh này lên remote, bạn sẽ cần dùng lệnh `push –force` (hoặc an toàn hơn là `–force-with-lease`, sẽ được đề cập ở phần sau).
4. `git push –force-with-lease`: Cách force push an toàn
Sau khi rebase, amend hoặc bất kỳ thao tác nào viết lại commit, lịch sử local của bạn không còn khớp với remote. Bạn có thể dùng `git push –force`, nhưng lệnh này đơn phương tuyên bố: “Phiên bản local của tôi là chính xác, hãy ghi đè lên remote.”
Đây là một rủi ro lớn, vì bạn có thể vô tình ghi đè lên commit của người khác đã push lên cùng nhánh. Để an toàn hơn, hãy sử dụng:
“`bash
git push –force-with-lease
“`
Lệnh này sẽ kiểm tra: “Hãy force push phiên bản của tôi, nhưng chỉ khi nhánh remote vẫn đang ở trạng thái mà tôi mong đợi.” Nếu nhánh remote đã thay đổi (có thể do đồng nghiệp của bạn vừa push), Git sẽ từ chối thay vì ghi đè mù quáng. Luôn ưu tiên lệnh này khi có thể.
5. `git rebase -i`: Vua của các lệnh Git – Tương tác và sắp xếp lại commit
Đây là một trong những lệnh mạnh mẽ nhất, cho phép bạn编辑 (chỉnh sửa) lại lịch sử commit gần đây một cách trực quan. Hãy tưởng tượng bạn đã commit từng dòng code nhỏ, tạo ra lịch sử như: “Thêm validation”, “Sửa validation”, “Sửa lại validation”, “Xoá debug log”… Giờ bạn muốn biến mớ hỗn độn đó thành một lịch sử Git gọn gàng.
Lệnh `git rebase -i HEAD~5` (ví dụ) sẽ mở một trình soạn thảo với danh sách 5 commit cuối cùng.
“`
pick a111111 Thêm form đăng nhập
pick b222222 Thêm validation
pick c333333 Sửa lỗi chính tả
pick d444444 Sửa validation
pick e555555 Xoá log debug
“`
Bạn có thể thay thế `pick` bằng các từ khóa sau:
– `pick`: Giữ nguyên commit.
– `reword`: Giữ nội dung, sửa thông báo commit.
– `edit`: Tạm dừng để bạn chỉnh sửa nội dung commit đó.
– `squash`: Gộp commit này với commit trước đó.
– `fixup`: Tương tự `squash`, nhưng bỏ qua thông báo commit của commit sau.
– `drop`: Xoá hoàn toàn commit.
Ví dụ, để gộp 3 commit đầu tiên:
“`
pick a111111 Thêm form đăng nhập
squash b222222 Thêm validation
squash c333333 Sửa lỗi chính tả
“`
Kết quả bạn có thể sửa thông báo thành: “Thêm form đăng nhập với validation”. Việc dọn dẹp lịch sử commit không chỉ giúp bạn dễ theo dõi mà còn là một cách tôn trọng đồng nghiệp khi tạo Pull Request.
6. `git stash`: Tạm cất dấu công việc dang dở
Bạn đang hăng say phát triển một tính năng, nhưng đột nhiên phải chuyển sang sửa một lỗi khẩn cấp trên nhánh `main`. Code của bạn hiện tại còn dang dở, đầy log debug, chưa build được, và bạn không muốn commit một mớ hỗn độn đó.
Đây là lúc `git stash` phát huy tác dụng. Nó sẽ tạm thời lưu các thay đổi chưa được commit và đưa thư mục làm việc về trạng thái sạch.
“`bash
git stash # Lưu các file đã theo dõi
git stash -u # Lưu cả các file mới (untracked)
git stash push -u -m “WIP: Tính năng đăng nhập” # Lưu và đặt tên rõ ràng
“`
Sau khi sửa lỗi xong, bạn quay lại nhánh `feature` và khôi phục:
“`bash
git switch feature
git stash pop # Khôi phục và xóa stash khỏi danh sách
# hoặc
git stash apply stash@{0} # Khôi phục nhưng giữ nguyên stash
“`
Các lệnh hữu ích khác:
– `git stash list`: Xem danh sách tất cả các stash.
– `git stash show stash@{0}`: Xem nội dung thay đổi trong một stash.
– `git stash drop stash@{0}`: Xoá một stash cụ thể.
7. `git cherry-pick`: “Hái” một commit cụ thể từ nhánh khác
Đây là lệnh bạn sẽ dùng rất thường xuyên. Giả sử trên nhánh `feature-b` có một commit `E` chứa cấu hình cần thiết cho môi trường mới, và bạn muốn áp dụng nó vào nhánh `feature-a` hiện tại mà không cần merge toàn bộ.
“`bash
git cherry-pick
“`
Ví dụ:
“`bash
git cherry-pick a1b2c3d
“`
Git sẽ lấy các thay đổi từ commit `E` và áp dụng lên nhánh hiện tại, tạo ra một commit mới (`E’`) với mã hash mới. Lịch sử sẽ giống:
“`
A—B—C—E’
“`
Lệnh này đặc biệt hữu ích để “…cứu vãn tình thế” – khi nhánh hiện tại của bạn đã rối tung vì rebase sai, bạn có thể tạo một nhánh mới từ một commit sạch và `cherry-pick` từng commit quan trọng sang.
8. `git reset –soft`, `–mixed`, `–hard`: Ba cách quay lại quá khứ
Bạn từng bắt đầu viết code cho một ý tưởng, rồi nhận ra nó hoàn toàn sai lầm? `git reset` giúp bạn quay trở lại trạng thái trước đó. Hiệu quả của nó phụ thuộc vào cờ sử dụng:
Hãy nghĩ về 3 lớp: Lịch sử commit, Khu vực chuẩn bị (Staging area), và Thư mục làm việc (Working directory).
– **`git reset –soft HEAD~1`**: Di chuyển con trỏ HEAD về commit trước đó. Các thay đổi từ commit bị xóa vẫn được giữ nguyên trong Staging area (sẵn sàng để commit lại).
– **`git reset –mixed HEAD~1`** (hoặc chỉ `git reset HEAD~1` vì đây là mặc định): Di chuyển HEAD và để các thay đổi trong Thư mục làm việc dưới dạng chưa được staging. Bạn cần `git add` lại.
– **`git reset –hard HEAD~1`**: Di chuyển HEAD và cập nhật cả Staging area và Thư mục làm việc để khớp với commit đó. Tất cả thay đổi từ commit bị xóa sẽ bị loại bỏ vĩnh viễn. Hãy cẩn thận!
9. `git reflog`: Cứu cánh cuối cùng khi bạn lỡ tay “xóa” commit
`git reflog` là lệnh bạn rất ít dùng, nhưng sẽ là vị cứu tinh tuyệt đối trong những lúc hoảng loạn. Ví dụ, bạn lỡ chạy `git reset –hard` và xóa nhầm 2 ngày công việc.
`git log` chỉ hiển thị các commit có thể truy cập được từ lịch sử hiện tại. Sau khi reset, một số commit có thể “biến mất” khỏi `git log`. Tuy nhiên, Git vẫn giữ một bản ghi cục bộ về tất cả các lần di chuyển của con trỏ `HEAD`. Bạn xem nó bằng:
“`bash
git reflog
“`
Bạn sẽ thấy danh sách các thao tác gần đây, kèm mã hash của commit tại thời điểm đó. Chỉ cần tìm lại được commit đã mất, bạn có thể dùng `git reset –hard
“`bash
git branch rescue-branch
“`
Lưu ý: Reflog chỉ ghi lại các thao tác với commit. Nếu bạn thay đổi mà chưa từng commit hay stash, reflog không thể phục hồi được.
10. `git revert`: Đảo ngược commit một cách an toàn trên nhánh chia sẻ
Bạn vừa push một commit lên nhánh `develop` và mọi thứ bắt đầu gặp sự cố nghiêm trọng. Giờ bạn cần đảo ngược thay đổi đó. Dùng `git reset –hard` và force push? Đừng làm thế trên nhánh công cộng!
Cách an toàn và đúng đắn là:
“`bash
git revert
“`
Lệnh này **không xóa commit cũ**. Nó tạo ra một commit mới có nội dung đảo ngược hoàn toàn các thay đổi từ commit cần revert.
“`
A—B—C—D
“`
Trong đó `D` có thể có thông báo: `Revert “Add new payment logic”`. Điều này rất quan trọng vì nó giữ lại toàn bộ lịch sử: ai đó đã thay đổi, nó gây ra sự cố, và nó đã được hoàn nguyên. Đây là nguyên tắc vàng:
> Nhánh chia sẻ + Commit sai → Dùng `git revert`.
Hy vọng bài viết đã mang đến cho bạn những kiến thức hữu ích. Git là một công cụ vô cùng mạnh mẽ, và việc nắm vững các lệnh trung cấp sẽ giúp bạn làm việc hiệu quả và tự tin hơn rất nhiều. Nếu bạn có những lệnh Git yêu thích khác, đừng ngại chia sẻ trong phần bình luận!



