git reset 사용법
Git으로 작업하다 보면 방금 한 커밋을 취소하고 싶거나, 스테이징한 파일을 다시 내리고 싶을 때가 있죠.
이럴 때 가장 먼저 떠오르는 명령어가 바로 git reset입니다.
git reset은 현재 브랜치의 HEAD 위치를 옮기면서 커밋 히스토리와 스테이징 영역, 작업 디렉토리의 상태를 조절할 수 있는 강력한 명령어인데요.
그만큼 옵션에 따라 동작이 크게 달라지기 때문에 제대로 이해하고 써야 합니다.
이번 글에서는 git reset의 세 가지 모드를 중심으로 기본 개념부터 실전 활용법까지 함께 알아보겠습니다.
HEAD가 무엇인지 아직 익숙하지 않다면 Git에서 HEAD란 무엇인가?를 먼저 읽어보시면 좋겠습니다.
Git의 세 가지 영역
git reset의 동작을 이해하려면 먼저 Git이 파일을 관리하는 세 가지 영역을 알아야 합니다.
- 작업 디렉토리(Working Directory) — 실제로 파일을 편집하는 공간입니다. 에디터에서 코드를 수정하면 이 영역이 변경되죠.
- 스테이징 영역(Staging Area) — git add로 다음 커밋에 포함할 변경사항을 모아두는 공간입니다. 인덱스(Index)라고도 부릅니다.
- 커밋 히스토리(Repository) —
git commit으로 확정된 스냅샷들이 저장되는 공간입니다.HEAD가 가장 최근 커밋을 가리키고 있죠.
git reset은 이 세 가지 영역 중 어디까지 되돌릴지를 옵션으로 조절합니다.
—soft 모드
--soft 옵션을 사용하면 HEAD만 지정한 커밋으로 이동하고, 스테이징 영역과 작업 디렉토리는 그대로 유지됩니다.
$ git reset --soft <커밋>
예를 들어 방금 한 커밋을 취소하되 변경사항은 스테이징 영역에 남겨두고 싶다면 이렇게 하면 됩니다.
$ git log --oneline -3
a1b2c3d (HEAD -> main) 세 번째 커밋
b2c3d4e 두 번째 커밋
c3d4e5f 첫 번째 커밋
$ git reset --soft HEAD~1
$ git log --oneline -2
b2c3d4e (HEAD -> main) 두 번째 커밋
c3d4e5f 첫 번째 커밋
커밋은 사라졌지만 변경사항이 스테이징 영역에 그대로 있기 때문에 git status를 실행하면 파일이 커밋 대기 상태로 남아있는 것을 확인할 수 있습니다.
$ git status
On branch main
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: src/app.js
커밋 메시지를 수정하고 싶거나 직전 커밋의 내용을 살짝 바꿔서 다시 커밋하고 싶을 때 유용하죠.
—mixed 모드 (기본값)
아무 옵션도 지정하지 않으면 git reset은 기본적으로 --mixed 모드로 동작합니다.
HEAD를 지정한 커밋으로 이동하면서 스테이징 영역도 함께 되돌리지만, 작업 디렉토리는 건드리지 않습니다.
$ git reset <커밋>
# 위 명령어는 아래와 동일합니다
$ git reset --mixed <커밋>
마찬가지로 직전 커밋을 취소하는 예시를 볼까요?
$ git log --oneline -3
a1b2c3d (HEAD -> main) 세 번째 커밋
b2c3d4e 두 번째 커밋
c3d4e5f 첫 번째 커밋
$ git reset HEAD~1
$ git log --oneline -2
b2c3d4e (HEAD -> main) 두 번째 커밋
c3d4e5f 첫 번째 커밋
여기까지는 --soft와 같은데요.
차이점은 스테이징 영역도 초기화된다는 것입니다.
$ git status
On branch main
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: src/app.js
변경사항이 “Changes not staged for commit”으로 표시되는 것을 볼 수 있죠. 파일 내용은 그대로지만 스테이징에서 내려온 상태입니다. 커밋을 되돌린 뒤 어떤 파일을 커밋에 포함할지 다시 선별하고 싶을 때 적합합니다.
—hard 모드
--hard 옵션을 사용하면 HEAD, 스테이징 영역, 작업 디렉토리를 모두 지정한 커밋 상태로 되돌립니다.
$ git reset --hard <커밋>
직전 커밋을 완전히 없었던 일로 만들고 싶다면 이렇게 합니다.
$ git reset --hard HEAD~1
HEAD is now at b2c3d4e 두 번째 커밋
이번에는 git status가 깨끗합니다.
$ git status
On branch main
nothing to commit, working tree clean
변경사항이 작업 디렉토리에서까지 완전히 사라졌기 때문입니다.
⚠️ 주의: --hard로 삭제된 변경사항은 커밋되지 않은 상태라면 복구하기가 매우 어렵습니다.
정말로 되돌릴 건지 한 번 더 확인하는 습관을 들이세요.
세 가지 모드 비교
세 모드의 차이를 표로 정리하면 다음과 같습니다.
| 모드 | HEAD 이동 | 스테이징 초기화 | 작업 디렉토리 초기화 |
|---|---|---|---|
--soft | ✅ | ❌ | ❌ |
--mixed (기본값) | ✅ | ✅ | ❌ |
--hard | ✅ | ✅ | ✅ |
아래쪽으로 갈수록 더 많은 것을 되돌린다고 기억하면 쉽습니다.
확신이 없을 때는 --soft부터 시작해서 필요에 따라 단계를 높이는 게 안전하겠죠.
스테이징 취소
커밋을 되돌리는 것 외에도 git reset은 스테이징을 취소하는 데 자주 쓰였습니다.
$ git add src/app.js
$ git status
On branch main
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: src/app.js
$ git reset HEAD src/app.js
Unstaged changes after reset:
M src/app.js
git reset HEAD <파일>을 실행하면 해당 파일이 스테이징 영역에서 내려오면서 작업 디렉토리에는 변경사항이 그대로 남습니다.
모든 파일의 스테이징을 한꺼번에 취소하려면 파일명 없이 실행하면 됩니다.
$ git reset HEAD
다만 Git 2.23 이후에는 같은 작업을 git restore —staged로 할 수 있어서 스테이징 취소 목적이라면 git restore가 더 직관적입니다.
# 모던 방식 (Git 2.23+)
$ git restore --staged src/app.js
# 레거시 방식
$ git reset HEAD src/app.js
git status를 실행하면 Git이 스테이징 취소 방법으로 git restore --staged를 안내하고 있는 것을 볼 수 있을 거예요.
여러 커밋 한 번에 되돌리기
git reset에 HEAD~1뿐 아니라 HEAD~n을 전달하면 여러 커밋을 한 번에 되돌릴 수 있습니다.
$ git log --oneline -5
e5f6g7h (HEAD -> main) 다섯 번째 커밋
d4e5f6g 네 번째 커밋
c3d4e5f 세 번째 커밋
b2c3d4e 두 번째 커밋
a1b2c3d 첫 번째 커밋
$ git reset --soft HEAD~3
$ git log --oneline -2
b2c3d4e (HEAD -> main) 두 번째 커밋
a1b2c3d 첫 번째 커밋
최근 3개 커밋이 한꺼번에 취소되었고, --soft를 사용했으므로 변경사항은 스테이징 영역에 모여있습니다.
이 상태에서 git commit을 실행하면 3개 커밋을 하나로 합치는(squash) 효과를 낼 수 있죠.
$ git commit -m "세 번째~다섯 번째 작업을 하나로 합침"
물론 특정 커밋 해시를 직접 지정해도 동일하게 동작합니다.
$ git reset --soft b2c3d4e
실전 활용: 커밋 메시지 수정
방금 커밋했는데 메시지에 오타가 있거나 내용을 보강하고 싶을 때가 있죠.
가장 간편한 방법은 git commit --amend이지만, git reset --soft를 활용할 수도 있습니다.
# 직전 커밋 취소 (변경사항은 스테이징에 유지)
$ git reset --soft HEAD~1
# 새로운 메시지로 다시 커밋
$ git commit -m "수정된 커밋 메시지"
--amend와 달리 커밋 내용을 추가하거나 빼기도 편해서 커밋 자체를 재구성하고 싶을 때 쓸 만하죠.
실전 활용: 잘못된 브랜치에 커밋한 경우
main 브랜치에서 작업해야 할 내용을 실수로 feature 브랜치에 커밋해버린 경우가 있죠.
이럴 때 git reset으로 깔끔하게 정리할 수 있습니다.
# feature 브랜치에 잘못 커밋된 상태
$ git log --oneline -2
a1b2c3d (HEAD -> feature) 잘못된 커밋
b2c3d4e 이전 커밋
# 커밋을 취소하면서 변경사항은 작업 디렉토리에 유지
$ git reset HEAD~1
# 올바른 브랜치로 전환
$ git switch main
# 변경사항을 올바른 브랜치에서 커밋
$ git add .
$ git commit -m "올바른 브랜치에 커밋"
브랜치 전환 시 커밋하지 않은 변경사항이 충돌하는 경우에는 git stash를 먼저 사용한 후 브랜치를 전환하면 됩니다.
git reset과 git revert 비교
git reset과 git revert는 둘 다 커밋을 되돌리는 명령어이지만 방식이 다릅니다.
git reset은 커밋 히스토리에서 커밋 자체를 제거합니다.
마치 커밋이 처음부터 없었던 것처럼 히스토리가 깔끔해지죠.
# 직전 커밋 제거 (히스토리에서 사라짐)
$ git reset HEAD~1
반면 git revert는 되돌리는 내용을 담은 새로운 커밋을 생성합니다.
원래 커밋은 히스토리에 그대로 남아있고, 그 변경을 취소하는 커밋이 추가되는 방식이죠.
# 직전 커밋을 되돌리는 새 커밋 생성 (원본 커밋은 히스토리에 유지)
$ git revert HEAD
| 기준 | git reset | git revert |
|---|---|---|
| 히스토리 변경 | 커밋 제거 (히스토리 재작성) | 새 커밋 추가 (히스토리 보존) |
| 협업 안전성 | 이미 push한 커밋에 사용하면 위험 | push한 커밋에도 안전 |
| 주 사용처 | 로컬에서 아직 push하지 않은 커밋 정리 | 이미 공유된 커밋의 변경 취소 |
핵심은 아직 push하지 않은 커밋은 git reset으로 깔끔하게 정리하고, 이미 원격 저장소에 push한 커밋은 git revert로 되돌리는 것입니다.
이미 push한 커밋을 git reset으로 제거하면 다른 팀원의 히스토리와 충돌이 생겨서 큰 혼란이 생길 수 있으니 주의하세요.
git reflog로 실수 복구
git reset --hard를 실행했는데 되돌리고 싶어졌다면 어떻게 해야 할까요?
다행히 Git은 HEAD가 이동한 모든 기록을 reflog에 남겨두기 때문에 복구가 가능합니다.
$ git reflog
b2c3d4e (HEAD -> main) HEAD@{0}: reset: moving to HEAD~1
a1b2c3d HEAD@{1}: commit: 세 번째 커밋
b2c3d4e HEAD@{2}: commit: 두 번째 커밋
여기서 a1b2c3d가 reset 전의 커밋이라는 것을 확인할 수 있죠.
이 커밋으로 다시 돌아가면 됩니다.
$ git reset --hard a1b2c3d
HEAD is now at a1b2c3d 세 번째 커밋
reflog는 보통 90일간 보관되니까 실수를 발견한 직후라면 대부분 복구할 수 있습니다.
다만 커밋되지 않은 작업 디렉토리의 변경사항은 reflog로도 살릴 수 없으니 --hard를 사용할 때는 항상 조심해야 합니다.
마치며
git reset은 커밋을 되돌리는 데 가장 많이 쓰이는 명령어입니다.
핵심 모드를 정리하면 다음과 같습니다.
git reset --soft: HEAD만 이동, 변경사항은 스테이징에 유지git reset --mixed(기본값): HEAD와 스테이징을 이동, 변경사항은 작업 디렉토리에 유지git reset --hard: HEAD, 스테이징, 작업 디렉토리를 모두 초기화
평소에는 --soft나 --mixed를 사용하고, --hard는 정말 필요할 때만 쓰는 게 안전합니다.
그리고 이미 push한 커밋을 되돌릴 때는 git reset 대신 git revert를 사용하는 것을 잊지 마세요.
더 자세한 내용은 Git 공식 문서를 참고하세요.
This work is licensed under
CC BY 4.0