프로젝트를 진행하던 중 git rebase 시 중복된 커밋이 존재 하는 상황을 맞이하였고, 이 원인과 해결하는 방법을 설명하고자 합니다.
다수의 인원이 동시에 개발하다보면 종종 현재 개발중인 특정 기능들이 필요한 상황이 발생하곤 합니다.
저희 프로젝트의 경우 Controller 구현 중, 이전 commit 의 취약점을 발견해 Service, Persistence 계층의 규약 (interface) 이 변경되는 상황이 있었습니다.
저는 이 때 rebase 를 유용하게 사용한다 들었습니다. 현 브랜치의 base commit 을 변경해 필요한 commit 위에 현 브랜치를 다시 세울 수 있기 때문입니다.
그래서 rebase 를 진행했지만 아래 그림처럼 예상치 못한 상황을 맞이하였습니다.
위 그림을 보면 필요한 commit 들 (초록색) 이 포함되긴 했지만, rebase 이전의 commit 들 (빨간색) 이 중복된 것을 알 수 있습니다.
그래서 제가 rebase 를 잘 못 알고있는 건가 헷갈렸고 위 현상의 원인을 분석하였습니다.
결론부터 말하자면 rebase 의 개념을 잘 못 알고 있는 것은 아니었습니다.
다만 Local 에서의 rebase 가 반드시 remote 에서의 rebase 로 이뤄지는 것은 아니었습니다.
(한마디로 Local 과 Remote 는 "다른 것" 임을 까먹었던 것...)
다음과 같은 상황을 생각해 보겠습니다.
저희는 개발하며 commit a1, 2, 3 를 생성해 remote 에 push 하였습니다. 그런데 작업 도중 다른 브랜치의 T1, T2 가 필요함을 느껴 Another Branch 에 rebase 를 하고자 합니다.
우리가 rebase 를 진행할 때는 대게 git bash 나 IDE 의 도움을 받습니다.
그런데 이들의 동작 그 자체는 remote branch 에 적용되지 않고 오직 local branch에서만 작용합니다.
즉, 우리가 개발하며 remote branch 에 영향을 주는 행동은 commit 후 push 할 때 뿐입니다.
그래서 Local 을 Another Branch 위에 rebase 하면 Local 은 위 그림처럼 "새로운 형태" 를 갖게 됩니다.
문제는 Local 의 A1, A2, A3 commit 은 기존 a1, a2, a3 와 다른 commit 이라는 점 입니다.
commit 의 식별자로 사용되는 SHA 는 commit 생성 시각, 변경점, 생성자 등 다양한 정보를 포함해 생성됩니다.
즉, A1, ... 과 a1, ... 은 모두 같은 변경점을 담고 있지만 서로 다른 commit 들 인 것입니다.
이 점이 문제되는 원인입니다. 확실히 Local 에서는 rebase 로 a1, a2, a3 가 사라지고 A1, A2, A3 가 새로 만들어지는 것은 어떠한 문제가 되지 않습니다.
하지만 Remote 는 어떨까요?
Remote 는 Local 에서 push 시에만 변경되니 Remote 에는 기존 커밋 a1, a2, a3 가 그대로 남아 있습니다.
때문에 Local 을 rebase 후 push 를 진행하면 Remote 위 그림의 형태를 갖게 됩니다.
즉, 우리 눈에는 중복된 commit 으로 보였지만, Remote 입장 에서는 확연히 다른 commit 들이었던 것 입니다.
결국 문제의 원인은 a1, a2, a3 와 A1, A2, A3 가 다른 commit 이었기 때문에 발생했습니다.
이 원인을 파악했으므로 이를 해결하는 방식은 매우 다양합니다.
Rebase 후 push --force
push --force 를 이용할 경우 현재 Local branch 의 형태 그대로 Remote 에 push 하게 됩니다.
때문에 Remote 의 a1, a2, a3 는 삭제되고 T1 -> T2 -> A1 ... 형태로 Remote 가 변화됩니다.
이 방법은 가장 강력하지만 (당연하게도) 아주 조스럽게 행해져야 합니다. Remote 의 형태를 확인도 없이 변경하기 때문입니다.
때문에 이는 Remote 에서 여러명이 작업하거나 Rebase 중 conflict 이 일어날 경우 그렇게 추천하지 않습니다.
Rebase 대신 Cherry pick or Merge
애초에 Rebase 를 사용하지 않는 방법 또한 존재합니다. 우리 Rebase 의 목적은 특정 기능이 포함된 commit 이 필요했기 때문 이었습니다.
그래서 꼭 필요한 commit 만 Cherry pick 하거나 Local 에 Another Branch 를 Merge 하는 것으로도 목적은 달성됩니다.
특히 Merge 는 브랜치간 병합이 일어났다는 commit 을 남길 수 있어 추후 commit tracking 에 용이할 수 있습니다.
Local branch 에서 rebase & push
아니면 Remote 와 동일한 새로운 Local-2 브랜치를 만드는 방법도 존재합니다.
새로운 Local-2 브랜치를 만들게 되면 로컬 브랜치이기 때문에 아직 (이름 그대로)Remote-2 브랜치가 존재하지 않습니다.
때문에 Local-2 브랜치에 Target branch 를 Rebase 하면 git fast-forward 로 인해 A1, A2, A3 같은 "새로운 commit" 이 생성되지 않습니다.
물론 처럼 진행할 경우, Remote-1, Remote-2 2 개의 브랜치가 저장소에 만들어질 것입니다.
어떤 방법이든 말 그대로 방법이 다를 뿐 목적은 동일합니다.
각자 현재 놓여진 상황에 따라 어느 방법이든 잘 선택해 사용하면 될 듯 합니다.
개인적인 추천은 2 번