Git Machete 기반 배포 브랜치 자동 체리픽 스크립트

곽태욱·2026년 3월 11일

목적

📚 Git Machete 배포 자동화 커스텀 스크립트 공유

Git Machete로 관리 중인 기능 브랜치(Leaf)들만 자동으로 추출하여, 지정한 배포 타겟 브랜치(deploy/*)에 안전하게 체리픽(Cherry-pick)하고 Push까지 마무리해 주는 자동화 스크립트의 설정 및 사용법을 공유합니다.

내용

다수의 기능(Feature) 브랜치를 개발 서버나 스테이징 서버에 배포할 때 일일이 브랜치를 이동하며 머지/체리픽하는 작업은 번거롭고 실수가 발생하기 쉽습니다. 특히 체리픽을 사용할 때 발생하는 '중복 커밋으로 인한 충돌'을 방지하고, 충돌이 발생하더라도 스크립트가 멈추지 않도록 개선한 자동화 도구가 필요했습니다.

주요 기능

  1. 타겟 다중 지정: deploy -t dev -t stage 처럼 여러 배포 브랜치를 한 번에 지정하여 순차적으로 작업할 수 있습니다.
  2. Leaf 브랜치 자동 추출: git machete list childless를 사용하여 자식 브랜치가 없는 가장 끝단(완성된 기능)의 브랜치들만 타겟팅합니다.
  3. 중복 커밋 완벽 방지 (git cherry): 타겟 브랜치에 이미 반영된 커밋인지 사전 검사하여, 이미 반영된 변경사항이면 안전하게 스킵(Skip)합니다.
  4. 오류 핸들링: 체리픽 도중 충돌(Conflict)이 발생하면 스크립트가 멈추지 않고, 해당 병합만 취소(-abort)한 뒤 다음 브랜치로 넘어갑니다.
  5. 자동 Push 및 요약: 작업이 끝나면 원격 저장소에 자동으로 Push하며, 충돌이 발생해 누락된 브랜치 목록을 마지막에 요약해서 보여줍니다.

설치 방법

  1. ~/.zshrc 파일에 아래의 스크립트를 붙여넣습니다.
# Git Machete Leaf 브랜치 자동 체리픽 & Push 스크립트
deploy() {
  local targets=()
  local branches=()
  local base_branch="master"

  # 1. 인자 파싱 (-t 또는 --target)
  while [[ $# -gt 0 ]]; do
    case "$1" in
      -t|--target)
        if [[ -n "$2" ]]; then
          targets+=("$2")
          shift 2
        else
          echo "❌ 오류: -t 또는 --target 뒤에 타겟 이름(dev, stage 등)을 지정해주세요."
          return 1
        fi
        ;;
      *)
        echo "❌ 알 수 없는 옵션입니다: $1"
        return 1
        ;;
    esac
  done

  if [[ ${#targets[@]} -eq 0 ]]; then
    echo "⚠️ 사용법: deploy -t dev -t stage"
    return 1
  fi

  # 2. Leaf 브랜치 목록 가져오기
  branches=(${(f)"$(git machete list childless)"})

  if [[ ${#branches[@]} -eq 0 ]]; then
    echo "⚠️ 체리픽할 Leaf 브랜치가 없습니다."
    return 1
  fi

  local original_branch=$(git branch --show-current)

  # 3. 타겟 브랜치별 작업 수행
  for t in "${targets[@]}"; do
    local target_branch="deploy/$t"
    local conflict_branches=()

    echo "\n======================================"
    echo "🚀 타겟 브랜치 준비: $target_branch"
    echo "======================================"

    git checkout "$target_branch" || { 
      echo "❌ $target_branch 브랜치로 이동할 수 없습니다."
      continue 
    }

    echo "⬇️ 원격 저장소와 동기화 중..."
    if git rev-parse --abbrev-ref "$target_branch@{upstream}" >/dev/null 2>&1; then
      git pull || { echo "❌ git pull 실패."; return 1; }
      echo "✅ 최신 동기화 완료."
    fi

    echo "\n🔄 Machete Leaf 브랜치 체리픽 시작 (기준: $base_branch)..."
    for b in "${branches[@]}"; do
      if [[ "$b" == "$target_branch" || "$b" == "$base_branch" ]]; then
        continue 
      fi
      
      # git cherry를 사용하여 타겟 브랜치에 '아직 없는(+)' 커밋만 추출
      local commits=($(git cherry HEAD "$b" "$base_branch" | awk '$1 == "+" {print $2}'))
      
      # 적용할 새로운 커밋이 없다면 쿨하게 스킵
      if [[ ${#commits[@]} -eq 0 ]]; then
        echo "  ⏭️  이미 모두 반영된 브랜치입니다 (스킵): $b"
        continue
      fi

      echo "  🍒 체리픽 시도 (${#commits[@]}개 새 커밋): $base_branch..$b"
      
      if git cherry-pick --empty=drop "${commits[@]}" >/dev/null 2>&1; then
        echo "  ✅ 체리픽 성공: $b"
      else
        echo "  🚨 충돌 발생: $b -> 체리픽을 취소하고 건너뜁니다."
        git cherry-pick --abort >/dev/null 2>&1
        conflict_branches+=("$b")
      fi
    done
    
    # 4. Push 및 요약
    echo "\n⬆️ 원격 저장소로 Push 중..."
    if git push -u origin "$target_branch" >/dev/null 2>&1; then
      echo "✅ Push 완료!"
    else
      echo "❌ Push 실패."
    fi

    echo "\n--------------------------------------"
    if [[ ${#conflict_branches[@]} -eq 0 ]]; then
      echo "🎉 $target_branch 작업 완료! (충돌 없음)"
    else
      echo "⚠️ $target_branch 작업 완료 (일부 충돌 발생)"
      for cb in "${conflict_branches[@]}"; do echo "  - $cb"; done
      echo "위 브랜치들은 수동 해결이 필요합니다."
    fi
  done

  echo "\n======================================"
  echo "원래 브랜치($original_branch)로 돌아갑니다."
  git checkout "$original_branch"
}

사용법

1. 현재 브랜치 상태 확인 (git machete status)

현재 Machete 트리가 아래와 같이 구성되어 있다고 가정해 보겠습니다.

master
│
├── feature/login
│   └── web/20260310/amplitude-notification (Leaf 브랜치 1)
│
└── feature/payment
    ├── web/20260106/1-recommend-context (Leaf 브랜치 2)
    └── web/20260310/books-frontend-bootstrap (Leaf 브랜치 3)

여기서 우리가 배포하려는 대상은 맨 끝단에 있는 3개의 Leaf 브랜치입니다.

2. .git/machete 파일 생성

아래와 같이 .git 폴더에 machete 파일을 생성합니다.

master
  feature/login
    web/20260310/amplitude-notification
  feature/payment
    web/20260106/1-recommend-context
    web/20260310/books-frontend-bootstrap

3. 스크립트 실행

개발(dev) 환경과 스테이징(stage) 환경에 순차적으로 배포하고 싶다면 터미널에 아래와 같이 입력합니다.

deploy -t dev -t stage

4. 예상되는 터미널 출력 결과 (예시)

스크립트가 실행되면 아래와 같이 진행 상황을 보여줍니다.

======================================
🚀 타겟 브랜치 준비: deploy/dev
======================================
⬇️ 원격 저장소와 동기화 중...
✅ 최신 동기화 완료.

🔄 Machete Leaf 브랜치 체리픽 시작 (기준: master)...
  🍒 체리픽 시도 (3개 새 커밋): master..web/20260310/amplitude-notification
  ✅ 체리픽 성공: web/20260310/amplitude-notification
  
  ⏭️ 이미 모두 반영된 브랜치입니다 (스킵): web/20260106/1-recommend-context
  
  🍒 체리픽 시도 (5개 새 커밋): master..web/20260310/books-frontend-bootstrap
  🚨 충돌 발생: web/20260310/books-frontend-bootstrap -> 체리픽을 취소하고 건너뜁니다.

⬆️ 원격 저장소로 Push 중...
✅ Push 완료!

--------------------------------------
⚠️ deploy/dev 작업 완료 (일부 충돌 발생)
다음 브랜치들은 충돌이 발생하여 건너뛰었습니다:
  - web/20260310/books-frontend-bootstrap
위 브랜치들은 수동 해결이 필요합니다.

======================================
원래 브랜치로 돌아갑니다.

💡 결과 해석:

  • amplitude-notification: 정상적으로 새 커밋들이 추출되어 체리픽 및 Push 되었습니다.
  • 1-recommend-context: 이전 배포 때 이미 deploy/dev에 들어간 커밋들이라 스크립트가 똑똑하게 이를 인지하고 스킵했습니다.
  • books-frontend-bootstrap: 다른 코드와 충돌이 발생했습니다. 스크립트는 멈추지 않고 해당 작업을 취소한 뒤 끝까지 실행되었으며, 개발자는 나중에 이 브랜치만 수동으로 체리픽하여 충돌을 해결하면 됩니다.

기타

  • git-machete CLI 툴이 설치되어 있어야 하며, .git/machete 파일에 브랜치 트리가 구성되어 있어야 정상 동작합니다.
  • Base 브랜치 설정: 스크립트 상단의 local base_branch="master" 부분을 바꾸면 다른 브랜치를 base 브랜치로 설정할 수 있습니다.
profile
이유와 방법을 알려주는 메모장 겸 블로그 (Frontend, AI, 경제, 책)

0개의 댓글