LabHub

Git 실전 · 되돌리기(reset·revert·reflog) · 이론

되돌리기는 두 질문으로 결정된다

LabHub 에서 이어서 보기

한 줄 요약

되돌릴 방법은 두 질문으로 결정된다. 되돌리려는 것이 어디에 있는가(워킹 트리 / 인덱스 / 커밋), 그리고 그 커밋이 이미 다른 사람에게 갔는가.

왜 이게 필요했나

Git 되돌리기 명령이 어렵게 느껴지는 이유는 명령이 많아서가 아니라, 명령을 고르는 기준을 배운 적이 없어서다. 첫 번째 질문이 명령의 종류를 정하고, 두 번째 질문이 이력을 고쳐도 되는지를 정한다.

여기에 큰 안심 하나를 얹어 두자. 커밋된 적이 있는 것은 거의 항상 복구되고, 커밋된 적이 없는 것은 거의 복구되지 않는다. 그래서 위험한 순간은 "커밋을 지울 때"가 아니라 "커밋하지 않은 변경을 덮어쓸 때"다.

어떻게 동작하나

reset 의 세 모드는 되돌리는 범위가 다르다.

| 모드 | 되돌리는 범위 | 잃는 것 | 쓰는 때 |
| --- | --- | --- | --- |
| --soft | 브랜치 참조만 이동 | 없음 | 커밋 여러 개를 하나로 다시 묶을 때 |
| --mixed(기본) | 인덱스까지 | 스테이징 상태 | 스테이징을 다시 고르고 싶을 때 |
| --hard | 인덱스와 워킹 트리까지 | 커밋되지 않은 모든 변경 | 로컬 실험을 통째로 버릴 때 |
| --keep | 인덱스까지, 단 겹치는 로컬 변경이 있으면 중단 | 없음 | 덮어쓰기 전에 거부받고 싶을 때 |

위험도는 명령 이름이 아니라 "이 명령이 커밋되지 않은 변경을 덮어쓰는가"로 매긴다.

커밋이 이미 다른 사람의 저장소에 있다면 그 커밋을 없애는 모든 방법은 다른 사람의 이력을 깨뜨린다. 이력을 고치는 대신 이력을 덧붙이는 것이 답이다. 그래서 revert 를 쓴다. 강제 푸시로 지우면 이미 받아 간 사람들의 로컬은 그대로이고, 그들이 다음에 푸시하면 지웠던 커밋이 되살아난다.

머지 revert 에는 함정이 하나 있다. 머지를 revert 한 뒤 브랜치를 고쳐 다시 머지하면 되돌렸던 변경이 돌아오지 않는다. Git 은 머지를 이력의 도달 가능성으로만 판단하기 때문이다. 정답은 revert 를 revert 하는 것이다.

현장에서 만나는 모습

git reset --hard 로 커밋 두 개를 날린 다음 얼굴이 하얘지는 장면은 흔하다. 대개는 괜찮다. git reflog 를 열어 HEAD@{1} 로 돌아가면 된다. 다만 두 가지를 알아 두어야 한다. 브랜치를 삭제하면 그 브랜치의 참조 로그도 함께 삭제된다(HEAD 의 reflog 에는 남는다). 그리고 기본 만료는 도달 가능 90일, 도달 불가 30일이다.

안전망을 즉시 없애는 명령도 있다. git reflog expire --expire=now --allgit gc --prune=now 다. 이 둘을 "정리"라고 부르며 습관적으로 돌리는 팀은 사고가 날 때 복구 수단이 없다.

정말 마지막 수단은 git fsck --full --unreachable --no-reflogs 로 떠도는 객체를 찾고 git cat-file -p <blob> > 파일 로 꺼내는 것이다. 스테이징만 해 둔 내용도 이미 객체이므로 이 방법으로 살아난다.

다음 실습에서 할 것

같은 저장소를 여러 벌 만들어야 하므로, 먼저 저장소를 찍어 내는 스크립트를 만듭니다. 그다음 amend, reset 세 모드, revert, reflog 복구를 각각 별도의 저장소에서 실행해 결과 차이를 눈으로 비교하고, 마지막에 상황별 치트시트를 정리합니다.