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
23 changes: 23 additions & 0 deletions src/commands/githubActions/bumpUpstream/github/branchNeedsPr.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/** The two Github methods this needs. */
interface BranchPrSource {
branchExists(branch: string): Promise<boolean>;
getPrsFromBranch(branch: string): Promise<unknown[]>;
}

/**
* True when a bump branch was pushed but never got a PR, open or closed, for
* example because the token was not allowed to create pull requests. Such a
* branch would otherwise make every later run skip that release. A branch whose
* PR was closed stays skipped: closing it was a decision.
*/
export async function branchNeedsPr({
repo,
branchName
}: {
repo: BranchPrSource;
branchName: string;
}): Promise<boolean> {
if (!(await repo.branchExists(branchName))) return false;
const prs = await repo.getPrsFromBranch(branchName);
return prs.length === 0;
}
1 change: 1 addition & 0 deletions src/commands/githubActions/bumpUpstream/github/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./branchNeedsPr.js";
export * from "./closeOldPrs.js";
export * from "./getBumpPrBody.js";
export * from "./getGithubSettings.js";
Expand Down
13 changes: 12 additions & 1 deletion src/commands/githubActions/bumpUpstream/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { GitSettings, GithubSettings, UpstreamSettings } from "./types.js";
import { printSettings, getInitialSettings } from "./settings/index.js";
import { ManifestFormat } from "../../../files/manifest/types.js";
import {
branchNeedsPr,
closeOldPrs,
getBumpPrBody,
getGithubSettings,
Expand Down Expand Up @@ -107,7 +108,17 @@ async function gaBumpUpstreamHandler({
const githubSettings = await getGithubSettings(dir, upstreamSettings);
const { branchName, repo } = githubSettings;
if (!(await isBranchNew({ branchName, repo, dir }))) {
// We assume the PR was already opened
if (await branchNeedsPr({ repo, branchName })) {
// An earlier run pushed the branch but could not open its PR
console.log(`Branch ${branchName} exists without a PR, opening it`);
await attemptToOpenPR({
commitMsg: createCommitMessage(upstreamSettings),
upstreamSettings,
githubSettings
});
return;
}
// The PR was already opened
console.log(`Branch ${branchName} already exists`);
return;
}
Expand Down
18 changes: 18 additions & 0 deletions src/providers/github/Github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,24 @@ export class Github {
return openPrsResponse.data;
}

/**
* Returns PRs in any state (open or closed) where head branch equals `branch`
* Only branches and PRs originating from the same repo
*/
async getPrsFromBranch(branch: string) {
const prsResponse = await this.octokit.rest.pulls.list({
owner: this.owner,
repo: this.repo,
state: "all",
headers: {
"X-GitHub-Api-Version": "2022-11-28"
},
head: `${this.owner}:${branch}`
});

return prsResponse.data;
}

/**
* Returns all branches; cleanup must never infer deletion from a partial page.
* @returns branch = {
Expand Down
39 changes: 39 additions & 0 deletions test/commands/gaBumpUpstream/branchNeedsPr.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { expect } from "chai";
import { branchNeedsPr } from "../../../src/commands/githubActions/bumpUpstream/github/index.js";

const branchName = "dappnodebot/bump-upstream/org/repo@1.2.0";

function fakeRepo({
remoteBranch,
prStates
}: {
remoteBranch: boolean;
prStates: string[];
}) {
return {
branchExists: async () => remoteBranch,
getPrsFromBranch: async () => prStates.map(state => ({ state }))
};
}

describe("command / gaBumpUpstream / branchNeedsPr", () => {
it("is true for a pushed bump branch that never got a PR", async () => {
const repo = fakeRepo({ remoteBranch: true, prStates: [] });
expect(await branchNeedsPr({ repo, branchName })).to.equal(true);
});

it("is false when the branch has an open PR", async () => {
const repo = fakeRepo({ remoteBranch: true, prStates: ["open"] });
expect(await branchNeedsPr({ repo, branchName })).to.equal(false);
});

it("is false when the branch's PR was closed", async () => {
const repo = fakeRepo({ remoteBranch: true, prStates: ["closed"] });
expect(await branchNeedsPr({ repo, branchName })).to.equal(false);
});

it("is false when the branch exists only locally", async () => {
const repo = fakeRepo({ remoteBranch: false, prStates: [] });
expect(await branchNeedsPr({ repo, branchName })).to.equal(false);
});
});