Skip to content

Commit 2dcb8f0

Browse files
committed
feat: add git node staging command
Add a new `git node staging` command that automates cherry-picking commits into staging branches. It works by cherry-picking all commits that have no conflicts and skipping any commits that have conflicts while automating the sending of GitHub PR interactions to request backport for these commits, sending a message to the original PR and properly labelling it. Usage: Fetches a commit list using `branch-diff` and automatically cherry-picks / skips commits based on whether or not they land cleanly: git node staging Sets a custom reporter at the end of the automated operation: git node staging --reporter=json Limits to 10 the number of commits to be cherry-picked: git node staging --pagination=10 Automates the backport request message, this won't run any of the `branch-diff` or cherry-pick routines. Useful for when you removed a faulty commit from the branch and want to signal to PR author and collaborators that commit now needs backporting, just use its PR#: git node staging --backport=12345 More: The automate cherry-pick logic also includes local persistency of the ongoing commit list, in case a fatal error happens during the command execution, it's possible to resume after cleaning up the git repo state by running `git node staging` again.
1 parent 3afe24f commit 2dcb8f0

File tree

3 files changed

+717
-6
lines changed

3 files changed

+717
-6
lines changed

components/git/staging.js

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import CLI from '../../lib/cli.js';
2+
import { runPromise } from '../../lib/run.js';
3+
import { Staging } from '../../lib/staging.js';
4+
5+
export const command = 'staging';
6+
export const describe = 'Automatic port commits to a release line branch';
7+
8+
const stagingOptions = {
9+
autoSkip: {
10+
describe: 'Automatically skip commits with conflicts that have to be manually resolved',
11+
type: 'boolean'
12+
},
13+
backport: {
14+
describe: 'The PR ID / number to backport, skip staging commits',
15+
type: 'number'
16+
},
17+
continue: {
18+
describe: 'Continue the staging process after a conflict',
19+
type: 'boolean'
20+
},
21+
paginate: {
22+
describe: 'Sets a maximum number of commits to port',
23+
type: 'number'
24+
},
25+
releaseLine: {
26+
describe: 'The major version of the target release',
27+
type: 'number'
28+
},
29+
reporter: {
30+
describe: 'The reporter to use for the output',
31+
type: 'string',
32+
default: 'markdown'
33+
},
34+
skip: {
35+
describe: 'Continue the staging process marking the current commit as skipped',
36+
type: 'boolean'
37+
},
38+
skipGH: {
39+
describe: 'Skip all `gh` cli actions. Will not read / add label to GitHub PRs',
40+
type: 'boolean'
41+
}
42+
};
43+
44+
export function builder(yargs) {
45+
return yargs
46+
.options(stagingOptions)
47+
.example('git node staging --releaseLine=23',
48+
'Port commits to the v1.x-staging branch');
49+
}
50+
51+
export function handler(argv) {
52+
const logStream = process.stdout.isTTY ? process.stdout : process.stderr;
53+
const cli = new CLI(logStream);
54+
const dir = process.cwd();
55+
56+
return runPromise(main(argv, cli, dir)).catch((err) => {
57+
if (cli.spinner.enabled) {
58+
cli.spinner.fail();
59+
}
60+
throw err;
61+
});
62+
}
63+
64+
async function main(argv, cli, dir) {
65+
const {
66+
autoSkip,
67+
backport,
68+
paginate,
69+
releaseLine,
70+
reporter,
71+
skip,
72+
skipGH
73+
} = argv;
74+
const staging = new Staging({
75+
cli,
76+
dir,
77+
cont: argv.continue,
78+
autoSkip,
79+
paginate,
80+
releaseLine,
81+
reporter,
82+
skip,
83+
skipGH
84+
});
85+
if (backport) {
86+
await staging.requestBackport(backport);
87+
} else {
88+
await staging.run();
89+
}
90+
}

lib/prepare_release.js

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -523,12 +523,24 @@ export default class ReleasePreparation extends Session {
523523
const { newVersion } = this;
524524
const proposalBranch = `v${newVersion}-proposal`;
525525

526-
await runAsync('git', [
527-
'checkout',
528-
'-b',
529-
proposalBranch,
530-
base
531-
]);
526+
try {
527+
await forceRunAsync('git', [
528+
'checkout',
529+
'-b',
530+
proposalBranch,
531+
base
532+
], { captureStdout: true, captureStderr: true, ignoreFailures: false });
533+
} catch (err) {
534+
const branchExistsRE = /fatal: a branch named '.*' already exists/i;
535+
if (branchExistsRE.test(err.stderr)) {
536+
await runAsync('git', [
537+
'checkout',
538+
proposalBranch
539+
]);
540+
} else {
541+
throw err;
542+
}
543+
}
532544
return proposalBranch;
533545
}
534546

0 commit comments

Comments
 (0)