git branch -D 로 브랜치를 지웠는데 그 안에 아직 develop 으로 합치지 않은 작업이 있었다. 그리고 지우고 나서 얼마 지나지 않아 그 안의 작업이 다시 필요해졌다.
Table of contents
Open Table of contents
상황 — 지우고 나서 필요해진 작업
git branch -a 목록에는 아무것도 안 나왔고 이름을 가지고 찾을 방법도 없었다. 브랜치를 지웠으니 그 안의 작업도 함께 사라진 것으로 보이는 상태였다.
그런데 브랜치는 커밋을 가리키는 이름일 뿐이므로 이름이 없어져도 커밋 자체는 남는다. 그러면 남은 문제는 이름을 잃은 그 커밋을 무엇으로 찾아내느냐 하나였다.
해결 — 출력에 남은 해시
지울 때 나온 Deleted branch … (was <sha>) 메시지 안에 그 브랜치가 가리키던 커밋의 짧은 해시가 들어 있었다. 그 값으로 git cat-file -t 를 돌려 보니 commit 객체가 지워지지 않고 그대로 살아 있었다.
되돌릴 수 없어 보이는 조작에도 그 시점의 좌표가 출력에 남는 경우가 있고 그것이 유일한 단서가 된다. git branch <이름> <sha> 로 그 해시를 가리키는 브랜치를 다시 만드니 원래 상태로 돌아왔다.
대안 — 출력을 놓쳤을 때의 경로
터미널을 이미 닫아서 그 출력이 남아 있지 않으면 다른 경로가 필요해진다. git reflog 에 checkout: moving from 으로 브랜치를 오간 흔적이 남아 있으므로 거기서 해시를 찾을 수 있었다.
가리키는 것이 없는 커밋을 훑어 주는 git fsck --unreachable 로도 찾을 수 있었다. 세 경로 중 하나로는 대체로 그 해시가 나오므로 순서대로 시도하면 된다.
주의 — 남는 기간과 거부되는 명령
가리키는 것이 없는 커밋이 영원히 남지는 않고 git 공식 문서대로 gc.reflogExpireUnreachable 기본 30일과 gc.pruneExpire 기본 2주가 지나면 git gc 가 정리한다. 바로 알아채면 되살릴 수 있고 한참 지난 뒤에는 그것도 불가능해진다.
삭제에는 두 가지 방법이 있었는데 지금처럼 -D 로 지우고 해시를 따로 적어 두는 것과 합치지 않은 커밋이 있으면 거부하는 -d 만 쓰는 것이다. 뒤쪽을 골랐다. 이유는 적어 두는 절차는 급할 때 빠지지만 거부는 빠뜨릴 수 없어서다. 대신 정말 버릴 브랜치도 git log develop..<이름> 으로 목록을 본 뒤 -D 를 한 번 더 쳐야 하는 것을 감수했고 지운 것과 해시는 파일로 남겼다.
판단 기준 — 되살리는 것과 합치는 것
브랜치를 되살린 뒤에 그 안에 무엇이 들어 있는지를 git log --oneline develop..<이름> 으로 확인했다. 들어 있던 커밋 중 일부는 그 사이에 다른 방식으로 이미 다시 만들어져 있었다.
그래서 전부 합치지 않고 지금 실제로 필요한 커밋만 git cherry-pick 으로 골라서 가져왔다. 되살리는 것과 합치는 것은 다른 판단이고 되살렸다고 그대로 합치면 이미 있는 것과 겹친다.
정리
- 브랜치는 커밋을 가리키는 이름이므로 이름이 없어져도 커밋은 남는다
- 삭제 출력에 그 시점의 해시가 남아 있다
- 되돌릴 수 없어 보이는 조작에도 좌표가 남는 경우가 있다
- 출력을 놓쳤으면
git reflog나git fsck --unreachable로 찾는다 - 가리키는 것이 없는 커밋은 기간이 지나면 정리된다
- 합치지 않은 커밋이 있으면 거부되는
-d를 쓴다 - 지운 대상과 해시를 파일로 남긴다
- 되살리는 것과 합치는 것은 다른 판단이다