- Published on
Git rebase 후 PR 꼬임, reflog로 안전 복구
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서로 다른 브랜치 히스토리가 섞이고, PR에서 커밋이 갑자기 사라지거나(혹은 이상하게 늘어나거나), 리뷰 코멘트가 “Outdated”로 바뀌는 상황은 보통 rebase와 force push 조합에서 발생합니다. 특히 팀에서 PR을 오래 열어두고 여러 번 리베이스하거나, 로컬에서 rebase --onto 같은 고급 옵션을 쓰다가 기준 커밋을 잘못 잡으면 PR이 “꼬인” 것처럼 보이기 쉽습니다.
이 글은 그런 상황에서 reflog를 타임머신처럼 사용해 “정상 상태였던 시점”의 커밋으로 되돌린 뒤, PR을 안전하게 복구하는 방법을 다룹니다. 결론부터 말하면, git reflog만 있으면 로컬에서 일어난 대부분의 히스토리 사고는 복구 가능합니다.
참고로, 강제 푸시로 이미 원격이 덮여버린 PR을 더 폭넓게 복구하는 시나리오는 아래 글도 같이 보면 좋습니다.
- Git rebase 후 강제푸시로 꼬인 PR 복구법
- rebase 충돌을 줄이고 재발을 막고 싶다면: Git rebase 충돌을 자동 재사용 - rerere 설정법
PR이 “꼬였다”는 신호와 원인
자주 보이는 증상
- PR 커밋 목록이 갑자기 바뀜(커밋이 사라지거나, 중복되거나, 엉뚱한 커밋이 포함됨)
- PR diff가 예상과 다르게 커짐(이미 머지된 변경이 다시 뜨는 느낌)
- 리뷰 코멘트가 대부분 “Outdated”로 변함
- CI가 갑자기 다른 커밋을 기준으로 돌기 시작함
원인: rebase는 커밋을 “새로 만든다”
rebase는 기존 커밋을 그대로 옮기는 게 아니라, 새 커밋을 재생성합니다. 그래서 커밋 해시가 바뀝니다. 그 상태에서 원격 브랜치에 --force 또는 --force-with-lease로 푸시하면, PR이 바라보는 히스토리가 통째로 교체됩니다.
여기서 핵심은 “원래 커밋 해시”가 사라진 게 아니라, 브랜치 포인터가 다른 곳을 가리키게 된 것이라는 점입니다. 이 포인터 이동의 흔적을 기록해두는 로그가 바로 reflog입니다.
reflog란 무엇이며 왜 복구가 가능한가
git reflog는 로컬 저장소에서 HEAD와 브랜치 레퍼런스가 어떤 커밋을 가리켰는지의 이동 기록입니다.
- 브랜치가 어디를 가리켰는지 “이전 위치”를 알 수 있음
reset,rebase,checkout,merge등으로 바뀐 포인터 이동이 남음- 실수로
reset --hard를 해도, 그 직전 위치를 reflog에서 찾을 수 있음
주의할 점도 있습니다.
- 기본적으로 로컬에서만 보장됩니다(원격의 reflog는 일반적으로 접근하지 못함)
- 오래 지나면 GC 정책에 따라 기록이 정리될 수 있습니다
- 따라서 사고 직후 빠르게 복구하는 게 가장 좋습니다
복구 전략: “원래 좋았던 커밋”을 찾아 브랜치를 되돌린다
복구의 큰 흐름은 다음과 같습니다.
- 현재 상태를 백업 브랜치로 남긴다
reflog에서 “정상 상태였던 시점”의 커밋을 찾는다- 해당 커밋으로 브랜치를 되돌린다(보통
reset --hard) - 원격 브랜치를 안전하게 갱신한다(가능하면
--force-with-lease)
이 과정을 PR 브랜치(예: feature/my-work)에서 수행하면 됩니다.
1) 먼저 안전장치: 현재 상태 백업하기
복구는 되돌리는 작업이기 때문에, 실수하면 더 꼬일 수 있습니다. 먼저 현재 상태를 보존해두면 마음이 편합니다.
# 현재 작업 브랜치 확인
git status
git branch
# (예) PR 브랜치로 이동
git checkout feature/my-work
# 현재 상태를 백업 브랜치로 고정
git branch backup/feature-my-work-accident
원격에도 백업을 남기고 싶다면 다음처럼 푸시해도 좋습니다.
git push origin backup/feature-my-work-accident
2) reflog에서 “정상 커밋” 찾기
이제 reflog를 확인합니다.
git reflog --date=iso
출력 예시는 대략 이런 형태입니다.
c1a2b3c HEAD@{0}: rebase (finish): returning to refs/heads/feature/my-work
9f8e7d6 HEAD@{1}: rebase (pick): add validation
1a1b1c1 HEAD@{2}: rebase (start): checkout main
777aaaa HEAD@{3}: commit: implement endpoint
555bbbb HEAD@{4}: checkout: moving from main to feature/my-work
여기서 우리가 찾는 건 보통 다음 중 하나입니다.
rebase (start)직전의HEAD@{n}- “정상 PR이던 시점”에 해당하는 커밋 메시지
checkout: moving from ...직후의 커밋
팁: 브랜치 레퍼런스의 reflog도 직접 볼 수 있습니다.
git reflog show feature/my-work --date=iso
3) 원하는 시점으로 되돌리기: reset vs branch 생성
방법 A: 해당 커밋으로 브랜치 포인터를 되돌리기
정상 커밋이 예를 들어 HEAD@{3}이라면:
# 작업 트리까지 그 시점으로 되돌림 (주의: 로컬 변경사항 날아감)
git reset --hard HEAD@{3}
혹은 커밋 해시를 직접 써도 됩니다.
git reset --hard 777aaaa
로컬 변경사항을 보존해야 한다면 --hard 대신 --keep 또는 --merge를 검토할 수 있지만, PR 복구 목적에서는 보통 “히스토리를 정확히 되돌리는 것”이 우선이라 --hard를 많이 씁니다. 대신, 필요하면 git stash로 임시 보관을 먼저 하세요.
git stash push -m "wip before reflog recovery"
방법 B: 되돌리고 싶은 커밋에서 새 브랜치 파기
현재 브랜치를 건드리는 게 부담스럽다면, reflog에서 찾은 커밋으로 새 브랜치를 만들어 검증한 뒤 교체할 수도 있습니다.
# 정상 상태 커밋에서 새 브랜치 생성
git checkout -b feature/my-work-recovered 777aaaa
이 방식은 “복구 후보”를 별도 브랜치로 만들기 때문에 안전합니다.
4) 원격 PR 브랜치 복구 푸시하기
로컬 브랜치를 정상 커밋으로 되돌렸다면, 이제 원격 브랜치를 갱신해야 PR이 복구됩니다.
여기서 중요한 건 --force 대신 가능하면 --force-with-lease를 쓰는 것입니다.
--force는 무조건 덮어씀--force-with-lease는 “원격이 내가 마지막으로 알던 상태와 동일할 때만” 덮어씀
# 현재 브랜치가 feature/my-work 라는 가정
git push origin feature/my-work --force-with-lease
만약 방법 B로 feature/my-work-recovered를 만들었다면, 원격의 기존 PR 브랜치를 이 브랜치로 덮어쓸 수 있습니다.
# 로컬 recovered 브랜치의 커밋을 원격 feature/my-work에 강제로 반영
git push origin feature/my-work-recovered:feature/my-work --force-with-lease
이후 PR 페이지에서 커밋/디프가 정상으로 돌아왔는지 확인합니다.
5) PR이 여전히 이상할 때 점검 체크리스트
1) PR base 브랜치가 바뀌지 않았는지 확인
가끔 PR의 base가 main에서 develop으로 바뀌어 diff가 폭증하는 경우가 있습니다. PR 설정에서 base를 확인하세요.
2) 로컬과 원격이 같은 커밋을 가리키는지 확인
git fetch origin
git rev-parse HEAD
git rev-parse origin/feature/my-work
두 값이 다르면, 푸시가 제대로 반영되지 않았거나 다른 사람이 추가 푸시했을 수 있습니다.
3) “원래 커밋”이 이미 다른 곳에 남아있는지 찾기
reflog에서 커밋 해시를 찾았는데 그 커밋이 실제로 존재하는지 확인하려면:
git show 777aaaa
또는 dangling 커밋 탐색이 필요하면:
git fsck --no-reflogs --lost-found
다만 대부분의 PR 꼬임은 reflog만으로 해결됩니다.
실전 예시: 잘못된 rebase를 되돌리고 PR 복구
상황:
feature/my-work에서main최신을 반영하려고git rebase main수행- 충돌 해결 후 실수로 엉뚱한 커밋을 드랍하거나,
--onto를 잘못 사용 git push --force로 원격을 덮어써 PR이 이상해짐
복구:
# 1) 백업
git checkout feature/my-work
git branch backup/feature-my-work-before-recovery
# 2) reflog 확인
git reflog --date=iso
# 3) 정상 시점으로 이동 (예: HEAD@{6}이 정상)
git reset --hard HEAD@{6}
# 4) 원격 복구
git push origin feature/my-work --force-with-lease
이후 PR diff가 정상으로 돌아오면 복구 완료입니다.
재발 방지: rebase와 force push를 안전하게 쓰는 습관
--force-with-lease를 기본으로
git push --force-with-lease
팀원이 같은 브랜치에 푸시했는데 내가 모르고 덮어쓰는 사고를 크게 줄여줍니다.
rebase 전에 “기준점” 태그/브랜치 남기기
rebase 직전에 임시 태그를 남기면, reflog를 안 보더라도 즉시 돌아갈 수 있습니다.
git tag safety/feature-my-work-before-rebase
또는 브랜치로 남겨도 됩니다.
git branch safety/feature-my-work-before-rebase
충돌이 반복된다면 rerere로 비용 줄이기
같은 충돌을 여러 번 해결하고 있다면 rerere를 켜서 rebase 반복 비용을 줄일 수 있습니다.
git config --global rerere.enabled true
git config --global rerere.autoupdate true
자세한 흐름은 다음 글을 참고하세요.
마무리
PR이 꼬였을 때 가장 중요한 건 “당황해서 추가로 히스토리를 더 만지지 않는 것”입니다. 먼저 백업 브랜치를 만들고, git reflog에서 정상 상태였던 커밋을 찾아 브랜치 포인터를 되돌린 뒤, --force-with-lease로 원격을 복구하면 대부분의 케이스는 깔끔하게 해결됩니다.
만약 이미 여러 번 강제 푸시가 누적되어 원격 상태 자체가 복잡해졌다면, reflog 기반 복구와 함께 원격 브랜치/PR 전략까지 포함한 절차를 정리한 글도 같이 보길 권합니다.