본문 바로가기
TYLER SONGBlog
← Blog index
CS학습4 min read

Reset: HEAD와 브랜치를 과거 커밋으로 되돌리는 세 가지 모드

`git reset`은 현재 브랜치가 가리키는 커밋을 지정한 커밋으로 옮기는 명령이다. `--soft`, `--mixed`, `--hard` 세 모드는 커밋 이력, 스테이징 영역(Index), 작업 디렉터리(Working Directory) 중 어디까지 되돌릴지가 다르다. 로컬 커밋을 정리할 때 유용하지만, 이미 공유한 이력에 쓰면 협업에 문제가 생긴다.

By 송민성

1. 개념

git reset은 현재 브랜치가 가리키는 커밋을 다른 커밋으로 옮기는 명령이다. 브랜치 포인터(HEAD가 가리키는 브랜치)를 이동시키고, 모드에 따라 스테이징 영역(Index)과 작업 디렉터리(Working Directory)까지 그 커밋의 상태로 맞춘다.

Git에는 파일 상태를 담는 세 영역이 있다.

영역설명
커밋 이력(HEAD)저장소에 기록된 마지막 커밋
스테이징 영역(Index)다음 커밋에 들어갈 내용
작업 디렉터리(Working Directory)실제 파일이 놓인 디렉터리

reset의 세 모드는 이 세 영역 중 어디까지 되돌리는지에 따라 나뉜다.

2. 왜 사용하는가

  • 잘못 만든 커밋을 취소하고 다시 커밋하고 싶을 때
  • 여러 개의 작은 커밋을 하나로 합치고 싶을 때(--soft)
  • git add로 올린 파일을 스테이징에서 내리고 싶을 때(--mixed)
  • 작업 중인 변경을 모두 버리고 특정 커밋 상태로 돌아가고 싶을 때(--hard)

3. 동작 원리

git reset <커밋>은 최대 세 단계로 동작한다.

  1. HEAD가 가리키는 브랜치를 `<커밋>`으로 이동한다. (모든 모드 공통)
  2. Index를 `<커밋>`의 스냅샷으로 갱신한다. (--mixed, --hard)
  3. 작업 디렉터리를 `<커밋>`의 스냅샷으로 갱신한다. (--hard만)
모드HEAD(브랜치) 이동Index 갱신작업 디렉터리 갱신
--softOXX
--mixed (기본값)OOX
--hardOOO

커밋 객체는 reset으로 즉시 삭제되지 않는다. 브랜치가 더 이상 가리키지 않을 뿐이며, git reflog로 이전 위치를 찾아 복구할 수 있다. 참조되지 않는 객체는 나중에 가비지 컬렉션(GC)으로 정리된다.

4. 코드 예제

연습용 저장소를 만들어 세 모드의 차이를 확인한다.

shell
mkdir reset-demo && cd reset-demo git init git config user.name "demo" git config user.email "demo@example.com" echo "one" > a.txt && git add a.txt && git commit -m "C1: one" echo "two" >> a.txt && git commit -am "C2: two" echo "three" >> a.txt && git commit -am "C3: three" git log --oneline

`--soft`: 커밋만 취소하고 변경은 스테이징에 남긴다.

shell
git reset --soft HEAD~1 git status --short # M a.txt (스테이징된 상태) git log --oneline # C1, C2만 남음

`--mixed`: 커밋과 스테이징을 취소하고 변경은 작업 디렉터리에 남긴다.

shell
git commit -m "C3: three" # 다시 커밋 git reset --mixed HEAD~1 # 기본값이라 --mixed 생략 가능 git status --short # M a.txt (스테이징 안 된 상태)

`--hard`: 변경까지 모두 버린다.

shell
git commit -am "C3: three" git reset --hard HEAD~1 cat a.txt # one, two 만 남음 git status --short # 출력 없음

reset 취소하기: reflog로 복구

shell
git reflog # HEAD의 이동 기록 확인 git reset --hard HEAD@{1} # reset 직전 위치로 복귀 cat a.txt # one, two, three

특정 파일만 스테이징에서 내리기

shell
echo "four" >> a.txt git add a.txt git reset HEAD a.txt # 또는 git restore --staged a.txt git status --short # M a.txt

5. 시간 복잡도 또는 성능 특성

reset의 비용은 커밋 이력의 길이가 아니라 되돌릴 때 달라지는 파일의 수에 영향을 받는다.

  • --soft: 브랜치 포인터(ref 파일)만 갱신하므로 파일 수와 상관없이 매우 빠르다.
  • --mixed, --hard: Index와 작업 디렉터리를 대상 커밋과 비교해 갱신하므로, 변경된 파일이 많을수록 시간이 늘어난다.

정확한 소요 시간은 저장소 크기와 파일시스템에 따라 달라지므로 일반적인 수치로 단정할 수 없다.

6. 실무 사용 사례

  • 커밋 합치기(squash): 푸시 전 작은 커밋 3개를 git reset --soft HEAD~3으로 되돌린 뒤 하나로 다시 커밋한다.
  • 실수로 add한 파일 제거: 비밀 키나 빌드 산출물을 git add했다면 git reset HEAD <파일>로 스테이징에서 내린다.
  • 로컬 브랜치 초기화: 원격 브랜치와 동일한 상태로 맞추려면 git fetch 후 git reset --hard origin/main을 사용한다.
  • 커밋 메시지·내용 수정: 직전 커밋이 마음에 들지 않으면 git reset --soft HEAD~1로 되돌려 다시 커밋한다. 직전 커밋 한 개만 고친다면 git commit --amend가 더 간단하다.

7. 주의할 점

  • `--hard`는 커밋하지 않은 변경을 복구할 수 없게 버린다. 추적 중인 파일의 작업 내용은 reflog에도 남지 않는다. 실행 전에 git status와 git stash로 확인한다.
  • 이미 푸시한 커밋에는 reset을 쓰지 않는다. 이력을 바꾸면 다른 사람의 저장소와 어긋나고, 반영하려면 강제 푸시(force push)가 필요하다. 공유된 이력은 git revert로 되돌리는 커밋을 새로 추가하는 방식이 안전하다.
  • `HEAD~1`과 `HEAD^`의 의미: 직전 부모 커밋을 가리키는 표현이다. HEAD~3은 세 단계 이전 커밋이다.
  • 브랜치 상태 확인: detached HEAD 상태에서 reset하면 브랜치가 아닌 HEAD만 이동한다. 어느 브랜치에 있는지 git branch로 먼저 확인한다.
  • reflog는 영구 보관소가 아니다. 기본 보관 기간이 지나면 정리되므로 reset 실수는 되도록 빨리 복구한다.

8. 핵심 정리

  • git reset은 현재 브랜치가 가리키는 커밋을 옮기는 명령이다.
  • --soft는 HEAD만, --mixed(기본값)는 HEAD와 Index를, --hard는 HEAD, Index, 작업 디렉터리 모두를 되돌린다.
  • reset 후에도 이전 커밋은 git reflog로 찾아 복구할 수 있다.
  • --hard로 버린 커밋되지 않은 변경은 복구할 수 없다.
  • 공유된 이력에는 reset 대신 revert를 사용한다.