Skip to content
Open
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
6 changes: 4 additions & 2 deletions packages/plugins/scm-github/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1351,10 +1351,12 @@ function createGitHubSCM(): SCM {
blockers.push("Review required");
}

// Conflicts / merge state
// Conflicts / merge state — `noConflicts` means "not explicitly CONFLICTING"
// (matches graphql-batch `hasConflicts`), so draft / BLOCKED / UNKNOWN do not
// look like merge conflicts to lifecycle conflict reactions.
const mergeable = (data.mergeable ?? "").toUpperCase();
const mergeState = (data.mergeStateStatus ?? "").toUpperCase();
const noConflicts = mergeable === "MERGEABLE";
const noConflicts = mergeable !== "CONFLICTING";
if (mergeable === "CONFLICTING") {
blockers.push("Merge conflicts");
} else if (mergeable === "UNKNOWN" || mergeable === "") {
Expand Down
23 changes: 21 additions & 2 deletions packages/plugins/scm-github/test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1174,6 +1174,7 @@ describe("scm-github plugin", () => {

const result = await scm.getMergeability(pr);
expect(result.blockers).toContain("Review required");
expect(result.noConflicts).toBe(true);
});

it("reports merge conflicts as blockers", async () => {
Expand All @@ -1191,7 +1192,7 @@ describe("scm-github plugin", () => {
expect(result.blockers).toContain("Merge conflicts");
});

it("reports UNKNOWN mergeable as noConflicts false", async () => {
it("treats UNKNOWN mergeable as non-conflicting", async () => {
mockGh({ state: "OPEN" }); // getPRState
mockGh({
mergeable: "UNKNOWN",
Expand All @@ -1202,7 +1203,7 @@ describe("scm-github plugin", () => {
mockGh([{ name: "build", state: "SUCCESS" }]);

const result = await scm.getMergeability(pr);
expect(result.noConflicts).toBe(false);
expect(result.noConflicts).toBe(true);
expect(result.blockers).toContain("Merge status unknown (GitHub is computing)");
expect(result.mergeable).toBe(false);
});
Expand All @@ -1222,6 +1223,24 @@ describe("scm-github plugin", () => {
expect(result.mergeable).toBe(false);
});

it("draft PR with UNKNOWN + BLOCKED merge state is not treated as conflicting (#1314)", async () => {
mockGh({ state: "OPEN" }); // getPRState
mockGh({
mergeable: "UNKNOWN",
reviewDecision: "APPROVED",
mergeStateStatus: "BLOCKED",
isDraft: true,
});
mockGh([{ name: "build", state: "SUCCESS" }]);

const result = await scm.getMergeability(pr);
expect(result.noConflicts).toBe(true);
expect(result.mergeable).toBe(false);
expect(result.blockers).toContain("PR is still a draft");
expect(result.blockers).toContain("Merge status unknown (GitHub is computing)");
expect(result.blockers).toContain("Merge is blocked by branch protection");
});

it("reports multiple blockers simultaneously", async () => {
mockGh({ state: "OPEN" }); // getPRState
mockGh({
Expand Down
Loading