테마
되돌리기와 복구
로컬에서 커밋을 정리하고, 실수한 히스토리를 되돌리고, reflog로 복구하는 방법을 한 흐름으로 정리합니다.
HEAD와 세 가지 참조 방식
HEAD는 지금 작업 중인 위치를 가리키는 포인터입니다.
Git에서 과거 상태를 가리키는 방법은 크게 세 가지로 나눠서 보면 이해가 쉽습니다.
1. 상대 참조
| 표현 | 의미 |
|---|---|
HEAD | 현재 위치 |
HEAD~ | 첫 번째 부모로 1단계 이전 |
HEAD~3 | 첫 번째 부모 기준 3단계 이전 |
HEAD^ | 첫 번째 부모 |
HEAD^2 | merge commit일 때 두 번째 부모 |
bash
git switch --detach HEAD~
git switch --detach HEAD~3~와 ^의 차이
단순 직선 히스토리에서는 둘이 비슷하게 보이지만, merge commit에서는 다릅니다. ~는 첫 번째 부모를 따라가고, ^2는 두 번째 부모를 가리킬 수 있습니다.
2. reflog 참조
| 표현 | 의미 |
|---|---|
HEAD@{0} | 가장 최근 HEAD 위치 |
HEAD@{3} | 3번 전 HEAD 위치 |
main@{1} | main 브랜치가 이전에 가리키던 위치 |
HEAD@{yesterday} | 어제 시점의 HEAD |
HEAD@{"1 week ago"} | 1주 전 HEAD |
bash
git show HEAD@{2}
git show HEAD@{yesterday}
git log main@{1}3. 직전 브랜치 참조
| 표현 | 의미 |
|---|---|
@{-1} | 방금 전 체크아웃했던 브랜치 |
@{-2} | 그 이전 브랜치 |
bash
git switch @{-1}브랜치 전환 기본은 06. 브랜치 전략에서 먼저 보고, 여기서는 복구할 때 이 참조를 어떻게 쓰는가에 집중하면 됩니다.
git commit --amend
가장 최근 커밋에 파일을 추가하거나 커밋 메시지를 수정할 때 사용합니다.
파일 추가를 깜빡했을 때
bash
git add 추가할파일.txt
git commit --amend커밋 메시지만 수정할 때
bash
git commit --amend기본형은 에디터를 열어 현재 메시지를 확인한 뒤 수정하는 방식이 더 안전합니다.git commit --amend -m "..."도 가능하지만, 본문이나 트레일러를 덮어쓸 수 있어 무조건 기본형으로 쓰는 편이 낫습니다.
--amend는 로컬 정리용이다
이미 push한 커밋을 amend하면 원격 이력과 달라집니다. 공유 브랜치에서는 revert를 먼저 검토하고, 개인 PR 브랜치라면 나중에 git push --force-with-lease까지 함께 이해하고 써야 합니다.
git reset - 포인터와 작업 상태 되돌리기
git reset은 HEAD, 스테이징 영역, 워킹 디렉토리를 어느 정도까지 되돌릴지 결정하는 명령어입니다.
| 명령어 | HEAD 이동 | 스테이징 | 워킹 디렉토리 |
|---|---|---|---|
git reset --soft <target> | 이동 | 유지 | 유지 |
git reset --mixed <target> | 이동 | 초기화 | 유지 |
git reset --hard <target> | 이동 | 초기화 | 초기화 |
자주 쓰는 예시
bash
# 최근 커밋만 해제하고 내용은 남기기
git reset --soft HEAD~
# 스테이징만 취소하고 내용은 워킹 디렉토리에 남기기
git reset --mixed HEAD~
# 스테이징 취소만 하고 현재 내용은 그대로 두기
git restore --staged event.txt
# 특정 커밋 상태로 완전히 되돌리기
git reset --hard <commit>--hard는 가장 마지막 선택지다
git reset --hard는 현재 워킹 디렉토리 변경까지 버립니다. 복구 문서를 읽을 때도 기본 복구 순서는 find -> 새 브랜치 생성 -> 검증 -> 정말 필요하면 reset입니다.
git revert - 공유 브랜치에서 안전하게 취소하기
git revert는 특정 커밋을 지우는 대신, 반대 작업을 하는 새 커밋을 생성합니다.
| 구분 | git reset | git revert |
|---|---|---|
| 동작 | 이력을 이동하거나 삭제 | 되돌리는 새 커밋 생성 |
| 기존 커밋 | 사라질 수 있음 | 유지됨 |
| 원격 공유 브랜치 | 위험 | 안전 |
| 협업 친화성 | 낮음 | 높음 |
bash
git revert HEAD
git revert abc1234merge commit을 되돌릴 때는 부모를 지정하는 -m 옵션이 필요합니다. 이 단계는 초급 범위를 넘기므로, 실무에서는 먼저 팀 컨벤션을 확인하는 편이 안전합니다.
git reflog - 로컬 히스토리 안전망
git log는 현재 브랜치에 남아 있는 커밋 이력을 보여줍니다.git reflog는 HEAD나 브랜치 포인터가 어디를 가리켔는지까지 기록합니다.
핵심 특징
| 항목 | 의미 |
|---|---|
| 범위 | 로컬 저장소 전용 |
| 기본 대상 | HEAD |
| 추가 특징 | HEAD reflog는 브랜치 전환도 기록 |
| 주요 명령 | git reflog, git reflog show <ref> |
| 별칭 관계 | git reflog show는 사실상 git log -g 계열 출력 |
자주 쓰는 명령
bash
# 현재 HEAD reflog
git reflog
# 특정 브랜치 reflog
git reflog show main
# 시간 정보까지 보기 좋게 출력
git reflog show main --date=iso --no-decorate
# 특정 시점 상태 확인
git show HEAD@{yesterday}보관 기간
reflog는 영구 보관이 아닙니다.
| 설정 | 기본값 | 의미 |
|---|---|---|
gc.reflogExpire | 90일 | 도달 가능한 reflog 항목 |
gc.reflogExpireUnreachable | 30일 | 도달 불가(dangling) 항목 |
실수로 삭제한 커밋 복구에서는 사실상 30일 창이 더 중요합니다.
git reflog find는 정식 명령이 아니다
원래 자료처럼 git reflog find <검색어>를 쓰면 안 됩니다. 검색이 필요하면 아래처럼 처리합니다.
bash
git reflog | grep reset
git log -g --grep="commit"git reflog expire --expire=now --all은 평소엔 거의 안 쓴다
존재는 알아둘 만하지만, 학습용 기준에서는 "오래된 로컬 reflog를 정리하는 고급 명령" 정도로만 보면 충분합니다.
reflog로 안전하게 복구하기
복구의 기본 순서는 다음 네 단계입니다.
1. 먼저 위치를 찾는다
bash
git reflog
git reflog show main --date=iso --no-decorate2. 새 브랜치로 안전하게 고정한다
bash
git switch -c recovery HEAD@{3}이렇게 하면 복구 후보 지점을 새 브랜치로 먼저 보존할 수 있습니다.
이 단계를 건너뛰고 곧바로 reset --hard를 치는 습관이 가장 위험합니다.
3. 무엇을 하고 싶은지에 따라 명령을 고른다
| 상황 | 추천 명령 |
|---|---|
| 브랜치 전체를 그 시점으로 되돌리고 싶다 | git reset --hard <hash> |
| 잃어버린 특정 커밋만 현재 브랜치에 다시 올리고 싶다 | git cherry-pick <hash> |
| 현재 체크아웃하지 않은 다른 브랜치 포인터만 옮기고 싶다 | git branch -f <branch> <hash> |
특정 커밋만 가져오기
bash
git cherry-pick <hash>다른 브랜치 포인터 옮기기
bash
git switch <다른-브랜치>
git branch -f main <hash>git branch -f는 현재 체크아웃 중인 브랜치에 바로 쓸 수 없다
현재 머물고 있는 브랜치를 강제로 옮기려 하면 Git이 거부합니다. 현재 브랜치를 움직일 때는 보통 git reset --hard <hash>가 맞고, 다른 브랜치를 움직일 때 git branch -f를 씁니다.
마지막 fallback
reflog가 만료되었거나 단서를 놓쳤다면 git fsck --lost-found로 dangling commit을 찾는 방법도 있습니다. 다만 reflog보다 읽기 어렵고 마지막 수단에 가깝습니다.
Detached HEAD 상태
특정 커밋을 직접 가리키면 detached HEAD 상태가 됩니다.
bash
git switch --detach HEAD@{2}
git switch --detach <commit>이 상태는 "그 시점 코드를 잠깐 확인하거나 실험"할 때는 괜찮습니다.
문제는 이 상태에서 새 커밋을 만들고 브랜치로 연결하지 않으면, 나중에 찾기 어려워질 수 있다는 점입니다.
빠져나오는 법
bash
# 그냥 나가기
git switch main
# 지금 상태를 새 브랜치로 보존하기
git switch -c spikegit rebase -i - 로컬 히스토리 정리
여러 로컬 커밋을 다듬고 순서를 조정하고 합치는 대표 도구입니다.
bash
git rebase -i HEAD~4자주 보는 액션
| 액션 | 의미 |
|---|---|
pick | 그대로 유지 |
reword | 메시지만 수정 |
edit | 내용 수정 후 계속 진행 |
squash | 이전 커밋과 합치고 메시지도 함께 정리 |
fixup | 이전 커밋과 합치되 메시지는 버림 |
drop | 해당 커밋 제거 |
가장 실무적인 흐름: fixup + autosquash
코드 리뷰 피드백 반영 후 커밋을 예쁘게 정리할 때 자주 씁니다.
bash
git commit --fixup <정리할-대상-커밋>
git rebase -i --autosquash HEAD~4--autosquash를 쓰면 fixup 커밋이 자동으로 제자리로 이동하고 fixup 액션까지 맞춰집니다.
충돌이 나면
bash
git rebase --continue
git rebase --skip
git rebase --abort| 명령 | 의미 |
|---|---|
--continue | 충돌 해결 후 계속 |
--skip | 현재 커밋 건너뜀 |
--abort | 시작 전 상태로 복귀 |
알아두면 좋은 옵션
| 옵션 | 의미 |
|---|---|
--update-refs | 함께 움직여야 할 로컬 브랜치 참조 갱신 보조 |
--keep-base | 현재 base를 유지하며 rebase |
--rebase-merges | merge 구조를 유지한 채 rebase |
push한 이력을 다시 쓸 때
공유 브랜치에서는 rebase -i보다 revert가 안전합니다.
내가 혼자 쓰는 PR 브랜치라면 이후 push는 git push --force-with-lease를 기본으로 보고, 환경이 허용하면 --force-if-includes도 검토할 수 있습니다. 맨 --force는 피하는 편이 좋습니다.
언제 무엇을 사용하나?
핵심 정리
| 명령어 | 용도 | 이력 | 핵심 주의점 |
|---|---|---|---|
git commit --amend | 직전 커밋 수정 | 덮어쓰기 | 기본은 로컬만 |
git reset --soft | 커밋 해제, 내용과 스테이징 유지 | 이동 | 정리용 |
git reset --mixed | 커밋 해제, 스테이징만 초기화 | 이동 | 기본 reset |
git reset --hard | 특정 시점으로 완전 복귀 | 이동 | 워킹 디렉토리 삭제 위험 |
git revert | 되돌리는 새 커밋 생성 | 보존 | 공유 브랜치 기본 선택 |
git reflog | 포인터 이동 기록 확인 | 보존 | 로컬 전용, 만료됨 |
git cherry-pick | 특정 커밋만 다시 적용 | 보존 | 충돌 가능 |
git branch -f | 다른 브랜치 포인터 강제 이동 | 이동 | 현재 브랜치엔 직접 못 씀 |
git rebase -i | 로컬 히스토리 정리 | 재작성 | push 후엔 매우 신중 |