Why should I know this?

git 복구 본문

Study

git 복구

die4taoam 2019. 9. 25. 17:22

 

 

Git/복구 : GyparkWiki

[edit]1. 커밋까지 한 상태에서 git-reset --hard 옵션으로 덮어써 버린 경우 커밋 개체들이 아직 .git 디렉토리 아래에 남아 있기 때문에 복구가 그다지 어렵지 않다 (시간이 흐르면 - 며칠? 한달? 정확히 모르겠으나 아주 짧진 않다 - 가비지 컬렉션에 의해 없어지니 주의) git-reflog 로 HEAD가 가리켰었던 커밋 객체들의 목록을 볼 수도 있고 다음 섹션에 언급할 git-fsck 를 써서 찾을 수도 있고 등등. 어떻게든 해당 커

gypark.pe.kr

 

1. 커밋까지 한 상태에서 git-reset --hard 옵션으로 덮어써 버린 경우

 

 

커밋 개체들이 아직 .git 디렉토리 아래에 남아 있기 때문에 복구가 그다지 어렵지 않다 (시간이 흐르면 - 며칠? 한달? 정확히 모르겠으나 아주 짧진 않다 - 가비지 컬렉션에 의해 없어지니 주의)

 

  • git-reflog 로 HEAD가 가리켰었던 커밋 객체들의 목록을 볼 수도 있고
  • 다음 섹션에 언급할 git-fsck 를 써서 찾을 수도 있고
  • 등등.

어떻게든 해당 커밋 개체를 찾았으면, 그 개체를 다시 현재 브랜치에 연결하고 다시 git-reset 으로 브랜치 포인터를 이동시켜서 해결할 수 있겠다. 아래 링크들 참고.

 

 

 

[edit]2. 인덱스까지만 만든 상태에서 git-reset --hard 옵션으로 덮어써 버린 경우

주인장이 실제로 겪은 사례.

 

 

[edit]2.1. 상황 재현

 

 

(저장소 만들고) $ git init Initialized empty Git repository in d:/Temp/git/.git/ ( d1 디렉토리와, 그 아래 file1 을 만든 후 커밋) $ mkdir d1 $ echo "hello" > d1/file1 $ git add d1 $ git ci -m "first commit" [master (root-commit) be77038] first commit The file will have its original line endings in your working directory. 1 files changed, 1 insertions(+), 0 deletions(-) create mode 100644 d1/file1 ( d2 디렉토리와, 그 아래 file2 를 만든 후 add로 index에 추가하고) $ mkdir d2 $ echo "hello 2" > d2/file2 $ git add d2 $ git status # On branch master # Changes to be committed: # (use "git reset HEAD <file>..." to unstage) # # new file: d2/file2 # ( 여기서 실수 ) $ git reset --hard HEAD HEAD is now at be77038 first commit $ ls d1 ( d2 디렉토리가 날아갔다 )

(그런데, 이 상황 재현은 막상 아래 복구 과정에서는 내가 경험했던 대로 결과가 나오지 않는다. 즉 제대로 재현이 안 됨)

조금 더 수정한 후에 커밋하는 게 좋겠다고 생각해서, 일단 만들어진 인덱스, 즉 stage 영역을 이전 상태로 되돌릴려고 했다. 이 경우는 git-reset의 옵션으로 --mixed 를 주거나, --mixed가 기본값이니까 아예 옵션을 적지 말았어야 했다. (아니면 그냥 무시하고 더 작업한 다음에 git-add를 해도 되는 거긴 한데, 이 때 뭐가 씌웠는지ㅋ) 그러나 --hard 옵션을 주는 바람에 작업 디렉토리가 통채로 현재 HEAD가 가리키는 상태로 되돌아가버렸고, 새로 추가한 d2 디렉토리와 그 내용들이 사라져 버렸다.

 

 

 

 

[edit]2.2. 복구 방법

지워진 디렉토리가 사실 썩 중요한 것도 아니었고, 커밋을 하지 않은 상태라서 살리는 게 녹록치 않을 것 같아 포기하려 했는데, 트위터에 날렸다고 한마디 했더니 git-fsck를 써 보라는 리플을 받고1 문서 좀 뒤지면서 시도해 봄

 

  • git-fsck 를 써서, 다른 개체에 의해 레퍼런스되고 있지 않은 개체들의 목록을 뽑았다.

$ git fsck --lost-found Checking object directories: 100% (256/256), done. Checking objects: 100% (627/627), done. dangling blob 0c6abc2d5faabc05babf319996ed80620797999f dangling tree b5bff3dcdc0f8fa00c9bfa2be1f64f7af5feec3f

    • 개체 두 개가 나오는데, 하나는 파일을 나타내는 blob 오브젝트이고, 또 하나는 디렉토리 트리를 나타내는 tree 오브젝트이다.
    • 앞에서 "상황 재현"이 제대로 안 된다고 했는데, 이 시점에서 지워진 d2 디렉토리를 가리킬 tree 오브젝트가 떠야 할 것 같은데 막상 해 보면 그 디렉토리 아래 있는 file2를 가리키는 blob만 나오더라. 이 경우 복구는 그 blob의 내용을 복사해서 다시 파일을 만들어 주는 식으로 하면 될 듯.

 

  • git-show 또는 git-cat-file 을 써서 두 개체의 내용을 살펴보았다.

$ git show 0c6a #!perl use 5.010; use strict; use warnings; ... $ git show b5bf tree b5bf 20120403_sparse_matrix/ ... 20120526_montecarlo/ <-- 사라진 디렉토리 ... advent_calendar_2012_lotus/ <-- 사라진 디렉토리 ...

    • b5bf 개체의 내용이, 현재 저장소의 루트 디렉토리의 내용을 가리키고 있는데, 여기에 보니까 복구하기를 원하는 디렉토리가 있더라.

 

  • 저 두 디렉토리 안의 내용까지 제대로 들어있는지 확인차, git-cat-file 로 계속 따라가 보았다.

$ git cat-file -p b5bf ... 040000 tree 0842f02ec6dadb851ec2439c5fdfd643f6e3e877 20120526_montecarlo ... $ git cat-file -p 0842 040000 tree 8b3468edc53b977fde3f5e2ae423009fa487c9e0 _Inline 100644 blob f22308643a862993af01681ef5a5e8fcfebe88fe sphere.pl 100644 blob ff9098d4b4b4a16ed54d418a3ec43b8c4f5cffbc test.c 100644 blob 04f766def6dcd4a1d297a84bc25cd9df385373bc test.o $ git cat-file -p f223 #!/usr/bin/env perl use Inline C; use strict; ...

    • 디렉토리 아래 다시 디렉토리와 파일들이 있고, 특히나 내가 살리고픈 파일들이 존재하는 것을 확인

 

  • 이제 잃어버린 파일들을 찾아내는 방법까진 알았으니, 복구하는 방법을 고민해 봤는데,
    • 위처럼 각 파일들의 blob 개체를 찾아서, 그 내용을 복사해서 새로 파일을 만드는 방법. 이건 딱히 위험할 게 없어보이는데, 파일이 여러 개라서 꽤 귀찮을 듯 하다.
    • 저 트리를 통채로 반영하는 방법이 있는데, 이게 행여 잘못되면 저장소의 상태가 더 악화되지 않을까 걱정이 된다.
    • 일단 현재 저장소 전체를 복사해서 백업해 두고, 과감하게 두번째 방법을 시도해보았다.

 

  • git-read-tree 로 트리 개체의 내용을 통채로 index에 반영한다.

$ git read-tree b5bf $ git status # On branch master # Changes to be committed: ... # new file: 20120526_montecarlo/sphere.pl ... # Changes not staged for commit: ... # deleted: 20120526_montecarlo/sphere.pl ...

    • 이건 좀 묘하다. ls 해보면 아직 작업 디렉토리는 복구되지 않았고, git-status 로 확인하면 복구하려고 했던 파일들이 저렇게 두 군데에 나온다. 즉 인덱스에는 반영이 되어서 커밋되길 기다리고 있고, 막상 작업 디렉토리에서는 지워져 있는데 이건 인덱스에 반영이 안 되었다는 얘기
    • 이 상태에서 계속 진행하려면, 일단 커밋을 해서 인덱스의 내용을 커밋으로 반영하고, 그걸 다시 reset --hard 로 작업 디렉토리에 반영하면 된다.
    • 또는 git-checkout-index 를 써서 인덱스의 내용을 작업 디렉토리로 체크아웃시킬 수도 있다2.

 

  • 위와 같이 하는 대신, git-read-tree 에서 옵션을 주어서 작업 디렉토리에 반영하는 것을 한번에 할 수도 있다.

$ git read-tree -u -m b5bf $ git status # On branch master # Changes to be committed: ... # new file: 20120526_montecarlo/sphere.pl ... $ ls ... 20120526_montecarlo <-- 살아났다 ...

    • -m은 트리 개체의 내용을 인덱스에 머지한다. (그런데 -m을 쓰지 않았을 때와 뭐가 다른지 정확히 모르겠음. 인덱스와 트리 개체에 동일한 파일이 서로 다른 내용으로 존재할 경우 영향을 미칠 듯 하기도)
    • -u는 머지 후의 결과를 작업 디렉토리에 반영한다.
    • 이제 실제 디렉토리에도 파일들이 살아났다
    • 이제 이 상태에서 커밋하면 완료
Comments