커밋을 잘못했을 때 되돌리는 법을 검색하면 resetrevert가 같이 나와요. 이름도 비슷해서 아무거나 썼다가 협업 브랜치를 망가뜨린 사람을 여럿 봤어요. 저도 그중 하나였고요. 이 둘은 되돌린다는 목적은 같지만 방식이 완전히 달라요. 오늘은 실제 명령으로 그 차이를 눈으로 확인해볼게요.

78.1 커밋을 되돌리는 방법이 왜 두 개나 되나요?

핵심 차이는 히스토리를 지우느냐, 남기느냐예요. reset은 커밋을 없던 일로 만들어요. 브랜치를 과거 지점으로 되감아서, 그 뒤 커밋들이 히스토리에서 사라지죠. revert는 반대로, 그 커밋의 변경을 취소하는 새 커밋을 하나 더 쌓아요. 되돌렸다는 기록까지 남기는 거예요.


그래서 아직 나만 가진 로컬 커밋을 정리할 땐 reset이 편하고, 이미 남과 공유한 커밋을 되돌릴 땐 revert가 안전해요. 왜 그런지는 명령을 돌려보면서 하나씩 볼게요.

78.2 reset의 세 가지 모드가 뭔가요?

reset에는 --soft, --mixed, --hard 세 모드가 있어요. 커밋 세 개(A, B, C)가 쌓인 상태에서 C를 되돌린다고 해볼게요. 셋의 차이는 되돌린 변경을 어디까지 남기느냐예요.


--soft는 커밋만 취소하고 변경 내용은 staged 상태로 남겨요. 커밋 메시지만 다시 쓰고 싶을 때 좋아요.


$ git reset --soft HEAD~1
$ git status --short
A c.txt


--mixed는 기본값이에요. 커밋을 취소하고 변경을 staged에서도 내려 작업 폴더에만 남겨요. 위 status에서 A(staged)였던 게 ??(추적 안 됨)로 바뀐 걸 보세요.


$ git reset --mixed HEAD~1
$ git status --short
?? c.txt


--hard는 제일 강력하고 제일 위험해요. 커밋도, staged도, 작업 폴더의 파일까지 전부 되돌려요. 방금 만든 c.txt 파일 자체가 사라지죠.


$ git reset --hard HEAD~1
HEAD is now at 3c60bd1 B: 기능 추가
$ ls
a.txt b.txt


한 줄로 외워요. soft는 커밋만, mixed는 커밋과 staged, hard는 파일까지 되돌린다. --hard는 저장 안 한 변경을 진짜로 날리니까 실행 전에 늘 한 박자 멈추세요.

78.3 revert는 뭐가 다른가요?

이번엔 커밋 세 개(홈페이지, 결제 기능, 이벤트 배너)가 있고, 가운데 결제 기능만 되돌리고 싶다고 해볼게요. reset으로 되감으면 그 뒤 이벤트 배너까지 딸려 사라져요. 이럴 때 revert가 딱이에요.


$ git revert HEAD~1
[main 8d6bcef] Revert "feat: 결제 기능 추가"
1 file changed, 1 deletion(-)
delete mode 100644 pay.js


히스토리를 보면 아무것도 사라지지 않았어요. 결제 기능 커밋은 그대로 남아 있고, 그걸 취소하는 새 커밋이 맨 위에 하나 얹혔죠. 이벤트 배너도 무사하고요.


$ git log --oneline
8d6bcef Revert "feat: 결제 기능 추가"
724aea3 feat: 이벤트 배너
10fece9 feat: 결제 기능 추가
cddf841 feat: 홈페이지 작성


결과적으로 pay.js 파일은 지워졌지만, 어떻게 지웠는지가 기록으로 남아요. 나중에 결제 기능을 다시 살릴 때도 이 revert 커밋만 또 revert하면 되니까 추적이 깔끔해요.

78.4 이미 push한 건 뭘로 되돌리나요?

여기가 사람들이 제일 많이 사고 치는 지점이에요. reset은 히스토리를 지우기 때문에, 이미 원격에 올려 남과 공유한 커밋에 쓰면 내 히스토리와 원격 히스토리가 어긋나요. 강제 push로 밀어붙이면 동료 작업이 꼬이고요. 리베이스로 겪는 사고랑 원인이 똑같아요.


그래서 규칙은 이래요. 아직 push 안 한 로컬 커밋은 reset으로 편하게 정리하고, 이미 push해서 공유된 커밋은 revert로 되돌려요. revert는 새 커밋을 쌓을 뿐 기존 히스토리를 안 건드리니, 그냥 평범하게 push하면 끝이에요. 강제 push가 필요 없죠. 팀 브랜치에서 뭔가 되돌려야 하면 거의 항상 revert가 정답이에요.

78.5 파일 하나만 되돌리려면요?

지금까지는 커밋을 되돌리는 이야기였어요. 그런데 아직 커밋도 안 한, 그냥 편집하다 망친 파일 하나를 원래대로 돌리고 싶을 때가 더 많죠. 그럴 땐 git restore가 딱이에요. 마지막 커밋 상태로 그 파일만 되돌려줘요.


$ git status --short
M note.txt
$ git restore note.txt


실행하면 note.txt의 편집이 싹 사라지고 마지막 커밋 내용으로 돌아와요. 주의할 건 이 변경도 저장 안 한 걸 버리는 거라 되살리기 어려워요. 정말 버릴 게 맞는지 한 번 보고 치세요.


실수로 git add까지 해서 staged가 됐다면, 커밋 없이 스테이지에서만 내리고 싶을 때가 있죠. 그땐 git restore --staged예요. 파일 내용은 그대로 두고 staged 표시만 풀어줘요.


$ git restore --staged staged.txt


정리하면 커밋을 되돌릴 땐 reset과 revert, 커밋 전 파일을 되돌릴 땐 restore예요. 옛날 자료엔 이 자리에 git checkout이 나오기도 하는데, 헷갈려서 요즘은 restore로 갈라놨어요. 파일 되돌리기는 restore, 이렇게 외우면 편해요.

78.6 실수로 reset --hard 했어요, 복구되나요?

가슴 철렁한 순간이죠. 그런데 대부분 살릴 수 있어요. Git은 reflog(reference log)라는 곳에 브랜치가 거쳐 온 모든 지점을 몰래 적어둬요. reset으로 사라진 커밋도 여기엔 남아 있어요.


$ git reflog
3c60bd1 HEAD@{0}: reset: moving to HEAD~1
2cf6fca HEAD@{1}: commit: C: 실수한 커밋
3c60bd1 HEAD@{2}: reset: moving to HEAD~1


보이세요? 방금 --hard로 날린 C: 실수한 커밋의 해시 2cf6fca가 그대로 있어요. 이 해시로 되감으면 커밋이 살아 돌아와요.


$ git reset --hard 2cf6fca
HEAD is now at 2cf6fca C: 실수한 커밋


다시 로그를 보면 C가 멀쩡히 돌아와 있어요. 그러니 --hard로 뭘 날렸다고 바로 절망하지 마세요. 먼저 git reflog부터 열어보는 거예요. 물론 처음부터 커밋을 안 지웠으면 이 고생도 없었겠죠.

78.7 되돌리기, 이것만 기억해요

정리할게요. 첫째, reset은 히스토리를 되감고 revert는 되돌리는 새 커밋을 쌓아요. 둘째, reset의 soft는 커밋만, mixed는 staged까지, hard는 파일까지 되돌려요. 셋째, 공유 안 한 로컬은 reset, 이미 공유한 커밋은 revert가 안전해요.


넷째, --hard로 날렸어도 git reflog로 되살릴 수 있어요. 이 네 가지만 손에 익으면 커밋을 잘못해도 당황하지 않아요. 되돌리기는 실수를 지우는 기술이 아니라, 실수해도 괜찮게 만들어주는 안전망이에요. 그 안전망을 믿고 오늘도 마음 편히 커밋하세요.