Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 3 additions & 10 deletions .github/scripts/find-viewed-auto-merge-targets.mjs
Original file line number Diff line number Diff line change
@@ -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)으로
Expand All @@ -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 } }
Expand Down Expand Up @@ -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} 만 읽었다` };
Expand Down
16 changes: 5 additions & 11 deletions .github/scripts/find-viewed-auto-merge-targets.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ function pullRequest(overrides = {}) {
number: 1,
isDraft: false,
headRefOid: "0123456789abcdef0123456789abcdef01234567",
reviewDecision: "APPROVED",
isInMergeQueue: false,
autoMergeRequest: null,
changedFiles: 2,
Expand All @@ -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);
Expand Down Expand Up @@ -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);
}
});
Expand Down
5 changes: 2 additions & 3 deletions .github/workflows/viewed-auto-merge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 로 승인을 지우지
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 화면에서 예약을 끈다.
Loading