Khi làm việc với Git, việc tạo ra các commit nhằm lưu trữ lại các thay đổi đã thực hiện trên mã nguồn vào kho lưu trữ Git. Tuy nhiên sẽ có những trường hợp mà chúng ta cần hủy bỏ những commit này, chẳng hạn như có lỗi trong các thay đổi, commit nhầm tập tin hoặc tạo commit nhầm nhánh. Đây chính là lúc Git Reset trở thành công cụ cực kì hữu ích. Nhưng sức mạnh của Git reset cũng tiềm ẩn các rủi ro nếu không được sử dụng đúng cách.

Đọc bài viết này để hiểu rõ hơn về:

  • Git reset là gì?
  • Cơ chế hoạt động của git reset
  • Các chế độ reset
  • Những trường hợp sử dụng và lưu ý khi dùng git reset

Git reset là gì?

Trước khi đi vào tìm hiểu Git Reset, chúng ta sẽ cùng xem qua các trạng thái của tập tin trong Git và cách mà Git quản lý tập tin trong mã nguồn. Trạng thái của các tập tin trong mã nguồn sẽ được Git quản lý bằng ba thành phần chính (thường được gọi là “cấu trúc 3 cây”) bao gồm:

  1. Working Directory (Thư mục làm việc): Đây là nơi mà lập trình viên thao tác, chỉnh sửa các tập tin trong mã nguồn. Nội dung của các tập tin trong khu vực này có thể ở trạng thái đã thay đổi, nhưng chúng vẫn chưa được theo dõi bởi Git
  2. Staging Area (Vùng tạm/chỉ mục): Đây là khu vực lưu trữ tạm thời các thay đổi của tập tin để chuẩn bị commit. Khu vực này giúp lập trình viên kiểm soát những thông tin nào sẽ được commit
  3. Repository (Kho lưu trữ): Nơi lưu trữ lịch sử commit, các phiên bản của mã nguồn

git mô hình 3 cây - itviec blog

Git Reset là một công cụ rất linh hoạt để thực hiện việc hoàn tác các thay đổi đã được commit và lưu trữ trong repository. Git reset giúp loại bỏ các commit, đưa những thay đổi trong tập tin trở lại khu vực Staging hoặc Khu vực làm việc, hay thậm chí là xóa bỏ dữ liệu thay đổi đó.

Cú pháp cơ bản của git reset:

git reset [<options>] [<commit>]

Trong đó:

  • options: là các tùy chọn chế độ của git reset, bao gồm –soft, –mixed, –hard, –merge, –keep
  • commit: xác định commit mà bạn muốn di chuyển con trỏ HEAD đến, có thể là HEAD~<số commit> (số commit tính từ commit mới nhất), commit_hash (mã băm của một commit), branch_name (một nhánh cụ thể)

Cơ chế hoạt động của Git reset

Git Reset là một lệnh mạnh mẽ trong Git, cơ chế hoạt động của nó dựa trên việc di chuyển con trỏ HEAD, làm thay đổi lịch sử commit và điều chỉnh trạng thái tập tin trong Repository, Staging Area và Working Directory.

git reset - itviec blog

Các git hoạt động có thể được mô tả theo các bước sau:

  1. Xác định commit và di chuyển HEAD: con trỏ HEAD sẽ được di chuyển đến commit đích được chỉ định và xem đó là commit cuối cùng trong lịch sử
  2. Quản lý trạng thái tập tin trong các khu vực: Git sẽ cập nhật lại trạng thái của tập tin trong vùng Staging và Working Directory dựa trên các chế độ được chỉ định.

Các chế độ trong git reset

Chúng ta hãy cùng tìm hiểu về cơ chế hoạt động, cách sử dụng các chế độ reset thông qua ví dụ sau:

Chúng ta có một lịch sử commit bao gồm 2 commit:

git reset - itviec blog

Bên cạnh đó là sự thay đổi trong Staging Area và Working Directory như sau:

git reset - itviec blog

–mixed (chế độ mặc định)

Cú pháp:

git reset --mixed <commit>

Công dụng:

  • Di chuyển HEAD về commit được chỉ định.
  • Xóa các thay đổi trong Staging Area nhưng giữ nguyên các thay đổi trong Working Directory.
  • Dùng để sửa các thay đổi staged mà bạn không muốn commit nữa.

Ví dụ sử dụng thông tin giả định bên trên, ta muốn hoàn tác commit mới nhất là “implement feature login”. Bởi vì –mixed là tùy chọn mặc định, vì vậy chúng ta có thể sử dụng cú pháp rút gọn như sau:

git reset HEAD~1

Lúc này con trỏ HEAD sẽ được di chuyển đến vị trí HEAD~1 (commit đứng trước commit “implement feature login”) và các thay đổi (bao gồm thay đổi từ commit bị reset) sẽ được đưa trở lại Working Directory:

git reset - itviec blog

Còn lịch sử commit sẽ chỉ còn lại commit “add file sample”

git reset - itviec blog

–soft

Cú pháp:

git reset --soft <commit>

Công dụng:

  • Git reset –soft sẽ di chuyển con trỏ HEAD về commit được chỉ định
  • Đưa các thay đổi trở lại Staging Area và giữ nguyên các thay đổi ở Working Directory
  • Được sử dụng khi bạn muốn hoàn tác commit nhưng vẫn giữ các thay đổi ở Staging Area để có thể sửa hoặc tạo commit mới.

Sử dụng thông tin giả định bên trên, ví dụ ta muốn hoàn tác commit mới nhất là “implement feature login”, ta có thể sử dụng cú pháp như sau:

git reset --soft HEAD~1

Lúc này con trỏ HEAD sẽ được di chuyển đến vị trí HEAD~1 (commit đứng trước commit “implement feature login”) và các thay đổi từ commit bị reset sẽ được đưa trở lại Staging Area, còn các thay đổi hiện tại ở Staging Area và Working Directory sẽ vẫn được giữ nguyên.

git reset - itviec blog

–hard

Cú pháp:

git reset --hard <commit>

Công dụng:

  • Di chuyển HEAD về commit chỉ định
  • Xóa mọi thay đổi trong cả Staging Area và Working Directory
  • Cẩn thận: Các thay đổi chưa commit sẽ bị mất hoàn toàn và không thể khôi phục

Sử dụng thông tin giả định bên trên, ví dụ ta muốn hoàn tác commit mới nhất là “implement feature login”, ta có thể sử dụng cú pháp như sau:

git reset --hard HEAD~1

Lúc này con trỏ HEAD sẽ được di chuyển đến vị trí HEAD~1 (commit đứng trước commit “implement feature login”) và các thay đổi từ commit bị reset sẽ được đưa trở lại Staging Area, nhưng các thay đổi ở Staging Area và Working Directory sẽ bị xóa bỏ hoàn toàn.

–merge

Cơ chế hoạt động của –merge sẽ giống với –mixed khi hủy bỏ commit và đưa tất cả thay đổi về Working Directory. Tuy nhiên nó chỉ giữ lại các thay đổi không bị xung đột.

Cú pháp:

git reset --merge <commit>

Công dụng:

  • Di chuyển HEAD về commit chỉ định
  • Giữ lại các thay đổi không xung đột trong Working Directory
  • Nếu có xung đột, Git sẽ tạm dừng quá trình reset và yêu cầu xử lý thủ công

–keep

Cơ chế hoạt động của –keep sẽ giống với –mixed khi hủy bỏ commit và đưa tất cả thay đổi về Working Directory. Tuy nhiên nó chỉ giữ lại các thay đổi không bị ghi đè.

Cú pháp:

git reset --keep <commit>

Công dụng:

  • Di chuyển HEAD về commit chỉ định
  • Giữ lại các thay đổi không bị ghi đè trong Working Directory
  • Nếu có xung đột, Git sẽ tạm dừng quá trình reset và yêu cầu xử lý thủ công

Những trường hợp sử dụng và lưu ý khi dùng git reset

Git reset là một lệnh mạnh mẽ trong Git. Nó được sử dụng trong nhiều trường hợp khi kết hợp với các tùy chọn:

  1. Chỉnh sửa commit mới nhất: sử dụng –soft để hủy commit mới nhất và đưa các thay đổi về lại staging area. Sau đó có thể tiến hành chỉnh sửa để tạo lại commit mới
  2. Loại bỏ các file đã được thêm vào Staging Area: sử dụng –mixed để đưa các thay đổi trong khu vực Staging Area về lại Working Directory. Tuy nhiên hành động này có thể được thao tác dễ dàng trên các IDE như vscode
  3. Xóa bỏ commit và nội dung thay đổi mà không để lại dấu vết: sử dụng –hard để hủy bỏ commit cũng như xóa bỏ tất cả thay đổi trong Staging Area và Working Directory
  4. Khi muốn hoàn tác các commit chưa được đẩy lên remote repository

Mặc dù Git reset cung cấp các khả năng mạnh mẽ để thay đổi lịch sử commit cũng như quản lý trạng thái thay đổi của tập tin. Tuy nhiên nó cũng tiềm ẩn nhiều rủi ro nếu không được sử dụng đúng cách. Dưới đây là một số lưu ý khi sử dụng git reset:

  1. Git reset –hard có thể gây mất dữ liệu: Bởi vì –hard sẽ hủy bỏ toàn bộ các thay đổi trong commit bị reset và dữ liệu thay đổi chưa được commit. Vì vậy hãy chắc chắn rằng không có dữ liệu nào cần được giữ lại trước khi thực hiện –hard
  2. Không sử dụng git reset khi đã đẩy commit lên remote repository: Bởi vì git reset có thể làm thay đổi lịch sử commit, vì vậy việc thực hiện git reset trên remote repository có thể dẫn đến xung đột và mất dữ liệu với những thành viên khác.
  3. Luôn kiểm tra trạng thái lịch sử commit trước khi reset: Điều này giúp bạn đảm bảo rằng đã không thực hiện reset nhầm commit.
  4. Sử dụng Git stash trước khi reset: Điều này giúp tránh bị mất dữ liệu chưa được commit nếu sử dụng –hard hoặc tránh bị xung đột khi sử dụng các chế độ khác.

So sánh git reset và git revert

Git reset và Git revert được được sử dụng để hoàn tác các thay đổi trong repository. Tuy nhiên cơ chế hoạt động của chúng lại hoàn toàn khác nhau:

Tiêu chí Git reset Git revert
Cú pháp git reset <options> <commit> git revert <commit>
Cơ chế hoạt động Hoàn tác commit bằng cách thay đổi lịch sử, vị trí con trỏ HEAD Tạo commit mới với nội dung đảo ngược so với commit cần hoàn tác
Thay đổi lịch sử commit Thay đổi lịch sử commit (xóa bỏ commit khỏi lịch sử) Giữ nguyên lịch sử, chỉ bổ sung commit đảo ngược
Thay đổi Staging Area, Working Directory Có thể làm thay đổi tùy thuộc vào cơ chế sử dụng Không làm thay đổi
Phạm vi sử dụng Chỉ nên sử dụng ở nhánh cục bộ, chưa được đẩy lên remote repository An toàn khi sử dụng trên remote repository
Trường hợp sử dụng Xóa commit khỏi lịch sử và không lưu lại dấu vết Khi cần hoàn tác commit nhưng vẫn giữ nguyên lịch sử

Các câu hỏi thường gặp về Git reset

So sánh giữa git reset và git checkout

Git reset và Git checkout là hai lệnh hữu ích,  phổ biến trong Git. Mặc dù chúng đều có tác động đến con trỏ HEAD, tuy nhiên chúng có những chức năng và trường hợp sử dụng khác nhau, dưới đây là bảng so sánh chi tiết:

Tiêu chí Git reset Git checkout
Cách hoạt động Đưa con trỏ HEAD về commit được chỉ định và loại bỏ commit đứng sau nó. Di chuyển HEAD sang một nhánh khác hoặc commit khác
Thay đổi lịch sử commit Có, xóa bỏ commit khỏi lịch sử Không làm thay đổi lịch sử
Mục đích Hoàn tác commit và thay đổi lịch sử Chuyển nhánh hoặc khôi phục tập tin
Ảnh hưởng Staging Area Có thể thay đổi Không
Ảnh hưởng Working Directory Có thể thay đổi Chỉ thay đổi file hoặc nhánh

Đọc thêm: Git Checkout: Các trường hợp sử dụng kèm ví dụ chi tiết

Làm sao để khôi phục dữ liệu khi vô tình dùng “git reset –hard”

Nếu bạn vô tình sử dụng git reset –hard dẫn đến commit bị xóa và nội dung thay đổi trong đó cũng biến mất. Lúc này bạn có thể sử dụng git reflog để truy vết lịch sử và lấy lại nội dung đã mất.

Git reflog là một lệnh để xem lại tất cả các thay đổi diễn ra trong repositoryvà chúng ta có thể truy xuất đến trạng thái của mã nguồn tại thời điểm diễn ra các thay đổi đó.

Ví dụ:

Trong nhánh main đang có 2 commit như sau:

def5678 HEAD@{1}: commit: Sửa lỗi chức năng tìm kiếm
ghi9012 HEAD@{2}: commit: Tạo tính năng tìm kiếm

Bạn sử dụng git reset –hard để xóa commit “Sửa lỗi chức năng tìm kiếm”, nhưng bây giờ bạn cần đưa commit này trở lại, lúc này bạn sử dụng git reflog để xem lại hoạt động như sau:

git reflog

Output:

abc1234 (HEAD -> main) HEAD@{0}: reset: moving to HEAD~1
def5678 HEAD@{1}: commit: Sửa lỗi chức năng tìm kiếm
ghi9012 HEAD@{2}: commit: Tạo tính năng tìm kiếm

Trong đó ta sẽ thấy:

  • HEAD@{0} là commit hiện tại sau khi git reset –hard.
  • HEAD@{1}, HEAD@{2} là các commit trước khi bị reset.

Bây giờ, để lấy lại commit “Sửa lỗi chức năng tìm kiếm”, ta có thể sử dụng git reset kết hợp với commit hash của commit đó:

git reset --hard def5678

Lúc này, lịch sử commit sẽ trở lại có 2 commit như ban đầu.

Sự khác biệt giữa HEAD~ và HEAD^ khi dùng git reset

Khi sử dụng git reset, HEAD~ và HEAD^ là các tham chiếu commit để chỉ định các commit trước đó. Tuy nhiên giữa chúng có một số khác biệt sau:

HEAD~

HEAD~x dùng để chỉ commit x commit trước so với HEAD. Nếu không chỉ định số x, mặc định là HEAD~1, tức là commit ngay trước HEAD. Ví dụ ta có:

abc123 (HEAD -> main) Commit A
def456 Commit B
ghi789 Commit C

Lúc này, HEAD~1 sẽ tương ứng với commit B và HEAD~2 sẽ tương ứng với commit C.

HEAD^

HEAD^ sẽ trỏ đến commit cha (commit liền kề trước) của commit hiện tại.

Điều này đặc biệt hữu ích đối với commit merge. Nếu HEAD hiện tại đang là một commit merge (được tạo ra từ hoạt động git merge), thì HEAD^1 sẽ tương ứng với commit liền kề trước đó ở nhánh chính hiện tại và HEAD^2 sẽ tương ứng với commit liền kề trước đó ở nhánh phụ.

Tổng kết

Git reset là một công cụ hữu ích để bạn có thể kiểm soát, chỉnh sửa và hoàn tác các commits một cách dễ dàng. Tuy nhiên để sử dụng nó được hiệu quả và tránh các sự cố ngoài ý muốn, cần đòi hỏi lập trình viên phải hiểu rõ được cơ chế hoạt động và cách sử dụng nó.

Qua bài viết này, ITviec hi vọng đã mang đến cho bạn đọc những thông tin hữu ích về cách sử dụng Git reset, để bạn có thể sử dụng hiệu quả và từng bước làm chủ được hệ thống quản lý phiên bản Git.