Skip to content

Commit 4d5e9bb

Browse files
committed
feat(land): add skipChecks option
skipChecks will allow skipping certain checks. Useful to skip "commit after last approval" on commit queue. Ref: nodejs/node#34770
1 parent d356fd4 commit 4d5e9bb

File tree

4 files changed

+96
-24
lines changed

4 files changed

+96
-24
lines changed

components/git/land.js

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@ const { parsePRFromURL } = require('../../lib/links');
44
const getMetadata = require('../metadata');
55
const CLI = require('../../lib/cli');
66
const Request = require('../../lib/request');
7+
const PRChecker = require('../../lib/pr_checker');
78
const { runPromise } = require('../../lib/run');
89
const LandingSession = require('../../lib/landing_session');
910
const epilogue = require('./epilogue');
1011
const yargs = require('yargs');
1112

13+
const availableChecks = Object.values(PRChecker.availableChecks);
14+
1215
const landOptions = {
1316
apply: {
1417
describe: 'Apply a patch with the given PR id',
@@ -42,6 +45,21 @@ const landOptions = {
4245
describe: 'Land a backport PR onto a staging branch',
4346
default: false,
4447
type: 'boolean'
48+
},
49+
skipChecks: {
50+
describe: 'Comma-separated list of checks to skip while landing. ' +
51+
`Available options are: ${availableChecks}`,
52+
coerce: (arg) => {
53+
const checks = arg.split(',');
54+
for (const check of checks) {
55+
if (!availableChecks.includes(check)) {
56+
throw new Error(`Invalid check '${check}'. Available options are: ` +
57+
`${availableChecks}`);
58+
}
59+
}
60+
return checks;
61+
},
62+
type: 'string'
4563
}
4664
};
4765

@@ -89,7 +107,10 @@ function handler(argv) {
89107

90108
const provided = [];
91109
for (const type of Object.keys(landOptions)) {
92-
if (type === 'yes') continue; // --yes is not an action
110+
if (['yes', 'skipChecks'].includes(type)) {
111+
// Those are not actions
112+
continue;
113+
}
93114
if (argv[type]) {
94115
provided.push(type);
95116
}
@@ -160,7 +181,7 @@ async function main(state, argv, cli, req, dir) {
160181
return;
161182
}
162183
session = new LandingSession(cli, req, dir, argv.prid, argv.backport);
163-
const metadata = await getMetadata(session.argv, cli);
184+
const metadata = await getMetadata(session.argv, cli, argv.skipChecks);
164185
if (argv.backport) {
165186
const split = metadata.metadata.split('\n')[0];
166187
if (split === 'PR-URL: ') {

components/metadata.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const MetadataGenerator = require('../lib/metadata_gen');
99

1010
const fs = require('fs');
1111

12-
module.exports = async function getMetadata(argv, cli) {
12+
module.exports = async function getMetadata(argv, cli, skipChecks = []) {
1313
const credentials = await auth({
1414
github: true,
1515
jenkins: true
@@ -40,7 +40,7 @@ module.exports = async function getMetadata(argv, cli) {
4040
cli.separator();
4141

4242
const checker = new PRChecker(cli, data, request, argv);
43-
const status = await checker.checkAll(argv.checkComments);
43+
const status = await checker.checkAll(argv.checkComments, skipChecks);
4444
return {
4545
status,
4646
request,

lib/pr_checker.js

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,16 @@ const { PRBuild } = require('./ci/build-types/pr_build');
2828

2929
const GIT_CONFIG_GUIDE_URL = 'https://github.com/nodejs/node/blob/99b1ada/doc/guides/contributing/pull-requests.md#step-1-fork';
3030

31+
const CHECKS = {
32+
COMMIT_AFTER_REVIEW: 'commit-after-review',
33+
CI: 'ci',
34+
REVIEWS_AND_WAIT: 'reviews-and-wait',
35+
MERGEABLE_STATE: 'mergeable-state',
36+
PR_STATE: 'pr-state',
37+
GIT_CONFIG: 'git-config',
38+
AUTHOR: 'author'
39+
};
40+
3141
class PRChecker {
3242
/**
3343
* @param {{}} cli
@@ -55,6 +65,10 @@ class PRChecker {
5565
);
5666
}
5767

68+
static get availableChecks() {
69+
return CHECKS;
70+
}
71+
5872
get waitTimeSingleApproval() {
5973
if (this.argv.waitTimeSingleApproval === undefined) {
6074
return WAIT_TIME_SINGLE_APPROVAL;
@@ -69,24 +83,33 @@ class PRChecker {
6983
return this.argv.waitTimeMultiApproval;
7084
}
7185

72-
async checkAll(checkComments = false) {
73-
const status = [
74-
this.checkCommitsAfterReview(),
75-
await this.checkCI(),
76-
this.checkReviewsAndWait(new Date(), checkComments),
77-
this.checkMergeableState(),
78-
this.checkPRState(),
79-
this.checkGitConfig()
86+
async checkAll(checkComments = false, exclude = []) {
87+
const checks = [
88+
[CHECKS.COMMIT_AFTER_REVIEW, async() => this.checkCommitsAfterReview()],
89+
[CHECKS.CI, async() => this.checkCI()],
90+
[CHECKS.REVIEWS_AND_WAIT,
91+
async() => this.checkReviewsAndWait(new Date(), checkComments)],
92+
[CHECKS.MERGEABLE_STATE, async() => this.checkMergeableState()],
93+
[CHECKS.PR_STATE, async() => this.checkPRState()],
94+
[CHECKS.GIT_CONFIG, async() => this.checkGitConfig()]
8095
];
8196

8297
if (this.data.authorIsNew()) {
83-
status.push(this.checkAuthor());
98+
checks.push([CHECKS.AUTHOR, async() => this.checkAuthor()]);
99+
}
100+
101+
const status = [];
102+
for (const [checkName, check] of checks) {
103+
if (exclude.includes(checkName)) {
104+
continue;
105+
}
106+
status.push(await check());
84107
}
85108

86109
// TODO: check for pre-backport, Github API v4
87110
// does not support reading files changed
88111

89-
return status.every((i) => i);
112+
return status.every(s => s);
90113
}
91114

92115
getTSC(people) {

test/unit/pr_checker.test.js

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ describe('PRChecker', () => {
5959
return PRData.prototype.getThread.call(this);
6060
}
6161
};
62+
const checks = PRChecker.availableChecks;
6263
const checker = new PRChecker(cli, data, {}, argv);
6364

6465
let checkReviewsAndWaitStub;
@@ -69,18 +70,20 @@ describe('PRChecker', () => {
6970
let checkPRState;
7071
let checkGitConfigStub;
7172

72-
before(() => {
73-
checkReviewsAndWaitStub = sinon.stub(checker, 'checkReviewsAndWait');
74-
checkCIStub = sinon.stub(checker, 'checkCI');
75-
checkAuthorStub = sinon.stub(checker, 'checkAuthor');
73+
beforeEach(() => {
74+
checkReviewsAndWaitStub =
75+
sinon.stub(checker, 'checkReviewsAndWait').returns(true);
76+
checkCIStub = sinon.stub(checker, 'checkCI').returns(true);
77+
checkAuthorStub = sinon.stub(checker, 'checkAuthor').returns(true);
7678
checkCommitsAfterReviewStub =
77-
sinon.stub(checker, 'checkCommitsAfterReview');
78-
checkMergeableStateStub = sinon.stub(checker, 'checkMergeableState');
79-
checkPRState = sinon.stub(checker, 'checkPRState');
80-
checkGitConfigStub = sinon.stub(checker, 'checkGitConfig');
79+
sinon.stub(checker, 'checkCommitsAfterReview').returns(true);
80+
checkMergeableStateStub =
81+
sinon.stub(checker, 'checkMergeableState').returns(true);
82+
checkPRState = sinon.stub(checker, 'checkPRState').returns(true);
83+
checkGitConfigStub = sinon.stub(checker, 'checkGitConfig').returns(true);
8184
});
8285

83-
after(() => {
86+
afterEach(() => {
8487
checkReviewsAndWaitStub.restore();
8588
checkCIStub.restore();
8689
checkAuthorStub.restore();
@@ -92,7 +95,7 @@ describe('PRChecker', () => {
9295

9396
it('should run necessary checks', async() => {
9497
const status = await checker.checkAll();
95-
assert.strictEqual(status, false);
98+
assert.strictEqual(status, true);
9699
assert.strictEqual(checkReviewsAndWaitStub.calledOnce, true);
97100
assert.strictEqual(checkCIStub.calledOnce, true);
98101
assert.strictEqual(checkAuthorStub.calledOnce, true);
@@ -101,6 +104,31 @@ describe('PRChecker', () => {
101104
assert.strictEqual(checkPRState.calledOnce, true);
102105
assert.strictEqual(checkGitConfigStub.calledOnce, true);
103106
});
107+
108+
it('should run no checks if all excluded', async() => {
109+
const status = await checker.checkAll(false, Object.values(checks));
110+
assert.strictEqual(status, true);
111+
assert.strictEqual(checkReviewsAndWaitStub.called, false);
112+
assert.strictEqual(checkCIStub.called, false);
113+
assert.strictEqual(checkAuthorStub.called, false);
114+
assert.strictEqual(checkCommitsAfterReviewStub.called, false);
115+
assert.strictEqual(checkMergeableStateStub.called, false);
116+
assert.strictEqual(checkPRState.called, false);
117+
assert.strictEqual(checkGitConfigStub.called, false);
118+
});
119+
120+
it('should skip excluded checks', async() => {
121+
const status =
122+
await checker.checkAll(false, [checks.COMMIT_AFTER_REVIEW, checks.CI]);
123+
assert.strictEqual(status, true);
124+
assert.strictEqual(checkReviewsAndWaitStub.calledOnce, true);
125+
assert.strictEqual(checkCIStub.called, false);
126+
assert.strictEqual(checkAuthorStub.calledOnce, true);
127+
assert.strictEqual(checkCommitsAfterReviewStub.called, false);
128+
assert.strictEqual(checkMergeableStateStub.calledOnce, true);
129+
assert.strictEqual(checkPRState.calledOnce, true);
130+
assert.strictEqual(checkGitConfigStub.calledOnce, true);
131+
});
104132
});
105133

106134
describe('checkReviewsAndWait', () => {

0 commit comments

Comments
 (0)