@@ -50,6 +50,7 @@ const git_1 = __nccwpck_require__(3374);
50
50
const utils = __importStar(__nccwpck_require__(918));
51
51
const experimentalDefaults = {
52
52
detect_merge_method: false,
53
+ conflict_resolution: `fail`,
53
54
downstream_repo: undefined,
54
55
downstream_owner: undefined,
55
56
};
@@ -227,8 +228,9 @@ class Backport {
227
228
});
228
229
continue;
229
230
}
231
+ let uncommitedShas;
230
232
try {
231
- yield this.git.cherryPick(commitShasToCherryPick, this.config.pwd);
233
+ uncommitedShas = yield this.git.cherryPick(commitShasToCherryPick, this.config.experimental.conflict_resolution , this.config.pwd);
232
234
}
233
235
catch (error) {
234
236
const message = this.composeMessageForCherryPickFailure(target, branchname, commitShasToCherryPick);
@@ -266,6 +268,7 @@ class Backport {
266
268
head: branchname,
267
269
base: target,
268
270
maintainer_can_modify: true,
271
+ draft: uncommitedShas !== null,
269
272
});
270
273
if (new_pr_response.status != 201) {
271
274
console.error(JSON.stringify(new_pr_response));
@@ -329,15 +332,30 @@ class Backport {
329
332
// The PR was still created so let's still comment on the original.
330
333
}
331
334
}
332
- const message = this.composeMessageForSuccess(new_pr.number, target, this.shouldUseDownstreamRepo() ? `${owner}/${repo}` : "");
333
- successByTarget.set(target, true);
334
- createdPullRequestNumbers.push(new_pr.number);
335
- yield this.github.createComment({
336
- owner: workflowOwner,
337
- repo: workflowRepo,
338
- issue_number: pull_number,
339
- body: message,
340
- });
335
+ // post success message to original pr
336
+ {
337
+ const message = uncommitedShas !== null
338
+ ? this.composeMessageForSuccessWithConflicts(new_pr.number, target, this.shouldUseDownstreamRepo() ? `${owner}/${repo}` : "", branchname, uncommitedShas, this.config.experimental.conflict_resolution)
339
+ : this.composeMessageForSuccess(new_pr.number, target, this.shouldUseDownstreamRepo() ? `${owner}/${repo}` : "");
340
+ successByTarget.set(target, true);
341
+ createdPullRequestNumbers.push(new_pr.number);
342
+ yield this.github.createComment({
343
+ owner: workflowOwner,
344
+ repo: workflowRepo,
345
+ issue_number: pull_number,
346
+ body: message,
347
+ });
348
+ }
349
+ // post message to new pr to resolve conflict
350
+ if (uncommitedShas !== null) {
351
+ const message = this.composeMessageToResolveCommittedConflicts(target, branchname, uncommitedShas, this.config.experimental.conflict_resolution);
352
+ yield this.github.createComment({
353
+ owner,
354
+ repo,
355
+ issue_number: new_pr.number,
356
+ body: message,
357
+ });
358
+ }
341
359
}
342
360
catch (error) {
343
361
if (error instanceof Error) {
@@ -384,28 +402,51 @@ class Backport {
384
402
}
385
403
composeMessageForCheckoutFailure(target, branchname, commitShasToCherryPick) {
386
404
const reason = "because it was unable to create a new branch";
387
- const suggestion = this.composeSuggestion(target, branchname, commitShasToCherryPick);
405
+ const suggestion = this.composeSuggestion(target, branchname, commitShasToCherryPick, false );
388
406
return (0, dedent_1.default) `Backport failed for \`${target}\`, ${reason}.
389
407
390
408
Please cherry-pick the changes locally.
391
409
${suggestion}`;
392
410
}
393
411
composeMessageForCherryPickFailure(target, branchname, commitShasToCherryPick) {
394
412
const reason = "because it was unable to cherry-pick the commit(s)";
395
- const suggestion = this.composeSuggestion(target, branchname, commitShasToCherryPick);
413
+ const suggestion = this.composeSuggestion(target, branchname, commitShasToCherryPick, false, "fail" );
396
414
return (0, dedent_1.default) `Backport failed for \`${target}\`, ${reason}.
397
415
398
416
Please cherry-pick the changes locally and resolve any conflicts.
399
417
${suggestion}`;
400
418
}
401
- composeSuggestion(target, branchname, commitShasToCherryPick) {
402
- return (0, dedent_1.default) `\`\`\`bash
419
+ composeMessageToResolveCommittedConflicts(target, branchname, commitShasToCherryPick, confictResolution) {
420
+ const suggestion = this.composeSuggestion(target, branchname, commitShasToCherryPick, true, confictResolution);
421
+ return (0, dedent_1.default) `Please cherry-pick the changes locally and resolve any conflicts.
422
+ ${suggestion}`;
423
+ }
424
+ composeSuggestion(target, branchname, commitShasToCherryPick, branchExist, confictResolution = "fail") {
425
+ if (branchExist) {
426
+ if (confictResolution === "draft_commit_conflicts") {
427
+ return (0, dedent_1.default) `\`\`\`bash
428
+ git fetch origin ${target}
429
+ git worktree add -d .worktree/${branchname} origin/${target}
430
+ cd .worktree/${branchname}
431
+ git switch ${branchname}
432
+ git reset --hard HEAD^
433
+ git cherry-pick -x ${commitShasToCherryPick.join(" ")}
434
+ git push --force-with-lease
435
+ \`\`\``;
436
+ }
437
+ else {
438
+ return "";
439
+ }
440
+ }
441
+ else {
442
+ return (0, dedent_1.default) `\`\`\`bash
403
443
git fetch origin ${target}
404
444
git worktree add -d .worktree/${branchname} origin/${target}
405
445
cd .worktree/${branchname}
406
446
git switch --create ${branchname}
407
447
git cherry-pick -x ${commitShasToCherryPick.join(" ")}
408
448
\`\`\``;
449
+ }
409
450
}
410
451
composeMessageForGitPushFailure(target, exitcode) {
411
452
//TODO better error messages depending on exit code
@@ -421,6 +462,13 @@ class Backport {
421
462
return (0, dedent_1.default) `Successfully created backport PR for \`${target}\`:
422
463
- ${downstream}#${pr_number}`;
423
464
}
465
+ composeMessageForSuccessWithConflicts(pr_number, target, downstream, branchname, commitShasToCherryPick, conflictResolution) {
466
+ const suggestionToResolve = this.composeMessageToResolveCommittedConflicts(target, branchname, commitShasToCherryPick, conflictResolution);
467
+ return (0, dedent_1.default) `Created backport PR for \`${target}\`:
468
+ - ${downstream}#${pr_number} with remaining conflicts!
469
+
470
+ ${suggestionToResolve}`;
471
+ }
424
472
createOutput(successByTarget, createdPullRequestNumbers) {
425
473
const anyTargetFailed = Array.from(successByTarget.values()).includes(false);
426
474
core.setOutput(Output.wasSuccessful, !anyTargetFailed);
@@ -593,12 +641,49 @@ class Git {
593
641
}
594
642
});
595
643
}
596
- cherryPick(commitShas, pwd) {
644
+ cherryPick(commitShas, conflictResolution, pwd) {
597
645
return __awaiter(this, void 0, void 0, function* () {
598
- const { exitCode } = yield this.git("cherry-pick", ["-x", ...commitShas], pwd);
599
- if (exitCode !== 0) {
646
+ const abortCherryPickAndThrow = (commitShas, exitCode) => __awaiter(this, void 0, void 0, function* () {
600
647
yield this.git("cherry-pick", ["--abort"], pwd);
601
648
throw new Error(`'git cherry-pick -x ${commitShas}' failed with exit code ${exitCode}`);
649
+ });
650
+ if (conflictResolution === `fail`) {
651
+ const { exitCode } = yield this.git("cherry-pick", ["-x", ...commitShas], pwd);
652
+ if (exitCode !== 0) {
653
+ yield abortCherryPickAndThrow(commitShas, exitCode);
654
+ }
655
+ return null;
656
+ }
657
+ else {
658
+ let uncommittedShas = [...commitShas];
659
+ // Cherry-pick commit one by one.
660
+ while (uncommittedShas.length > 0) {
661
+ const { exitCode } = yield this.git("cherry-pick", ["-x", uncommittedShas[0]], pwd);
662
+ if (exitCode !== 0) {
663
+ if (exitCode === 1) {
664
+ // conflict encountered
665
+ if (conflictResolution === `draft_commit_conflicts`) {
666
+ // Commit the conflict, resolution of this commit is left to the user.
667
+ // Allow creating PR for cherry-pick with only 1 commit and it results in a conflict.
668
+ const { exitCode } = yield this.git("commit", ["--all", `-m BACKPORT-CONFLICT`], pwd);
669
+ if (exitCode !== 0) {
670
+ yield abortCherryPickAndThrow(commitShas, exitCode);
671
+ }
672
+ return uncommittedShas;
673
+ }
674
+ else {
675
+ throw new Error(`'Unsupported conflict_resolution method ${conflictResolution}`);
676
+ }
677
+ }
678
+ else {
679
+ // other fail reasons
680
+ yield abortCherryPickAndThrow([uncommittedShas[0]], exitCode);
681
+ }
682
+ }
683
+ // pop sha
684
+ uncommittedShas.shift();
685
+ }
686
+ return null;
602
687
}
603
688
});
604
689
}
@@ -1003,6 +1088,15 @@ function run() {
1003
1088
No experimental config options known for key '${key}'.\
1004
1089
Please check the documentation for details about experimental features.`);
1005
1090
}
1091
+ if (key == "conflict_resolution") {
1092
+ if (experimental[key] !== "fail" &&
1093
+ experimental[key] !== "draft_commit_conflicts") {
1094
+ const message = `Expected input 'conflict_resolution' to be either 'fail' or 'draft_commit_conflicts', but was '${experimental[key]}'`;
1095
+ console.error(message);
1096
+ core.setFailed(message);
1097
+ return;
1098
+ }
1099
+ }
1006
1100
}
1007
1101
const github = new github_1.Github(token);
1008
1102
const git = new git_1.Git(execa_1.execa);
0 commit comments