Git Flow, GitHub Flow, trunk-based: 어떤 Git 워크플로를 고를까
Git Flow는 main과 develop이라는 장기 브랜치와 feature, release, hotfix라는 임시 브랜치를 쓰는 모델로, 계획된 버전 릴리스가 있는 제품에 맞습니다. 웹 서비스처럼 자주 배포하는 경우에는 보통 GitHub Flow(main에서 짧은 브랜치를 떼고 pull request로 합치기)나 feature flag를 쓰는 trunk-based development가 더 간단합니다.
Git 워크플로란 무엇이고 왜 중요한가
Git 워크플로는 팀이 어떤 브랜치를 쓰고, 어디서 만들고, 어디로 합치고, 무엇을 릴리스 준비 완료로 볼지에 대해 합의한 규칙입니다. Git 자체는 이런 것을 강요하지 않습니다. 합의가 없으면 모두 제각각 작업하고, 한 달 뒤에는 수십 개의 브랜치가 생기고, 무엇이 배포되었는지 알 수 없으며, 병합은 몇 시간짜리 충돌 해결이 됩니다.
가장 많이 알려진 모델은 Git Flow, GitHub Flow(Feature Branch Workflow의 한 형태), trunk-based development입니다. 이것들은 "옛것과 새것"이 아니라 조건에 따라 고르는 도구입니다. 릴리스를 얼마나 자주 내보내는지, 동시에 몇 개의 버전을 지원하는지, 자동 테스트가 얼마나 잘 갖춰져 있는지가 기준입니다.
Git Flow: develop과 release가 있는 모델
Git Flow는 2010년 Vincent Driessen이 설명했습니다. 이 모델은 두 개의 장기 브랜치를 중심으로 합니다.
- main(오래된 프로젝트에서는 master) — 출시된 코드만 둡니다. 이 브랜치의 모든 커밋은 릴리스에 해당하며 태그로 표시됩니다;
- develop — 다음 릴리스에 들어갈 완성된 기능을 모으는 통합 브랜치입니다.
그리고 세 종류의 임시 브랜치가 있습니다.
| 브랜치 | 만드는 기준 | 합치는 곳 | 용도 |
|---|---|---|---|
| feature/* | develop | develop | 새 기능 개발 |
| release/* | develop | main과 develop | 버전 안정화: 버그 수정, 버전 번호, 문서 |
| hotfix/* | main | main과 develop | 출시된 버전의 긴급 수정 |
순수 Git으로 하는 Git Flow
기능 하나와 릴리스 하나의 흐름은 다음과 같습니다.
# 기능
git switch develop
git switch -c feature/cart-discounts
# ... 커밋 ...
git switch develop
git merge --no-ff feature/cart-discounts
git branch -d feature/cart-discounts
# 릴리스
git switch -c release/1.4.0 develop
# ... 버전 수정과 버그 수정 ...
git switch main
git merge --no-ff release/1.4.0
git tag -a v1.4.0 -m "Release 1.4.0"
git switch develop
git merge --no-ff release/1.4.0
git branch -d release/1.4.0
이 모델에서 --no-ff는 핵심입니다. fast-forward가 가능한 경우에도 병합 커밋을 만들어서, 어떤 커밋들이 하나의 기능이나 릴리스에 속했는지 히스토리에 그대로 남깁니다.
git-flow 확장 도구
이 명령들을 손으로 입력하지 않으려면 git-flow 도구를 쓸 수 있습니다. 요즘은 보통 유지보수되는 포크인 git-flow-avh를 설치합니다. 패키지 관리자에서는 이 이름으로 찾을 수 있습니다. 이 도구는 하위 명령을 추가합니다.
git flow init -d
git flow feature start cart-discounts
git flow feature finish cart-discounts
git flow release start 1.4.0
git flow release finish 1.4.0
git flow hotfix start 1.4.1
git flow hotfix finish 1.4.1
git flow init은 브랜치 이름과 접두사를 묻습니다(-d를 붙이면 기본값을 씁니다). finish 명령은 필요한 병합을 모두 수행하고, 태그를 달고, 임시 브랜치를 삭제합니다. 많은 GUI 클라이언트도 버튼으로 Git Flow를 지원합니다.
Git Flow의 장단점
장점: 구조가 명확하고, 코드의 상태가 분명합니다(main은 고객이 쓰는 것, develop은 다음 버전). 릴리스 안정화 단계가 따로 있고, 핫픽스 관리가 편합니다. 앱 스토어 심사를 거치거나 고객 장비에 설치되는 모바일 앱, 상용 소프트웨어, 라이브러리, 펌웨어에 잘 맞습니다.
단점: 브랜치와 병합이 많고, 오래 사는 기능 브랜치는 develop과 점점 어긋나며, release와 hotfix의 이중 병합을 잊기 쉽습니다. 하루에 여러 번 배포하는 웹 서비스에서는 release 브랜치가 번거로운 형식주의가 됩니다. 모델을 처음 제안한 저자도 2020년에 자신의 글에 메모를 달아, 지속적으로 배포하는 소프트웨어라면 GitHub Flow 같은 단순한 방식을 살펴보라고 권했습니다.
GitHub Flow: 짧은 브랜치와 pull request
GitHub Flow는 Feature Branch Workflow를 가볍게 만든 형태입니다. 규칙은 몇 줄이면 됩니다.
- main은 항상 동작하는 상태이며 배포할 수 있어야 합니다.
- 모든 작업은 main에서 떼어 낸, 의미가 분명한 이름의 브랜치에서 합니다.
- 브랜치를 자주 push하고, 논의를 위해 pull request(merge request)를 엽니다.
- 리뷰와 초록불(통과한) CI를 거친 뒤 main에 합칩니다.
- main에 합쳐진 것은 바로 배포합니다.
git switch main
git pull
git switch -c feature/new-payment
# ... 명확한 메시지로 커밋 ...
git push -u origin feature/new-payment
# pull request를 열고, 리뷰와 CI를 통과한 뒤 main에 병합
장점: 오래 사는 브랜치가 하나뿐이고 규칙이 적으며, 아이디어에서 운영까지의 경로가 짧고, 코드 리뷰가 자연스럽게 들어갑니다. 단점: 여러 버전을 지원하거나 릴리스 전에 안정화하는 장소가 없고, 좋은 자동 테스트가 없다면 "main은 항상 준비되어 있다"는 말은 희망 사항일 뿐입니다.
추가로 staging, production 같은 환경 브랜치를 두는 방식을 GitLab Flow라고 부르기도 합니다. 운영 배포가 즉시 일어나지 않고 일정에 맞추거나 사람이 확인한 뒤에 일어날 때 유용합니다.
Trunk-based development
Trunk-based development에서는 모든 개발자가 하나의 브랜치(trunk, 즉 main)에 자주, 최소 하루에 한 번 통합합니다. 브랜치를 만든다고 해도 며칠이 아니라 몇 시간만 삽니다. 아직 끝나지 않은 기능은 main에 들어가지만 feature flag 뒤에 숨깁니다.
if (featureFlags.isEnabled("new-checkout", user)) {
renderNewCheckout();
} else {
renderOldCheckout();
}
가장 큰 장점은 무거운 충돌이 거의 없다는 것입니다. 변경이 작고, 모두가 맥락을 기억하는 동안 바로 합쳐집니다. 이 모델은 말 그대로 지속적 통합을 실천하며, 빠른 CI와 높은 테스트 커버리지를 갖춘 성숙한 팀에 잘 맞습니다. 릴리스 브랜치는 허용되지만, 출시 직전에 trunk에서 떼어 내고, 수정은 먼저 trunk에 한 뒤 cherry-pick으로 옮깁니다.
단점: 규율과 인프라가 필요합니다. 빠른 테스트, flag, 모니터링이 있어야 합니다. 초보자에게는 매일 main에 커밋하는 것이 무섭게 느껴지고, 약한 CI는 trunk를 계속 깨진 상태로 만듭니다. flag도 제때 지워야 합니다. 그렇지 않으면 코드에 죽은 분기가 쌓입니다.
Git Flow, GitHub Flow, trunk-based 비교
| 기준 | Git Flow | GitHub Flow | Trunk-based |
|---|---|---|---|
| 장기 브랜치 | main과 develop | main | main(trunk) |
| 기능 브랜치 수명 | 며칠에서 몇 주 | 며칠 | 몇 시간 또는 브랜치 없음 |
| 릴리스 주기 | 계획된 버전 | 병합할 때마다 | 지속적 |
| 여러 버전 지원 | 가능(태그와 hotfix) | 불편함 | release 브랜치로 가능 |
| CI와 테스트 요구 | 중간 | 높음 | 매우 높음 |
| 큰 충돌 위험 | 높음 | 중간 | 낮음 |
| 진입 장벽 | 규칙을 익혀야 함 | 최소 | 문화와 feature flag 필요 |
| 전형적인 프로젝트 | 모바일 앱, 라이브러리, 설치형 제품 | 웹 서비스, 오픈 소스, 스타트업 | 자주 배포하는 대형 제품 |
팀에 맞는 Git 워크플로 고르기
다음 질문에 답해 보세요.
- 출시된 버전을 여러 개 지원해야 하나요? 고객이 2.x와 3.x를 쓰고 둘 다 수정이 필요하다면 Git Flow나 release 브랜치를 쓰는 trunk-based가 맞습니다.
- 얼마나 자주 배포하나요? 분기마다라면 Git Flow도 괜찮습니다. 매일이라면 GitHub Flow나 trunk-based를 고르세요.
- 테스트를 얼마나 신뢰하나요? CI가 느리고 커버리지가 약하다면, 리뷰를 필수로 한 GitHub Flow로 시작해서 점차 브랜치 수명을 줄이세요.
- 팀의 규모와 경험은 어떤가요? 작은 팀이나 초보자에게는 GitHub Flow가 가장 간단합니다. "main은 항상 동작해야 한다"는 한 가지 규칙과 모든 변경에 대한 pull request만 있으면 됩니다.
실용적인 조언: "그게 일반적이니까" Git Flow를 고르지 마세요. 가장 단순하면서 지금 문제를 해결하는 과정으로 시작하고, 안정화 단계나 오래된 버전 지원처럼 구체적인 고통이 생길 때 복잡도를 더하세요.
어떤 워크플로에서든 통하는 규칙
- 서버에서 main을 보호하세요. 직접 push를 금지하고, 리뷰와 초록불 CI를 병합 조건으로 두세요.
- 브랜치 이름 규칙을 정하세요:
feature/,fix/,hotfix/와 이슈 번호. - 브랜치는 짧게 유지하세요. 오래 살수록 병합이 아파집니다. 주기적으로 main을 merge나 rebase로 가져오세요.
- pull request의 병합 방식(merge commit, squash, rebase) 하나를 골라 일관되게 쓰세요.
- 릴리스에는 의미 있는 버전 번호(semantic versioning)를 붙인 주석 태그를 다세요.
모델별 핫픽스: 같은 버그, 세 가지 절차
모델의 차이는 운영 환경의 긴급 수정에서 가장 잘 드러납니다.
Git Flow에서는 main에서 hotfix/1.4.1을 만들고, 수정 후 이 브랜치를 태그 v1.4.1과 함께 main에 합치고, 반드시 develop에도(또는 release 브랜치가 있다면 그곳에도) 합칩니다. 두 번째 병합을 잊는 것은 고전적인 실수입니다. 다음 릴리스에서 버그가 되살아납니다.
git switch -c hotfix/1.4.1 main
git commit -am "할인 계산 수정"
git switch main && git merge --no-ff hotfix/1.4.1
git tag -a v1.4.1 -m "Hotfix 1.4.1"
git switch develop && git merge --no-ff hotfix/1.4.1
GitHub Flow에서는 핫픽스가 main에서 떼어 낸 평범한 짧은 브랜치이며, 리뷰에서 우선순위를 높입니다. 병합 후에는 같은 자동 배포로 운영에 나가며, 별도의 절차는 없습니다.
trunk-based에서는 수정을 먼저 trunk에 커밋하고, 운영에서 release 브랜치가 돌고 있다면 git cherry-pick -x <해시>로 옮깁니다. -x 플래그는 원본 커밋을 가리키는 줄을 메시지에 추가해서 히스토리에서 연결이 보이게 합니다. 문제가 새 기능 때문이라면, 코드를 건드리지 않고 feature flag를 끄는 것만으로 충분한 경우도 많습니다.
면접에서 "어떤 Git 워크플로를 써 봤나요?"라는 질문에 답하기
면접관은 이름을 아는지보다 절충점을 이해하는지를 봅니다. 좋은 답변은 모델의 이름을 말하고, 그 안에서 브랜치와 리뷰가 어떻게 동작했는지 설명하고, 왜 그 프로젝트에 맞았는지 말하는 것입니다. 예를 들면 이렇습니다. "GitHub Flow를 썼습니다. main에서 브랜치를 떼고, 리뷰와 CI가 필수인 pull request를 올리고, 병합 후 자동 배포했습니다. 일주일에 여러 번 배포해서 release 브랜치는 오히려 속도를 떨어뜨리므로 Git Flow는 쓰지 않았습니다." 팀이 커지면 무엇을 바꾸겠느냐는 질문까지 답할 수 있으면 더 좋습니다.
정리
Git Flow는 develop, release, hotfix 브랜치를 갖춘 엄격한 모델로, 계획된 버전과 여러 릴리스 지원이 필요한 제품에 좋습니다. GitHub Flow는 더 단순하며 대부분의 웹 프로젝트에 맞고, trunk-based development는 최고의 속도를 주지만 성숙한 CI와 feature flag를 요구합니다. 워크플로는 유행이 아니라 릴리스 빈도와 테스트 품질에 맞춰 고르세요. 세 방식의 명령과 흔한 실수는 Git 강의에서 실제 저장소로 실습합니다.
자주 묻는 질문
Git Flow는 구식인가요?
아니요. 다만 적용 범위가 좁아졌습니다. 지속적으로 배포하는 웹 서비스에는 과하지만, 모바일 앱, 라이브러리, 설치형 소프트웨어에서는 여전히 편리합니다.
릴리스가 잦다면 develop 브랜치가 필요한가요?
보통은 필요 없습니다. develop은 main과 중복되고 병합을 하나 더 만듭니다. 릴리스가 잦다면 main과 짧은 기능 브랜치만으로 충분합니다.
Git Flow에서 rebase를 써도 되나요?
아직 push하지 않았거나 나만 쓰는 기능 브랜치에 develop을 rebase로 가져오는 것은 괜찮습니다. 하지만 고전적인 모델에서 develop과 main으로의 최종 병합은 히스토리 구조를 지키기 위해 merge --no-ff로 합니다.
feature flag가 오래 사는 기능 브랜치보다 나은 이유는 무엇인가요?
코드가 바로 main에 통합되어 나머지 코드베이스와 어긋나지 않으며, 기능을 켜고 끄는 결정이 코드 되돌리기 없이 따로 이루어질 수 있습니다.
Git Flow에서 GitHub Flow로 옮기려면 어떻게 하나요?
진행 중인 릴리스를 마무리하고, develop을 main에 합치고, CI가 main에서 배포하도록 설정을 바꾼 뒤, develop을 삭제하세요. 이후 새 브랜치는 main에서만 만드세요.