콘텐츠로 이동

Git Flow & Branching

분류: Layer 9 - 아키텍처 & 설계 패턴

Git 브랜치 전략 (Git Flow / GitHub Flow / Trunk-Based Development)

섹션 제목: “Git 브랜치 전략 (Git Flow / GitHub Flow / Trunk-Based Development)”

팀이 코드를 어떻게 나누고, 합치고, 배포할지를 정의하는 브랜치 운영 규칙으로, 협업 충돌을 줄이고 안정적인 배포를 가능하게 하는 개발 워크플로우다.


브랜치 전략이 없으면 생기는 문제

섹션 제목: “브랜치 전략이 없으면 생기는 문제”

개발자 5명이 동시에 main 브랜치에 직접 커밋한다고 상상해보자. A가 결제 기능을 개발하는 도중 B가 인증 모듈을 수정하고, 그 사이 C가 긴급 버그를 고쳐야 한다. 결과는 예측 불가능한 충돌, 테스트되지 않은 코드의 배포, 롤백의 악몽이다.

  • 병렬 개발 안정성: 서로 다른 기능을 독립된 공간에서 개발
  • 배포 품질 보장: 리뷰, 테스트를 거친 코드만 main에 합류
  • 롤백 용이성: 문제가 생기면 어느 지점으로 되돌릴지 명확
  • 이력 추적: “언제 어떤 기능이 왜 들어갔는지” 히스토리 파악

DevOps Research and Assessment(DORA) 지표에 따르면 Trunk-Based Development를 채택한 고성능 팀은 그렇지 않은 팀보다 배포 빈도가 182배, 변경 리드타임이 127배 빠른 것으로 나타났다(2024년 기준). 브랜치 전략 선택은 팀의 속도와 안정성 모두에 직결된다.


Git Flow는 대형 마트처럼 동작한다. 상품이 **진열대(main)**에 오르려면 입고 창고(develop)검품실(release)진열대(main) 순서를 거쳐야 한다. 급히 필요한 상품은 검품실을 건너뛰고 **응급 코너(hotfix)**를 통해 바로 진열대에 오른다.

main ──●──────────────────────●── (v1.0)
│ │
│ hotfix/bug ──┤── (긴급 패치)
│ │
develop ─────●──●──●──●──●──────────●──
│ │
feature/login ────●────────┘
feature/payment └──●──●──┘
release/1.0 ──────────────────●──● (QA, 버전 태깅)
브랜치역할생성 기준삭제 시점
main배포된 프로덕션 코드최초 1회영구 유지
develop통합 개발 브랜치main에서 파생영구 유지
feature/*기능 단위 개발develop에서 파생develop 병합 후 삭제
release/*배포 준비 (QA, 버그 픽스)develop에서 파생main 병합 후 삭제
hotfix/*프로덕션 긴급 패치main에서 파생main + develop 병합 후 삭제
Terminal window
# 1. feature 브랜치 생성 (develop에서 파생)
git checkout develop
git checkout -b feature/user-auth
# 기능 개발 후
git add .
git commit -m "feat: JWT 인증 구현"
# 2. develop에 병합
git checkout develop
git merge --no-ff feature/user-auth
git branch -d feature/user-auth
# 3. release 브랜치 생성
git checkout -b release/1.0.0
# 버전 정보 업데이트, QA 버그 픽스
git commit -m "chore: 버전 1.0.0 설정"
# 4. main에 병합 (배포)
git checkout main
git merge --no-ff release/1.0.0
git tag -a v1.0.0 -m "Release 1.0.0"
# 5. develop에도 병합 (release에서 수정된 내용 반영)
git checkout develop
git merge --no-ff release/1.0.0
git branch -d release/1.0.0

예상 출력:

Switched to branch 'develop'
Switched to a new branch 'feature/user-auth'
[feature/user-auth abc1234] feat: JWT 인증 구현
3 files changed, 87 insertions(+)
Switched to branch 'develop'
Merge made by the 'ort' strategy.
Deleted branch feature/user-auth (was abc1234).
Terminal window
# 프로덕션 긴급 버그 발생
git checkout main
git checkout -b hotfix/null-pointer-fix
git commit -m "fix: 결제 시 null pointer 예외 수정"
# main과 develop 모두에 반영
git checkout main
git merge --no-ff hotfix/null-pointer-fix
git tag -a v1.0.1 -m "Hotfix 1.0.1"
git checkout develop
git merge --no-ff hotfix/null-pointer-fix
git branch -d hotfix/null-pointer-fix

”왜 —no-ff 옵션이 중요한가” — 머지 전략의 원리

섹션 제목: “”왜 —no-ff 옵션이 중요한가” — 머지 전략의 원리”

Git Flow에서 --no-ff(no fast-forward)는 단순한 습관이 아니라 이력 추적성을 위한 필수 옵션이다.

Fast-forward 머지 (--ff, 기본값):
main: ──A──B──C──D── (feature 커밋이 main에 직접 이어붙음)
→ "언제 어떤 feature가 main에 들어왔는지" 경계가 보이지 않음
No-fast-forward 머지 (--no-ff):
main: ──A──────────M── (머지 커밋 M이 경계를 만듦)
╲ ╱
feature: ──B──C──D
→ git log --graph로 "이 기능이 언제 합쳐졌는지" 명확하게 추적 가능
→ git revert M 한 번으로 기능 전체를 되돌릴 수 있음

fast-forward로 머지하면 feature 브랜치의 커밋이 main에 직접 이어붙어서, 나중에 “이 기능을 통째로 되돌리고 싶다”면 커밋 하나하나를 일일이 revert해야 한다. --no-ff는 머지 커밋을 남겨서 하나의 revert로 기능 전체를 안전하게 되돌릴 수 있게 해준다.

📖 더 보기: A successful Git branching model (nvie.com) — Git Flow 원저자가 —no-ff를 필수로 권장하는 이유 설명 (입문)

장점:

  • 릴리즈 버전 관리가 명확하다 (v1.0, v1.1, v2.0 체계적 관리)
  • 여러 버전을 동시에 지원해야 하는 경우 적합 (모바일 앱, 패키지 라이브러리)
  • QA 팀이 별도로 있는 조직에 적합

단점:

  • 브랜치가 많아 복잡하다
  • developreleasemain 단계가 많아 배포까지 시간이 걸린다
  • CI/CD를 구축해도 배포 속도 향상이 제한적이다
  • 원저자 Vincent Driessen 본인도 2020년에 “웹 앱에는 Git Flow가 맞지 않는다”고 인정했다

GitHub Flow는 편의점처럼 단순하다. 물건이 필요하면 발주(feature 브랜치 생성)입고 검수(PR + 리뷰)바로 진열(main 머지 + 배포). 창고 단계 없이 빠르게 순환한다.

main ──●──────────────────────────────●── (항상 배포 가능)
│ │
feature/ └──●──●──●── (PR 생성) ────── ┘
↑ ↑
커밋 코드 리뷰

GitHub Flow는 딱 두 가지 브랜치 유형만 존재한다:

  1. main: 항상 배포 가능한 상태를 유지
  2. feature/* (또는 fix/*, chore/*): 모든 작업 브랜치
1. main에서 브랜치 생성
git checkout -b feature/add-search
2. 작업 후 원격에 push
git push -u origin feature/add-search
3. Pull Request 생성 (GitHub UI)
- 제목: "feat: 상품 검색 기능 추가"
- 리뷰어 지정
- CI/CD 자동 트리거
4. 코드 리뷰 + 수정
5. main에 Merge
6. 자동 배포 (CI/CD)
Terminal window
# 1. 브랜치 생성
git checkout main
git pull origin main
git checkout -b feature/product-search
# 2. 개발
git add .
git commit -m "feat: 상품명 기반 검색 API 추가"
git push -u origin feature/product-search
# 3. PR 생성 (GitHub CLI 사용 시)
gh pr create \
--title "feat: 상품 검색 기능 추가" \
--body "## 변경 내용\n- SearchController 추가\n- SearchService 구현" \
--reviewer team-lead
# 4. 리뷰 승인 후 머지
gh pr merge --squash
# 5. 로컬 정리
git checkout main
git pull origin main
git branch -d feature/product-search

예상 출력:

Switched to a new branch 'feature/product-search'
[feature/product-search def5678] feat: 상품명 기반 검색 API 추가
5 files changed, 142 insertions(+)
Branch 'feature/product-search' set up to track remote branch 'feature/product-search' from 'origin'.
https://github.com/team/repo/pull/42
✓ Pull request #42 successfully merged and closed

“왜 GitHub Flow에서 main은 항상 배포 가능해야 하는가” — 지속적 배포의 전제 조건

섹션 제목: ““왜 GitHub Flow에서 main은 항상 배포 가능해야 하는가” — 지속적 배포의 전제 조건”

GitHub Flow의 핵심 규율은 **“main = 배포 가능”**이다. 이 규율이 무너지면 전체 전략이 붕괴한다.

main이 배포 불가능한 상태일 때 벌어지는 일:
1. 개발자 A가 feature 브랜치를 main에서 파생
2. 그런데 main에 이미 깨진 코드가 있음
3. A의 feature 브랜치도 시작부터 깨진 상태
4. A가 PR을 올려도 CI가 실패 → "원래 실패하는 거예요"
5. CI 실패가 정상이 되면 아무도 CI 결과를 신뢰하지 않음
6. 결국 리뷰 없이 머지 → 품질 붕괴
main이 항상 배포 가능할 때:
1. 어떤 시점에서든 main을 배포해도 안전
2. feature 브랜치가 CI 실패 → "내 코드에 문제가 있다"로 확신
3. CI 결과를 팀 전체가 신뢰
4. 긴급 상황에 즉시 main을 배포해서 대응 가능

이 원칙을 지키려면 Branch Protection Rules에서 “Require status checks to pass”가 필수다.

📖 더 보기: GitHub Flow Guide — GitHub Docs — GitHub Flow의 공식 가이드. main 브랜치 보호 전략 포함 (입문)

장점:

  • 배우기 쉽고 관리 포인트가 적다
  • PR이 협업 + 리뷰 + 배포 트리거를 모두 처리
  • 소규모 팀(2~10명)에 가장 적합
  • CI/CD와 자연스럽게 연동

단점:

  • 여러 버전을 동시 지원하기 어렵다
  • main이 항상 배포 가능해야 한다는 규율이 필요하다
  • 대규모 팀에서는 PR 병목이 생길 수 있다

TBD는 실시간 뉴스 방송처럼 동작한다. 기자(개발자)들이 하루에도 여러 번 뉴스(커밋)를 방송(main)에 올린다. 미완성 기사는 방송하지 않는 것이 아니라, **자막으로 “준비 중”(Feature Flag)**이라고 표시한 채로 방송한다.

모든 개발자가 하루에 1~2회 이상 main 브랜치에 직접 병합한다. 브랜치를 만들더라도 최대 1~2일 안에 병합한다. 미완성 기능은 Feature Flag로 감춘다.

main ──●──●──●──●──●──●──●──● (하루에 수십 번 커밋)
↑ ↑ ↑
개발자A B C (모두 main에 직접 or 단기 브랜치로)
// Nest.js 예시: Feature Flag로 미완성 기능 숨기기
@Injectable()
export class ProductService {
constructor(private readonly featureFlagService: FeatureFlagService) {}
async getProducts(userId: string) {
// Flag가 켜진 사용자에게만 새 검색 기능 노출
if (await this.featureFlagService.isEnabled("new-search", userId)) {
return this.newSearchProducts();
}
return this.legacySearchProducts();
}
}
// 환경변수 기반 간단 구현
const FEATURE_FLAGS = {
"new-search": process.env.FEATURE_NEW_SEARCH === "true",
"payment-v2": process.env.FEATURE_PAYMENT_V2 === "true",
};
Terminal window
# 작은 단위로 자주 커밋 (큰 PR 대신 작은 커밋들)
git checkout main
git pull origin main
# 기능의 일부만 구현 (완성되지 않아도 OK, Flag로 숨김)
git add src/search/search.service.ts
git commit -m "feat: 검색 서비스 기본 구조 추가 (flag: new-search)"
git push origin main
# CI가 자동으로 빌드/테스트 실행
# 통과하면 자동 배포

TBD가 머지 충돌을 줄이는 이유 (동작 원리 심화)

섹션 제목: “TBD가 머지 충돌을 줄이는 이유 (동작 원리 심화)”

왜 브랜치 수명이 짧을수록 충돌이 적은가?

머지 충돌은 두 브랜치가 같은 코드 영역을 각자 다르게 수정할 때 발생한다. 브랜치가 2일이면 베이스(main)와 2일치 차이만 존재한다. 브랜치가 2주면 14일치 차이가 쌓인다. 충돌 가능성은 수명에 비례한다.

Git Flow (브랜치 수명 2주):
main: A──B──C──D──E──F──G──H──I──J──K──L──M──N (14일간 14개 커밋)
feature: └──────────────────────────────────────● (병합 시 14개와 충돌 가능)
TBD (브랜치 수명 1일):
main: A──B──C──D──E──F──G──H──I──J──K──L──M──N
└─● ← 1일치 = 최대 2~3개 커밋과 충돌 가능

Feature Flag가 필요한 이유

TBD에서는 기능이 완성되기 전에도 main에 병합한다. 그러면 미완성 코드가 프로덕션에 배포된다. **Feature Flag는 “코드는 배포됐지만 기능은 꺼져있다”**는 상태를 만드는 장치다.

배포(Deploy) ≠ 기능 출시(Release)
코드 배포 기능 출시
주문 서비스 v2.0 ──────── Flag ON(특정 팀만) ──── Flag ON(전체)
main 병합 1주 후 2주 후
TBD: 미완성이어도 배포
Flag: OFF 상태로 숨겨짐
// AWS AppConfig 기반 Feature Flag (실무 패턴)
import {
AppConfigDataClient,
GetLatestConfigurationCommand,
} from "@aws-sdk/client-appconfigdata";
@Injectable()
export class FeatureFlagService {
private readonly client = new AppConfigDataClient({
region: "ap-northeast-2",
});
async isEnabled(flagName: string, userId?: string): Promise<boolean> {
try {
const command = new GetLatestConfigurationCommand({
ConfigurationToken: process.env.APPCONFIG_TOKEN,
});
const response = await this.client.send(command);
const config = JSON.parse(
new TextDecoder().decode(response.Configuration),
);
// 사용자별 점진적 롤아웃 (userId 해시로 10% 사용자에게만 노출)
const flag = config.flags[flagName];
if (!flag?.enabled) return false;
if (!userId || flag.rolloutPercentage >= 100) return flag.enabled;
// 사용자 ID 해시로 일관된 그룹 배정
const hash = userId
.split("")
.reduce((acc, c) => acc + c.charCodeAt(0), 0);
return hash % 100 < flag.rolloutPercentage;
} catch {
return false; // Flag 서비스 장애 시 기능 OFF (안전한 기본값)
}
}
}
// AppConfig 설정 예시 (JSON)
{
"flags": {
"new-search": {
"enabled": true,
"rolloutPercentage": 10 // 전체 사용자의 10%에게만 노출
},
"payment-v2": {
"enabled": false // 완전히 꺼짐
}
}
}

📖 더 보기: Trunk Based Development 공식 사이트 — Feature Flag의 유형(Release Toggle, Experiment Toggle 등)과 관리 전략 (중급)

장점:

  • 배포 빈도 최대화 (하루 수십 번도 가능)
  • 머지 충돌 최소화 (브랜치 수명이 짧아서)
  • CI/CD 파이프라인과 완벽하게 연동
  • DORA 지표 기준 고성능 팀의 표준

단점:

  • 강력한 코드 리뷰 문화가 전제 조건
  • 자동화 테스트 커버리지가 없으면 위험
  • Feature Flag 관리 비용이 발생 (오래된 Flag는 기술 부채가 됨)
  • 주니어가 많은 팀에서는 도입이 어렵다

브랜치 보호 규칙은 팀이 정한 브랜치 전략을 강제로 지키게 해주는 GitHub 설정이다.

GitHub Repository → Settings → Branches → Branch protection rules → Add rule
설정효과권장 여부
Require a pull request before merging직접 push 차단, PR만 허용항상 권장
Require approvals (최소 1명)최소 N명의 리뷰 승인 필요소규모 팀: 1명, 대규모: 2명
Require status checks to passCI 테스트 통과 후에만 머지 가능항상 권장
Require conversation resolution모든 리뷰 코멘트 해결 후 머지권장
Restrict who can push특정 사람/팀만 push 허용대규모 팀 권장
Require linear historySquash 또는 Rebase만 허용 (Merge Commit 불가)히스토리 정리에 유용
Do not allow bypassing관리자도 규칙 적용실수 방지
Terminal window
# GitHub CLI로 브랜치 보호 규칙 설정
gh api repos/{owner}/{repo}/branches/main/protection \
--method PUT \
--field required_status_checks='{"strict":true,"contexts":["ci/build","ci/test"]}' \
--field enforce_admins=true \
--field required_pull_request_reviews='{"required_approving_review_count":1}' \
--field restrictions=null

PR을 main에 합칠 때 세 가지 방식이 있다. 어떤 방식을 선택하느냐에 따라 git 이력이 달라진다.

시나리오: feature 브랜치에 커밋 3개 (A, B, C)가 있고 main에 머지

# Merge Commit (--no-ff)
main: ──1──2──────────────M──
╲ ╱
feature: ──A──B──C
결과: main에 A, B, C, M (머지 커밋) 모두 보임
# Squash Merge
main: ──1──2────────ABC──
결과: main에 A+B+C가 하나의 커밋(ABC)으로 압축
feature 브랜치의 개별 커밋은 사라짐
# Rebase
main: ──1──2──A'──B'──C'──
결과: A, B, C가 main 위에 재배치 (새 해시값)
선형 이력 유지
방식이력추천 상황주의사항
Merge Commit복잡, 원본 보존오래 유지할 브랜치, 감사 추적 필요git log가 복잡해짐
Squash Merge깔끔, 정보 손실feature 브랜치 완료 후 삭제할 때git bisect 어려워짐
Rebase선형, 해시 변경공유되지 않은 브랜치공유 브랜치에는 절대 사용 금지
Repository Settings → Pull Requests
☑ Allow merge commits
☑ Allow squash merging
☑ Allow rebase merging

팀 컨벤션을 통일하고 싶다면 두 개를 비활성화하고 하나만 남긴다.

# .github/workflows/main.yml (GitHub Actions 예시)
name: CI/CD Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
- run: npm ci
- run: npm test # PR 단계: 테스트만
- run: npm run lint
deploy:
needs: test
if: github.ref == 'refs/heads/main' # main 머지 시만 배포
runs-on: ubuntu-latest
steps:
- name: Deploy to Production
run: |
echo "Deploying to AWS..."
# aws ecs update-service ...

BackOps / Nest.js + AWS 스택 기준으로 각 전략의 현실적 적합성을 분석한다.

소규모 팀 (2~5명) + 빠른 배포 필요GitHub Flow 추천

  • PR 기반 리뷰로 품질 유지
  • main 머지 즉시 AWS ECS/Lambda 자동 배포
  • 복잡한 브랜치 관리 오버헤드 없음

중규모 팀 (5~15명) + 버전 릴리즈 존재Git Flow 추천

  • 배포 주기가 주 1회 이상이라면 release 브랜치가 QA를 돕는다
  • hotfix 흐름이 긴급 패치를 안전하게 처리

고성능 팀 + 강한 CI/CD 문화Trunk-Based Development 추천

  • 자동화 테스트 커버리지 80% 이상 필요
  • Feature Flag 인프라 (AWS AppConfig, LaunchDarkly 등)

Nest.js 프로젝트에서의 브랜치 네이밍

섹션 제목: “Nest.js 프로젝트에서의 브랜치 네이밍”
Terminal window
# 추천 브랜치 네이밍 컨벤션
feature/TICKET-123-user-auth # Jira 티켓 번호 포함
fix/TICKET-456-null-pointer # 버그 수정
chore/update-nestjs-version # 유지보수
release/v1.2.0 # 릴리즈 (Git Flow 사용 시)
hotfix/payment-crash # 긴급 패치

기준Git FlowGitHub FlowTrunk-Based Dev
브랜치 수5종 (main, develop, feature, release, hotfix)2종 (main, feature)1종 (main) + 단기 브랜치
배포 빈도낮음 (주 1회~월 1회)중간 (PR 완료마다)높음 (하루 수십 회)
팀 규모중대형 (10명+)소중형 (2~15명)규모 무관, 문화 중요
학습 곡선높음낮음중간 (규율 필요)
CI/CD 친화성낮음높음매우 높음
버전 동시 지원쉬움어려움어려움
머지 충돌 위험높음 (브랜치 수명 길다)중간낮음 (수명 짧다)
적합한 제품패키지, 앱SaaS, 웹서비스클라우드 네이티브

”우리 팀에 맞는 전략” 판단 기준

섹션 제목: “”우리 팀에 맞는 전략” 판단 기준”
1. 배포 주기가 얼마나 자주인가?
- 월 1회 이하 → Git Flow
- 주 1~3회 → GitHub Flow
- 하루 여러 번 → TBD
2. 여러 버전을 동시에 지원해야 하는가?
- YES → Git Flow
- NO → GitHub Flow or TBD
3. 자동화 테스트 커버리지가 충분한가?
- 낮음 (< 50%) → Git Flow or GitHub Flow (안전망 필요)
- 높음 (> 80%) → TBD 도입 가능
4. 팀에 코드 리뷰 문화가 있는가?
- 없거나 약함 → Git Flow (프로세스로 보완)
- 있음 → GitHub Flow or TBD

문제 1: main에 직접 push했을 때 브랜치 보호 규칙이 막는 경우

섹션 제목: “문제 1: main에 직접 push했을 때 브랜치 보호 규칙이 막는 경우”

증상:

remote: error: GH006: Protected branch update failed for refs/heads/main.
remote: error: Required status check "ci/test" is failing.
To https://github.com/team/repo.git
! [remote rejected] main -> main (protected branch hook declined)
error: failed to push some refs to 'https://github.com/team/repo.git'

원인: Branch Protection Rules에서 PR 없이 직접 push를 차단하거나, CI 테스트가 통과하지 않으면 머지가 불가능하도록 설정됨

해결 방법:

Terminal window
# 1. 새 브랜치로 이동
git checkout -b fix/my-direct-commit
# 2. PR을 통해 머지 요청
git push -u origin fix/my-direct-commit
gh pr create --title "fix: 직접 푸시한 변경사항" --base main
# (임시 방편: 관리자 권한으로 "Allow administrators" 비활성화 - 비권장)

문제 2: feature 브랜치가 오래되어 develop과 충돌이 심각한 경우

섹션 제목: “문제 2: feature 브랜치가 오래되어 develop과 충돌이 심각한 경우”

증상:

CONFLICT (content): Merge conflict in src/auth/auth.service.ts
CONFLICT (content): Merge conflict in src/user/user.module.ts
Automatic merge failed; fix conflicts and then commit the result.

원인: 오래된 feature 브랜치가 develop 브랜치와 많이 벌어져서 같은 파일을 동시에 수정한 경우

해결 방법:

Terminal window
# develop 최신 상태로 feature 브랜치 업데이트 (rebase 권장)
git checkout feature/old-branch
git fetch origin
git rebase origin/develop
# 충돌 발생 시 파일 직접 수정 후
git add src/auth/auth.service.ts
git rebase --continue
# 해결 불가 시 abort 후 merge로 대체
git rebase --abort
git merge origin/develop

근본 해결: 브랜치 수명을 짧게 유지한다. feature 브랜치는 최대 3~5일 이내에 병합을 목표로 설계한다.


문제 3: Squash Merge 후 같은 브랜치를 계속 사용할 때 충돌

섹션 제목: “문제 3: Squash Merge 후 같은 브랜치를 계속 사용할 때 충돌”

증상:

hint: You have divergent branches and need to specify how to reconcile them.
fatal: Need to specify how to reconcile divergent branches.

원인: Squash Merge는 feature 브랜치의 커밋들을 하나로 합쳐서 새 커밋을 만든다. 따라서 feature 브랜치의 원본 커밋과 main의 squash 커밋은 Git이 “다른 변경사항”으로 인식한다. 이 상태에서 feature 브랜치를 계속 사용하면 이미 반영된 내용을 다시 충돌로 처리한다.

해결 방법:

Terminal window
# Squash Merge 후에는 반드시 브랜치를 삭제하고 새로 생성
git branch -d feature/old-branch # 로컬 삭제
git push origin --delete feature/old-branch # 원격 삭제
# 추가 작업이 있다면 새 브랜치 생성
git checkout -b feature/continuation main

문제 4: Feature Flag 정리 안 해서 기술 부채 누적 (TBD)

섹션 제목: “문제 4: Feature Flag 정리 안 해서 기술 부채 누적 (TBD)”

증상:

코드베이스에 Feature Flag 분기가 50개 이상 존재한다.
어떤 Flag가 아직 사용 중이고 어떤 것이 이미 100% 릴리즈된 건지 모른다.
새 기능 개발 시 기존 Flag와 조합이 꼬여서 예상치 못한 버그 발생.

원인: Feature Flag는 TBD에서 미완성 코드를 숨기기 위한 임시 장치다. 기능이 100% 릴리즈되면 Flag와 분기 코드를 제거해야 하는데, 이를 방치하면 코드 경로가 기하급수적으로 늘어난다 (Flag 2개 = 4가지 경로, 10개 = 1024가지 경로).

해결 방법:

Terminal window
# 1. Flag 인벤토리 관리 (스프레드시트 또는 자동화)
# flag_name | created_date | owner | status | removal_target_date
# new-search | 2024-01-15 | @팀원A | 100% rolled out | 2024-02-15
# 2. 릴리즈 완료된 Flag의 코드 정리
# ❌ 정리 전
if (await featureFlagService.isEnabled('new-search', userId)) {
return this.newSearchProducts();
}
return this.legacySearchProducts();
# ✅ 정리 후 (100% 릴리즈 확인 후)
return this.newSearchProducts();
# + legacySearchProducts() 메서드 삭제
# 3. CI에서 만료된 Flag 감지 (날짜 기반)
# jest나 lint 규칙으로 만료일이 지난 Flag가 있으면 빌드 실패 처리

📖 더 보기: Feature Flags Best Practices — Flagsmith — Feature Flag 생명주기 관리와 정리 전략 (중급)


문제 5: Git Flow에서 hotfix를 develop에 반영하지 않아 버그 재발

섹션 제목: “문제 5: Git Flow에서 hotfix를 develop에 반영하지 않아 버그 재발”

증상: main에 긴급 패치를 배포했는데, 다음 release에서 같은 버그가 다시 나타남

원인: hotfix 브랜치를 main에만 병합하고 develop에 반영하지 않음. develop → release → main 흐름에서 hotfix가 덮어씌워짐

해결 방법:

Terminal window
# hotfix 완료 시 반드시 두 브랜치 모두 병합
git checkout main
git merge --no-ff hotfix/critical-fix
git tag -a v1.0.1
git checkout develop # ← 이 단계를 절대 빠뜨리지 말 것
git merge --no-ff hotfix/critical-fix
git branch -d hotfix/critical-fix

예방: Git Flow 도구(git flow) 사용 시 자동으로 두 브랜치에 병합한다.

Terminal window
git flow hotfix start critical-fix
# 수정 후
git flow hotfix finish critical-fix # main + develop 자동 병합

  • maindevelop 브랜치가 분리되어 있는가?
  • feature 브랜치는 항상 develop에서 파생하는가?
  • hotfix 브랜치 병합 후 develop에도 반영하는가?
  • release 브랜치에서 QA 후 main에 태그를 남기는가?
  • main 브랜치는 항상 배포 가능한 상태인가?
  • 모든 변경사항이 PR을 통해 병합되는가?
  • Branch Protection Rules가 설정되어 있는가?
  • CI/CD가 PR 단계에서 자동 실행되는가?
  • 리뷰어 지정 및 승인 프로세스가 있는가?
  • 자동화 테스트 커버리지가 충분한가? (70% 이상 권장)
  • Feature Flag 시스템이 있는가? (환경변수, 외부 서비스 등)
  • 브랜치 수명이 1~2일 이내로 유지되는가?
  • 팀에 코드 리뷰 문화가 확립되어 있는가?
  • CI가 모든 커밋에 즉시 실행되는가?
  • 팀 전원이 합의한 브랜치 네이밍 컨벤션이 있는가?
  • 커밋 메시지 컨벤션이 있는가? (Conventional Commits 등)
  • Squash / Merge Commit / Rebase 방식이 통일되어 있는가?
  • Branch Protection Rules이 main (그리고 develop) 브랜치에 적용되어 있는가?

키워드설명
main / master프로덕션에 배포된 안정 코드 브랜치
developGit Flow의 통합 개발 브랜치
feature branch기능 단위 작업 브랜치
release branch배포 준비 브랜치 (QA, 버전 태깅)
hotfix branch프로덕션 긴급 패치 브랜치
trunkTBD에서 main을 부르는 다른 이름
Feature Flag코드 배포와 기능 노출을 분리하는 패턴
Pull Request (PR)코드 리뷰 + 브랜치 병합 요청
Branch Protection RulesGitHub에서 브랜치에 적용하는 보호 규칙
Squash Merge여러 커밋을 하나로 압축 후 병합
Rebase커밋을 다른 브랜치 위에 재배치
Merge Commit두 브랜치의 이력을 하나로 합치는 커밋
CI/CD자동 빌드/테스트/배포 파이프라인
DORA metricsDevOps 성과 측정 지표 (배포 빈도, 리드타임 등)


Terminal window
# git-flow CLI 도구 설치 (macOS)
brew install git-flow-avh
# 새 저장소에서 git flow 초기화
mkdir git-flow-practice && cd git-flow-practice
git init
git flow init
# 기본값으로 Enter 연타

예상 출력:

Which branch should be used for bringing forth production releases?
- master
Branch name for production releases: [master] main
Which branch should be used for integration of the "next release"?
- develop
Branch name for "next release" development: [develop]
How to name your supporting branch prefixes?
Feature branches? [feature/]
Bugfix branches? [bugfix/]
Release branches? [release/]
Hotfix branches? [hotfix/]
Support branches? [support/]
Version tag prefix? []
Gitflow configuration stored in .git/config
Terminal window
# feature 브랜치 시작
git flow feature start my-feature

예상 출력:

Switched to a new branch 'feature/my-feature'
Summary of actions:
- A new branch 'feature/my-feature' was created, based on 'develop'
- You are now on branch 'feature/my-feature'
Terminal window
# 파일 작성 후 커밋
echo "# My Feature" > feature.md
git add feature.md
git commit -m "feat: my feature 추가"
# feature 브랜치 완료
git flow feature finish my-feature

예상 출력:

Switched to branch 'develop'
Updating 1a2b3c4..5d6e7f8
Fast-forward
feature.md | 1 +
1 file changed, 1 insertion(+)
Deleted branch feature/my-feature (was 5d6e7f8).
Summary of actions:
- The feature branch 'feature/my-feature' was merged into 'develop'
- Feature branch 'feature/my-feature' has been locally deleted
- You are now on branch 'develop'

실습 2: 현재 브랜치 보호 규칙 확인 (GitHub CLI)

섹션 제목: “실습 2: 현재 브랜치 보호 규칙 확인 (GitHub CLI)”
Terminal window
# 현재 저장소의 브랜치 보호 규칙 조회
gh api repos/{owner}/{repo}/branches/main/protection \
--jq '{
required_reviews: .required_pull_request_reviews.required_approving_review_count,
status_checks: .required_status_checks.contexts,
enforce_admins: .enforce_admins.enabled
}'

예상 출력:

{
"required_reviews": 1,
"status_checks": ["ci/build", "ci/test"],
"enforce_admins": true
}

Terminal window
# 테스트 저장소 생성
mkdir merge-practice && cd merge-practice
git init
echo "initial" > README.md
git add . && git commit -m "initial commit"
# Merge Commit 방식
git checkout -b feature/a
echo "feature a" > a.txt
git add . && git commit -m "feat: A 기능 1"
echo "update" >> a.txt
git add . && git commit -m "feat: A 기능 2"
git checkout main
git merge --no-ff feature/a -m "Merge: feature/a"
git log --oneline --graph

예상 출력:

* f3a1b2c Merge: feature/a
|\
| * 9d8e7f6 feat: A 기능 2
| * 1a2b3c4 feat: A 기능 1
|/
* 0000001 initial commit
Terminal window
# Squash Merge 방식
git checkout -b feature/b
echo "feature b" > b.txt
git add . && git commit -m "feat: B 기능 1"
echo "update" >> b.txt
git add . && git commit -m "feat: B 기능 2"
git checkout main
git merge --squash feature/b
git commit -m "feat: B 기능 (squash)"
git log --oneline --graph

예상 출력:

* a1b2c3d feat: B 기능 (squash) ← 2개 커밋이 1개로 압축됨
* f3a1b2c Merge: feature/a
|\
| * 9d8e7f6 feat: A 기능 2
| * 1a2b3c4 feat: A 기능 1
|/
* 0000001 initial commit

Git Flow는 버전 관리 명확성, GitHub Flow는 단순성과 속도, Trunk-Based Development는 최고의 배포 빈도를 추구하며, 팀 규모·배포 주기·테스트 문화에 따라 최적의 전략을 선택해야 한다.