← posts/b.log()

blog92@web:~$ cat posts/github-06-reset-revert-and-reflog.md

GIT3 min read

GitHub 완전 정복 6편 — reset·revert·reflog로 되돌리는 안전한 복구의 기술

HEAD 참조 표기법과 상황별 되돌리기 맵, reset 세 모드가 무엇을 지우는지, push한 커밋에는 왜 revert가 유일한 정답인지, reflog로 날린 커밋을 되살리는 법과 그 한계, stash·clean·restore의 위험도를 다룹니다.

이전 편: 5편 — Pull Request, 머지 전략, force push, 브랜치 전략

이 편이 이 시리즈에서 가장 중요합니다. Git을 "무서운 도구"로 만드는 건 전부 되돌리기이고, 원리를 알면 대부분 복구 가능합니다.

4.1 먼저 알아야 할 것 — HEAD와 참조 표기법

text
HEAD          현재 위치
HEAD~1        한 칸 전 (= HEAD^)
HEAD~3        세 칸 전
HEAD^2        병합 커밋의 두 번째 부모
abc1234       커밋 해시
main@{1}      main이 1번 전에 가리키던 곳 ★
HEAD@{2.hours.ago}   2시간 전의 HEAD ★
main..feature        feature에만 있는 커밋
main...feature       양쪽 각자 갈라진 커밋 전부

HEAD@{2.hours.ago}는 reflog가 그 시점까지 거슬러 올라가지 못하면 경고와 함께 가장 오래된 기록을 돌려줍니다.

4.2 상황별 되돌리기 맵

text
아직 add 안 함 (작업 디렉토리 수정)
   └─▶ git restore <file>              ⚠ 변경 영구 삭제
 
add 했음 (스테이징)
   └─▶ git restore --staged <file>     🟡 안전 (파일 내용 유지)
 
commit 했음 / push 전
   ├─ 메시지만 고치기 → git commit --amend
   ├─ 커밋 취소, 변경 유지 → git reset --soft HEAD~1   🟡
   ├─ 커밋+스테이징 취소  → git reset HEAD~1          🟡
   └─ 전부 날리기        → git reset --hard HEAD~1    ⚠
 
commit 했음 / push 했음
   └─▶ git revert <commit>             ✅ 유일하게 안전한 정답

4.3 reset의 3가지 모드 (핵심)

bash
git reset --soft  HEAD~1   # 커밋만 취소, 변경은 스테이징에 남음
git reset --mixed HEAD~1   # (기본값) 커밋+스테이징 취소, 파일은 그대로
git reset --hard  HEAD~1   # 전부 삭제 ⚠⚠ 커밋 안 된 변경 복구 불가
text
              커밋   스테이징   작업파일
--soft        취소    유지       유지       ← 커밋 다시 쓰고 싶을 때
--mixed       취소    취소       유지       ← add부터 다시 하고 싶을 때
--hard        취소    취소       삭제 ⚠     ← 전부 없던 일로

가장 흔한 사용:

bash
# "방금 커밋 메시지 잘못 썼다" (아직 push 전)
git commit --amend -m "올바른 메시지"
 
# "커밋에 파일 하나 빠뜨렸다"
git add forgotten.ts
git commit --amend --no-edit
 
# "최근 3개 커밋을 하나로 다시 묶고 싶다"
git reset --soft HEAD~3
git commit -m "feat: 기능 전체 구현"

--amend도 새 커밋을 만드는 것입니다(해시가 바뀜). push 전에만 자유롭게 쓰세요.

4.4 revert — push한 것을 되돌리는 유일한 정답

bash
git revert abc1234                 # 되돌리는 새 커밋 생성
git revert HEAD                    # 마지막 커밋 취소
git revert abc1234..def5678        # 범위
git revert -n abc1234              # 커밋 없이 스테이징만 (여러 개 묶을 때)
git revert -m 1 <merge-commit>     # 병합 커밋 되돌리기 (부모 1 기준) ★
text
reset (히스토리 삭제)          revert (히스토리 추가)
A ─ B ─ C ─ D                  A ─ B ─ C ─ D ─ D'
A ─ B                          (D'는 D를 상쇄하는 커밋)

왜 push한 커밋에는 reset이 아니라 revert인가?

reset 후 push하려면 --force가 필요합니다. 즉 원격 히스토리를 파괴하는 것이고, 그 커밋을 이미 받아간 모든 동료의 로컬이 원격과 어긋납니다. 동료가 그 상태에서 pull하면 삭제했던 커밋이 되살아나거나 충돌 지옥이 열립니다.

revert는 아무것도 지우지 않고 "취소하는 변경"을 앞에 추가합니다. 히스토리가 선형으로 유지되므로 누구의 로컬도 깨지지 않습니다. 대신 "실수했다가 되돌렸다"는 기록이 남는데, 그게 오히려 정직한 기록입니다.

안전 원칙 #4 — 원격에 이미 있는 것은 지우지 말고 덮어쓰세요. revert는 느리고 못생겼지만 아무도 다치지 않습니다.

병합 커밋 revert는 주의가 필요합니다. -m 1로 되돌린 뒤 나중에 같은 브랜치를 다시 머지하면, Git은 "이미 병합했다"고 판단해서 변경이 안 들어옵니다. 이럴 땐 revert 커밋을 다시 revert하거나 브랜치를 새로 따야 합니다.

4.5 reflog — 최후의 보루

Git에서 커밋된 것은 거의 사라지지 않습니다. reset --hard로 날렸어도, 브랜치를 -D로 지웠어도(브랜치 자체의 reflog는 같이 지워지지만 HEAD의 reflog에 흔적이 남습니다 — 삭제 시 출력되는 Deleted branch … (was abc1234) 해시도 단서입니다), rebase가 꼬였어도 reflog에 기록이 남습니다.

bash
git reflog                    # HEAD가 이동한 모든 기록 (읽기 전용, 안전)
git reflog show main          # 특정 브랜치의 이동 기록
text
abc1234 HEAD@{0}: reset: moving to HEAD~3
def5678 HEAD@{1}: commit: feat: 중요한 기능      ← 날린 줄 알았던 커밋
ghi9012 HEAD@{2}: commit: fix: 버그 수정
bash
# 복구 1 — 그 시점으로 되돌리기
git reset --hard HEAD@{1}
 
# 복구 2 — 새 브랜치로 안전하게 꺼내기 (이쪽 권장) ★
git branch rescue def5678
git switch rescue

복구 2를 권하는 이유: 현재 상태를 건드리지 않고 잃어버린 커밋만 되살립니다. 확인한 뒤 필요한 것만 cherry-pick 하면 됩니다.

bash
# reflog에도 없다면 (극단적 상황)
git fsck --lost-found        # 고아 객체 탐색

reflog의 한계

  • 기본 만료 기간 90일(도달 불가능한 것은 30일)
  • 로컬 전용 — clone하면 reflog는 따라오지 않습니다
  • 커밋되지 않은 변경은 기록되지 않습니다 ⚠

마지막 항목이 가장 중요합니다. reset --hard가 진짜로 위험한 이유는 커밋을 지워서가 아니라(reflog로 복구됨) 아직 커밋 안 한 작업 디렉토리 변경을 지우기 때문입니다. 그건 Git이 한 번도 본 적 없는 데이터라 어디에도 없습니다.

안전 원칙 #5 — 커밋하지 않은 변경은 Git의 보호 밖에 있습니다. 위험한 명령 전에는 git stash 하거나 임시로라도 커밋하세요. "일단 커밋"은 언제나 되돌릴 수 있습니다.

4.6 stash — 잠깐 치워두기

bash
git stash                              # 현재 변경 임시 저장
git stash push -m "로그인 작업 중"      # 메시지 붙여서
git stash -u                           # untracked 파일도 포함 ★
git stash -p                           # 일부만 선택해서
 
git stash list                         # 목록 (안전)
git stash show -p stash@{0}            # 내용 확인 (안전)
 
git stash pop                          # 적용 + 스택에서 제거
git stash apply stash@{1}              # 적용 + 스택에 유지 ★ 더 안전
git stash drop stash@{0}               # 삭제 ⚠
git stash clear                        # 전부 삭제 ⚠⚠
git stash branch new-branch            # stash를 새 브랜치로

pop보다 apply를 권합니다. pop은 적용 중 충돌이 나면 상황이 애매해지고, 성공하면 스택에서 사라집니다. apply로 적용해서 확인한 뒤 drop하는 2단계가 안전합니다.

-u를 잊는 게 단골 실수입니다. 기본 stash는 추적되지 않는 새 파일을 저장하지 않습니다. 새로 만든 파일이 있다면 -u를 붙이세요.

4.7 clean — 정말 위험한 명령

bash
git clean -n         # ★ 먼저 이것부터. 무엇이 지워질지 미리보기 (dry-run)
git clean -f         # 추적 안 되는 파일 삭제 ⚠
git clean -fd        # 디렉토리까지 ⚠⚠
git clean -fdx       # .gitignore 대상까지 전부 ⚠⚠⚠ (.env, node_modules 포함)

clean으로 지운 파일은 Git이 추적한 적 없으므로 reflog에도 없고, 복구 수단이 전혀 없습니다. 항상 -n으로 먼저 확인하세요. 특히 -x는 .env 같은 설정 파일까지 날립니다.

4.8 아직 커밋 안 한 변경 되돌리기

bash
git restore file.ts               # 작업 디렉토리 변경 취소 ⚠ 복구 불가
git restore --staged file.ts      # 스테이징만 해제 🟡 안전
git restore --source=HEAD~2 file.ts   # 2커밋 전 버전으로 되돌리기
git restore .                     # 전부 ⚠⚠
COMMENTS (…)

댓글을 불러오는 중이에요.

NEW COMMENT0 / 1000
⌘↵ 전송

blog92@web:~$ cd ..