오늘 나에게 생긴 질문은 아주 현실적이었다.

“PR에 체리픽 라벨을 달았는데, 그러면 다음 단계는 뭐지?”

처음 들으면 체리픽은 뭔가 개발자들이 멋있게 부르는 마법 주문 같다. 이름도 귀엽다. cherry-pick. 체리를 하나 톡 따서 다른 바구니에 넣는 느낌.

그런데 릴리스 브랜치가 끼어들면 이야기가 조금 달라진다. 이건 체리를 따는 일이기도 하지만, 사실은 “기차표를 두 장 끊는 일”에 더 가깝다.

하나는 메인 노선으로 가는 표

하나는 릴리스 노선으로 갈아타는 표

develop은 본선, release는 지선

대부분의 제품 개발 흐름에는 본선이 있다.

우리 상황에서는 develop이 그 본선이다. 새 기능과 버그 수정은 먼저 여기에 들어간다. PR을 만들고, 리뷰를 받고, CI를 통과하고, 머지한다.

그런데 이미 릴리스 준비가 시작된 버전은 조금 다르다. 예를 들어 11.5 계열이 이미 따로 운영되고 있다면, 그 계열은 release/11.5 같은 지선 위에 있다.

본선에 올라탄 기차가 자동으로 지선까지 들어가지는 않는다.

즉, PR이 develop에 머지된다고 해서 이미 갈라져 나간 릴리스 브랜치에 자동으로 반영되는 것은 아니다.

그래서 체리픽이 필요하다.

체리픽은 “이 수정만 저 브랜치에도 태워줘”라는 요청

체리픽은 간단히 말하면 이런 요청이다.

이 PR의 변경분을 release 브랜치에도 따로 반영해줘

여기서 핵심은 “전체 develop을 release로 합치는 것”이 아니라 “이 수정만 골라 옮기는 것”이다.

그래서 체리픽이다. 체리 한 알만 고른다.

릴리스 브랜치 입장에서는 이게 중요하다. 릴리스 직전에는 안정성이 최우선이라, develop에 들어간 모든 변경을 다 받으면 위험할 수 있다. 하지만 특정 버그 수정은 꼭 포함해야 할 수 있다.

그럴 때 체리픽은 안전벨트가 된다.

라벨은 체리픽 예약표다

이번에 붙인 라벨은 이런 형태였다.

cherry-pick: release/11.5

이 라벨은 “지금 당장 체리픽을 끝냈다”는 뜻이 아니다.

정확히는 예약표에 가깝다.

이 PR이 머지되면, release/11.5로 체리픽해줘

자동화가 잘 되어 있는 저장소에서는 PR이 닫히고 머지되는 순간 GitHub Actions가 이 라벨을 읽는다. 그리고 라벨에 적힌 대상 브랜치로 체리픽 PR을 만들거나, 자동 머지까지 시도한다.

그래서 순서가 중요하다.

  1. PR에 체리픽 라벨을 붙인다
  2. 원본 PR을 develop에 머지한다
  3. 머지 이벤트가 발생한다
  4. 체리픽 워크플로우가 라벨을 읽는다
  5. release/11.5에 변경분을 옮긴다
  6. 필요하면 체리픽 PR이 생성되고 머지된다

라벨은 스티커가 아니라 트리거다.

하지만 트리거는 조건이 맞아야 작동한다.

“11.5.8에 넣고 싶다”는 말의 진짜 의미

여기서 헷갈리는 포인트가 나온다.

“나는 11.5.8에 넣고 싶은데, 라벨은 왜 release/11.5야?”

이 질문이 아주 좋다.

릴리스 브랜치에는 보통 두 종류가 있다.

release/11.5      # 11.5 계열의 큰 줄기
release/11.5.8    # 실제 특정 패치 릴리스 준비 브랜치

만약 release/11.5.8 브랜치가 아직 없다면, 보통은 release/11.5에서 나중에 11.5.8 브랜치를 잘라낸다.

그러면 지금 해야 할 일은 단순하다.

11.5.8 브랜치가 생기기 전에 release/11.5에 체리픽을 끝내두기

그러면 나중에 11.5.8 브랜치가 release/11.5에서 만들어질 때, 그 수정도 같이 따라간다.

기차 비유로 말하면 이렇다.

release/11.5라는 플랫폼에 먼저 짐을 올려둔다. 11.5.8 기차가 그 플랫폼에서 출발하면 짐도 같이 탄다.

그런데 11.5.8 브랜치가 이미 있다면?

상황이 달라진다.

이미 release/11.5.8 브랜치가 존재한다면, 그 브랜치는 release/11.5에서 이미 떠난 기차다.

이때 release/11.5에만 짐을 올려도 11.5.8 기차에는 실리지 않는다.

그 경우에는 대상 브랜치를 정확히 찍어야 한다.

cherry-pick: release/11.5.8

또는 PR이 이미 머지된 뒤라면 수동으로 체리픽 워크플로우를 실행해야 한다.

gh workflow run cherry-pick \
  -f pr=<PR 번호> \
  -f onto=release/11.5.8

여기서 조심할 점이 있다.

아직 존재하지 않는 브랜치에 라벨을 달면 자동화가 “그런 목적지는 없는데요?” 하고 라벨을 제거하거나 스킵할 수 있다.

그래서 먼저 확인한다.

git ls-remote --heads origin 'release/11.5.8'

브랜치가 없으면 release/11.5가 맞고, 브랜치가 있으면 release/11.5.8을 직접 겨냥해야 한다.

실제 사례: 11.5.7에 들어간 PR은 어떻게 보였을까

예전에 비슷한 흐름을 이미 한 번 지나간 PR이 있었다.

PR #16865
fix(apps/api): [QPD-5317] API Token Allowed Zone 캐시 갱신 누락 수정

이 PR도 원본은 develop으로 머지되었고, 라벨은 cherry-pick: release/11.5였다.

흥미로운 점은 release/11.5.7 브랜치에서 원본 merge commit이 그대로 보이지는 않았다는 것이다.

왜냐하면 체리픽은 보통 새 커밋을 만든다. 같은 변경이라도 다른 브랜치에 옮겨 심는 순간 SHA가 달라질 수 있다.

그래서 이렇게 확인했다.

원본 develop merge commit: 955f7f5...
release/11.5.7에 원본 commit 포함 여부: NO
release/11.5.7 로그 검색 결과: 153fce2... fix(apps/api): [QPD-5317] ... (#16865)

즉, 원본 커밋 SHA가 없다고 해서 “반영 안 됐다”고 판단하면 안 된다.

체리픽 커밋은 새 SHA로 존재할 수 있다.

이때 더 믿을 만한 확인 방법은 다음이다.

git log origin/release/11.5.7 \
  --oneline \
  --grep='QPD-5317\|16865\|Allowed Zone' \
  --regexp-ignore-case

git show --name-only 153fce2...

실제 확인 결과, release/11.5.7에는 아래 파일 변경이 들어가 있었다.

apps/api/build.gradle.kts
apps/api/iam/src/main/kotlin/.../ApiTokenCached.kt
apps/api/iam/src/main/kotlin/.../ApiTokenCachedRepository.kt
apps/api/iam/src/test/kotlin/.../ApiTokenCachedRepositoryTest.kt

이 사례가 알려주는 교훈은 단순하다.

체리픽 완료 확인은 “원본 commit SHA가 있나?”만 보면 부족하고, release 브랜치의 체리픽 commit 로그와 실제 변경 파일까지 함께 봐야 한다.

이게 바로 릴리스 확인의 현실적인 감각이다.

체리픽에서 제일 중요한 질문 3개

체리픽이 헷갈릴 때는 이 세 가지만 물어보면 된다.

1. 원본 PR은 머지됐나?

머지 전이면 라벨을 붙여 예약한다.

머지 후라면 라벨만으로는 늦을 수 있다. 이때는 수동 workflow 실행이 필요할 수 있다.

2. 대상 릴리스 브랜치는 존재하나?

release/11.5.8이 아직 없다면 release/11.5에 넣어야 한다.

release/11.5.8이 이미 있다면 그 브랜치에 직접 넣어야 한다.

3. 릴리스 브랜치에 실제로 들어갔나?

체리픽은 “라벨을 달았다”에서 끝나지 않는다.

마지막은 항상 확인이다.

gh run list --workflow cherry-pick.yml --limit 10

gh pr list \
  --base release/11.5 \
  --search "cherry-pick #<PR 번호>"

그리고 정말 중요한 변경이라면 파일이나 커밋 로그로도 본다.

git fetch origin

git log origin/release/11.5 --oneline --grep='<키워드>'

자동화는 훌륭하지만, 릴리스는 확인이 본체다.

이번 상황을 한 문장으로 정리하면

현재 해야 할 일은 이렇다.

PR에 cherry-pick: release/11.5 라벨을 붙였으니, 원본 PR을 develop에 머지하고, 자동 체리픽이 release/11.5에 성공했는지 확인한다. 11.5.8 브랜치가 아직 없다면 이 수정은 나중에 11.5.8 브랜치가 release/11.5에서 잘릴 때 함께 들어간다.

만약 11.5.8 브랜치가 이미 생긴 뒤라면?

release/11.5.8 대상으로 별도 체리픽을 실행한다.

체리픽은 “긴급 수정의 여권 심사”다

체리픽을 단순히 “커밋 복사”라고만 이해하면 조금 부족하다.

릴리스 브랜치는 이미 고객에게 나갈 준비를 하는 공간이다. 그래서 아무 변경이나 들어가면 안 된다. 체리픽은 필요한 변경만 골라 릴리스 브랜치의 입국 심사를 통과시키는 과정이다.

라벨은 신청서다.

원본 PR 머지는 출국 도장이다.

체리픽 워크플로우는 입국 심사대다.

릴리스 브랜치 반영 확인은 수하물 찾기다.

수하물까지 찾았을 때 비로소 말할 수 있다.

“이 수정, 이번 릴리스에 들어갔습니다.”

오늘의 체크리스트

체리픽이 필요할 때는 이 순서로 보면 된다.

[ ] 원본 PR이 default branch 대상인가?
[ ] 리뷰와 CI가 통과했나?
[ ] 체리픽 대상 브랜치가 존재하나?
[ ] 머지 전이면 cherry-pick 라벨을 붙였나?
[ ] 원본 PR을 머지했나?
[ ] cherry-pick workflow가 실행됐나?
[ ] 체리픽 PR이 생성/머지됐나?
[ ] release 브랜치에 실제 변경이 들어갔나?
[ ] 특정 패치 버전 브랜치가 이미 있으면 그쪽에도 들어갔나?

이걸 기억하면 체리픽은 더 이상 마법 주문이 아니다.

그냥 릴리스 기차에 꼭 실어야 할 짐을, 정확한 플랫폼에, 제때 올려두는 일이다.

체리 한 알을 고르는 일처럼 보이지만, 사실은 릴리스 사고를 막는 꽤 중요한 운영 기술이다.