Git recovery reset --hard

Một ngày đẹp trời khi đang làm việc, bạn dù vô tình hay cố ý chạy câu lệnh git reset --hard {commit}. Xong! vậy là tất cả những gì bạn hùng hục làm cả ngày đã mất tiêu. Giờ là lúc chúng ta nghĩ về cách giải quyết hậu quả và khôi phục lại dữ liệu đã mất. Câu hỏi đặt ra là liệu dữ liệu có khôi phục lại được 100% hay không? Hãy xem xét các khả năng sau:

  • 1. Nếu tất cả các file thay đổi đã được commit

May mắn là trường hợp này việc khôi phục lại dữ liệu khá đơn giản và nhanh gọn.

Trạng thái của repo trước khi reset hard:

git log --oneline

ff31686 fourth commit
ac79570 third commit
b6eb67c second commit
b20d416 first commit

Giả sử đã reset hard về second commit bằng lệnh:

git reset --hard b6eb67c

Kết quả:

git log --oneline
b6eb67c second commit
b20d416 first commit

Để khôi phục lại dữ liệu thì cần quay lại commit fourth commit, để reset HEAD về commit nào đó thì cơ bản chúng ta phải biết mã SHA1 của commit đó. Dùng lệnh git reflog để liệt kê history của con trỏ HEAD:

git reflog
b6eb67c [email protected]{0}: reset: moving to b6eb67c
ff31686 [email protected]{1}: commit: fourth commit
ac79570 [email protected]{2}: reset: moving to [email protected]{1}
b6eb67c [email protected]{3}: reset: moving to b6eb67c
ac79570 [email protected]{4}: commit: third commit
b6eb67c [email protected]{5}: reset: moving to [email protected]{3}
b20d416 [email protected]{6}: reset: moving to [email protected]{1}
b6eb67c [email protected]{7}: checkout: moving from master to b6eb67c
b20d416 [email protected]{8}: reset: moving to b20d416
b6eb67c [email protected]{9}: commit: second commit
b20d416 [email protected]{10}: commit (initial): first commit

Check log tìm được mã SHA1 cuả fourth commit. Tạo một nhánh để recovery dữ liệu:

git branch recover-branch ff31686

Check kết quả và thấy dữ liệu đã hoàn toàn được khôi phục:

git log --oneline

ff31686 fourth commit
ac79570 third commit
b6eb67c second commit
b20d416 first commit
  • 2. Những thay đổi chưa được track và commit

Vì các file thay đổi chưa được track, tức bị git lờ đi nên khi lệnh git reset --hard {commit} chúng không bị tác động. Do đó trường hợp này suy biến về như trường hợp 1. Các bước làm cũng tương tự.

  • 3. Những thay đổi đã được add nhưng chưa được commit

Trạng thái hiện tại:

echo "file3 recovery" > file3.txt
git add .
git status
On branch recover-branch
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	modified:   file3.txt

Reset --hard:

git reset --hard HEAD
HEAD is now at ff31686 fourth commit

git status
On branch recover-branch
nothing to commit, working directory clean

Sau khi reset tất cả những thay đổi được tạo trong file3.txt đã biến mất, vì nó chưa được commit nên chúng ta không thể dùng reset HEAD về commit nào đó được. Khi sử dụng lệnh git add tức là thay đổi đã được đánh index, tạo thành objects và được lưu đâu đó trong repo tuy nhiên thay đổi này lại không được ref đến bởi commit nào. Trong git có một utility giúp chúng ta check được các objects không được tham chiếu tới (dangling):

git fsck --full

Checking object directories: 100% (256/256), done.
dangling blob 9daeafb9864cf43055ae93beb0afd6c7d144bfa4
dangling blob 2894e252ee1a560df965662a8c138b9e16e7dd3b
dangling blob e9e638322fe1703200d5af40e691af0208cf3a97
dangling blob 76dfd6c5ed7005dd04f626ad89291069992bf927
dangling blob fcfd9ad9834ed177ea525aec9473b2178099a64e
dangling blob 7d1b7da2833de3def992628293a159b56fab5a8d
dangling blob 3e65ed2670ae277e9d042659dd8639cd0f0f7d9c

Để check xem liệu object nào mà bạn đã làm mất trước khi reset --hard, sử dụng git show {sha1} để check nội dung. Sau khi check lần lượt từ dưới lên phát hiện một object:

git show 3e65ed2670ae277e9d042659dd8639cd0f0f7d9c
file3 recovery

file3 recovery chính là nội dung đã thay đổi trước khi reset. Bạn có thể copy nội dung này tạo thành file mới để khôi phục lại dữ liệu.

Như vậy cách này gặp khó khăn với trường hợp nhiều file thay đổi bị mất vì phải lần lượt check nội dung của các object lơ lửng hay dangling blob rất mất thời gian và công sức.

  • 4. Những thay đổi trong các file đã track từ trước đó mà chưa add

Đây là trường hợp thốn nhất

Trạng thái hiện tại:

git log --oneline
ff31686 fourth commit
ac79570 third commit
b6eb67c second commit
b20d416 first commit

Sau đó:

echo  "hello" > file3.txt
[email protected]:~/workspace/gt_test$ git status
On branch recover-branch
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

	modified:   file3.txt

Và sau đó reset --hard

git reset --hard
HEAD is now at ff31686 fourth commit

Xong! đến đây thì vô phương cứu chữa rồi. Vì bạn chưa đánh index cho những thay đổi này tức chưa git add nên chúng sẽ chẳng được lưu ở đâu trên git cả nên sẽ không thể dùng git để recover chúng được. Nếu may mắn bạn đang sử dụng một IDE có chức năng xem local history thì mới có thể khôi phục lại chúng.

Đây cũng là trường hợp để chúng ta cân nhắc khi sử dụng git reset --hard. Thay vì sử dụng option --hard tùy vào mục đích mà bạn có thể sử dụng các option khác bớt nguy hiểm hơn như: --soft, --mixed, --keep, --merge ...