Sau khi đã làm quen với các lệnh Git cơ bản, đã đến lúc khám phá những lệnh Git nâng cao để tối ưu hóa quy trình làm việc, giải quyết các tình huống phức tạp, và làm chủ hoàn toàn hệ thống quản lý phiên bản Git. Bài viết này sẽ giới thiệu một số lệnh Git nâng cao dễ hiểu, được sắp xếp logic, giúp bạn mở rộng khả năng sử dụng Git và trở thành một người dùng Git thành thạo hơn.
Danh sách chọn nhanh các lệnh Git nâng cao (Mục lục):
1. Thay đổi Lịch sử Commit
Nhóm lệnh này cho phép bạn chỉnh sửa lịch sử commit, thay đổi thứ tự commit, gộp commit, hoặc loại bỏ commit. Lưu ý: Thay đổi lịch sử commit đã push lên remote repository có thể gây ra vấn đề cho người khác trong nhóm, nên cần thận trọng khi sử dụng, đặc biệt khi làm việc nhóm.
a. git rebase
Mô tả: Rebase (tái tạo cơ sở) là một cách tích hợp các thay đổi từ một nhánh vào nhánh khác, tương tự như git merge
, nhưng rebase
tạo ra một lịch sử commit tuyến tính hơn, sạch sẽ hơn. Rebase hoạt động bằng cách di chuyển các commit của nhánh hiện tại lên trên đỉnh của nhánh mục tiêu.
Ví dụ: Giả sử bạn đang ở nhánh feature-A
, muốn tích hợp các thay đổi mới nhất từ nhánh main
vào feature-A
, bạn có thể dùng:
git checkout feature-A
git rebase main
Giải thích:
git checkout feature-A
: Chuyển sang nhánhfeature-A
(nhánh bạn muốn rebase).git rebase main
: Rebase nhánhfeature-A
lên trên đỉnh của nhánhmain
.- Git sẽ tạm thời “gỡ bỏ” các commit của
feature-A
. - Sau đó, Git sẽ “áp dụng lại” từng commit của
feature-A
, nhưng bắt đầu từ commit mới nhất củamain
.
- Git sẽ tạm thời “gỡ bỏ” các commit của
- Kết quả là nhánh
feature-A
sẽ có lịch sử commit tuyến tính, như thể bạn đã tạo nhánhfeature-A
từ commit mới nhất củamain
.
Ưu điểm của rebase:
- Lịch sử commit sạch sẽ, tuyến tính, dễ đọc và theo dõi.
- Tránh các nhánh hợp nhất (merge commit) phức tạp trong lịch sử.
Nhược điểm của rebase:
- Thay đổi lịch sử commit, có thể gây khó khăn cho việc cộng tác nếu đã push nhánh lên remote và người khác đã dựa vào lịch sử đó. Không nên rebase nhánh public (nhánh đã được chia sẻ với người khác).
- Rebase có thể trở nên phức tạp khi có xung đột (rebase conflicts) xảy ra trong quá trình áp dụng lại commit.
b. git cherry-pick
Mô tả: Cherry-pick (chọn lọc) cho phép bạn chọn một commit cụ thể từ một nhánh khác và áp dụng nó vào nhánh hiện tại. Hữu ích khi bạn chỉ muốn lấy một số thay đổi nhất định từ nhánh khác, thay vì hợp nhất toàn bộ nhánh.
Ví dụ: Giả sử bạn muốn lấy commit có hash abcdef123456
từ nhánh develop
và áp dụng vào nhánh feature-B
, bạn dùng:
git checkout feature-B
git cherry-pick abcdef123456
Giải thích:
git checkout feature-B
: Chuyển sang nhánhfeature-B
(nhánh bạn muốn cherry-pick commit vào).git cherry-pick abcdef123456
: Chọn commit có hashabcdef123456
từ nhánh khác và áp dụng nó vào nhánhfeature-B
.- Git sẽ cố gắng áp dụng các thay đổi của commit đó vào nhánh hiện tại. Nếu thành công, một commit mới sẽ được tạo ra trên nhánh
feature-B
, chứa các thay đổi được cherry-pick.
Lưu ý: Cherry-pick tạo ra một commit mới với cùng nội dung thay đổi, nhưng hash commit sẽ khác với commit gốc. Nếu có xung đột, bạn cần giải quyết xung đột tương tự như khi merge.
c. git revert
Mô tả: Revert (đảo ngược) tạo ra một commit mới để hoàn tác (undo) các thay đổi của một commit đã có. Không giống như git reset
(sẽ xóa commit khỏi lịch sử), git revert
tạo ra một commit mới “đối nghịch” với commit bạn muốn hoàn tác, giữ lại lịch sử commit gốc.
Ví dụ: Giả sử bạn muốn hoàn tác commit có hash 123456abcdef
, bạn dùng:
git revert 123456abcdef
Giải thích:
git revert 123456abcdef
: Tạo một commit mới để hoàn tác commit123456abcdef
.- Git sẽ tự động tạo một commit mới, chứa các thay đổi “ngược lại” với commit
123456abcdef
. Ví dụ, nếu commit123456abcdef
thêm dòng code, commit revert sẽ xóa dòng code đó. - Commit revert sẽ có thông điệp commit mặc định, bạn có thể chỉnh sửa thông điệp này.
Ưu điểm của revert:
- An toàn hơn
git reset
, vì giữ lại lịch sử commit gốc. - Dễ dàng hoàn tác commit đã push lên remote và chia sẻ với người khác, vì không thay đổi lịch sử đã public.
Khi nào dùng revert: Khi bạn muốn hoàn tác một commit đã push lên remote hoặc khi bạn muốn giữ lại lịch sử commit đầy đủ.
d. git reset
Mô tả: Reset (đặt lại) cho phép bạn đưa nhánh hiện tại về một commit trước đó. git reset
có thể thay đổi staging area, working directory, và lịch sử commit, tùy thuộc vào các tùy chọn bạn sử dụng. Lệnh này rất mạnh mẽ và có thể nguy hiểm nếu không hiểu rõ cách dùng.
Các loại reset (tùy chọn phổ biến):
- `git reset –soft [commit]`: Soft reset. Đặt lại HEAD về commit chỉ định, nhưng giữ nguyên staging area và working directory. Các thay đổi sau commit chỉ định vẫn được staging. Thường dùng để “gộp” nhiều commit lại thành một commit lớn hơn.
- `git reset –mixed [commit]` (mặc định nếu không có tùy chọn): Mixed reset. Đặt lại HEAD về commit chỉ định, và reset staging area để khớp với commit chỉ định. Nhưng vẫn giữ nguyên working directory. Các thay đổi sau commit chỉ định sẽ ở trạng thái “not staged” trong working directory. Thường dùng để “bỏ staging” các thay đổi, nhưng vẫn giữ lại các thay đổi trong working directory.
- `git reset –hard [commit]`: Hard reset. Đặt lại HEAD về commit chỉ định, reset staging area, và reset working directory để hoàn toàn khớp với commit chỉ định. Mọi thay đổi sau commit chỉ định sẽ bị mất hoàn toàn, không thể phục hồi nếu chưa commit hoặc stash. Rất nguy hiểm, cần dùng cẩn thận. Thường dùng để “xóa bỏ” các commit và thay đổi không mong muốn một cách triệt để.
Ví dụ (mixed reset): Giả sử bạn muốn reset về commit trước commit hiện tại (HEAD^), bạn dùng:
git reset --mixed HEAD^
Giải thích:
git reset --mixed HEAD^
: Reset nhánh hiện tại về commit trước commit HEAD (commit cha).--mixed
: Reset HEAD và staging area, nhưng giữ working directory.HEAD^
: Tham chiếu đến commit cha của HEAD (commit hiện tại). Bạn cũng có thể dùng hash commit cụ thể (ví dụ:git reset --mixed abcdef123456
).- Sau khi reset, các thay đổi từ commit HEAD trở về sau sẽ bị “bỏ staging” và nằm trong working directory ở trạng thái “not staged”. Bạn có thể commit lại hoặc bỏ chúng đi.
Cảnh báo: `git reset –hard` rất nguy hiểm, có thể mất dữ liệu vĩnh viễn nếu bạn không cẩn thận. Hãy chắc chắn bạn hiểu rõ mình đang làm gì trước khi dùng git reset --hard
, đặc biệt là khi reset về commit quá khứ xa xôi.
2. Kiểm soát Staging Area Nâng cao
Nhóm lệnh này giúp bạn kiểm soát staging area một cách linh hoạt hơn, bỏ staging các thay đổi, hoặc tạm thời cất giữ các thay đổi chưa commit.
a. git restore –staged [tên file]
Mô tả: Bỏ staging một file, tức là chuyển file từ staging area trở lại working directory ở trạng thái “not staged”. Hữu ích khi bạn đã staging nhầm file hoặc muốn loại bỏ một số thay đổi khỏi staging area trước khi commit.
Ví dụ: Giả sử bạn đã staging file script.js
nhưng muốn bỏ staging nó, bạn dùng:
git restore --staged script.js
Giải thích:
git restore --staged script.js
: Bỏ staging filescript.js
.- Sau khi lệnh này chạy, file
script.js
sẽ không còn ở staging area nữa, mà trở lại working directory ở trạng thái “Changes not staged for commit” (như khi bạn dùnggit status
kiểm tra). - Các thay đổi trong file
script.js
vẫn còn trong working directory, bạn có thể chỉnh sửa tiếp, staging lại, hoặc bỏ chúng đi.
b. git checkout — [tên file] (discard changes)
Mô tả: Discard changes (loại bỏ thay đổi) trong working directory cho một file cụ thể. Lệnh này sẽ khôi phục file trong working directory về phiên bản mới nhất trong staging area hoặc commit HEAD (nếu chưa staging). Mọi thay đổi chưa staging trong file sẽ bị mất.
Ví dụ: Giả sử bạn đã chỉnh sửa file style.css
trong working directory và muốn bỏ hết các thay đổi đó, khôi phục về phiên bản gốc, bạn dùng:
git checkout -- style.css
Giải thích:
git checkout -- style.css
: Loại bỏ các thay đổi trong filestyle.css
ở working directory.--
: Dấu phân cách để Git hiểu rằngstyle.css
là tên file, không phải tên branch.- Cảnh báo: Mọi thay đổi bạn đã thực hiện trong file
style.css
(kể từ commit hoặc staging gần nhất) sẽ bị mất vĩnh viễn. Hãy chắc chắn bạn muốn bỏ các thay đổi trước khi dùng lệnh này.
Khi nào dùng `git checkout — [file]`: Khi bạn muốn hủy bỏ nhanh chóng các thay đổi chưa staging trong một file và quay lại phiên bản gốc.
c. git stash
Mô tả: Stash (cất giữ) các thay đổi chưa commit của bạn. Stash giúp bạn tạm thời cất giữ các thay đổi trong working directory và staging area, để có thể chuyển sang nhánh khác làm việc, hoặc làm sạch working directory. Các thay đổi được stash sẽ được lưu vào một “ngăn chứa tạm thời” (stash stack) và bạn có thể khôi phục lại sau.
Các lệnh stash phổ biến:
- `git stash`: Stash các thay đổi hiện tại. Sẽ stash cả staging area và working directory.
- `git stash save “[Thông điệp stash]”`: Stash và đặt tên cho stash. Thông điệp giúp bạn dễ dàng nhớ stash này chứa gì.
- `git stash list`: Xem danh sách các stash đã lưu.
- `git stash apply`: Áp dụng stash mới nhất vào nhánh hiện tại. Stash vẫn còn trong stash list sau khi apply.
- `git stash pop`: Áp dụng stash mới nhất và xóa nó khỏi stash list. Thường dùng khi bạn muốn khôi phục stash và không cần giữ lại bản stash nữa.
- `git stash apply stash@{index}` hoặc `git stash pop stash@{index}`: Áp dụng hoặc pop một stash cụ thể từ stash list, thay vì stash mới nhất. Index là số thứ tự của stash trong danh sách (bắt đầu từ 0).
- `git stash drop stash@{index}`: Xóa một stash cụ thể khỏi stash list.
- `git stash clear`: Xóa tất cả các stash trong stash list.
Ví dụ (stash và pop):
# ... Bạn đang làm việc và có các thay đổi chưa commit ...
git stash save "WIP: Dở dang tính năng X" # Stash và đặt tên
Saved working directory and index state WIP: Dở dang tính năng X
HEAD is now at abcdef123 Merge branch 'main' into develop
... Bây giờ working directory của bạn đã sạch sẽ, bạn có thể chuyển nhánh khác ...
git checkout main
... Sau khi làm xong việc khác ở nhánh main, bạn muốn quay lại làm tiếp tính năng X ...
git checkout feature-X
git stash pop # Áp dụng và xóa stash mới nhất
Khi nào dùng stash:
- Khi bạn đang làm việc dở dang một tính năng, nhưng cần chuyển sang nhánh khác để sửa lỗi khẩn cấp hoặc làm việc khác.
- Khi bạn muốn làm sạch working directory tạm thời, nhưng không muốn commit các thay đổi dở dang.
3. Kiểm tra và So sánh Thay đổi
Nhóm lệnh này giúp bạn kiểm tra các thay đổi, so sánh giữa các phiên bản, và tìm kiếm lỗi trong lịch sử dự án.
a. git diff
Mô tả: Diff (difference – khác biệt) dùng để so sánh các thay đổi giữa các phiên bản khác nhau của code. git diff
có thể so sánh:
- Working directory vs. Staging area: Xem các thay đổi trong working directory so với staging area (những thay đổi chưa staging). (Sử dụng:
git diff
) - Staging area vs. Commit gần nhất (HEAD): Xem các thay đổi đã staging so với commit HEAD. (Sử dụng:
git diff --staged
hoặcgit diff --cached
) - Giữa hai commit, hai nhánh, hoặc hai file cụ thể: (Ví dụ:
git diff HEAD~2 HEAD
,git diff main feature-A
,git diff file1.txt file2.txt
)
Ví dụ (so sánh working directory vs. staging area):
git diff
Giải thích:
- Lệnh
git diff
không có tùy chọn sẽ so sánh working directory với staging area. - Kết quả sẽ hiển thị các dòng code đã được thêm vào (bắt đầu bằng
+
) và dòng code đã bị xóa (bắt đầu bằng-
) trong working directory so với staging area. - Diff output thường được hiển thị theo định dạng unified diff, dễ đọc và so sánh code.
Ví dụ (so sánh staging area vs. commit HEAD):
git diff --staged
Giải thích:
git diff --staged
hoặcgit diff --cached
sẽ so sánh staging area với commit HEAD.- Hiển thị các thay đổi đã được staging và sẽ được commit trong commit tiếp theo.
b. git bisect
Mô tả: Bisect (chia đôi) là một công cụ mạnh mẽ để tìm ra commit nào đã gây ra lỗi (bug) trong lịch sử commit của dự án. git bisect
sử dụng thuật toán tìm kiếm nhị phân để nhanh chóng xác định commit gây lỗi.
Quy trình dùng `git bisect`:
- Bắt đầu bisect:
git bisect start
- Đánh dấu commit “tốt” (không có lỗi):
git bisect good [commit tốt]
(thường là commit cũ hơn, biết chắc là không có lỗi). - Đánh dấu commit “xấu” (có lỗi):
git bisect bad [commit xấu]
(thường là commit hiện tại hoặc commit mới hơn, commit mà bạn phát hiện ra lỗi). - Git sẽ tự động checkout một commit ở giữa khoảng “tốt” và “xấu”.
- Kiểm tra commit hiện tại: Chạy chương trình, test, hoặc kiểm tra lỗi.
- Nếu commit hiện tại “tốt” (không có lỗi):
git bisect good
- Nếu commit hiện tại “xấu” (có lỗi):
git bisect bad
- Nếu commit hiện tại “tốt” (không có lỗi):
- Lặp lại bước 5. Git sẽ tiếp tục chia đôi khoảng tìm kiếm và checkout commit tiếp theo.
- Sau một số bước lặp, `git bisect` sẽ xác định được commit đầu tiên gây ra lỗi.
- Kết thúc bisect:
git bisect reset
(để quay lại nhánh làm việc ban đầu).
Ví dụ: Giả sử bạn phát hiện ra lỗi ở commit hiện tại, và biết commit v1.0
(tag v1.0
) là commit tốt, không có lỗi. Bạn có thể dùng git bisect
như sau:
git bisect start
git bisect good v1.0
git bisect bad HEAD # HEAD là commit hiện tại (xấu)
... Git checkout một commit ở giữa ...
... Kiểm tra chương trình ...
Giả sử commit này vẫn "xấu"
git bisect bad
... Git checkout commit tiếp theo ...
... Kiểm tra chương trình ...
Giả sử commit này "tốt"
git bisect good
... Lặp lại cho đến khi git bisect xác định commit gây lỗi ...
... Git thông báo commit gây lỗi: "First bad commit: [hash] ..."
git bisect reset # Kết thúc bisect, quay lại nhánh ban đầu
Khi nào dùng `git bisect`: Khi bạn biết rằng lỗi mới xuất hiện gần đây, và muốn tìm ra commit nào đã gây ra lỗi một cách nhanh chóng trong lịch sử commit dài.
Kết luận: Sử dụng lệnh Git nâng cao để làm chủ quy trình phát triển
Bài viết này đã giới thiệu một số lệnh Git nâng cao, giúp bạn mở rộng khả năng sử dụng Git và giải quyết các tình huống phức tạp hơn trong quá trình phát triển phần mềm. Từ việc chỉnh sửa lịch sử commit (rebase
, cherry-pick
, revert
, reset
), kiểm soát staging area (restore --staged
, checkout --
, stash
), đến kiểm tra và so sánh thay đổi (diff
, bisect
), các lệnh này cung cấp cho bạn những công cụ mạnh mẽ để quản lý phiên bản code một cách chuyên nghiệp.
Hãy thực hành các lệnh này, thử nghiệm trong các dự án cá nhân, và dần dần tích hợp chúng vào quy trình làm việc của bạn. Càng sử dụng thành thạo các lệnh Git nâng cao, bạn càng trở nên tự tin và hiệu quả hơn trong vai trò lập trình viên!
Chúc bạn tiếp tục thành công trên con đường chinh phục Git và phát triển sự nghiệp lập trình!