Skip to content

Commit a90d00a

Browse files
authored
Merge pull request #473 from wolfgangwalther/idempotency
Treat errors on repeated runs gracefully
2 parents bbd0c60 + 868b016 commit a90d00a

File tree

3 files changed

+92
-65
lines changed

3 files changed

+92
-65
lines changed

.editorconfig

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[*.ts]
2+
indent_size = 2
3+
indent_style = space

src/backport.ts

Lines changed: 88 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
CreatePullRequestResponse,
66
PullRequest,
77
MergeStrategy,
8+
RequestError,
89
} from "./github";
910
import { GithubApi } from "./github";
1011
import { Git, GitRefNotFoundError } from "./git";
@@ -343,39 +344,69 @@ export class Backport {
343344
this.config.pwd,
344345
);
345346
if (pushExitCode != 0) {
346-
const message = this.composeMessageForGitPushFailure(
347-
target,
348-
pushExitCode,
349-
);
350-
console.error(message);
351-
successByTarget.set(target, false);
352-
await this.github.createComment({
353-
owner: workflowOwner,
354-
repo: workflowRepo,
355-
issue_number: pull_number,
356-
body: message,
357-
});
358-
continue;
347+
try {
348+
// If the branch already exists, ignore the error and keep going.
349+
console.info(
350+
`Branch ${branchname} may already exist, fetching it instead to recover previous run`,
351+
);
352+
await this.git.fetch(
353+
branchname,
354+
this.config.pwd,
355+
1,
356+
this.getRemote(),
357+
);
358+
console.info(
359+
`Previous branch successfully recovered, retrying PR creation`,
360+
);
361+
// note that the recovered branch is not guaranteed to be up-to-date
362+
} catch {
363+
// Fetching the branch failed as well, so report the original push error.
364+
const message = this.composeMessageForGitPushFailure(
365+
target,
366+
pushExitCode,
367+
);
368+
console.error(message);
369+
successByTarget.set(target, false);
370+
await this.github.createComment({
371+
owner: workflowOwner,
372+
repo: workflowRepo,
373+
issue_number: pull_number,
374+
body: message,
375+
});
376+
continue;
377+
}
359378
}
360379

361380
console.info(`Create PR for ${branchname}`);
362381
const { title, body } = this.composePRContent(target, mainpr);
363-
const new_pr_response = await this.github.createPR({
364-
owner,
365-
repo,
366-
title,
367-
body,
368-
head: branchname,
369-
base: target,
370-
maintainer_can_modify: true,
371-
draft: uncommitedShas !== null,
372-
});
373-
374-
if (new_pr_response.status != 201) {
375-
console.error(JSON.stringify(new_pr_response));
382+
let new_pr_response: CreatePullRequestResponse;
383+
try {
384+
new_pr_response = await this.github.createPR({
385+
owner,
386+
repo,
387+
title,
388+
body,
389+
head: branchname,
390+
base: target,
391+
maintainer_can_modify: true,
392+
draft: uncommitedShas !== null,
393+
});
394+
} catch (error) {
395+
if (!(error instanceof RequestError)) throw error;
396+
397+
if (
398+
error.status == 422 &&
399+
(error.response?.data as any).errors.some((err: any) =>
400+
err.message.startsWith("A pull request already exists for "),
401+
)
402+
) {
403+
console.info(`PR for ${branchname} already exists, skipping.`);
404+
continue;
405+
}
406+
407+
console.error(JSON.stringify(error.response?.data));
376408
successByTarget.set(target, false);
377-
const message =
378-
this.composeMessageForCreatePRFailed(new_pr_response);
409+
const message = this.composeMessageForCreatePRFailed(error);
379410
await this.github.createComment({
380411
owner: workflowOwner,
381412
repo: workflowRepo,
@@ -390,12 +421,11 @@ export class Backport {
390421
const milestone = mainpr.milestone?.number;
391422
if (milestone) {
392423
console.info("Setting milestone to " + milestone);
393-
const set_milestone_response = await this.github.setMilestone(
394-
new_pr.number,
395-
milestone,
396-
);
397-
if (set_milestone_response.status != 200) {
398-
console.error(JSON.stringify(set_milestone_response));
424+
try {
425+
await this.github.setMilestone(new_pr.number, milestone);
426+
} catch (error) {
427+
if (!(error instanceof RequestError)) throw error;
428+
console.error(JSON.stringify(error.response));
399429
}
400430
}
401431
}
@@ -404,16 +434,14 @@ export class Backport {
404434
const assignees = mainpr.assignees.map((label) => label.login);
405435
if (assignees.length > 0) {
406436
console.info("Setting assignees " + assignees);
407-
const add_assignee_response = await this.github.addAssignees(
408-
new_pr.number,
409-
assignees,
410-
{
437+
try {
438+
await this.github.addAssignees(new_pr.number, assignees, {
411439
owner,
412440
repo,
413-
},
414-
);
415-
if (add_assignee_response.status != 201) {
416-
console.error(JSON.stringify(add_assignee_response));
441+
});
442+
} catch (error) {
443+
if (!(error instanceof RequestError)) throw error;
444+
console.error(JSON.stringify(error.response));
417445
}
418446
}
419447
}
@@ -430,10 +458,11 @@ export class Backport {
430458
pull_number: new_pr.number,
431459
reviewers: reviewers,
432460
};
433-
const set_reviewers_response =
461+
try {
434462
await this.github.requestReviewers(reviewRequest);
435-
if (set_reviewers_response.status != 201) {
436-
console.error(JSON.stringify(set_reviewers_response));
463+
} catch (error) {
464+
if (!(error instanceof RequestError)) throw error;
465+
console.error(JSON.stringify(error.response));
437466
}
438467
}
439468
}
@@ -443,33 +472,29 @@ export class Backport {
443472
...new Set([...labelsToCopy, ...this.config.add_labels]),
444473
];
445474
if (labels.length > 0) {
446-
const label_response = await this.github.labelPR(
447-
new_pr.number,
448-
labels,
449-
{
475+
try {
476+
await this.github.labelPR(new_pr.number, labels, {
450477
owner,
451478
repo,
452-
},
453-
);
454-
if (label_response.status != 200) {
455-
console.error(JSON.stringify(label_response));
479+
});
480+
} catch (error) {
481+
if (!(error instanceof RequestError)) throw error;
482+
console.error(JSON.stringify(error.response));
456483
// The PR was still created so let's still comment on the original.
457484
}
458485
}
459486

460487
if (this.config.add_author_as_assignee == true) {
461488
const author = mainpr.user.login;
462489
console.info("Setting " + author + " as assignee");
463-
const add_assignee_response = await this.github.addAssignees(
464-
new_pr.number,
465-
[author],
466-
{
490+
try {
491+
await this.github.addAssignees(new_pr.number, [author], {
467492
owner,
468493
repo,
469-
},
470-
);
471-
if (add_assignee_response.status != 201) {
472-
console.error(JSON.stringify(add_assignee_response));
494+
});
495+
} catch (error) {
496+
if (!(error instanceof RequestError)) throw error;
497+
console.error(JSON.stringify(error.response));
473498
}
474499
}
475500

@@ -667,11 +692,9 @@ export class Backport {
667692
return dedent`Git push to origin failed for ${target} with exitcode ${exitcode}`;
668693
}
669694

670-
private composeMessageForCreatePRFailed(
671-
response: CreatePullRequestResponse,
672-
): string {
695+
private composeMessageForCreatePRFailed(error: RequestError): string {
673696
return dedent`Backport branch created but failed to create PR.
674-
Request to create PR rejected with status ${response.status}.
697+
Request to create PR rejected with status ${error.status}.
675698
676699
(see action log for full response)`;
677700
}

src/github.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
*/
99

1010
import * as github from "@actions/github";
11+
export { RequestError } from "@octokit/request-error";
1112

1213
export interface GithubApi {
1314
getRepo(): Repo;

0 commit comments

Comments
 (0)