본문으로 건너뛰기
개발 머꼬
개발 노트Git
hohyeon.dev13

rebase 충돌에서 --ours를 골랐는데 남의 코드가 남은 이유

  • #Common Pitfall
  • #Engineering Note
  • #Git

문제 발생

리베이스 중 충돌이 나서 "내 코드를 남기자"는 생각으로 --ours를 골랐는데, 정작 남은 것은 상대 브랜치의 코드였습니다.

$ git rebase main
CONFLICT (content): Merge conflict in src/search.ts
$ git checkout --ours src/search.ts   # 내 코드가 남을 줄 알았음

머지에서는 같은 옵션이 의도대로 동작했기 때문에 더 헷갈렸습니다.

원인 분석

ours/theirs는 "브랜치 이름"이 아니라 지금 체크아웃되어 있는 쪽 / 지금 적용하려는 쪽을 가리키는 상대적인 이름입니다. 그래서 리베이스에서는 머지와 반대가 됩니다.

공식 문서가 직접 설명합니다 — 리베이스는 작업 브랜치의 커밋을 <upstream> 위에 하나씩 다시 재생(replay) 하는 방식이라, 충돌이 났을 때 ours로 보고되는 쪽은 <upstream>에서 시작해 지금까지 재생된 결과이고, theirs작업 브랜치입니다. 즉 양쪽이 뒤바뀝니다.

정리하면 이렇습니다.

상황ourstheirs
git merge feature (main에서)main (지금 체크아웃한 쪽)feature
git rebase main (feature에서)main + 지금까지 재생된 커밋feature (지금 적용 중인 커밋)

리베이스에서 체크아웃되어 있는 것은 내 브랜치가 아니라 재생 대상이 되는 토대입니다. 내 커밋은 그 위에 하나씩 "가져와 붙이는" 쪽이라 theirs가 됩니다.

cherry-pickrevert도 같은 구조입니다 — 지금 HEAD가 ours, 가져오는 커밋이 theirs입니다.

해결 방안

  1. 브랜치 이름으로 생각하지 말고 "지금 무엇을 어디에 붙이는 중인가"로 생각합니다. 리베이스 중 내 커밋을 남기고 싶으면 --theirs입니다.
git checkout --theirs src/search.ts   # 리베이스 중 = 내 브랜치 쪽
git add src/search.ts
git rebase --continue
  1. 헷갈리면 실제 커밋을 확인합니다. 리베이스 중에는 지금 적용 중인 커밋이 REBASE_HEAD로 잡힙니다.
git log -1 --oneline REBASE_HEAD   # 지금 재생 중인 내 커밋
git log -1 --oneline HEAD          # 지금까지 쌓인 토대
  1. 한쪽을 통째로 고르지 말고 3-way 마커를 봅니다. 대부분의 충돌은 양쪽 변경을 모두 살려야 합니다. diff3 스타일을 켜면 공통 조상까지 보여서 판단이 쉬워집니다.
git config --global merge.conflictStyle zdiff3
  1. 잘못 골랐으면 그 파일만 되돌립니다. 리베이스 전체를 취소할 필요는 없습니다.
git checkout --merge src/search.ts   # 충돌 상태로 되돌리기
git rebase --abort                   # 정말 처음부터 다시
  1. 같은 충돌이 반복되면 rerere를 켭니다. 한 번 해결한 충돌의 해법을 기록해 두었다가 같은 충돌에 자동으로 적용합니다. 긴 브랜치를 여러 번 리베이스할 때 효과가 큽니다.
git config --global rerere.enabled true

공식 문서

마지막 수정

좋아요북마크

댓글0

아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.