본문 바로가기
🍎 iOS/DevNote

[Github] 우리는 왜 Squash & Merge와 No Fast-forward Merge 방식을 채택했을까?

by @Eddy 2024. 2. 4.
728x90

서론

DDD 개발 동아리에서 진행 중인 프로젝트에서, 브랜치 전략과 함께 어떻게하면 커밋기록을 깔끔하게 관리할 수 있을까 라는 고민으로 Merge 방식에 대해 생각해보게 되었습니다.

Commit History 관리를 잘해야 한다. 라는 얘기는 들어봤지만, 막상 프로젝트를 하면 잘 확인하지도 않는 Commit History가 왜 중요하지 라는 의문을 갖고 있었습니다. 이번 글에서는 프로젝트에서 Squash & Merge와 No Fast-Forward Merge를 사용하기까지 공부했던 내용들과 사용하면서 느낀 바를 정리하고자 합니다.

 

단순하게 Merge를 해왔다면

지금까지 저는 단순히 Conflict만을 피하기 위해 Rebase 후, Default Merge 방식만을 채택해왔습니다. 

그에 따른 결과는 아래의 이미지와 같습니다.

지난 팀 프로젝트에서의 Commit History (GitKraken)

제가 경험한 개발에서는 항상 Commit과 PR은 되도록 작고, 명확해야 한다 라고 배웠습니다. 하지만 Commit History에 자잘한 기록들이 하나도 빠짐없이 남겨져 있다면, 어떤 기록이 중요한 지, 무엇을 확인해야 하는지 알기 어려워집니다. 그 이전에 `확인해야겠다` 라는 생각조차 들지 않을지도 모르지만요. 심지어 위 이미지처럼 잘못된 Commit Convention이 적용되었다는 사실을 확인했다면, 더이상 보고 싶지 않겠죠?? ㄷㄷ

 

이미 남겨져버린 History는 어쩔 수 없습니다. 하지만, 앞으로의 History는 관리할 수 있습니다.

어떤 Merge 방법이 있는지 알아봅시다.

 

다양한 Merge 방식들

Default Merge

Pull Request를 merge할 때, 별 생각없이 눌렀던 Merge Pull Request는 별다른 조치를 취하지 않는 Merge입니다.

상위 브랜치로부터 현재 브랜치의 최신화도 이뤄지지 않으며, 모든 Commit History를 남기는 Merge라고 할 수 있습니다.

 

이 방식의 문제점은, 현재 브랜치의 시점과 상위 브랜치의 시점이 어긋난 상황에서 상위 브랜치로 merge를 하게 될 때 발생하는 Conflict에 있습니다. 이를 해결하기 위한 Rebase는 뒤에서 설명하겠습니다.

 

 

Rebase & Merge

순서는 Squash & Merge가 먼저이지만 Rebase & Merge를 먼저 설명하는 이유는, Default Merge와 크게 다르지 않다고 느꼈기 때문입니다. 차이점은 현재 Merge하려는 상위 브랜치와 하위브랜치 간의 시점이 어긋나 발생하는 Conflict를 예방해준다는 점입니다. 또한 Merge Commit 기록이 남지 않아 하나의 브랜치에서 작업한 것처럼 보여지게 됩니다.

 

이 외에 Merge와는 다르지만, terminal에서 `git rebase { 최신화 시점으로 하고 싶은 대상 브랜치 }`를 사용해 상위 브랜치의 기록을 가져옴과 동시에 시간대를 일치시킬 수 있어 Rebase 자체만으로도 유용한 기능 중 하나입니다.

 

 

Squash & Merge

Squash & Merge는 상위 브랜치로부터 현재 브랜치의 최신화를 적용하지는 않지만, 현재 브랜치의 모든 commit History를 상위브랜치에서 하나의 새로운 Commit으로 퉁! 치는 방식이라고 할 수 있습니다. 기존 계획과 크게 다른 내용이 없다면 PR 제목이 Commit 제목이 될 것 같습니다.

다만, 하나의 Commit으로 정리되기 때문에 어느 시점에 Merge가 되었는지 판단하기 어려울 수 있습니다.

 

그렇다면 Squash & Merge를 했을 때, Commit History는 어떻게 남을까요?

(좌) Merge 전 / (우) Merge 후

별 차이가 없는거 아닌가? 라는 생각이 드는데, 일단 하위 브랜치에서 develop 브랜치로 연결되는 라인이 없고, develop에서 `나, 커밋. 등장.`이라는 느낌처럼 갑자기 등장합니다. 이 부분이 아까 전에 말했던 상위 브랜치에서 하나의 새로운 commit으로 퉁! 친다 고 했던 부분인데, 상위 브랜치에서 새로운 Commit을 생성하기 때문입니다.

 

GitKraken으로 보니 이점이 명확하게 보이지 않아 아쉬우니, Github의 Commit History를 확인해보겠습니다.

(좌) 기본 Merge / (우) Squash Merge

왼쪽은 처음에 보여드린 프로젝트의 Commit History이고, 오른쪽은 Squash Merge를 적용한 Commit History입니다.

둘 다 Develop 브랜치에 merge된 Commit 기록인데

왼쪽은 무수히 많은 커밋이 Develop 브랜치에 쌓여있는 반면, 오른쪽은 굉장히 깔쌈(?)하게 2개의 Commit 기록만 있는 것을 볼 수 있습니다.

 

이어서 Git의 Commit Graph를 보면, 이렇게 깔끔하게 정리되었다는 사실을 알 수 있습니다.

(좌) Merge한 하위 branch가 남아있는 상태 / (우) Merge한 하위 Branch를 제거한 상태

여기서도 Merge가 되었음에도 develop과 하위 브랜치가 연결되지 않은 모습이 보여집니다.

 

No Fast-forward Merge

아직 사용하지 않아 설명이 부족할 수 있지만, 제가 이해한 바에 따르면 다음과 같습니다.

 

이 방식은 github에는 표시되지 않은 방식이지만, terminal을 통해 사용할 수 있습니다.

먼저 Fast-forward Merge는 앞서 언급한 Default Merge 방식을 말합니다.

Fast-forward란, Commit A에 따르고 있는 History가 전부 Commit B를 따르고 있을 때, Commit A가 Commit B에 Fast-forward하다 라고 말합니다. 

 

그리고 Merge를 하다보면,  `Merge pull request ~~~`라는 Commit기록이 남아있는 것을 본 적이 있을 겁니다. 이를 Merge 커밋이라고 합니다.

 

일반적으로 기본 Merge를 했을 때, Merge 커밋이 남지 않지만, fast-forward관계가 아닐 때에는 Merge커밋을 남기게 됩니다.

Merge 커밋이 남아있는 게 잘못된 건 아니지만, Merge 커밋을 남기는 이유와 남기지 않는 이유가 명확하지 않다면 기록을 확인하는 입장에서 혼란스러울 수 있습니다.

Merge 커밋 예시

제가 속한 프로젝트에서는 No Fast-forward Merge를 채택했으며, 이 방식을 사용하면 항상 Merge커밋이 남게 됩니다.

No Fast-forward Merge을 채택한 이유는, 앞서 언급한 '왜' Merge를 했고, 해당 Merge에 대한 모든 커밋 기록이 남아야하는 상황이 있다고 판단했기 때문입니다. 주로 HotFix, fix 등의 Trouble 상황이며, 이를 해결하는 과정은 모두 기록되어야 한다라고 판단했습니다.

 

그럼 No Fast-forward Merge는 어떻게 하는걸까요? 

 git merge --no-ff {브랜치 이름}

터미널에서 이처럼 입력하면 No Fast-forward Merge를 할 수 있습니다.

 

 

끝맺음

사실 별 내용은 없죠..? 그냥.. 이거 보여주려고 어그로 끌었습니다.

 

간단하게 이야기하면 이번 프로젝트에서 `왜 우리는 여러가지 Merge 방식 중 Squash Merge와 No Fast-forward Merge 방식을 채택했을까?`에 대해 평소보다 좀 더 깊게 고민하고 결정했다는 이야기였습니다. 다른 Merge 방식에 대한 설명을 곁들인..

 

이 글을 읽고 계신 분들은 중요하다면 중요하고, 관심없다면 안 중요한 Commit History Graph를 어떻게 관리하고 계신가요? 브랜치 전략과 Merge 방식에 대해 고민하면서 회사나 다른 팀 프로젝트에서는 어떻게 관리하는지가 궁금해졌습니다. 관리하는 방법에 대해 댓글로 남겨주시면 감사하겠습니다.

반응형

댓글