Git Survival Guide — Article 04

브랜치 전략은 팀의
거울이다

Git Flow, GitHub Flow, Trunk-Based Development. 세 가지 철학, 세 가지 문화. 당신의 팀은 어떤 거울을 보고 있는가.

Part I

세 가지 철학

브랜치 전략은 단순한 기술 선택이 아니다. 팀이 코드를 어떻게 만들고, 어떻게 검증하고, 어떻게 배포하는지를 규정하는 문화적 결정이다. 2010년 Vincent Driessen이 "A successful Git branching model"을 발표한 이후, Git 세계는 세 가지 거대한 철학으로 나뉘었다. 어느 것이 옳고 그른 것이 아니다. 팀의 규모, 배포 주기, 제품의 성격에 따라 답이 달라진다.

Strategy 01

Git Flow

main + develop + feature + release + hotfix. 다섯 종류의 브랜치가 엄격한 계층 구조를 형성한다. 릴리즈 주기가 길고, 배포 전 QA가 필수인 환경에 적합하다.

Strategy 02

GitHub Flow

main + feature branch + PR. 브랜치는 두 종류뿐이다. feature에서 작업하고, PR로 리뷰받고, main에 머지하면 즉시 배포. 단순함이 핵심이다.

Strategy 03

Trunk-Based

main에 직접 커밋하거나, 1~2일짜리 짧은 브랜치. Feature Flag로 미완성 코드를 숨기고, CI/CD 파이프라인이 모든 것을 자동으로 검증한다.

Git Flow

Vincent Driessen, 2010 — 엄격한 계층의 미학

main은 프로덕션과 1:1로 동기화된다. develop은 다음 릴리즈의 통합 브랜치다. 개발자는 feature 브랜치에서 작업하고, develop에 머지한다. 릴리즈가 다가오면 release 브랜치를 만들어 최종 QA를 진행하고, main에 머지하면서 태그를 단다. 프로덕션에 긴급 버그가 발생하면 hotfix 브랜치로 패치한다. 모바일 앱, 패키지 소프트웨어, 삼성 SDS, LG CNS 등 대기업 SI 프로젝트에서 여전히 주류다. 릴리즈 주기가 2주 이상이고, QA 팀이 별도로 존재하는 조직에 적합하다.

단점: 브랜치 복잡도 높음, CI/CD 파이프라인 설정 복잡, CD에 부적합
GitHub Flow

Scott Chacon, 2011 — 단순함의 승리

main 브랜치 하나와 feature 브랜치만 존재한다. 개발자는 main에서 feature 브랜치를 만들고, 작업이 끝나면 Pull Request를 연다. 코드 리뷰를 거쳐 main에 머지되면 즉시 프로덕션에 배포된다. 웹 서비스, SaaS 제품에 가장 적합하다. 한국에서는 토스, 당근마켓, 배달의민족 등 테크 기업이 이 전략을 채택하고 있다.

적합: 웹 서비스, SaaS, 빠른 배포 주기, 소규모~중규모 팀
Trunk-Based Development

Google, Meta — 극단적 단순함

모든 개발자가 main(trunk)에 직접 커밋하거나, 최대 1~2일 내에 머지되는 극히 짧은 브랜치를 사용한다. 미완성 코드는 Feature Flag로 숨긴다. Google의 모노레포(10억 줄 이상의 코드)가 이 전략으로 운영된다. CI/CD 파이프라인의 자동화 수준이 매우 높아야 하며, 테스트 커버리지가 낮으면 재앙이 된다. 한국에서는 카카오뱅크, 토스 일부 팀이 도입을 진행 중이다.

필수: Feature Flags, 높은 테스트 커버리지, 강력한 CI/CD

브랜치 전략을 바꾸는 것은
코드를 바꾸는 것이 아니라 문화를 바꾸는 것이다.

Part II

브랜치 전략 선택 가이드

"우리 팀에 맞는 전략이 뭔가?"라는 질문에 정답은 없다. 하지만 팀 규모, 배포 빈도, 제품 특성 세 가지 축으로 선택지를 좁힐 수 있다. 중요한 것은 전략을 선택한 후 팀 전체가 동일한 규칙을 따르는 것이다. 전략의 혼재가 전략의 부재보다 위험하다.

팀 규모별 권장 전략
팀 규모 권장 전략 이유
1~3명 GitHub Flow 리뷰어가 부족하므로 프로세스가 단순해야 한다. Trunk-Based도 가능하지만, PR 기반 리뷰 습관을 초기에 잡는 것이 장기적으로 유리하다.
4~10명 GitHub Flow 또는 Trunk-Based 배포 주기에 따라 갈린다. 일 1회 이상 배포하면 Trunk-Based, 주 1~2회면 GitHub Flow가 적합하다.
10명 이상 Trunk-Based (Feature Flag 필수) 장기 브랜치는 머지 지옥을 초래한다. Feature Flag로 기능 단위 격리가 필수다. Google, Meta의 선택에는 이유가 있다.
엔터프라이즈 Git Flow 또는 변형 QA 프로세스, 감사 추적, 릴리즈 승인이 필요한 규제 산업(금융, 의료)에서는 Git Flow의 엄격함이 오히려 장점이다.
배포 빈도별 권장 전략
배포 빈도 권장 전략 핵심 요건
주 1회 이하 Git Flow release 브랜치에서 충분한 QA 기간 확보. 핫픽스 프로세스 정의 필수.
주 2~5회 GitHub Flow 자동화 테스트 + CI 파이프라인. PR 머지 = 배포 트리거.
일 수십 회 Trunk-Based Feature Flag 인프라, 카나리 배포, 자동 롤백. 테스트 커버리지 80% 이상.
의사결정 흐름
Q1 릴리즈 주기가 2주 이상이고, QA팀이 별도로 있는가?
YES -> Git Flow / NO -> 다음 질문
Q2 하루에 여러 번 배포하며, Feature Flag 인프라가 있는가?
YES -> Trunk-Based / NO -> 다음 질문
Q3 PR 기반 코드 리뷰 문화가 정착되어 있는가?
YES -> GitHub Flow / NO -> GitHub Flow (도입하면서 시작)
전략 비교 요약
항목 Git Flow GitHub Flow Trunk-Based
브랜치 수 5종 2종 1종 (+ 초단기)
복잡도 높음 낮음 낮음 (인프라 복잡)
배포 주기 주~월 일~주 시간~일
머지 충돌 잦음 보통 적음
필수 인프라 QA 환경 CI/CD Feature Flag + CI/CD
한국 사례 삼성 SDS, LG CNS 토스, 당근, 배민 카카오뱅크 (도입 중)
Part III

PR을 잘 쓰는 사람이 팀을 바꾼다

Pull Request는 코드를 머지하는 관문이 아니다. 팀의 지식을 공유하는 채널이다. Google의 연구에 따르면, 리뷰어가 PR을 읽는 데 걸리는 시간은 코드 라인 수에 비례하지 않는다. 200~400줄이 리뷰 효율의 최적 구간이다. 400줄을 넘으면 리뷰어의 집중도가 급격히 떨어지고, "LGTM"(Looks Good To Me)이라는 고무도장만 찍힌다. 1,000줄짜리 PR은 리뷰가 아니라 통과 의례다.

Rule 01

작게 쪼개라

한 PR에 하나의 관심사. 리팩토링과 기능 추가를 한 PR에 담지 않는다. "이것도 고쳤습니다"는 리뷰어에 대한 폭력이다. Google 내부 데이터: 200줄 이하 PR의 머지 속도가 800줄 이상 대비 3배 빠르다.

Rule 02

맥락을 제공하라

"왜"를 먼저 쓴다. 리뷰어는 코드를 읽기 전에 PR 본문을 읽는다. Jira/Linear 티켓 번호, 변경 이유, 영향 범위가 없는 PR은 리뷰할 수 없다.

PR 템플릿 — 한국 팀 실전용
## What (무엇을 변경했는가)
주문 목록 API에 cursor 기반 페이지네이션을 추가했습니다.
관련 티켓: JIRA-1234 / Linear FE-567
## Why (왜 변경했는가)
기존 offset 방식이 10만 건 이상에서 3초 이상 응답 지연을 발생시켰습니다.
QA팀 리포트: QA-2026-089
## How to test (어떻게 테스트하는가)
1. pytest tests/api/test_orders.py -v 실행
2. /v1/orders?cursor=xxx&limit=20 호출 확인
3. 10만 건 데이터에서 응답 시간 500ms 이하 확인
## Screenshots / Logs
변경 전: avg 3.2s / 변경 후: avg 0.3s (벤치마크 첨부)

한국 팀에서는 Jira, Linear, Notion의 티켓 번호를 PR 제목에 포함하는 것이 관례다. 카카오는 사내 Git 가이드에서 PR 제목 형식을 [JIRA-번호] 변경 요약으로 규정하고 있고, 네이버 Works도 유사한 컨벤션을 따른다. 형식이 중요한 것이 아니라, 팀 전체가 동일한 형식을 따르는 것이 중요하다.

Draft PR — 완성 전에 피드백받기

Draft PR은 "아직 완성되지 않았지만 방향을 확인받고 싶다"는 신호다. 대규모 리팩토링이나 아키텍처 변경을 시작할 때, 3일 동안 혼자 작업한 뒤 "이건 좀..."이라는 리뷰를 받는 것보다, 첫날 Draft PR을 열어서 설계 방향을 합의하는 것이 합리적이다. GitHub에서는 "Ready for review" 버튼을 누르기 전까지 머지가 차단된다.

Draft PR 생성 # Draft PR 생성 (GitHub CLI) gh pr create --draft --title "[JIRA-1234] 주문 API 페이지네이션 변경" # Draft → Ready for review 전환 gh pr ready
Conventional Commits — 커밋 메시지 자동화

커밋 메시지에 규칙이 없으면 git log는 곧 쓰레기통이 된다. Conventional Commitsfeat:, fix:, chore: 등의 접두사로 커밋의 성격을 명시하는 표준이다. 이 규칙을 도구로 강제하면 사람의 의지에 의존하지 않아도 된다.

Conventional Commits 설정 # commitizen — 대화형 커밋 메시지 생성 npm install -D commitizen cz-conventional-changelog # package.json에 추가 "config": { "commitizen": { "path": "cz-conventional-changelog" } } # commitlint — 커밋 메시지 규칙 검증 npm install -D @commitlint/cli @commitlint/config-conventional # commitlint.config.js module.exports = { extends: ['@commitlint/config-conventional'] }; # husky — Git Hooks로 자동 강제 npx husky add .husky/commit-msg 'npx commitlint --edit $1'
Conventional Commits 예시 feat: 주문 목록 cursor 페이지네이션 추가 fix: 결제 금액 소수점 반올림 오류 수정 refactor: OrderService 의존성 주입 구조 변경 docs: API 엔드포인트 문서 업데이트 test: 주문 생성 통합 테스트 추가 chore: ESLint 설정 업데이트 ci: GitHub Actions 캐시 설정 추가 # BREAKING CHANGE 포함 시 feat!: 주문 API 응답 형식 변경 (v1 → v2)

좋은 PR은 코드를 설명하지 않는다.
결정을 설명한다.

Part IV

merge vs rebase vs squash
히스토리 전쟁

"merge할까, rebase할까?" — Git을 쓰는 모든 팀이 한 번은 겪는 논쟁이다. 정답은 없지만 원칙은 있다. 각 전략이 만들어내는 히스토리의 모양이 다르고, 그 모양이 팀의 작업 방식에 영향을 준다. 히스토리의 가독성과 추적 가능성 사이의 트레이드오프를 이해해야 한다.

Merge

토폴로지 보존

두 브랜치의 히스토리를 그대로 보존하고 머지 커밋을 생성한다. 브랜치가 언제 분기되고 합류했는지 그래프로 확인할 수 있다. 히스토리가 복잡해지지만, 작업의 맥락이 보존된다.

Rebase

선형 히스토리

feature 브랜치의 커밋을 main 위에 재배치한다. 히스토리가 일직선이 되어 읽기 쉽다. 단, Golden Rule: 이미 push한 커밋은 절대 rebase하지 않는다.

Squash Merge

PR 단위 정리

feature 브랜치의 모든 커밋을 하나로 압축하여 main에 머지한다. main의 히스토리가 PR 단위로 깔끔해진다. 개별 커밋 히스토리는 사라진다.

merge — 토폴로지 보존 # feature 브랜치를 main에 머지 (머지 커밋 생성) git checkout main git merge feature/order-api # 결과 히스토리 * Merge branch 'feature/order-api' |\ | * feat: 주문 목록 API 추가 | * feat: 주문 상세 API 추가 | * test: 주문 API 테스트 추가 |/ * 이전 커밋
rebase — 선형 히스토리 # feature 브랜치를 main 위로 재배치 git checkout feature/order-api git rebase main git checkout main git merge feature/order-api # fast-forward # 결과 히스토리 (일직선) * feat: 주문 목록 API 추가 * feat: 주문 상세 API 추가 * test: 주문 API 테스트 추가 * 이전 커밋
squash merge — PR 단위 정리 # feature 브랜치의 모든 커밋을 하나로 압축하여 머지 git checkout main git merge --squash feature/order-api git commit -m "feat: 주문 API 추가 (#123)" # 결과 히스토리 (1커밋 = 1PR) * feat: 주문 API 추가 (#123) * 이전 커밋
Rebase의 Golden Rule
  1. 이미 push한 커밋은 절대 rebase하지 않는다. 다른 개발자가 같은 커밋을 기반으로 작업 중일 수 있다
  2. rebase는 커밋의 SHA를 변경한다. push된 커밋의 SHA가 바뀌면 협업자의 히스토리가 꼬인다
  3. 로컬에서만 작업한 커밋은 자유롭게 rebase해도 된다
  4. force push가 필요한 상황이 오면 git push --force-with-lease를 사용한다. --force보다 안전하다
  5. 팀에서 rebase를 쓰기로 했다면, 모든 멤버가 Golden Rule을 이해해야 한다
한국 팀의 현실 — git pull --rebase

한국 IT 기업의 상당수가 git pull --rebase를 기본값으로 설정한다. 일반 git pull은 매번 불필요한 머지 커밋을 생성하여 히스토리를 지저분하게 만들기 때문이다. 글로벌 설정으로 한 번 지정하면 모든 프로젝트에 적용된다.

git pull --rebase 설정 # 글로벌 설정 — 모든 프로젝트에 적용 git config --global pull.rebase true # 프로젝트별 설정 git config pull.rebase true # autostash — rebase 전 자동 stash, 후 자동 pop git config --global rebase.autoStash true
머지 충돌 해결 — 실전 가이드

머지 충돌은 피할 수 없다. 두 사람이 같은 파일의 같은 줄을 수정하면 Git은 자동으로 합칠 수 없다. VS Code의 3-way 머지 에디터가 가장 직관적인 해결 도구다. Git 2.35+에서 지원하는 diff3 스타일은 충돌 영역에 원본(base)까지 보여주어 판단을 돕는다.

머지 충돌 해결 # diff3 스타일로 충돌 표시 (원본 포함) git config --global merge.conflictstyle diff3 # 충돌 발생 시 diff3 출력 <<<<<<< HEAD (현재 브랜치) const pageSize = 20; ||||||| base (원본) const pageSize = 10; ======= const pageSize = 50; >>>>>>> feature/pagination (머지 대상) # VS Code에서 3-way 머지 에디터 열기 git mergetool # VS Code가 기본 머지 도구로 설정된 경우 # 충돌 해결 후 git add . git commit # 자동 생성된 머지 커밋 메시지 사용

히스토리는 읽히기 위해 존재한다.
읽히지 않는 히스토리는 쓰지 않은 것과 같다.

Part V

Git Hooks — 팀의 품질 게이트

코드 리뷰만으로는 품질을 보장할 수 없다. 리뷰어도 사람이고, 피로하면 놓친다. Git Hooks는 커밋, 푸시 등 Git 이벤트에 자동으로 실행되는 스크립트다. lint 규칙 위반, 타입 에러, 테스트 실패를 커밋 단계에서 차단한다. 사람의 의지가 아닌 시스템으로 품질을 강제하는 것이 핵심이다.

Hook 01

pre-commit

커밋 직전에 실행. lint, 포맷팅, 타입 체크를 수행한다. lint-staged와 함께 사용하면 변경된 파일만 검사하여 속도를 유지한다.

Hook 02

commit-msg

커밋 메시지 작성 후에 실행. Conventional Commits 형식 검증, 티켓 번호 포함 여부 확인 등을 자동으로 수행한다.

Hook 03

pre-push

push 직전에 실행. 전체 테스트 스위트를 돌려서 깨진 코드가 원격 저장소에 올라가는 것을 방지한다.

husky + lint-staged — Node.js 프로젝트

husky는 Git Hooks를 프로젝트에 쉽게 설정하는 도구다. lint-staged는 staged 파일(git add된 파일)만 대상으로 lint를 실행한다. 이 조합이 Node.js 생태계의 표준이다.

pre-commit lint-staged + ESLint + Prettier 변경된 파일만 대상으로 lint와 포맷팅을 검사한다
# husky + lint-staged 설치 npm install -D husky lint-staged npx husky init # .husky/pre-commit npx lint-staged # package.json — lint-staged 설정 "lint-staged": { "*.{js,ts,tsx}": [ "eslint --fix", "prettier --write" ], "*.css": [ "prettier --write" ] }
commit-msg Conventional Commits 검증 커밋 메시지가 feat:/fix:/chore: 형식을 따르는지 자동 확인
# commitlint 설치 npm install -D @commitlint/cli @commitlint/config-conventional # commitlint.config.js module.exports = { extends: ['@commitlint/config-conventional'], rules: { 'subject-case': [2, 'never', ['start-case', 'pascal-case']], 'header-max-length': [2, 'always', 100] } }; # .husky/commit-msg npx commitlint --edit $1
pre-push 테스트 실행 push 전에 테스트를 돌려서 깨진 코드가 원격에 올라가지 않도록 한다
# .husky/pre-push npm test # 또는 더 세밀하게 #!/bin/sh echo "Running tests before push..." npm run test:unit || { echo "Tests failed. Push aborted." exit 1 } echo "All tests passed."
pre-commit (Python) — Python 프로젝트

Python 생태계에서는 pre-commit 프레임워크가 표준이다. .pre-commit-config.yaml 파일 하나로 여러 언어의 hook을 관리한다. ruff, black, mypy, isort 등 Python 도구뿐 아니라, prettier, eslint 같은 다른 언어 도구도 함께 설정할 수 있다.

Python pre-commit 프레임워크 .pre-commit-config.yaml 하나로 모든 hook을 관리한다
# 설치 pip install pre-commit pre-commit install # .pre-commit-config.yaml repos: - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.9.4 hooks: - id: ruff # lint args: [--fix] - id: ruff-format # format - repo: https://github.com/pre-commit/mirrors-mypy rev: v1.14.1 hooks: - id: mypy # type check - repo: https://github.com/pre-commit/pre-commit-hooks rev: v5.0.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: check-yaml - id: check-json - id: detect-private-key # 보안: 비밀키 감지
Git Hooks 도입 시 주의사항
  1. --no-verify를 습관적으로 쓰는 팀원이 있으면 의미가 없다. CI에서 동일한 검사를 이중으로 실행해야 한다
  2. pre-commit hook이 5초를 넘으면 개발자가 비활성화한다. lint-staged로 변경 파일만 검사하여 속도를 유지한다
  3. pre-push에서 전체 테스트를 돌리면 push가 수분 걸릴 수 있다. 빠른 단위 테스트만 돌리고, 통합 테스트는 CI에 위임한다
  4. husky v9+는 .husky/ 디렉토리에 쉘 스크립트를 직접 작성한다. package.json의 scripts로 위임하지 않는다
  5. 팀 전체가 동일한 hook을 사용하려면 설정 파일을 git에 커밋해야 한다. .husky/는 기본적으로 커밋 대상이다
Git Hooks 실전 조합 — 추천 설정 # 권장 파이프라인 pre-commit → lint-staged (ESLint + Prettier) ← 빠름 (변경 파일만) commit-msg → commitlint (Conventional Commits) ← 즉시 pre-push → unit test ← 보통 (단위 테스트만) CI (remote) → full test + build + security scan ← 느림 (전체) # 핵심 원칙 # 로컬 hook = 빠르게, 자주 실행되는 검사 # CI = 느리지만 포괄적인 검사 # 둘 다 통과해야 머지 가능

좋은 도구는 팀의 습관이 된다
좋은 전략은 팀의 문화가 된다

브랜치 전략, PR, 커밋 규칙, Git Hooks. 결국 도구가 아니라 합의의 문제다. 팀이 같은 방향을 보고 있다면, 어떤 전략이든 작동한다.