Git 실전 · 브랜치 전략 · 이론
전략을 고르는 질문은 하나다
한 줄 요약
브랜치 전략을 고르는 질문은 "어떤 그림이 예쁜가"가 아니라 "장애가 났을 때 이 저장소에서 원인 커밋을 어떻게 찾을 계획인가"다.
왜 이게 필요했나
Git Flow, GitHub Flow, Trunk-Based 는 서로 다른 배포 주기를 전제로 만들어졌다. 전제를 무시하고 이름만 가져오면 매일 마찰이 생긴다.
| 전략 | 브랜치 종류 | 병합 빈도 | 맞는 곳 |
| --- | --- | --- | --- |
| Git Flow | 5종 이상 | 주 1~2회 | 정기 릴리스, 모바일 앱, 패키지 |
| GitHub Flow | 2종 | 일 1~3회 | CD 를 실천하는 SaaS |
| Trunk-Based | 1~2종 | 일 5~10회 이상 | 브랜치 수명 1일 이내, Feature Flag 필수 |
Trunk-Based 는 CI 요구 수준이 매우 높다. Feature Flag 없이 시작하면 미완성 코드가 그대로 배포된다. 반대로 GitOps 환경에서 Git Flow 의 release 브랜치는 자동 동기화 패턴과 마찰이 크다. 그래서 GitOps 를 쓴다면 GitHub Flow 또는 Trunk-Based 를 권한다.
공통 규칙 하나는 어디서나 통한다. 3일 이상 살아 있는 feature 브랜치는 merge conflict 의 온상이다.
어떻게 동작하나
병합 방식은 세 가지고 각각 다른 것을 남긴다.
--no-ff머지: 부모가 둘인 커밋이 생겨 "이 묶음이 언제 통합됐는가"가 이력에 남는다.- 빨리 감기(fast-forward): 머지 커밋 없이 브랜치 포인터만 앞으로 간다. 이력은 일직선이 되지만 통합 시점은 남지 않는다.
- 스쿼시 머지: 커밋을 하나로 눌러 붙인다. 깔끔해 보이지만 세 가지를 가져간다. 이분 탐색 해상도가 PR 크기로 고정되고, 체리픽 정밀도를 잃고, Git 입장에서 그 브랜치는 머지된 적이 없다(새 커밋의 부모가 원본 브랜치가 아니기 때문). 그래서 스쿼시 머지를 쓰는 팀은 머지 직후 소스 브랜치를 지운다는 규칙을 함께 가져가야 한다.
체리픽은 저장된 diff 를 오려 붙이는 것이 아니다. Git 은 애초에 diff 를 저장하지 않고 스냅숏만 저장한다. 그래서 대상 커밋의 부모 트리를 공통 조상으로 삼아 삼자 병합을 수행한다. 체리픽이 충돌하는 이유가 여기에 있다.
현장에서 만나는 모습
핫픽스를 릴리스 브랜치에서 고치고 main 에 반영하지 않아 다음 릴리스에서 같은 버그가 되살아나는 일은 어느 팀에나 한 번은 생긴다. 릴리스 브랜치를 운영한다면 "핫픽스는 반드시 main 으로도 가져온다"가 정책 문장으로 적혀 있어야 한다.
태그도 마찬가지다. 가벼운 태그는 그냥 포인터라 누가 언제 왜 붙였는지 남지 않는다. 릴리스 태그는 -a 로 주석을 붙여야 나중에 조사에 쓸 수 있다.
다음 실습에서 할 것
저장소를 처음부터 만들고, 기능 브랜치를 --no-ff 로 병합해 통합 기록을 남기고, 작은 수정은 빨리 감기로 올리고, 릴리스 태그를 붙이고, 릴리스 브랜치의 핫픽스를 체리픽으로 main 에 반영한 뒤, 병합된 브랜치만 골라 정리합니다. 마지막에는 이 저장소의 실제 숫자를 세어 전략 비교표를 씁니다.