Skip to content
Apothem
런북

단독 메인테이너 PR 병합 의식

메인테이너가 자신의 풀 리퀘스트를 직접 병합할 수 있는 경우와, 승인을 요구하고 이를 관리자에게도 적용하는 브랜치 보호를 위한 의식입니다.

프로젝트의 브랜치 보호 아래에서 PR을 자가 병합하기 위한 정식 참조. main에 현재 적용된 보호는 승인 검토 0개를 요구하고 enforce_admins를 꺼 둔 상태이며, 이는 브랜치 보호 페이지에 기록되어 있습니다. 따라서 메인테이너는 자신의 풀 리퀘스트를 직접 병합합니다. 이 페이지의 임시 토글 의식은 보호가 승인을 요구하고 이를 관리자에게도 적용하는 경우에만 적용됩니다.


1. 교착 상태

현재 적용된 main 보호에는 다음 설정이 있습니다.

  • required_pull_request_reviews.required_approving_review_count: 0
  • enforce_admins: false

이 설정에서는 교착 상태가 없습니다. 메인테이너의 풀 리퀘스트는 필수 상태 검사가 녹색이 되고 검토 대화가 해결되면 gh pr merge <pr-number> --squash --delete-branch로 병합됩니다.

교착 상태는 보호에 다음의 더 엄격한 두 설정이 모두 있는 경우에만 존재합니다.

  • required_pull_request_reviews.required_approving_review_count: 1
  • enforce_admins: true

이 규칙들은 메인테이너가 여러 명인 시나리오에서는 올바릅니다(모든 변경이 동료 검토를 거친 PR을 통해 main에 도달하며, 관리자도 예외가 아님). 그러나 단독 메인테이너에게는 교착 상태를 일으킵니다. GitHub 정책은 본인이 작성한 PR에 대한 자가 승인을 허용하지 않으며, enforce_admins=true는 검토 승인 요구 사항에 대한 gh pr merge --admin 재정의를 차단하기 때문입니다. enforce_admins: false에서는 --admin이 승인 요구 사항을 우회하므로 토글이 필요 없지만, 실패(빨간색) 상태인 필수 검사도 함께 우회하므로 사용하기 전에 검사가 녹색인지 확인하세요.

세 가지 해결 경로가 있습니다.

경로사용 시점
required_approving_review_count: 0을 영구적으로 설정(현재 설정)저장소가 무기한 단독 메인테이너 체제로 남을 경우에만 채택. 보호의 검토 의도를 뒤집음
임시 토글 의식보호가 enforce_admins=true와 함께 승인을 요구하는 동안의 단독 메인테이너 자가 병합. 병합 윈도우 밖에서는 보호 불변식을 유지함
쓰기 권한을 가진 CI 봇 신원을 프로비저닝여러 메인테이너의 온보딩이 임박한 경우에만 채택. 봇 승인은 보호가 의도한 사람의 검토를 약화시킴

이 저장소는 현재 영구 승인 0개 경로를 사용합니다. 보호가 승인을 요구하고 이를 관리자에게도 적용할 때는 언제나 임시 토글 의식이 정식 경로입니다.

2. 임시 토글 의식

의식의 불변식: 의식 시작 시점의 보호 상태는 종료 시점의 보호 상태와 같습니다. 완화는 병합 윈도우 안에서만 존재합니다.

수동 형식:

# 1. Open the PR, push branch, wait for CI green.
gh pr create --base main --head <feature-branch> --title "..." --body "..."
gh pr checks <pr-number>            # poll until all green

# 2. Relax approver-count to 0.
gh api repos/<owner>/<repo>/branches/main/protection/required_pull_request_reviews \
    -X PATCH -F required_approving_review_count=0

# 3. Squash-merge with admin override (admin override now succeeds because the
#    review-approval requirement is the only block enforce_admins refuses).
gh pr merge <pr-number> --squash --delete-branch --admin

# 4. Restore approver-count to 1.
gh api repos/<owner>/<repo>/branches/main/protection/required_pull_request_reviews \
    -X PATCH -F required_approving_review_count=1

래핑된 형식: 2 → 3 → 4단계를 단일 비대화형 시퀀스로 실행하고 3단계 실패 시 try / finally로 롤백 안전성을 제공하는 원자적 Python 래퍼는 scripts/dev/admin_merge.py를 참조하세요.

python scripts/dev/admin_merge.py <pr-number>

3. 불변식

  • 토글 윈도우 최소화. 2 → 3 → 4단계는 단일 비대화형 시퀀스로 실행됩니다. 보호는 병합 API 호출이 지속되는 동안에만 완화된 상태에 있으며, 관찰된 모든 실행에서 1초 미만이었습니다.
  • 복원은 무조건적입니다. 4단계는 3단계가 실패해도 실행됩니다(래퍼의 try / finally 절이 모든 종료 경로에서 복원을 보장함). 실패한 병합 시도는 보호를 완화된 상태로 남기지 않습니다.
  • enforce_admins가 켜져 있는 동안에는 CI 게이팅이 내내 유지됩니다. required_status_checks는 토글의 영향을 받지 않습니다. enforce_admins: true이면 병합에는 여전히 구성된 상태 검사 컨텍스트가 녹색이어야 합니다. enforce_admins: false이면 3단계의 --admin 병합이 실패(빨간색) 상태인 필수 검사도 우회하므로, 그 상태에서는 의식을 실행하지 마세요.
  • 감사 추적이 보입니다. gh pr merge --admin 주석은 PR의 병합 메타데이터에 나타나며, 보호 API 토글은 저장소 감사 로그에 메인테이너의 신원으로 나타납니다.

4. 의식을 사용하지 말아야 할 때

보호가 승인 검토 0개를 요구하거나 enforce_admins를 꺼 둔 동안에는 의식이 필요 없습니다. 1절에 설명된 대로 직접 병합하세요.

이 의식은 메인테이너 본인 PR의 자가 병합을 위한 것입니다. 동료 검토자(쓰기 권한을 가진 기여자)가 있는 경우에는 표준 gh pr review --approve + gh pr merge --squash --delete-branch 경로가 올바르며 의식은 필요 없습니다.

저장소에 두 번째 메인테이너가 합류하면 의식을 폐기하세요. 표준 PR 검토 흐름은 어떤 토글 없이도 보호가 의도한 사람의 검토를 되살립니다.

5. 과거 실행 감사

모든 의식 실행은 운영자가 사후에 검사할 수 있는 세 가지 지속적인 표면에 남습니다.

  • PR의 병합 메타데이터. gh pr view <pr-number> --json mergedBy,mergedAt,mergeCommit은 병합 행위자, 병합 타임스탬프, 스쿼시 커밋의 SHA를 반환합니다. mergedBy 행위자는 3단계를 실행한 메인테이너 신원과 일치합니다.
  • 저장소의 감사 로그. gh api /repos/<owner>/<repo>/actions/runs --jq '.workflow_runs[] | select(.event=="push")'는 병합 후 CI 실행을 병합 커밋과 연관 짓습니다. 저장소의 보호 규칙 감사 로그(Settings → Branches UI를 통한 gh api /repos/<owner>/<repo>/branches/main/protection 기록)는 병합 구간을 감싸는 2단계와 4단계 토글을 보여 줍니다.
  • 메인테이너의 git 히스토리. git log main --first-parent --oneline은 main의 모든 스쿼시 병합 커밋을 나열합니다. 커밋 제목에는 해당 PR 번호(gh pr merge --squash 기본값에 따른 (#<pr-number>) 접미사)가 들어 있으므로, 운영자는 과거의 어떤 반영이든 그 PR과 실행 의식까지 거슬러 추적할 수 있습니다.

scripts/dev/admin_merge.py가 진입점일 때, 그 비대화형 로그(stderr)는 세 번의 보호 API 호출과 병합 API 호출을 순서대로 나열합니다. stderr를 tee로 파이프하면(2>&1 | tee) GitHub 측 로그 보존 기간에 의존하지 않고 실행별 감사 조각을 캡처할 수 있습니다.

On this page