이 글은 회사를 다닌 경험이 없는 학생 개발자가 서비스를 출시하고 사용자가 늘어나면서 GitHub-flow 방식으로는 형상 관리가 더 이상 적합하지 않다는 것을 깨달은 것에 대한 일대기를 담은 글입니다.
사이드 프로젝트를 신나게 진행하던 저는 Github-Flow를 이용해서 신나게 MVP 개발을 했습니다. MVP 개발을 마친 저는, 플레이스토어에 호기롭게 배포를 했고, 미약하지만 사용자가 생겼습니다. 완벽한 SW는 없다는 명언답게, 당연히 버그가 폭발합니다. A를 고치니 B가 안되고, 불필요해보였던 코드 C를 지우니 원래 동작하던 기능이 동작하지 않는 버그가 발생합니다. 급하게 작업해서 올린 빌드 파일은 어느 PR까지 포함시켜서 배포했는지 알 수 없습니다. 아무 곳에도 버전에 대한 기록이 없기 때문이죠.
명확한 버저닝(versioning) 없이는, 혼자서만 배포를 하고 버전 관리를 하는 상황에서도 생각보다 어렵습니다. 협업을 하는 경우에는 더 힘들구요. 버저닝을 신뢰성 있게 하는 방법에 대해서 고민에 빠지게 됩니다. 바로 이때 눈에 띄인게 Git-Flow입니다.
처음에는 master와 develop 브랜치가 존재합니다. 물론 develop 브랜치는 master에서부터 시작된 브랜치입니다. develop 브랜치에서는 상시로 버그를 수정한 커밋들이 추가됩니다. 새로운 기능 추가 작업이 있는 경우 develop 브랜치에서 feature 브랜치를 생성합니다. feature 브랜치는 언제나 develop 브랜치에서부터 시작하게 됩니다. 기능 추가 작업이 완료되었다면 feature 브랜치는 develop 브랜치로 merge 됩니다. develop에 이번 버전에 포함되는 모든 기능이 merge 되었다면 QA를 하기 위해 develop 브랜치에서부터 release 브랜치를 생성합니다. QA를 진행하면서 발생한 버그들은 release 브랜치에 수정됩니다. QA를 무사히 통과했다면 release 브랜치를 master와 develop 브랜치로 merge 합니다. 마지막으로 출시된 master 브랜치에서 버전 태그를 추가합니다.
한마디가 좀 길죠? 미안합니다.
위 줄글은 우아한 기술 블로그에서 그대로 가져온 내용입니다. 줄글이어서 가독성은 안좋지만 정말 이렇게 쉽게 git-flow를 설명하고 있는 글은 이것 이상으로 보지 못하여서 가져왔습니다.
자, 그러면 위 문장들을 하나하나 뜯어보겠습니다.
처음에는 master
와 develop
브랜치가 존재합니다. 물론 develop
브랜치는 master
에서부터 시작된 브랜치입니다.
develop
브랜치에서는 상시로 버그를 수정한 커밋들이 추가됩니다. 새로운 기능 추가 작업이 있는 경우 develop
브랜치에서 feature
브랜치를 생성합니다. feature
브랜치는 언제나 develop
브랜치에서부터 시작하게 됩니다. 기능 추가 작업이 완료되었다면 feature
브랜치는 develop
브랜치로 merge 됩니다.
develop
에 이번 버전에 포함되는 모든 기능이 merge 되었다면 QA를 하기 위해 develop
브랜치에서부터 release
브랜치를 생성합니다. QA를 진행하면서 발생한 버그들은 release
브랜치에 수정됩니다.
QA를 무사히 통과했다면 release
브랜치를 master
와 develop
브랜치로 merge 합니다. 마지막으로 출시된 master
브랜치에서 버전 태그를 추가합니다.
이 정도로 나누어서 읽어보면 훨씬 쉽게 눈에 들어올 것 같습니다.
production(main/master)
: 현재 출시 완료된 branchdevelop
: 다음 출시 버전을 개발하는 branch *default branchfeature/**
: 기능 개발을 위한 branchfeature/member-api
release/1.1.1
: 출시 전 QA를 위한 branchhotfix/**
: 출시된 버전의 버그를 수정하는 branchhotfix/login
feature/login
, fix/mypage-layout
버전에 대한 신뢰성이 생긴다.
질서없이 PR을 main에 모두 머지 한다면, 현재 배포한 버전이 어디까지의 PR이 담겼는지 모릅니다.
설령, 부분적으로 리팩토링 한 PR을 develop에 머지해가면서 리팩토링을 신나게 진행하던 중에, 현재 배포되어있는 버전(구 버전 레거시)에서 에러가 났다면!!!! git-flow라면 어디서 부터 추적해야할지 막막할 것입니다.
사람은 망각의 동물이기 때문에, 이 작업을 하고 배포를 했었는지 조차 기억나지 않으며, 현재 배포된 버전에 어디 PR까지 적용이 되어서 올라갔는지 모릅니다.
사후 처리를 바르고, 빠르게 하기 위해 git-flow를 써야합니다.
* 토이 프로젝트나, 처음하는 개발 프로젝트, 배포를 안할 프로젝트나 배포 하기 전 단기 Sprint 기간이라면 Github-Flow를 추천힙니다.
전에 제가 작성한 Github-Flow 에 대한 설명이 궁금하다면 읽어보시죠.
새 브랜치 생성은 develop
branch를 기준으로 한다. (*default branch)
feature
branch에서 작업을 신나게 한다. (ex. feature/login
)
🔥주의🔥 현재 브랜치를 생성한 후에 develop
에 머지된 PR이 있어서 develop
에 있는 내용을 현재 브랜치로 끌어와야할때는, 현재 브랜치 기준으로 rebase를 한다.
(Merge 아님 주의) rebase 하면서 충돌 있으면 충돌 풀고, force push 한다.
→ rebase가 아나라 merge의 방식으로 하면 다른 PR에서 작성한 커밋들이 내 브랜치에 중복해서 찍힌다. (심지어 내 PR에 같이 올라가는데, 이게 너무 역겹다.)
* rebase란?
rebase 하려는 브랜치(develop)의 HEAD와 현재 내 브랜치의 HEAD를 같게 만든 후, (충돌을 풀고) 내가 현재 브랜치에서 작성한 커밋들을 HEAD 위에 쌓는 방식이다.
깃허브에서 develop으로의 merge를 위해 PR을 생성한다. [코드리뷰]
develop
<- feature/login
깃허브에서 squash & merge 방식으로 머지하기
into develop
from fix/141-fix-remote-config
작업을 어느 정도해서 배포 할 때가 되었으면, develop
브랜치를 기준으로 release
브랜치를 생성한다. (ex. release/1.1.12
)
release
브랜치에서 버저닝과 QA를 진행한다.
production
← release
깃허브 PR로 머지
develop
← production
깃허브 PR로 머지
develop
와 production
의 HEAD를 같게 만든다.
방법은 간단합니다.
현재 배포된 레거시가 가득한 구 버전을 develop
브랜치로 두고서, refactoring
브랜치를 새로 팝니다.
리팩토링에 해당되는 작업들은 develop
이 아닌 refactoring
브랜치로 머지합니다. 리팩토링(혹은 마이그레이션)을 다 진행했다면, refactoring
브랜치를 기준으로 release
브랜치를 파고 QA 진행 후에 production
에 머지하면 됩니다.
이렇게 하지 않았을 때 생기는 불상사는, github-flow로 리팩토링을 진행하는 도중, 현재 배포된 버전에서 에러가 발생했을시 수정할 수 없다는 치명적인 부분입니다.
왜냐? 이미 리팩토링 중인 상태이기 때문에 이전 버전이 살아 있다는 보장도 없거와, 레거시 코드와 리팩한 코드가 혼재되어 있어 제대로 작동하지 않을 가능성이 높아요. 그래서 현재 배포된 브랜치를 그대로 살려두고, 리팩토링 브랜치는 따로 두어야합니다.
프로젝트는 배포 이전과 이후로 나뉜다고 합니다. 흔히 프로젝트의 성공과 실패를 배포로 나누기도 하죠.
배포 이전에는 빠른 개발을 위해 github-flow를 사용하는 것이 맞아요. 빠르게 feature 개발을 쳐내야하니까요.
하지만 사용자가 있는 서비스는 달라요. 이전 버전으로 쉽게 돌아갈 수도 있어야 하고, 버전에 대한 신뢰성도 있어야해요. 팀의 규모가 커지게 되면 배포를 한 사람이서 전담하기도 힘들구요. 이러한 기타 등등의 많은 이유로 배포 이후에는 git-flow를 장려합니다.