Git 브랜치, 어떻게 관리해야 할까? - 개인의 기록에서 팀의 자동화까지

개발자라면 누구나 한 번쯤 Git 브랜치 이름을 어떻게 지을지 고민해 본 경험이 있을 겁니다. 단순히 dev, feature를 사용하는 것을 넘어, 더 체계적인 규칙을 만들고 싶다는 욕심이 생기기 마련이죠.

최근 저도 이런 고민에 빠졌습니다. 여러 LLM 모델을 다양한 개발 머신에서 테스트하는 상황에서, 작업 환경을 명확히 기록하고 싶다는 생각이 들었습니다.

나의 첫 아이디어: “llm이름/개발머신이름-날짜 형식은 어떨까?” 예: gemini/macbook-20250708

이 아이디어는 ‘언제’, ‘어떤 환경에서’ 작업했는지 추적하기엔 더할 나위 없이 좋아 보였습니다. 하지만 이내 중요한 한 가지를 놓치고 있다는 것을 깨달았습니다. 바로 ‘무엇을’ 했는지에 대한 정보가 빠져있다는 점입니다.

이 작은 질문에서 시작된 고민은 동료 AI들과의 협력적인 대화를 통해 점차 발전했고, 마침내 개인의 기록을 넘어 팀 전체의 생산성을 높이는 ‘자동화 워크플로우’라는 거대한 그림으로 완성되었습니다. 이 여정을 여러분과 함께 공유하고자 합니다.

기(起): 규칙의 시작, ‘목적’과 ‘환경’을 모두 담다

저의 첫 아이디어는 ‘작업 내용’을 알기 어렵다는 명백한 단점이 있었습니다. 브랜치 이름만 보고서는 새로운 기능을 개발한 것인지, 버그를 수정한 것인지 알 수 없었죠.

이를 해결하기 위해 일반적인 브랜치 전략의 장점인 **‘작업의 목적’**과 제 아이디어의 장점인 **‘작업 환경’**을 결합한 하이브리드 전략이 탄생했습니다.

[하이브리드 브랜치 전략]타입/환경/기능-설명

이 규칙에 따르면 브랜치는 다음과 같은 모습을 갖추게 됩니다.

  • feature/gemini-macbook/add-translation-api
  • bugfix/claude-macmini/fix-login-error
  • experiment/gpt4-macbook/test-new-prompt 이제 브랜치 이름만 봐도 “Gemini 모델을 맥북에서 작업했고, 번역 API 추가 기능을 개발했구나!” 하고 명확하게 알 수 있게 되었습니다. 의도의 명확성과 환경의 추적성이라는 두 마리 토끼를 모두 잡은 셈입니다.

승(承): 규칙을 지키는 힘, ‘자동화’로 강제하기

아무리 좋은 규칙이라도 지키지 않으면 무용지물입니다. 사람은 누구나 실수를 하기 마련이고, 바쁘다 보면 규칙을 잊어버리기도 합니다. 그래서 우리는 ‘시스템’의 힘을 빌리기로 했습니다.

규칙을 사람이 아닌, 기술이 검증하도록 만든 것입니다.

1. 1차 방어선: 개인의 실수를 막는 Git Hooks

개발자가 원격 저장소에 코드를 올리기(push) 전, 개인의 컴퓨터에서 브랜치 이름이 규칙에 맞는지 자동으로 검사하는 장치입니다. .git/hooks/pre-push 스크립트를 설정해두면, 규칙에 어긋나는 브랜치는 아예 푸시되지 않아 실수를 원천 봉쇄할 수 있습니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/bin/bash

# 허용된 타입 목록
ALLOWED_TYPES="feature|bugfix|refactor|experiment|docs"
# 브랜치 네이밍 정규표현식 (타입/환경/설명#이슈번호 형식)
BRANCH_REGEX="^($ALLOWED_TYPES)\/([a-z0-9]+-[a-z0-9]+)\/([a-z0-9]+-)*[a-z0-9]+(#\d+)?$"

# 현재 푸시하려는 브랜치 이름 가져오기
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)

# main, dev 등 보호된 브랜치는 검사에서 제외
if [[ "$CURRENT_BRANCH" == "main" || "$CURRENT_BRANCH" == "dev" ]]; then
    exit 0
fi

# 정규식 검사
if [[ ! "$CURRENT_BRANCH" =~ $BRANCH_REGEX ]]; then
    echo "[POLICY ERROR] 잘못된 브랜치 이름입니다."
    echo "규칙: 타입/llm-머신/기능-설명[#이슈번호]"
    echo "예시: feature/gemini-macbook/add-login-feature#123"
    exit 1 # Push 중단
fi

exit 0

2. 2차 방어선: 팀의 규칙을 수호하는 CI/CD

혹시 모를 예외(예: git push --no-verify)에 대비해, GitHub Actions와 같은 CI/CD 파이프라인에서 최종 검증을 수행합니다. Pull Request가 생성될 때마다 브랜치 이름을 검사하여, 규칙에 맞지 않으면 머지 자체를 막아버리는 강력한 수문장 역할을 합니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# .github/workflows/branch-lint.yml
name: Branch Naming Convention
on:
  pull_request:
    types: [opened, edited, synchronize]

jobs:
  lint-branch-name:
    runs-on: ubuntu-latest
    steps:
      - name: Check Branch Name
        run: |
          BRANCH_NAME="${{ github.head_ref }}"
          REGEX="^(feature|bugfix|refactor|experiment|docs)\/([a-z0-9]+-[a-z0-9]+)\/([a-z0-9]+-)*[a-z0-9]+(#\d+)?$"
          if [[ ! "$BRANCH_NAME" =~ $REGEX ]]; then
            echo "::error::Branch name '${BRANCH_NAME}' does not follow the convention: 타입/llm-머신/기능-설명"
            exit 1
          fi

전(轉): 규칙의 활용, ‘생산성’을 폭발시키다

이제 우리에겐 잘 구조화되고, 시스템에 의해 검증되는 브랜치 이름이 있습니다. 여기서 한 단계 더 나아가, 이 똑똑한 브랜치 이름을 개발 생산성을 높이는 데 적극적으로 활용하기 시작했습니다.

1. 릴리스 노트 초안 자동 생성

더 이상 수작업으로 릴리스 노트를 작성하지 않습니다. main 브랜치에 머지된 브랜치들의 타입기능-설명을 추출하여 릴리스 노트 초안을 자동으로 생성하는 스크립트를 만들었습니다.

  • feature/... 브랜치는 ‘✨ 새로운 기능’ 섹션에
  • bugfix/... 브랜치는 ‘🐛 버그 수정’ 섹션에 자동으로 분류되어 정리됩니다.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/bin/bash
# generate_release_notes.sh
# 사용법: ./generate_release_notes.sh v1.2.0

VERSION=$1
OUTPUT_FILE="RELEASE_NOTES_${VERSION}.md"

echo "# Release ${VERSION}" > $OUTPUT_FILE
echo "" >> $OUTPUT_FILE

# 타입별로 머지된 PR 목록을 조회하여 추가
for TYPE in feature bugfix docs; do
  # 첫 글자를 대문자로 변경하여 헤더 생성
  HEADER="$(tr 'a-z' 'A-Z' <<< ${TYPE:0:1})${TYPE:1}s"
  echo "## ✨ ${HEADER}" >> $OUTPUT_FILE

  # GitHub CLI를 사용하여 해당 타입의 PR 목록을 가져와 포맷팅
  gh pr list --state merged --search "base:main" --json headRefName,title,number \
    | jq -r ".[] | select(.headRefName|startswith(\"${TYPE}/\")) | \"- [#\(.number)] \(.title)\"" >> $OUTPUT_FILE

  echo "" >> $OUTPUT_FILE
done

echo "릴리스 노트가 ${OUTPUT_FILE} 파일로 생성되었습니다."

2. 똑똑한 CI/CD 파이프라인

브랜치 타입에 따라 CI/CD 파이프라인이 동적으로 작동합니다. docs/... 브랜치는 불필요한 빌드나 테스트를 건너뛰고, experiment/... 브랜치는 자동으로 테스트 서버에 배포되어 빠르게 결과를 확인할 수 있습니다.

결(結): 궁극의 워크플로우를 향하여

우리의 여정은 여기서 멈추지 않았습니다. 잘 짜인 브랜치 규칙과 더불어, Conventional Commits라는 커밋 메시지 규칙을 도입했습니다. 그리고 이 모든 것을 semantic-release라는 강력한 자동화 도구와 결합했습니다.

그 결과, 다음과 같은 꿈의 워크플로우가 완성되었습니다.

  1. 개발자는 feat: add user profile과 같은 규칙에 맞춰 커밋하고 main 브랜치에 코드를 머지합니다.
  2. CI/CD 파이프라인이 이를 감지하고 semantic-release를 실행합니다.
  3. semantic-release는 커밋 메시지를 분석하여 다음 버전을 자동으로 결정합니다. (feat이므로 Minor 버전 상승!)
  4. CHANGELOG.md 파일을 자동으로 업데이트합니다.
  5. 결정된 버전으로 Git 태그를 생성하고, 변경 내역과 함께 GitHub Release 페이지를 자동으로 발행합니다.
  6. 마지막으로, 새로운 버전이 릴리스되었다는 알림을 팀 Slack 채널에 자동으로 전송합니다. 이 모든 과정은 아래와 같은 간단한 워크플로우 파일 하나로 설정됩니다.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# .github/workflows/release.yml
name: Semantic Release
on:
  push:
    branches:
      - main
jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v3
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
      - name: Install dependencies
        run: npm ci
      - name: Run semantic-release
        run: npx semantic-release
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          # NPM에 배포할 경우 NPM_TOKEN도 필요
          # NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

단순한 브랜치 네이밍 고민에서 시작된 이 여정은, 결국 개발자가 코드에만 집중할 수 있도록 돕는 완전 자동화된 배포 파이프라인 구축으로 이어졌습니다.

좋은 브랜치 전략은 단순히 코드를 정리하는 것을 넘어, 팀의 협업 방식을 바꾸고 개발 워크플로우 전체를 혁신하는 강력한 첫걸음이 될 수 있습니다. 여러분도 오늘, 팀의 브랜치 전략을 한번 점검해 보는 것은 어떨까요?