포스트

[Git] reset·revert·restore로 잘못된 변경 안전하게 되돌리기

Git restore, reset, revert의 영향 범위와 안전한 선택 기준, checkpoint와 reflog를 활용한 실무 복구 절차를 설명합니다.

working tree·index·commit을 구분해, 공유 전에는 기록을 안전하게 고치고 공유 후에는 이력을 보존하며 복구하는 실무 절차입니다.

앞선 글 브랜치부터 rebase와 PR 병합까지는 커밋을 만들고 원격과 통합하는 흐름을 설명했습니다. 다만 잘못된 파일을 수정·stage·commit하거나 이미 push한 뒤에는 어떤 명령을 골라야 하는지까지는 다루지 않았습니다. 이번 글은 그 다음 단계로, 변경이 어느 영역에 있고 다른 사람이 이미 보았는지를 기준으로 복구 명령을 선택합니다.

대상 환경과 실패 증상

Git 2.23 이상을 쓰는 Windows PowerShell 또는 일반 셸 환경을 가정합니다. 기능 브랜치에서 결제 설정 파일을 잘못 수정했고, 그 변경이 아직 working tree에만 있는지, stage됐는지, 로컬 commit인지, 원격에 push됐는지 불분명한 상황입니다. 이 구분 없이 git reset --hard부터 실행하면 필요한 변경까지 사라질 수 있습니다.

1. 되돌리기 전에 상태부터 고정합니다

복구 명령을 입력하기 전에 현재 브랜치, 수정 파일, staged 내용, 최근 commit을 한 화면에 기록합니다. 아래 확인 명령은 파일이나 이력을 바꾸지 않습니다.

git status --short --branch
git diff
git diff --cached
git log --oneline --decorate -8

기대 관찰값: git status --short의 첫 번째 열은 index, 두 번째 열은 working tree 상태입니다. 예를 들어 M config/checkout.yml은 아직 stage하지 않은 수정이고, M config/checkout.yml은 stage된 수정입니다.

중단 기준: diff에 남겨야 할 코드와 버릴 코드가 섞여 있거나, 현재 브랜치와 push 여부가 확실하지 않으면 아직 복구 명령을 실행하지 않습니다. 필요한 변경은 별도 브랜치의 WIP commit이나 명시적인 stash로 보존한 뒤 진행합니다. 특히 commit되지 않은 변경은 reflog로 되살릴 수 있다고 가정하면 안 됩니다.

2. 세 영역과 세 명령의 역할을 먼저 구분합니다

Git의 현재 상태는 편집 중인 working tree, 다음 commit 후보인 index, 현재 브랜치가 가리키는 HEAD로 나눠 볼 수 있습니다. restore는 파일 또는 index를 복원하고, reset은 브랜치 끝과 선택한 영역을 이동시키며, revert는 기존 commit을 지우지 않고 반대 변경을 담은 새 commit을 만듭니다.

working tree, index, HEAD 세 영역과 restore, reset soft, reset mixed, reset hard가 영향을 주는 범위, 공유된 커밋에는 revert로 새 커밋을 추가하는 원칙을 보여 주는 도식
명령 이름보다 먼저 움직이는 대상을 확인합니다. 공유 전 기록은 checkpoint 뒤 reset, 공유 후 기록은 revert가 기본입니다.
상황기본 선택보존되는 것주요 위험
수정만 했음restoreindex·commit버린 미commit 변경은 복구 어려움
stage만 취소restore --stagedworking tree 내용이후 restore까지 연달아 실행
로컬 commit 수정reset --soft 또는 --mixed선택에 따라 index·파일공유 commit의 이력 재작성
이미 push·공유revert기존 commit과 감사 이력충돌 또는 merge commit의 mainline 선택

3. 수정만 했다면 restore로 파일을 되돌립니다

아직 stage하지 않은 config/checkout.yml만 현재 index 상태로 되돌리려면 파일을 명시합니다. 먼저 diff를 보고, 전체 파일이 아니라 일부 덩어리만 버릴 때는 patch 모드를 사용합니다.

git diff -- config/checkout.yml
git restore -p -- config/checkout.yml
git status --short -- config/checkout.yml

기대 관찰값: patch 모드는 변경 덩어리마다 적용 여부를 묻습니다. 버리기로 선택한 덩어리는 working tree에서 사라지고, 남긴 덩어리는 diff에 계속 보입니다. 파일 전체를 되돌릴 것이 확실하다면 git restore --worktree -- config/checkout.yml을 사용합니다.

롤백 기준: restore 뒤 필요한 변경까지 사라졌다면 추가 정리 명령을 실행하지 말고, IDE local history나 미리 만든 WIP commit·stash를 확인합니다. Git reflog는 브랜치와 HEAD 같은 참조 이동을 기록할 뿐, commit하지 않은 파일 편집의 범용 휴지통이 아닙니다.

4. 잘못 stage했다면 내용은 남기고 index만 되돌립니다

commit 대상에 잘못 넣은 파일은 --staged로 index에서만 뺍니다. 이 단계는 working tree의 편집 내용을 지우지 않습니다.

git diff --cached -- config/checkout.yml
git restore --staged -- config/checkout.yml
git status --short -- config/checkout.yml
git diff -- config/checkout.yml

기대 관찰값: status 표시는 staged 열에서 working tree 열로 이동합니다. 내용은 그대로 남으므로 수정해서 다시 stage하거나, 정말 버려야 할 때만 별도의 working tree restore를 실행합니다.

진단 분기: stage를 취소했는데 diff가 비어 있다면 해당 파일이 HEAD와 같은지, 다른 경로를 보고 있지 않은지 확인합니다. 한 번에 index와 working tree를 모두 복원하는 옵션은 영향 범위가 커지므로 두 단계를 나눠 관찰하는 편이 안전합니다.

5. push 전 로컬 commit은 checkpoint 뒤 reset합니다

방금 만든 로컬 commit에 비밀 파일이나 잘못된 설정이 포함됐지만 아직 누구도 가져가지 않았다면 commit을 다시 만들 수 있습니다. 먼저 대상 commit을 확인하고 현재 위치를 가리키는 backup 브랜치를 만든 뒤 HEAD를 한 칸 옮깁니다.

git show --stat --oneline HEAD
git branch backup/checkout-before-reset
git reset --soft HEAD^
git status --short

기대 관찰값: 최근 commit은 현재 브랜치의 이력에서 빠지지만 그 변경은 index에 staged 상태로 남습니다. 불필요한 파일만 git restore --staged로 빼고 새 commit을 만들 수 있습니다. 문제가 생기면 backup 브랜치가 원래 commit을 계속 가리킵니다.

변경을 stage하지 않은 상태로 다시 검토하려면 mixed reset을 사용합니다. --mixed는 기본 모드지만, 복구 문서에서는 의도를 드러내기 위해 명시하는 편이 좋습니다.

git branch backup/checkout-before-mixed-reset
git reset --mixed HEAD^
git status --short
git diff

hard reset 기준: git reset --hard <commit>은 HEAD·index·working tree를 대상 commit에 맞춥니다. 추적 파일의 미commit 변경을 덮어쓰며 일부 untracked 경로도 영향을 받을 수 있습니다. 정확한 commit SHA, 별도 checkpoint, clean 여부를 모두 확인한 자동화 복구가 아니라면 기본 선택에서 제외합니다.

6. 이미 push했다면 revert로 새 복구 commit을 만듭니다

동료가 이미 가져갔거나 Pull Request에 보인 commit은 reset으로 없애면 공유 이력이 갈라집니다. 이때는 원격 상태를 가져와 대상 SHA를 확인하고, 그 변경의 반대를 새 commit으로 기록합니다.

git fetch origin
git status --short --branch
git log --oneline --decorate -10
git revert --no-edit <bad-commit-sha>
git show --stat --oneline HEAD

기대 관찰값: 기존 bad commit은 이력에 남고, 가장 위에 그 효과를 반대로 적용한 새 revert commit이 생깁니다. 테스트와 diff를 확인한 뒤 현재 브랜치 정책에 따라 push하거나 PR을 갱신합니다.

충돌이 나면 Git이 자동으로 끝내지 않고 중단합니다. 최종 파일을 직접 결정해 stage한 뒤 계속하거나, 복구 방향이 잘못됐으면 revert 시작 전으로 돌아갑니다.

git status
# 충돌 파일을 편집한 뒤
git add config/checkout.yml
git revert --continue

# 전체 revert를 취소하려면
git revert --abort

진단 분기: merge commit을 revert할 때는 어느 부모를 mainline으로 볼지 정하는 -m 선택이 필요합니다. 부모 번호를 추측하지 말고 git show --summary <merge-sha>로 부모와 병합 의도를 확인합니다. 잘못된 mainline revert는 이후 재병합에도 영향을 줄 수 있으므로 리뷰 없이 진행하지 않습니다.

7. reset으로 commit을 잃었다면 reflog에서 먼저 구조합니다

실수로 브랜치 끝을 과거로 옮겼더라도 이전 commit 객체가 즉시 사라지는 것은 아닙니다. reflog에서 reset 직전의 HEAD를 찾고, 원래 브랜치를 다시 움직이기 전에 구조 브랜치를 만들어 고정합니다.

git reflog --date=local -10
git show --stat <old-head-sha>
git branch rescue/checkout-lost-commit <old-head-sha>
git log --oneline --decorate rescue/checkout-lost-commit -3

기대 관찰값: 구조 브랜치가 잃어버린 commit을 가리키고, 그 commit의 파일과 메시지를 다시 검토할 수 있습니다. 필요한 변경은 cherry-pick하거나 두 브랜치 diff를 비교해 복원합니다.

한계: reflog는 해당 로컬 저장소의 참조 이동 기록이며 영구 백업이 아닙니다. 만료·정리될 수 있고, commit하지 않은 working tree 내용 자체를 자동으로 기록하지 않습니다. 확인 전에 git gc나 reflog expire 작업을 실행하지 않습니다.

8. 실제 장애 복구 순서와 종료 조건

  1. 관찰: status, working tree diff, cached diff, log를 저장합니다.
  2. 공유 여부 확인: 로컬 commit인지, 원격 또는 PR에 이미 보인 commit인지 확인합니다.
  3. checkpoint: reset 전에는 원래 HEAD를 가리키는 backup 브랜치를 만듭니다.
  4. 최소 범위 복구: 파일이면 restore, 로컬 commit이면 soft 또는 mixed reset, 공유 commit이면 revert를 선택합니다.
  5. 검증: diff, 테스트, 설정 검증을 실행하고 의도한 변경만 남았는지 확인합니다.
  6. 정리: 새 commit과 push는 검증 뒤에 진행하고, backup 브랜치는 복구가 끝났음을 확인한 뒤 삭제합니다.

완료 기준

의도하지 않은 파일이 diff에 없고, 관련 테스트나 설정 검증이 통과하며, 공유된 이력을 reset이나 강제 push로 다시 쓰지 않았고, 필요할 때 돌아갈 checkpoint가 확인돼야 복구를 끝냅니다.

핵심 정리

  • 파일 수정은 restore, 로컬 branch tip 수정은 reset, 공유된 commit 취소는 revert로 구분합니다.
  • stage 취소와 파일 내용 폐기는 서로 다른 단계입니다. 먼저 index만 되돌리고 diff를 다시 봅니다.
  • reset 전에는 backup 브랜치를 만들어 원래 commit을 가리키게 합니다.
  • hard reset은 기본 복구 명령이 아니라, 대상 SHA와 checkpoint가 확인된 제한적 도구입니다.
  • reflog는 잃어버린 commit 구조에 유용하지만 미commit 파일의 백업은 아닙니다.

공식 참고 자료

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.