1. 커밋까지 한 상태에서 git-reset --hard 옵션으로 덮어써 버린 경우
커밋 개체들이 아직 .git 디렉토리 아래에 남아 있기 때문에 복구가 그다지 어렵지 않다 (시간이 흐르면 - 며칠? 한달? 정확히 모르겠으나 아주 짧진 않다 - 가비지 컬렉션에 의해 없어지니 주의)
- git-reflog 로 HEAD가 가리켰었던 커밋 객체들의 목록을 볼 수도 있고
- 다음 섹션에 언급할 git-fsck 를 써서 찾을 수도 있고
- 등등.
어떻게든 해당 커밋 개체를 찾았으면, 그 개체를 다시 현재 브랜치에 연결하고 다시 git-reset 으로 브랜치 포인터를 이동시켜서 해결할 수 있겠다. 아래 링크들 참고.
2. 인덱스까지만 만든 상태에서 git-reset --hard 옵션으로 덮어써 버린 경우
주인장이 실제로 겪은 사례.
(저장소 만들고)
$ 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 디렉토리와 그 내용들이 사라져 버렸다.
지워진 디렉토리가 사실 썩 중요한 것도 아니었고, 커밋을 하지 않은 상태라서 살리는 게 녹록치 않을 것 같아 포기하려 했는데, 트위터에 날렸다고 한마디 했더니 git-fsck를 써 보라는 리플을 받고 문서 좀 뒤지면서 시도해 봄
- 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 를 써서 인덱스의 내용을 작업 디렉토리로 체크아웃시킬 수도 있다.
- 위와 같이 하는 대신, 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는 머지 후의 결과를 작업 디렉토리에 반영한다.
- 이제 실제 디렉토리에도 파일들이 살아났다
- 이제 이 상태에서 커밋하면 완료
4. Comments
컴퓨터분류