카카오스타일이 알파(스테이징) 환경에 오래된 브랜치를 배포해 이미 반영된 기능이 사라지던 문제를, 배포 시점에 주 브랜치 포함 여부를 검사해 막은 기록이다. 얕은 복제(shallow clone)를 쓰는 빌드 파이프라인이라 브랜치 비교가 안 되는 게 기술적 난점이었고, 공통 조상이 나올 때까지 커밋을 더 받아오는 스크립트로 해결했다.
핵심 포인트- 프로덕션까지 반영된 기능이 알파에서 사라지는 사고가 반복됐다. 오래된 커밋에서 딴 브랜치를 최신 반영 없이 알파에 배포하는 게 원인이었다.
- "알파 배포 시 main 브랜치를 포함해 주세요" 라는 규칙으로만 관리해 왔고, 규칙이라 당연히 계속 뚫렸다.
- 원격 저장소에 직접 물어볼 수 있는 git 명령은 사실상 `ls-remote` 뿐이라, 복제 전 비교는 포기했다.
- `--depth 1` 로 받은 뒤 공통 조상이 나올 때까지 `git fetch --deepen` 을 반복하고, `git rev-list --left-only --count` 로 주 브랜치가 앞선 커밋 수가 0 인지 검사한다.
- GitHub Actions 환경에서는 GitHub API 의 `compareCommitsWithBasehead` 로 복제 전에 `behind_by` 를 확인할 수 있다.
상세 정리- 환경 정의: 알파는 프로덕션과 구조가 같고 DB 만 분리된 검증 환경, 베타는 애플리케이션만 별도이고 DB 는 프로덕션을 쓰는 최종 검증 환경이다. 피처 플래그 대신 주로 베타로 최종 검증한다.
- 알파 환경이 하나뿐이라 팀 간 덮어쓰기 충돌도 있는데, 그건 점유 의사 표시나 팀별 개발 환경 분리로 다루고 이번 글은 다른 문제를 잡는다.
- 진짜 문제: 이미 프로덕션에 나간 기능이 알파에서 없어지는 케이스가 높은 비율로 발생했다. 내 기능만 배포했는데 무관해 보이는 기능이 죽으니 원인 추적에 여러 사람의 시간이 날아갔다.
- 빌드 구조: 배포 명령을 내리면 GitHub 에서 소스를 받아 도커 이미지를 빌드하고, 이때 브랜치를 지정한다. 속도 때문에 `git clone -b $BRANCH --depth 1 --jobs 2` 로 얕게 복제한다.
- 첫 시도의 벽: 로컬 저장소에서는 브랜치 비교가 되지만 얕은 복제라 비교가 불가능했다. `--depth` 나 `--shallow-since` 로 깊이를 늘려도 공통 조상이 포함될지 보장할 수 없다.
- 해결 조합 ①: `git fetch --depth 1 origin "+refs/heads/$BASE_BRANCH:refs/heads/$BASE_BRANCH"` 로 주 브랜치 ref 만 추가로 가져온다. `--no-single-branch` 로 전부 받는 것보다 가볍다.
- 해결 조합 ②: `while [ -z "$(git merge-base $BASE_BRANCH $BRANCH)" ]; do git fetch -q --deepen=20 origin ...; done` 으로 공통 조상이 나올 때까지 20 커밋씩 더 받는다.
- 판정: `git rev-list --left-only --count $BASE_BRANCH...$BRANCH` 가 0 이 아니면 주 브랜치가 앞서 있다는 뜻이므로 `$BASE_BRANCH is not merged` 를 출력하고 exit 1 로 배포를 중단한다. 배포 브랜치가 주 브랜치와 같은 경우에도 동작한다.
- GitHub Actions 경로: 초기 파이프라인은 AWS CodeBuild 지만 최근 서비스는 Actions 를 쓴다. `actions/github-script` 로 `repos.compareCommitsWithBasehead({ basehead: 'master...${context.sha}' })` 를 호출해 `behind_by === 0` 인지 보면 복제 없이 검사가 끝난다.
- 회고 톤: 1년 넘게 미뤄둔 이슈였고, 시스템 개선에 조금만 시간을 썼으면 없었을 반복 사고였다는 점을 글의 출발점으로 삼는다.
왜 읽나얕은 복제 기반 CI 에서 "베이스 브랜치 미반영 배포" 를 막고 싶은 인프라·플랫폼 담당자에게 그대로 붙여 쓸 수 있는 스크립트를 준다.