diff --git a/.github/scripts/find-viewed-auto-merge-targets.mjs b/.github/scripts/find-viewed-auto-merge-targets.mjs index 7087f07f6..ad697e553 100644 --- a/.github/scripts/find-viewed-auto-merge-targets.mjs +++ b/.github/scripts/find-viewed-auto-merge-targets.mjs @@ -1,7 +1,7 @@ // Viewed 기반 자동 머지 대상 판정 (#2248) // -// 지정한 한 사람(VIEWED_BY)이 PR 의 모든 파일을 Viewed 로 체크했고 PR 에 승인 대기·변경 요청이 없으면 -// «Merge when ready» 를 예약할 대상으로 고른다. 실제 머지는 GitHub 가 required check·승인 수·merge queue 를 통과시킨 뒤에만 +// 지정한 한 사람(VIEWED_BY)이 PR 의 모든 파일을 Viewed 로 체크하면 «Merge when ready» 를 예약할 대상으로 +// 고른다. 리뷰 상태는 보지 않는다(#2277, 리뷰 폐지). 실제 머지는 GitHub 가 required check·승인 수·merge queue 를 통과시킨 뒤에만 // 한다. // // viewerViewedState 는 토큰 주인 본인의 체크만 돌려준다. 그래서 VIEWED_BY 본인의 토큰(VIEWED_TOKEN)으로 @@ -17,10 +17,8 @@ import { appendFileSync } from "node:fs"; import { pathToFileURL } from "node:url"; -const BLOCKING_REVIEW_DECISIONS = new Set(["CHANGES_REQUESTED", "REVIEW_REQUIRED"]); - const PULL_REQUEST_FIELDS = ` - id number isDraft headRefOid reviewDecision isInMergeQueue changedFiles + id number isDraft headRefOid isInMergeQueue changedFiles author { login } autoMergeRequest { enabledAt } files(first: 100) { pageInfo { hasNextPage endCursor } nodes { path viewerViewedState } } @@ -49,11 +47,6 @@ export function decideViewedAutoMerge(pullRequest, files) { if (pullRequest.autoMergeRequest || pullRequest.isInMergeQueue) { return { enable: false, reason: "이미 자동 머지 예약 또는 merge queue 에 있다" }; } - // 승인 룰셋이 승인 0건을 요구하면 GitHub 는 reviewDecision 을 비워(null) 돌려준다. 막는 것은 - // 아직 승인이 필요하거나 변경 요청이 남은 상태뿐이다. - if (BLOCKING_REVIEW_DECISIONS.has(pullRequest.reviewDecision)) { - return { enable: false, reason: `reviewDecision 이 ${pullRequest.reviewDecision}` }; - } // 파일 목록을 다 못 읽었으면 «다 봤다» 고 단정하지 않는다. if (files.length === 0 || files.length !== pullRequest.changedFiles) { return { enable: false, reason: `파일 ${files.length}/${pullRequest.changedFiles} 만 읽었다` }; diff --git a/.github/scripts/find-viewed-auto-merge-targets.test.mjs b/.github/scripts/find-viewed-auto-merge-targets.test.mjs index ac2f0d749..8515ccbbc 100644 --- a/.github/scripts/find-viewed-auto-merge-targets.test.mjs +++ b/.github/scripts/find-viewed-auto-merge-targets.test.mjs @@ -16,7 +16,6 @@ function pullRequest(overrides = {}) { number: 1, isDraft: false, headRefOid: "0123456789abcdef0123456789abcdef01234567", - reviewDecision: "APPROVED", isInMergeQueue: false, autoMergeRequest: null, changedFiles: 2, @@ -35,18 +34,13 @@ test("하나라도 Viewed 가 아니면 예약하지 않는다 — 체크 뒤 } }); -test("승인 대기·변경 요청이면 Viewed 를 다 해도 예약하지 않는다", () => { - for (const reviewDecision of ["CHANGES_REQUESTED", "REVIEW_REQUIRED"]) { +test("리뷰 상태와 무관하게 Viewed 만으로 예약한다", () => { + for (const reviewDecision of [null, "APPROVED", "CHANGES_REQUESTED", "REVIEW_REQUIRED"]) { const decision = decideViewedAutoMerge(pullRequest({ reviewDecision }), [viewed("a.kt"), viewed("b.kt")]); - assert.equal(decision.enable, false, String(reviewDecision)); + assert.equal(decision.enable, true, String(reviewDecision)); } }); -test("승인을 요구하지 않는 룰셋(reviewDecision null)이면 Viewed 만으로 예약한다", () => { - const decision = decideViewedAutoMerge(pullRequest({ reviewDecision: null }), [viewed("a.kt"), viewed("b.kt")]); - assert.equal(decision.enable, true); -}); - test("파일 목록을 다 못 읽었으면 다 봤다고 단정하지 않는다", () => { assert.equal(decideViewedAutoMerge(pullRequest({ changedFiles: 3 }), [viewed("a.kt"), viewed("b.kt")]).enable, false); assert.equal(decideViewedAutoMerge(pullRequest({ changedFiles: 0 }), []).enable, false); @@ -88,9 +82,9 @@ test("merge queue 에 있는데 Viewed 가 풀리면 큐에서 빼고, 예약도 ); }); -test("승인·Draft 상태와 무관하게 Viewed 로만 해제를 가른다", () => { +test("리뷰·Draft 상태와 무관하게 Viewed 로만 해제를 가른다", () => { const files = [viewed("a.kt"), { path: "b.kt", viewerViewedState: "UNVIEWED" }]; - for (const overrides of [{ reviewDecision: "APPROVED" }, { reviewDecision: "REVIEW_REQUIRED" }, { isDraft: true }]) { + for (const overrides of [{ reviewDecision: "APPROVED" }, { reviewDecision: "CHANGES_REQUESTED" }, { isDraft: true }]) { assert.equal(decideViewedAutoMergeCancel(pullRequest({ ...scheduled, ...overrides }), files).disableAutoMerge, true); } }); diff --git a/.github/workflows/viewed-auto-merge.yml b/.github/workflows/viewed-auto-merge.yml index 0910b46a6..4a89af9fc 100644 --- a/.github/workflows/viewed-auto-merge.yml +++ b/.github/workflows/viewed-auto-merge.yml @@ -2,9 +2,8 @@ # # 적용 범위는 VIEWED_BY 본인이 작성한 PR 이다(#2270). 팀원·봇 PR 은 예약도 해제도 하지 않는다. # -# VIEWED_BY 가 PR 의 모든 파일을 Viewed 로 체크했고 PR 에 승인 대기·변경 요청이 없으면 «Merge when ready» 를 -# 대신 건다. 승인 룰셋이 승인 0건이면 reviewDecision 이 비어 있어도 대상이다. 머지는 GitHub 가 룰셋·required -# check·merge queue 를 모두 통과시킨 뒤에만 하므로 CI 가 빨가면 머지되지 않는다. +# VIEWED_BY 가 PR 의 모든 파일을 Viewed 로 체크하면 «Merge when ready» 를 대신 건다. 리뷰 상태는 보지 않는다. +# 머지는 GitHub 가 룰셋·required check·merge queue 를 모두 통과시킨 뒤에만 하므로 CI 가 빨가면 머지되지 않는다. # # 반대 방향도 매 실행이 맞춘다(#2252). 예약됐거나 merge queue 에 있는 VIEWED_BY 본인 PR 중 VIEWED_BY 기준 Viewed 가 다 차지 # 않은 것은 예약을 끄고 큐에서 뺀다. 새 push 로 바뀐 파일은 DISMISSED 가 되고 룰셋은 push 로 승인을 지우지 diff --git a/README.md b/README.md index 19453f492..a30bebaa2 100644 --- a/README.md +++ b/README.md @@ -187,4 +187,4 @@ Kover는 임의의 절대 커버리지 목표를 강제하지 않는다. 정확 - PR의 담당자는 올린 사람이 아니라 **어사인된 사람**이다. 어사인이 비어 있으면 [자동 어사인 workflow](.github/workflows/pr-assign-author.yml)가 작성자를 건다. 사람이 담당자를 바꾼 것은 되돌리지 않는다. - `develop`·`main` 머지에 리뷰 승인은 필요 없다. PR 경유, 필수 검사, `develop`의 merge queue는 그대로 요구된다. -- `develop` 대상 PR은 1hyok가 모든 파일을 Viewed로 체크하면 [Viewed 자동 머지 workflow](.github/workflows/viewed-auto-merge.yml)가 10분 안팎으로 자동 머지(Merge when ready)를 예약한다. 변경요청이 남은 PR은 예약하지 않는다. Viewed 체크에는 이벤트가 없어 주기 실행으로 훑고, 본인 체크만 조회되므로 1hyok의 읽기 전용 토큰을 `VIEWED_TOKEN` 시크릿으로 둔다. 필수 검사가 통과해야 merge queue로 들어가며, 예약을 멈추려면 파일 하나의 Viewed를 풀고 PR 화면에서 예약을 끈다. +- `develop` 대상 PR은 1hyok가 모든 파일을 Viewed로 체크하면 [Viewed 자동 머지 workflow](.github/workflows/viewed-auto-merge.yml)가 10분 안팎으로 자동 머지(Merge when ready)를 예약한다. Viewed 체크에는 이벤트가 없어 주기 실행으로 훑고, 본인 체크만 조회되므로 1hyok의 읽기 전용 토큰을 `VIEWED_TOKEN` 시크릿으로 둔다. 필수 검사가 통과해야 merge queue로 들어가며, 예약을 멈추려면 파일 하나의 Viewed를 풀고 PR 화면에서 예약을 끈다.