Mercurial에서 재배치(rebase) 요점 정리

커밋을 재배치(rebase)하는 일은 DVCS에서 매우 흔하며 이에 대해서는 이 글에서 소개를 했습니다. 하지만 이 내용만으로는 쉽게 이해하지 못하기도 하고, GUI 환경인 TortoiseHg에서도 여전히 헷갈려 하는 것을 많이 봤습니다. 그래서 이 유용한 기능을 어떻게 하면 쉽게 쓸 수 있는지 알려 드립니다.

재배치 ≈ 병합(merge)

재배치는 이력을 고쳐 쓰는 방법이지만 역시 병합이라고 볼 수 있습니다. 하지만 둘 사이에는 이력을 보여 주는 방법에 차이가 있습니다.

병합(merge)과 재배치(rebase)

그림으로 쉽게 이해할 수 있듯 병합은 분기한 이력을 그대로 남기면서 내용을 합칩니다. 재배치(rebase)는 분기한 이력을 없애고 마치 차례로 변경한 것처럼 변경 내용을 한 줄로 만듭니다. 이력만 보면 단순히 변경 내용 순서만 바꾼 것처럼 보이지만 재배치를 할 때 병합도 함께 합니다.

이처럼 병합은 흐름을 이해하기 쉽습니다. 언제 분기하고 언제 합쳤는지 분명합니다. 또한 이 과정을 누구나 쉽게 이해합니다. 반면 재배치는 병합에 대한 이력을 볼 수 없습니다. 마치 분기가 전혀 없었던 것처럼, 각자 순서를 정해 변경한 것처럼 보일 뿐입니다. DVCS에서는 분기가 쉽게 생기는데 이런 브랜치를 이용해 이력을 관리하고 기능 개발하는 일이 잦다면 재배치를 좋은 시선으로 보긴 힘들 겁니다.

그럼에도 불구하고 왜 재배치란 기능을 만들고 권하는 걸까요? 앞에서 알려드린 글에서도 알 수 있듯 Mercurial에서는 재배치 명령 이전에 같은 일을 할 수 있는 방법이 있었습니다. 즉 병합만큼이나 매우 일반적인 방법이라 할 수 있습니다. 이렇게 계속 얘기하는 걸 보면 분명 뭔가 장점이 있다는 느낌이 오지 않나요? 그렇지 않고서야 병합보다 몇 단계는 더 거쳐야 하는 이런 방법을 굳이 쓸 이유는 전혀 없을 테니까요.

재배치의 장점은 이력을 한 줄로 만들어 준다는 겁니다. 너무 허무한가요? 이미 앞에서 얘기한 건데 달랑 이 것 하나 얘기하고 끝이라니 실망이 클지 모르겠지만, 이 것 하나면 충분합니다. 개발 과정에서 브랜치가 많이 생긴다는 건 이미 얘기했습니다. 문제는 바로 많이 생긴다는 겁니다. 브랜치를 병합할 때마다 큰 의미 없는 병합 로그를 계속 남겨야 하고 이와 함께 이력 그래프 역시 점점 복잡해집니다. 결국 복잡하게 얽힌 그래프 속에서 어디서 분기하고 합쳐지는지 길을 잃고 헤매기 쉽습니다. 빙빙 꼬인 실타래보다는 한 줄로 늘어 놓은 가닥을 쫓아가기 쉽듯 이력을 확인할 때 분기 없이 한 줄로 나열할 수 있다면 그보다 더 보기 좋은 게 없죠. 그래서 병합보다 재배치를 더 권합니다. 하지만 여기엔 한 가지 큰 위험이 숨어 있습니다. 재배치를 잘못하면 변경 내용이 엉뚱하게 복제되어 버리는 문제죠. 이 문제에 대해서는 Introduction To Mercurial Phases (Part I) 내용 중, ‘이력을 다시 쓸 때 많이하는 실수 막기’에서 설명했습니다.

재배치 사용 방법

생각 외로 많이 헷갈려 하는 부분은 어느 것을 재배치해야 하는냐입니다. 이를 쉽게 파악하는 방법은 사실 위 내용에 이미 다 나왔지만 다시 설명해 보겠습니다. 두 가지 방법이 있는데 사실 설명만 다를 뿐 같은 얘기입니다.

Rebase_remote는 원격(remote) 저장소이고 이 저장소를 복제해 Rebase_local이라는 지역(local) 저장소를 만들었습니다. 처음 복제한 상태에서 두 저장소 상태는 같습니다.

원격 저장소 처음 상태

지역 저장소 처음 상태

먼저 원격 저장소에서 몇 가지 내용을 추가합니다.

원격 저장소 커밋 후

동시에 지역 저장소에서도 몇 가지 내용을 추가합니다.

지역 저장소 커밋 후

지역 저장소에서 변경한 내용을 원격 저장소로 보내려 하면 실패합니다. 원격 저장소에 변경 내용이 있다는 뜻이죠.

지역 저장소 push 실패 로그

이를 먼저 해결해야 하므로 원격 저장소 내용을 가져 옵니다.

원격 저장소 변경 내용을 가져온 후

빨강 사각형은 지역 저장소에서 변경한 내용이고 파랑 사각형은 원격 저장소에서 가져온 내용입니다. 이 때 선택지는 두 가지입니다. 병합하거나 재배치하거나. 지금은 재배치를 설명하고 있으니 병합은 설명하지 않겠습니다. 이제 남은 건 무엇을 재배치하느냐인데 앞에서 설명한 것처럼 이를 찾는 방법은 두 가지입니다.

  • 내가 변경한 내용을 재배치한다.
  • 초안(draft) 상태인 것을 재배치한다.

참 쉽죠? Mercurial 상태(phase)에 대한 글에서 설명했듯 내가 변경한 내용은 원격 저장소로 보내기 전까지 초안(draft) 상태입니다. 이 상태에서는 이력을 고칠 수 있습니다. 재배치는 이력을 고치는 것이므로 정확히는 초안(draft) 상태일 때만 할 수 있습니다. 이를 조금 다르게 얘기하면, 다른 개발자가 변경한 내용이 아니라 내가 변경한 내용을 옮기는 것과 같은 거죠.

만약 이를 생각하지 않고 원격 저장소에서 가져온, 다른 개발자가 변경한 내용을 재배치하면 어떻게 될까요. 위 그림에서 리비전 5인 변경 내용을 선택하고 재배치를 해 보겠습니다.

잘못된 재배치 방법

변경할 수 없는 내용은 재배치하지 못한다며 오류를 표시합니다. 이미 앞에서 설명한 것처럼 리비전 5는 원격 저장소에서 가져온 것으로 공개(public) 상태이므로 더 이상 이력을 고칠 수 없습니다. 재배치를 올바로 하려면 내가 변경한 것, 즉 초안 상태인 것을 옮겨야 합니다. 그러므로 원격 저장소에서 가져온 것을 기준으로 갱신합니다. 이 상태에서 보면 내가 변경한 것이 가지로 빠져 나와 있죠? 이 것을 잘라 옮기는 겁니다. 가지 전체를 옮기기 위해 리비전 2를 선택하고 재배치를 합니다.

올바른 재배치 방법

재배치 과정에서 충돌이 생기면 각 변경 내용마다 병합 과정을 거치며 결과는 다음과 같습니다.

지역 저장소 재배치 후

재배치를 사용하려면

재배치 명령을 아무리 찾아도 없다는 분을 위해 추가로 설명 드리면, 재배치는 기본 명령이 아니라 확장 명령이므로 설정을 해야 쓸 수 있습니다. TortoiseHg 전역 설정(global settings)에서 확장(Extensions)을 선택한 후 재배치(rebase)를 선택합니다. 만약 워크벤치(workbench)를 실행 중이면 종료 후 다시 실행해야 합니다. 이후 워크벤치에서 체인지셋을 마우스 오른쪽 버튼으로 선택하면 가장 아래에 Modify history → Rebase 로 쓸 수 있습니다.

Notes:
1. 변경 과정에서 자연스레 생기는 이런 브랜치를 익명 브랜치(anonymous branch)라고 합니다.
2. 다른 개발자가 변경한 것
3. 선택한 체인지셋을 포함해서 이후 내용을 옮기므로 선택할 때 주의합니다.
변경 과정에서 자연스레 생기는 이런 브랜치를 익명 브랜치(anonymous branch)라고 합니다.
다른 개발자가 변경한 것
선택한 체인지셋을 포함해서 이후 내용을 옮기므로 선택할 때 주의합니다.