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 ...
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 HEAD@{0}: reset: moving to b6eb67c ff31686 HEAD@{1}: commit: fourth commit ac79570 HEAD@{2}: reset: moving to HEAD@{1} b6eb67c HEAD@{3}: reset: moving to b6eb67c ac79570 HEAD@{4}: commit: third commit b6eb67c HEAD@{5}: reset: moving to HEAD@{3} b20d416 HEAD@{6}: reset: moving to HEAD@{1} b6eb67c HEAD@{7}: checkout: moving from master to b6eb67c b20d416 HEAD@{8}: reset: moving to b20d416 b6eb67c HEAD@{9}: commit: second commit b20d416 HEAD@{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 ngocnv@ngocnv:~/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 ...