왜 백포팅이 문제가 되는가

대부분의 팀은 main에서 개발하고, 운영 중인 버전마다 release/1.4, release/1.5 같은 장수(long-lived) 릴리스 브랜치를 유지한다. 이때 main에 머지된 보안 패치나 버그 수정은 지원 중인 모든 릴리스 브랜치로도 흘러가야 한다. 이 작업을 사람이 손으로 하면 세 가지가 반복적으로 깨진다. 첫째, 어떤 커밋을 어느 브랜치에 넣어야 하는지 판단이 누락된다. 둘째, cherry-pick 충돌이 났을 때 개발자가 컨텍스트를 다시 복원하느라 시간을 쓴다. 셋째, 백포트가 언제 완료됐는지 추적이 안 돼 릴리스 직전에야 누락이 드러난다.

기준: 라벨과 커밋 규칙 정하기

자동화의 전제는 "무엇을 백포트할지"를 기계가 읽을 수 있게 만드는 것이다. 가장 흔한 방식은 PR 라벨(backport/1.4)이다. 라벨이 여러 개면 대상 브랜치도 여러 개다. 라벨 대신 커밋 트레일러(Backport-To: 1.4)를 쓰면 squash 머지 이후에도 정보가 남는다는 장점이 있다. 팀 규모가 크면 라벨을, 커밋 히스토리를 엄격히 관리한다면 트레일러를 권한다.

GitHub Actions로 자동 cherry-pick

PR이 main에 머지되는 순간, 라벨을 읽어 대상 브랜치마다 cherry-pick 브랜치를 만들고 새 PR을 여는 흐름이다. 충돌이 나면 자동 PR을 만들지 않고 실패로 남겨 사람이 개입하게 한다.

name: backport
on:
  pull_request_target:
    types: [closed]

jobs:
  backport:
    if: github.event.pull_request.merged == true
    runs-on: ubuntu-latest
    permissions:
      contents: write
      pull-requests: write
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: korthout/backport-action@v3
        with:
          label_pattern: '^backport/(?<target>.+)$'
          pull_title: '[백포트 ${target}] ${pull_title}'
          merge_commits: 'skip'

충돌 처리를 스크립트로 명시하기

액션이 감춰주는 부분을 직접 제어하고 싶다면 cherry-pick을 스크립트로 두는 편이 디버깅에 유리하다. 핵심은 충돌 시 --abort로 되돌리고, 실패 목록을 명확히 남기는 것이다.

#!/usr/bin/env bash
set -euo pipefail

COMMIT="$1"      # 백포트할 커밋 SHA
TARGET="$2"      # 예: release/1.4

git fetch origin "$TARGET"
git switch -c "backport/${TARGET##*/}/${COMMIT:0:8}" "origin/$TARGET"

if git cherry-pick -x "$COMMIT"; then
  git push origin HEAD
  echo "OK: $COMMIT -> $TARGET"
else
  git cherry-pick --abort
  echo "CONFLICT: $COMMIT needs manual backport to $TARGET" >&2
  exit 1
fi

-x 옵션은 원본 커밋 SHA를 커밋 메시지에 기록해, 나중에 어떤 커밋에서 파생됐는지 추적할 수 있게 해준다.

전략 비교

방식장점주의점
라벨 기반 자동 PR도입이 쉽고 리뷰 흐름과 일치squash 머지 시 라벨 유실 가능
커밋 트레일러히스토리에 정보 영속커밋 규칙 강제 필요
정기 배치 스크립트대량 브랜치에 유리지연·중복 pick 위험

누락 방지: 미백포트 검출

자동화가 있어도 충돌로 실패한 항목은 반드시 사람 손을 탄다. 릴리스 직전에 "이 커밋이 아직 안 넘어왔다"를 찾아주는 점검을 CI에 넣어야 안심할 수 있다.

# main에는 있으나 release/1.4에 없는 커밋 목록
git log --cherry-pick --oneline --right-only \
  release/1.4...main -- src/ | grep -iE 'fix|CVE'

--cherry-pick은 이미 동일 변경이 백포트된 커밋을 제외해준다. 이 결과가 비어야 릴리스 준비가 끝난 것이다.

운영 시 주의점

몇 가지가 실무에서 반복적으로 문제를 일으킨다. 첫째, pull_request_target은 대상 브랜치 권한으로 실행되므로 신뢰할 수 없는 포크의 코드를 그대로 checkout하지 않도록 주의한다. 둘째, DB 마이그레이션이나 스키마 변경이 포함된 커밋은 자동 백포트 대상에서 제외하는 것이 안전하다. 릴리스 버전마다 스키마 상태가 다르기 때문이다. 셋째, 자동 생성된 백포트 PR도 정상 PR과 동일하게 CI와 리뷰를 거치게 해야 한다. 자동화의 목표는 사람의 판단을 없애는 것이 아니라, 기계가 처리할 수 있는 반복을 걷어내 사람이 충돌과 위험한 변경에만 집중하게 만드는 것이다.