Joonas' Note
All about git - 충돌(conflict)과 merge/rebase 쉽게 이해하기 본문
git에 대한 기본적인 컨셉과 용어는 아래의 글에서 정리했다.
그리고 commit 을 쌓기 위해 stage 컨셉에 대한 이해는 아래의 글에서 정리했다.
충돌 (Conflict)
충돌은 두 브랜치가 하나의 흐름(줄기)로 이어지지 못할 때 발생한다. 그리고 이런 경우는 생각보다 많다.
왜 이러한 충돌이 발생하는 지 이해하고, git의 컨셉만 잘 이해하면 앞으로 충돌을 해결하는 일은 어렵지 않다.
가장 흔한 상황은 개발 브랜치에서 버그 수정을 위해 임시로 브랜치를 새로 따고 작업한 경우이다.
이제 bugfix 브랜치에서 작업을 마치고 다시 develop 브랜치에도 변경한 코드를 적용하고 싶은데, develop 브랜치는 다른 이유로 브랜치 분기 이후로 커밋이 추가된 상황이다.
이전에 언급한 바와 같이 커밋(commit)은 하나의 변경 묶음 단위이다.
그렇다면 커밋(commit)의 기준으로 바라보았을 때, 두 브랜치를 어떻게 합칠 수 있을까?
뭐부터 해야할까?
먼저 간단한 상황을 만들어보자.
커밋 C는 파일 X에는 3을 추가하고 파일 Y은 2를 제거하고 5를 추가하는 내용이다.
커밋 D는 파일 X에는 4를 추가하고 파일 Y은 1을 제거하는 내용이다.
그럼 두 커밋을 합친 이후의 파일은 어떤 상태가 되어야 하는 가?
이것을 인지하는 것이 두 브랜치의 충돌을 해결하는 첫번째 순서이다.
두 브랜치가 갈라진 이후로 있었던 "모든 변경사항"을 합치고 싶은 것이 목적이라면, 그림으로는 다음과 같은 상태를 말할 것이다.
커밋 C, D 를 어떻게 처리할 지, 그리고 새로운 커밋 M 에 대한 여부에 따라, 크게 2가지 방법인 병합(merge)와 재배치(rebase)로 나뉜다.
즉, 네트워크를 어떤 모양으로 만들 지를 결정하는 것에 따라 나뉜다.
그러므로 두 방법 모두 결과적으로는 커밋 M 일 때의 파일 상태를 만드려는 것은 동일하다.
병합 (Merge)
먼저 직관적으로 이해하기 쉬운 병합(merge)부터 알아보자.
위에서 살펴본 바와 같이 우리가 필요한 것은 "두 브랜치에서 있었던 모든 변경 사항이 합쳐진 결과"대로 만드는 것이다.
즉, 그렇게 만드는 새로운 커밋을 하나 추가하면 해결된다. 이 방법이 바로 병합(merge)이다.
한 브랜치의 입장에서 (내 마지막 커밋 → "다른 브랜치의 모든 파일 상태")를 diff로 만들어서 그걸 commit 으로 삼는 것이다.
대신 이 방법을 사용하면, 마지막 상태를 기준으로 하나로 합치는 커밋 C+F 만 남기 때문에 아래와 같이 커밋 D, E, F 각각의 의미는 추적하기 어려워진다.
실제로 커밋에 기록된 정보를 보면, 합치면서 사용한 2개의 부모를 가지고 있다.
커밋 D, E, F를 추적하기 어려워진다고 말한 이유는, 위처럼 합쳐지는 merge commit 의 부모(parents) 정보를 따라가면 커밋 D, E, F 를 찾을 수는 있기 때문이다.
재배치 (Rebase)
많은 사람들이 재배치(rebase)를 이해하는 것을 포기하는데, 네트워크가 커밋(commit)으로 이루어졌다는 것을 잘 생각해보면 어렵지 않다.
병합(merge) 방법에서는 합쳐지는 다른 한 쪽의 커밋들이 유실되는 안타까운 일이 생긴다. 재배치(rebase)는 근본적으로 이런 일이 없도록 해보는 방법이다.
즉, 한 브랜치의 시작점(뿌리)을 바꾼다.
시간 여행과 관련한 매체를 많이 접한적이 있다면, 이것이 얼마나 간단한 변경이면서 가장 큰 파장을 불러일으키는지 잘 이해할 것이다.
다른 말로는 "시작점부터 역사를 새로 쓴다" = "시작부터 다시 커밋한다" 라고 말할 수 있다.
위와 같이 커밋 B에서 갈라진 브랜치에 커밋 D, E, F 가 있을 때, 이것을 커밋 C의 브랜치와 재배치(rebase)하는 방법은 다음과 같다.
먼저, 브랜치의 분기점(뿌리)인 커밋 D의 부모를 커밋 C로 바꾼다.
이 행동으로 우리가 기대하는 결과는, 마치 처음부터 "파란색 브랜치가 커밋 C에서 분기했던 것처럼" 만들 수 있다.
그렇다면 네트워크는 아래와 같은 모양이 될 것이고, 충돌 없이 브랜치를 그대로 한 줄기로 합칠 수 있게 된다.
하지만 위의 그림처럼, B→D와 C→D' 에는 차이가 있을 수 있다. 따라서 새롭게 커밋한다고 생각하는 것과 동일하다.
문제는 만약에 중간 커밋들 사이(D*→E, E→F 등)에서도 변경 내용이 완전히 동일하지 않은 상황이 있을 수 있다는 것이다.
이런 경우에는 rebase 프로세스를 잠시 멈추고 사용자에게 "지금 상태(status)가 이러한데 어떻게 커밋할 지 스테이지를 변경하고 다시 커밋하세요" 라는 과정을 거친다.
그리고 이 과정에서 기존의 commit hash id 가 변경될 수 있다.
커밋의 hash는 마치 블록체인과 같이 부모 커밋의 정보까지 포함하고 있기 때문에, 부모의 hash가 바뀌면 그 이후도 따라서 바뀌기 때문이다.
읽기 좋은 글
다음 글
'개발' 카테고리의 다른 글
git push 시 RPC failed; HTTP 400 해결법 (6) | 2024.04.07 |
---|---|
All about git - 원격 저장소와 작업하는 법(fetch/push/pull) (0) | 2023.08.20 |
All about git - 상태(status) 관리하기 (0) | 2023.07.15 |
All about git - 핵심 개념 정리 (0) | 2023.07.13 |
SOLID 원칙 - Dependency Inversion Principle (DIP; 의존관계 역전 원칙) (0) | 2023.05.16 |