Git 되돌리기 | '실수한 커밋 취소하고 싶어요' reset·revert·rebase 차이

Git 되돌리기 | '실수한 커밋 취소하고 싶어요' reset·revert·rebase 차이

이 글의 핵심

reset·revert·restore·checkout 차이, interactive rebase(squash·fixup·reword), 충돌 해결, force push·reflog 복구, merge vs rebase 팀 규칙까지 실무 중심으로 정리했습니다.

본문은 시리즈 게시글 Git 실전 가이드 #4: 되돌리기·rebase·정리와 동일합니다. 영문 번역은 git reset vs revert (English)를 참고하세요.

[Git 실전 가이드 #4] 되돌리기·rebase·정리

이전 글: Git 원격 저장소와 협업(#3)에서 push·pull·PR을 다뤘습니다.

커밋은 프로젝트의 스냅샷이라고 보시면 됩니다. 잘못 찍은 스냅샷을 고치거나, 여러 스냅샷 줄을 한 줄로 정리할 때 쓰는 대표 명령이 reset(HEAD를 옮겨 이력·스테이징·작업 디렉터리를 바꾸는 것), revert(되돌리는 내용을 담은 새 커밋을 추가해 이력을 유지하는 것), rebase(커밋을 다른 베이스 위에 다시 적용해 이력을 정리하는 것)입니다. 마지막 커밋만 취소하거나, 공유된 브랜치에서 안전하게 되돌리는 방법까지 각각의 주의점과 함께 정리했습니다.

이 글을 읽으면:

  • git reset --soft, --mixed, --hard 차이와 git restore·git switch와의 역할 분리를 이해할 수 있습니다.
  • git revertgit reset 선택 기준, push 후 되돌리기 패턴을 알 수 있습니다.
  • git rebase 원리·interactive rebase(squash, fixup, reword, edit)·rebase 충돌 해결을 다룰 수 있습니다.
  • git reset --hard·git push --force 위험과 reflog 복구, merge vs rebase·팀 규칙을 정리할 수 있습니다.

1. 되돌리기 개요

목적명령특징
”방금 커밋”만 취소하고 수정 이어가기git reset --soft HEAD~1커밋만 취소, 스테이징·작업 디렉터리는 유지
스테이징까지 취소git reset HEAD~1 (또는 —mixed)커밋·스테이징 취소, 파일 수정 내용은 유지
완전히 그 커밋 이전으로 (위험)git reset --hard HEAD~1커밋·스테이징·파일 변경 모두 제거
이미 push한 커밋을 “취소한 것처럼” 보이게git revert <커밋>새 “반대 커밋”을 만들어 이력 유지
브랜치 이력을 한 줄로 정리git rebase main커밋을 main 끝에 다시 붙임 (이력 변경)

이미 원격에 push한 커밋을 없애고 싶을 때는 reset —hard로 되돌리면 다른 사람 이력과 꼬이므로, revert로 “취소하는 커밋”을 새로 만드는 것이 안전합니다. rebase는 이력을 바꾸므로 아직 push하지 않은 로컬 브랜치에서만 쓰는 것을 권장합니다.


2. git reset: 커밋·스테이징 취소

reset —soft

커밋만 취소하고, 스테이징 영역과 작업 디렉터리는 그대로 둡니다:

git reset --soft HEAD~1
# git reset: HEAD 포인터를 이동시키는 명령
# --soft: 커밋만 취소 (스테이징, 작업 디렉터리 유지)
# HEAD~1: 현재 HEAD의 바로 이전 커밋
#
# 동작:
# 1. HEAD를 이전 커밋으로 이동
# 2. 취소된 커밋의 변경사항은 스테이징 영역에 남음
# 3. 작업 디렉터리는 변경 없음
#
# 결과:
# git status → 변경사항이 "Changes to be committed" 상태
# git commit으로 다시 커밋 가능

사용 시나리오:

# 시나리오 1: 커밋 메시지 수정
git commit -m "Fix bug"  # 오타 발견!
git reset --soft HEAD~1
git commit -m "Fix authentication bug"  # 메시지 수정

# 시나리오 2: 파일 추가 후 재커밋
git commit -m "Add feature"
# 아, 파일 하나 빠뜨렸다!
git reset --soft HEAD~1
git add forgotten_file.js
git commit -m "Add feature"  # 모든 파일 포함

# 시나리오 3: 여러 커밋을 하나로 합치기
git reset --soft HEAD~3  # 최근 3개 커밋 취소
git commit -m "Implement user authentication"  # 하나로 합침

HEAD 표기법:

HEAD~1  # 1개 이전 커밋
HEAD~2  # 2개 이전 커밋
HEAD~3  # 3개 이전 커밋

HEAD^   # 1개 이전 커밋 (HEAD~1과 동일)
HEAD^^  # 2개 이전 커밋 (HEAD~2와 동일)

# 특정 커밋 해시로도 가능
git reset --soft abc1234

reset —mixed (기본)

커밋스테이징을 취소하고, 파일 내용 변경만 작업 디렉터리에 남깁니다. “커밋과 스테이징을 취소하고, 일부만 골라서 다시 add하고 싶을 때” 사용합니다.

git reset HEAD~1
# 또는
git reset --mixed HEAD~1

이후 git status하면 변경 파일이 “unstaged”로 보이고, git add로 다시 스테이징한 뒤 git commit할 수 있습니다.

reset —hard

커밋·스테이징·작업 디렉터리를 모두 그 커밋 이전 상태로 되돌립니다. 작업 중이던 미커밋 변경도 사라지므로 사용 전에 필요한 내용은 stash나 다른 브랜치에 백업해 두는 것이 좋습니다.

git reset --hard HEAD~1

이미 push한 브랜치에서 reset —hardforce push하면, 같은 브랜치를 쓰는 다른 사람의 이력과 충돌할 수 있어, 팀 협업 시에는 되도록 피하는 것이 좋습니다.


3. git revert: 되돌리는 커밋 새로 만들기

revert란?

revert는 특정 커밋의 변경을 반대로 적용한 새 커밋을 만듭니다:

git revert HEAD
# git revert: 커밋을 되돌리는 새 커밋 생성
# HEAD: 가장 최근 커밋을 되돌림
#
# 동작:
# 1. HEAD 커밋의 변경사항을 분석
# 2. 그 변경을 반대로 적용 (추가 → 삭제, 삭제 → 추가)
# 3. 새로운 "Revert" 커밋 생성
# 4. 이력은 그대로 유지 (기존 커밋은 남아있음)
#
# 결과:
# A → B → C (HEAD)
#      ↓
# A → B → C → C' (Revert "C", HEAD)
# C'는 C의 변경을 취소하는 내용을 담은 새 커밋

# 특정 커밋 되돌리기
git revert abc1234
# abc1234 커밋의 변경을 취소하는 새 커밋 생성

# 병합 커밋 되돌리기
git revert abc1234 -m 1
# -m 1: 첫 번째 부모를 기준으로 되돌림
# -m 2: 두 번째 부모를 기준으로 되돌림
# 병합 커밋은 부모가 2개이므로 -m 옵션 필수

reset vs revert 비교:

# reset: 이력 변경 (커밋 삭제)
# A → B → C (HEAD)
git reset --hard HEAD~1
# A → B (HEAD)  ← C 커밋이 사라짐
# 위험: 이미 push했다면 다른 사람과 이력 불일치

# revert: 이력 유지 (새 커밋 추가)
# A → B → C (HEAD)
git revert HEAD
# A → B → C → C' (HEAD)  ← C'는 C를 취소하는 커밋
# 안전: 이력이 남아있어 협업에 안전

revert 충돌 처리:

git revert HEAD
# 충돌 발생!

# 1. 충돌 파일 수정
vim conflicted_file.js

# 2. 스테이징
git add conflicted_file.js

# 3. revert 계속
git revert --continue

# 또는 revert 취소
git revert --abort

실전 사용 예시:

# 시나리오: 프로덕션에 버그 있는 커밋이 push됨
git log --oneline
# abc1234 Add buggy feature  ← 이 커밋이 문제
# def5678 Update docs
# 789abcd Initial commit

# 해결: revert로 안전하게 되돌림
git revert abc1234
# "Revert 'Add buggy feature'" 커밋 생성
git push origin main
# 이력은 유지되면서 버그 수정됨

4. git rebase: 이력 한 줄로 정리

rebase의 의미

rebase는 “현재 브랜치의 커밋들을 다른 브랜치(또는 같은 브랜치의 특정 지점) 끝에 다시 붙이는 것”입니다. 결과적으로 이력이 한 줄로 정리되어 보기 쉬워지지만, 기존 커밋 해시가 바뀌므로 이미 push한 브랜치에 rebase하면 협업 시 문제가 됩니다.

사용 예 (feature를 main 최신 위에 올리기)

git checkout feature/login
git rebase main

main이 가리키는 최신 커밋 위에 feature/login의 커밋들이 “다시 적용”됩니다. 충돌이 나면 해당 파일을 수정한 뒤 git addgit rebase —continue를 반복합니다. rebase를 취소하려면 git rebase —abort를 사용합니다.

주의사항

  • 이미 원격에 push한 브랜치를 rebase하면, 그 브랜치를 다시 push할 때 git push —force가 필요하고, 같은 브랜치를 pull하는 다른 사람의 이력이 꼬일 수 있습니다. 따라서 아직 push하지 않은 로컬 브랜치에서만 rebase하는 것이 안전합니다.
  • main/master 같은 공용 브랜치에는 rebase를 하지 않는 것이 원칙입니다.

5. 실무에서 주의할 점

  • push 전: reset, rebase로 로컬에서만 이력을 정리해도 됩니다.
  • push 후: 이미 올린 커밋을 “없던 일로” 하려면 revert로 취소 커밋을 만드는 방식이 안전합니다. force push는 팀과 합의된 경우에만 사용합니다.
  • merge vs rebase: 팀에서 “main에 병합할 때는 항상 merge”로 통일해 두면 이력이 예측 가능하고, rebase는 개인 feature 브랜치에서만 쓰는 식으로 정하면 충돌을 줄일 수 있습니다.

6. 자주 묻는 질문 (FAQ)

Q. reset —hard 한 걸 복구할 수 있나요?

A. reset —hard 직후라면 git reflog로 이전 HEAD 위치를 찾을 수 있습니다. 예: git refloggit reset --hard HEAD@{1}로 그 시점으로 되돌릴 수 있습니다. 한동안 지났거나 reflog가 정리된 경우에는 복구가 어려울 수 있으므로, 중요한 변경은 reset —hard 전에 브랜치stash로 남겨 두는 것이 좋습니다.

Q. revert와 reset의 차이는?

A. reset은 “그 커밋으로 HEAD를 옮겨서” 이력을 바꿉니다(이전 커밋이 사라져 보임). revert는 “그 커밋을 없애는 새 커밋”을 만들어 이력을 지우지 않습니다. 이미 push한 커밋을 취소할 때는 revert를 쓰는 것이 안전합니다.

Q. rebase 중 충돌이 났을 때요?

A. 충돌한 파일을 편집한 뒤 git add <파일> 하고 git rebase —continue를 실행합니다. 계속 충돌이 나면 반복하고, rebase 자체를 포기하려면 git rebase —abort로 rebase 전 상태로 돌아갈 수 있습니다.


마무리

  • reset —soft/—mixed: 커밋(과 스테이징)만 취소, 로컬에서 수정 이어갈 때.
  • reset —hard: 커밋·스테이징·작업 디렉터리 모두 되돌림. push한 브랜치에는 사용 자제.
  • revert: “취소하는 커밋”을 새로 만들어, push한 이력을 안전하게 되돌릴 때.
  • rebase: 이력을 한 줄로 정리. 아직 push 안 한 브랜치에서만 사용 권장.

한 줄 요약: reset·revert·rebase로 커밋을 되돌리고 이력을 정리할 수 있으며, 이미 push한 브랜치에서는 revert가 안전합니다. 다음으로 Git 시리즈 목차에서 다른 주제를 골라 읽어보면 좋습니다.

이전 글: Git 실전 가이드 #3: 원격 저장소와 협업

시리즈 목차: Git 시리즈 전체 목차

reset이 건드리는 세 층 (요약)

flowchart LR
  subgraph layers [한 커밋 기준]
    H[HEAD / 커밋]
    I[스테이징 Index]
    W[작업 디렉터리]
  end
  H --> I --> W

설명: --soft는 HEAD만 이동, --mixed는 스테이징까지 풀고, --hard는 세 층을 모두 베이스 커밋과 맞춥니다.


고급: bisect·worktree·병합 시뮬레이션

git bisect로 회귀 커밋 찾기

히스토리가 길고 “언제부터 깨졌는지”를 모를 때, git bisect start알려진 나쁜 커밋알려진 좋은 커밋을 지정한 뒤, 중간 커밋마다 빌드·테스트를 돌려 좋/나쁨을 표시합니다. 자동화하려면 git bisect run ./scripts/check.sh처럼 0이면 좋음, 0이 아니면 나쁨 스크립트를 붙입니다. 플래키 테스트가 있으면 오판이 나므로, 먼저 테스트 안정화가 선행되어야 합니다.

git worktree로 병렬 작업

rebase·bisect가 진행 중일 때 다른 브랜치에서 급한 핫픽스가 필요하면, 같은 저장소에 두 번째 작업 트리를 추가합니다. git worktree add ../hotfix-main main 형태로 독립 디렉터리에서 커밋·푸시를 진행하면, 원래 트리의 미완료 작업을 건드리지 않습니다.

merge-tree로 충돌 예행 연습

git merge-tree $(git merge-base A B) A B처럼 가상 병합 결과를 볼 수 있어, 실제 merge/cherry-pick 전에 충돌 규모를 가늠합니다. CI에서 대상 브랜치와의 병합 가능성을 미리 검증하는 파이프라인에도 활용됩니다.

운영 팁: reflog와 GC

reflogHEAD 이동 기록이지만, 만료·GC 이후에는 사라질 수 있습니다. 중요한 실험 전 git branch backup/...로 포인터를 남기고, 공유 브랜치에서는 force push 금지 정책을 기본으로 두는 것이 안전합니다.


같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.

  • Git push pull 차이 | 원격 저장소·GitHub 협업·Pull Request 완벽 가이드
  • Git 브랜치와 병합 | “merge conflict 났어요” 충돌 해결 방법 (branch, merge)
  • Git 기초 입문 [#1] — 설치·커밋·브랜치·원격 저장소 한 번에

실전 팁 (Git 되돌리기 맥락)

  • 되돌리기 전 스냅샷: git branch backup/$(date +%Y%m%d) 또는 태그로 포인터를 남깁니다.
  • 공유 브랜치: reset --hard보다 revert 체인이 리뷰·감사 추적에 유리합니다.
  • 대화형 rebase: rebase -i에서 fixup/squash아직 push하지 않은 커밋에 우선 적용합니다.
  • 충돌 반복: 같은 파일을 여러 번 고치면 rerere(reuse recorded resolution)로 패턴을 재사용할 수 있습니다.

실전 체크리스트

reset·revert 선택

  • 이미 원격에 공유된 커밋인가? → 공유면 revert 우선 검토
  • 워킹트리 변경을 보존해야 하는가? → --soft/--mixed/stash 조합 결정
  • 팀에 force push 정책이 있는가? → 정책과 릴리스 노트에 맞출 것

rebase·정리

  • 충돌 해결 후 git rebase --continue 전에 테스트했는가?
  • rerere로 동일 충돌을 줄일 수 있는가?

복구

  • reflog로 이동 전 커밋을 확인했는가?
  • GC 전에 백업 브랜치를 만들었는가?

이 글에서 다루는 키워드 (관련 검색어)

Git, 되돌리기, reset, revert, rebase, git reset, git revert, reflog, restore, interactive rebase, force push, 커밋취소, 이력정리 등으로 검색하시면 이 글이 도움이 됩니다.


관련 글

  • Git 기초 입문 [#1] — 설치·커밋·브랜치·원격 저장소 한 번에
  • Git 브랜치와 병합 | merge conflict·branch
  • Git push pull 차이 | 원격 저장소·GitHub 협업·Pull Request 완벽 가이드
  • Git 실전 가이드 시리즈 목차 | 기초·브랜치·원격·rebase

심화 부록: 구현·운영 관점

이 부록은 앞선 본문에서 다룬 주제(「Git 되돌리기 | ‘실수한 커밋 취소하고 싶어요’ reset·revert·rebase 차이」)를 구현·런타임·운영 관점에서 다시 압축합니다. 도메인별 세부 구현은 글마다 다르지만, 입력 검증 → 핵심 연산 → 부작용(I/O·네트워크·동시성) → 관측의 흐름으로 장애를 나누면 원인 추적이 빨라집니다.

내부 동작과 핵심 메커니즘

flowchart TD
  A[입력·요청·이벤트] --> B[파싱·검증·디코딩]
  B --> C[핵심 연산·상태 전이]
  C --> D[부작용: I/O·네트워크·동시성]
  D --> E[결과·관측·저장]
sequenceDiagram
  participant C as 클라이언트/호출자
  participant B as 경계(런타임·게이트웨이·프로세스)
  participant D as 의존성(API·DB·큐·파일)
  C->>B: 요청/이벤트
  B->>D: 조회·쓰기·RPC
  D-->>B: 지연·부분 실패·재시도 가능
  B-->>C: 응답 또는 오류(코드·상관 ID)
  • 불변 조건(Invariant): 버퍼 경계, 프로토콜 상태, 트랜잭션 격리, FD 상한 등 단계별로 문장으로 적어 두면 디버깅 비용이 줄어듭니다.
  • 결정성: 순수 층과 시간·네트워크·스케줄에 의존하는 층을 분리해야 테스트와 장애 분석이 쉬워집니다.
  • 경계 비용: 직렬화, 인코딩, syscall 횟수, 락 경합, 할당·GC, 캐시 미스를 의심 목록에 둡니다.
  • 백프레셔: 생산자가 소비자보다 빠를 때 버퍼·큐·스트림에서 속도를 줄이는 신호를 어디에 둘지 정의합니다.

프로덕션 운영 패턴

영역운영 관점 질문
관측성요청 단위 상관 ID, 에러율·지연 p95/p99, 의존성 타임아웃·재시도가 대시보드에 보이는가
안전성입력 검증·권한·비밀·감사 로그가 코드 경로마다 일관적인가
신뢰성재시도는 멱등 연산에만 적용되는가, 서킷 브레이커·백오프·DLQ가 있는가
성능캐시·배치 크기·커넥션 풀·인덱스·백프레셔가 데이터 규모에 맞는가
배포롤백 룬북, 카나리/블루그린, 마이그레이션·피처 플래그가 문서화되어 있는가
용량피크 트래픽·디스크·FD·스레드 풀 상한을 주기적으로 검증하는가

스테이징은 데이터 양·네트워크 RTT·동시성을 프로덕션에 가깝게 맞출수록 재현율이 올라갑니다.

확장 예시: 엔드투엔드 미니 시나리오

앞선 본문 주제(「Git 되돌리기 | ‘실수한 커밋 취소하고 싶어요’ reset·revert·rebase 차이」)를 배포·운영 흐름에 맞춰 옮긴 체크리스트입니다. 도메인에 맞게 단계 이름만 바꿔 적용할 수 있습니다.

  1. 입력 계약 고정: 스키마·버전·최대 페이로드·타임아웃·에러 코드를 경계에 둔다.
  2. 핵심 경로 계측: 요청 ID, 단계별 지연, 외부 호출 결과 코드를 로그·메트릭·트레이스에서 한 흐름으로 본다.
  3. 실패 주입: 의존성 타임아웃·5xx·부분 데이터·락 대기를 스테이징에서 재현한다.
  4. 호환·롤백: 설정/마이그레이션/클라이언트 버전을 되돌릴 수 있는지 확인한다.
  5. 부하 후 검증: 피크 대비 p95/p99, 에러율, 리소스 상한, 알림 임계값을 점검한다.
handle(request):
  ctx = newCorrelationId()
  validated = validateSchema(request)
  authorize(validated, ctx)
  result = domainCore(validated)
  persistOrEmit(result, idempotentKey)
  recordMetrics(ctx, latency, outcome)
  return result

문제 해결(Troubleshooting)

증상가능 원인조치
간헐적 실패레이스, 타임아웃, 외부 의존성, DNS최소 재현 스크립트, 분산 트레이스·로그 상관관계, 재시도·서킷 설정 점검
성능 저하N+1, 동기 I/O, 락 경합, 과도한 직렬화, 캐시 미스프로파일러·APM으로 핫스팟 확인 후 한 가지씩 제거
메모리 증가캐시 무제한, 구독/리스너 누수, 대용량 버퍼, 커넥션 미반납상한·TTL·힙/FD 스냅샷 비교
빌드·배포만 실패환경 변수, 권한, 플랫폼 차이, lockfileCI 로그와 로컬 diff, 런타임·이미지 버전 핀
설정 불일치프로필·시크릿·기본값, 리전스키마 검증된 설정 단일 소스와 배포 매트릭스 표준화
데이터 불일치비멱등 재시도, 부분 쓰기, 캐시 무효화 누락멱등 키·아웃박스·트랜잭션 경계 재검토

권장 순서: (1) 최소 재현 (2) 최근 변경 범위 축소 (3) 환경·의존성 차이 (4) 관측으로 가설 검증 (5) 수정 후 회귀·부하 테스트.

배포 전에는 git addgit commitgit pushnpm run deploy 순서를 권장합니다.