영주의 개발노트

🏦 기술 부채 상환 | git merge 와 rebase에 대해 알아보자 본문

카테고리 없음

🏦 기술 부채 상환 | git merge 와 rebase에 대해 알아보자

0JUUU 2024. 11. 6. 21:16

너무 공감 🥹 (출처: 데브경수)

최근 두고두고 쌓아둔 업보가 거대한 눈덩이로 변해 나를 덮쳤었다. 이 경험을 소개하며 그동안 쌓여온 git에 대한 기술 부채를 조금 갚아보려 한다.


 

mac 세팅 중 사내 문서를 참고하여 아래 명령어로 git 세팅을 한 적이 있다. 

git config --global pull.rebase true

한 달이 지난 후 develop 브랜치에서 개발 브랜치를 생성한 후 작업 내용을 반영하고 리뷰를 받았다. 리뷰를 반영하던 중 현 develop 브랜치에는 새로 개발되어 반영되어 있으나 내 개발 브랜치에는 해당 내용이 없어 develop 브랜치의 최신화된 내용을 내 브랜치에 반영해야 하는 상황이었다. 기존에는 pull.rebase를 false로 기본 설정해놓았기에 여느 때와 다름없이 아래 명령어를 실행하였다. 이렇게 하니 에러가 발생했다.

# local-branch에서 하위 명령어 실행
git pull origin develop

# 개발 완료
git commit -m "커밋 메시지"
git push origin local-branch

이럴리가 없는데 😱 당황했지만 당황하지 않은 척 슬쩍 에러 메시지를 살펴보고 'pull 땡겨서 해결하면 되겠네 😎'라는 생각을 거친 후 이를 실행하였다.

git pull origin local-branch
git push origin local-branch

 

이 과정을 거치고나니, 대참사가 일어났다. 그동안 develop에 반영된 180개가 넘는 커밋이 내 pr에 변경사항으로 잡혔다. 또한 이와 관련된 모든 사람들이 리뷰어로 등록되었을 뿐만 아니라 리뷰 사이즈도 L에서 XXL로 변경되었다. (영파씨가 되... 🧥)

 

이를 해결하기 위해 황급히 리뷰를 닫고 origin에서 이름이 동일한 브랜치를 새로 따서 다시 pr을 생성하였던 경험이 있다. 이 와중에 도무지 이해가 가지 않던 부분이 있었다. 'develop에서 개발 내용을 땡겨왔고 이를 내 브랜치에 넣었을 뿐인데 도대체 왜 내 개발 브랜치에서 develop 브랜치로 머지하는 pr 내용에 그간의 개발 내용이 다 들어간 걸까?', '그동안의 개발 내용들은 이미 develop에 반영된 내용이 아닌가? 왜 변경점에 잡히는 거지?'라는 의문점을 안은 채로 우선 급한 불부터 껐었다. 이번에는 git의 rebase 설정에 대해 알아보고, 이후 이러한 일이 일어나게 된 원인과 이를 해결하기 위한 과정에 대해 공부하고자 한다. 

 

pull.rebase 설정

pull.rebase true/false의 개념에 대해 알아보자. rebase를 true 하는 건 rebase를 의미하는 것 같은데 rebase를 false 하면 어떻게 되는 걸까?

우선 pull이라는 건 '당긴다'는 의미로 원격 저장소 변경사항을 로컬 저장소로 가져오는 git 명령어이다. 원격 저장소 변경사항과 현재 내 로컬 브랜치를 합치는 과정이 필요하다. 이 과정에서 내 로컬 브랜치와 원격 저장소의 변경한 파일이 겹치는 등의 문제가 발생할 수 있으며, 이러한 경우 '충돌이 발생했다'라고 한다. git에서 브랜치를 합치는 방법으로는 merge와 rebase 2가지가 존재한다. 이 2가지를 짚어보기 전, 알고 가야 하는 선행 개념이 있다. fast-forward라는 것이다. 단계별로 설명하겠다.

 

1인으로 개발하는 초기 프로젝트가 있다고 가정하자. 하나의 브랜치로 모든 변경사항을 관리하고 있다. main이 가리키고 있는 개발사항까지 서비스에 반영된다. 1명이 1개의 브랜치를 관리하는 건 큰 문제가 없을 것이다. 해당 서비스는 성공적인 결과를 거두었고, 점점 커지게 된다. 개발자는 2명의 동료를 구하게 된다. 

1개는 문제없다

동료들은 각각 main에서 b1, b2라는 새로운 브랜치를 생성하여 자신이 개발한 내용들을 반영한다.

fast-forward

b1 브랜치의 내용을 실제 서비스에 반영하고자 한다. main에서 b1 브랜치를 합치게 되면, 이것이 fast-forward이다. b1 브랜치의 커밋이 main 브랜치의 최신 커밋을 기반으로 하고 있기 때문에 별도의 merge 과정 없이 main 브랜치 포인터가 b1 브랜치의 마지막 커밋으로 이동하게 된다. 즉, main 브랜치에서 b1 브랜치를 합칠 때 b1 브랜치가 main 브랜치 이후의 커밋을 가리키면 main 브랜치가 b1 브랜치와 동일한 커밋을 가리키도록 브랜치 포인터가 단순히 이동하게 된다. 이 과정을 거치면 아래와 같은 형태로 존재하게 된다.

merge

이제 b2 개발 내용을 합치고자 한다. 한 가지 문제가 있다. 이전에는 main 브랜치의 최신 커밋이 합칠 브랜치 커밋의 뿌리였기에 fast-forward가 가능했다. 하지만 지금 상황은 다르다. b2 최신 커밋은 c3에서 파생되었고, 현재 main 브랜치는 c4에 위치해 있다. 이 경우엔 각 브랜치가 가리키는 커밋 2개(c4와 c5)와 공통 조상 하나(c3)를 사용해 3-way Merge를 한다. 단순히 브랜치 포인터를 최신 커밋으로 이동시키는 게 아닌 3-way Merge의 결과를 별도의 커밋으로 만들고 main 브랜치를 해당 커밋으로 이동시킨다. 

 

rebase

rebase는 앞서 설명한 merge와는 다른 방식으로 이 둘을 합치게 된다. c4에서 변경된 것들을 다른 곳에 저장해두고 이를 다시 c5에 적용시킨다. 실제로는 두 브랜치가 나뉘기 전인 공통 커밋(c3)으로 이동하고 나서 rebase 할 b2 브랜치가 가리키는 커밋까지 diff를 차례로 만들어 임시로 저장해놓는다. 이후 b2 브랜치가 합칠 브랜치 main이 가리키는 커밋을 가리키게 하고 좀 전에 저장한 변경사항을 차례대로 적용하게 된다. 

이후 main 브랜치를 fast-forward 시킨다. 

결과적으로는 merge와 rebase 방식 모두 b1 브랜치의 개발 내용과 b2 브랜치의 개발 내용이 main에 반영되었다는 결과물은 동일하다. 하지만, rebase는 새로운 커밋을 만들지 않고 이어 붙이는 개념이기에 merge 하는 것보다 히스토리를 깨끗하게 관리할 수 있다.

위 내용을 토대로 정리해 보면 pull.rebase true는 원격 저장소와 현 로컬 브랜치의 내용을 병합할 때 rebase 방식을 사용하겠다는 의미이고, pull.rebase false는 merge 방식을 사용하겠다는 것이다. 

 

마치며

이번에는 git의 merge와 rebase 개념에 대해 알아보았다. 어떤 것이 문제였는지 이미 눈치를 챈 사람들도 있을 것이다. (천재시네요😎) 단계적으로 나아가는 것이 좋을 것 같아 개념 먼저 정리해 보았다. 다음에는 두 방식의 차이에 대해 간략하게 설명하고 문제 상황이 발생하게 된 원인 및 해결방안을 설명하고자 한다.