Skip to content

Commit 189c167

Browse files
andrewhughes101AshCripps
andauthored
git-node: add --backport flag to land (#383)
This commit adds functionality to land that allows you to land backports with the correct metadata Co-authored-by: AshCripps <[email protected]>
1 parent cbd01f5 commit 189c167

File tree

9 files changed

+136
-29
lines changed

9 files changed

+136
-29
lines changed

components/git/land.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ const landOptions = {
3737
describe: 'Assume "yes" as answer to all prompts and run ' +
3838
'non-interactively. If an undesirable situation occurs, such as a pull ' +
3939
'request or commit check fails, then git node land will abort.'
40+
},
41+
backport: {
42+
describe: 'Land a backport PR onto a staging branch',
43+
default: false,
44+
type: 'boolean'
4045
}
4146
};
4247

@@ -152,8 +157,14 @@ async function main(state, argv, cli, req, dir) {
152157
cli.log('run `git node land --abort` before starting a new session');
153158
return;
154159
}
155-
session = new LandingSession(cli, req, dir, argv.prid);
160+
session = new LandingSession(cli, req, dir, argv.prid, argv.backport);
156161
const metadata = await getMetadata(session.argv, cli);
162+
if (argv.backport) {
163+
const split = metadata.metadata.split('\n')[0];
164+
if (split === 'PR-URL: ') {
165+
cli.error('Commit message is missing PR-URL');
166+
}
167+
}
157168
return session.start(metadata);
158169
} else if (state === APPLY) {
159170
return session.apply();

docs/git-node.md

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,19 +40,22 @@ Options:
4040
--continue, -c Continue the landing session [boolean]
4141
--final Verify the landed PR and clean up [boolean]
4242
--abort Abort the current landing session [boolean]
43+
--backport Land a backport PR on a staging branch [boolean]
4344
--yes Assume "yes" as answer to all prompts and run
4445
non-interactively. If an undesirable situation occurs, such as
4546
a pull request or commit check fails, then git node land will
4647
abort. [boolean] [default: false]
4748
4849
4950
Examples:
50-
git node land 12344 Land https://github.com/nodejs/node/pull/12344 in
51-
the current directory
52-
git node land --abort Abort the current session
53-
git node land --amend Append metadata to the current commit message
54-
git node land --final Verify the landed PR and clean up
55-
git node land --continue Continue the current landing session
51+
git node land 12344 Land https://github.com/nodejs/node/pull/12344
52+
in the current directory
53+
git node land --abort Abort the current session
54+
git node land --amend Append metadata to the current commit message
55+
git node land --final Verify the landed PR and clean up
56+
git node land --continue Continue the current landing session
57+
git node land --backport 30072 Land https://github.com/nodejs/node/pull/30072
58+
as a backport in the current directory
5659
```
5760

5861
<a id="git-node-land-prerequisites"></a>

lib/landing_session.js

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,16 @@ const {
1313
const isWindows = process.platform === 'win32';
1414

1515
class LandingSession extends Session {
16-
constructor(cli, req, dir, prid) {
16+
constructor(cli, req, dir, prid, backport) {
1717
super(cli, dir, prid);
1818
this.req = req;
19+
this.backport = backport;
20+
}
21+
22+
get argv() {
23+
const args = super.argv;
24+
args.backport = this.backport;
25+
return args;
1926
}
2027

2128
async start(metadata) {
@@ -163,13 +170,25 @@ class LandingSession extends Session {
163170
amended.push('');
164171
}
165172

173+
const BACKPORT_RE = /BACKPORT-PR-URL\s*:\s*(\S+)/i;
174+
const PR_RE = /PR-URL\s*:\s*(\S+)/i;
175+
const REVIEW_RE = /Reviewed-By\s*:\s*(\S+)/i;
176+
166177
for (const line of metadata) {
167178
if (original.includes(line)) {
168179
if (line) {
169180
cli.warn(`Found ${line}, skipping..`);
170181
}
171182
} else {
172-
amended.push(line);
183+
if (line.match(BACKPORT_RE)) {
184+
let prIndex = amended.findIndex(datum => datum.match(PR_RE));
185+
if (prIndex === -1) {
186+
prIndex = amended.findIndex(datum => datum.match(REVIEW_RE)) - 1;
187+
}
188+
amended.splice(prIndex + 1, 0, line);
189+
} else {
190+
amended.push(line);
191+
}
173192
}
174193
}
175194

lib/links.js

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const FIXES_RE = /(Close[ds]?|Fix(e[ds])?|Resolve[sd]?)\s*:\s*(\S+)/mgi;
44
const FIX_RE = /(Close[ds]?|Fix(e[ds])?|Resolve[sd]?)\s*:\s*(\S+)/i;
55
const REFS_RE = /Refs?\s*:\s*(\S+)/mgi;
66
const REF_RE = /Refs?\s*:\s*(\S+)/i;
7+
const PR_RE = /PR-URL\s*:\s*(\S+)/i;
78
const cheerio = require('cheerio');
89

910
/**
@@ -36,15 +37,27 @@ class LinkParser {
3637
const m = item.match(REF_RE);
3738
if (!m) continue;
3839
const ref = m[1];
39-
const url = this.getRefUrlFromOP(ref);
40+
const url = this.getUrlFromOP(ref);
41+
if (url) result.push(url);
42+
}
43+
return result;
44+
}
45+
46+
getPRUrlsFromArray(arr) {
47+
const result = [];
48+
for (const item of arr) {
49+
const m = item.match(PR_RE);
50+
if (!m) continue;
51+
const prUrl = m[1];
52+
const url = this.getUrlFromOP(prUrl);
4053
if (url) result.push(url);
4154
}
4255
return result;
4356
}
4457

4558
// Do this so we can reliably get the correct url.
4659
// Otherwise, the number could reference a PR or an issue.
47-
getRefUrlFromOP(ref) {
60+
getUrlFromOP(ref) {
4861
const as = this.$('a');
4962
const links = as.map((i, el) => this.$(el)).get();
5063
for (const link of links) {
@@ -58,22 +71,22 @@ class LinkParser {
5871

5972
getFixes() {
6073
const text = this.$.text();
61-
const fixes = text.match(FIXES_RE);
62-
if (fixes) {
63-
return this.getFixesUrlsFromArray(fixes);
64-
}
65-
return [];
74+
const fixes = text.match(FIXES_RE) || [];
75+
return this.getFixesUrlsFromArray(fixes);
6676
}
6777

6878
getRefs() {
6979
const text = this.$.text();
70-
const refs = text.match(REFS_RE);
71-
if (refs) {
72-
return this.getRefsUrlsFromArray(refs);
73-
}
74-
return [];
80+
const refs = text.match(REFS_RE) || [];
81+
return this.getRefsUrlsFromArray(refs);
7582
}
76-
};
83+
84+
getAltPrUrl() {
85+
const text = this.$.text();
86+
const refs = text.match(PR_RE) || [];
87+
return this.getPRUrlsFromArray(refs);
88+
}
89+
}
7790

7891
const GITHUB_PULL_REQUEST_URL = /github.com\/([^/]+)\/([^/]+)\/pull\/(\d+)/;
7992

lib/metadata_gen.js

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,12 @@ class MetadataGenerator {
1010
* @param {PRData} data
1111
*/
1212
constructor(data) {
13-
const { owner, repo, pr, reviewers } = data;
13+
const { owner, repo, pr, reviewers, argv } = data;
1414
this.owner = owner;
1515
this.repo = repo;
1616
this.pr = pr;
1717
this.reviewers = reviewers;
18+
this.argv = argv;
1819
}
1920

2021
/**
@@ -31,15 +32,24 @@ class MetadataGenerator {
3132
const parser = new LinkParser(owner, repo, op);
3233
const fixes = parser.getFixes();
3334
const refs = parser.getRefs();
34-
35+
const altPrUrl = parser.getAltPrUrl();
3536
const meta = [
36-
`PR-URL: ${prUrl}`,
3737
...fixes.map((fix) => `Fixes: ${fix}`),
38-
...refs.map((ref) => `Refs: ${ref}`),
39-
...reviewedBy.map((r) => `Reviewed-By: ${r.reviewer.getContact()}`),
40-
'' // creates final EOL
38+
...refs.map((ref) => `Refs: ${ref}`)
4139
];
42-
40+
const backport = this.argv ? this.argv.backport : undefined;
41+
if (backport) {
42+
meta.unshift(`Backport-PR-URL: ${prUrl}`);
43+
meta.unshift(`PR-URL: ${altPrUrl}`);
44+
} else {
45+
// Reviews are only added here as backports should not contain reviews
46+
// for the backport itself in the metadata
47+
meta.unshift(`PR-URL: ${prUrl}`);
48+
meta.push(
49+
...reviewedBy.map((r) => `Reviewed-By: ${r.reviewer.getContact()}`)
50+
);
51+
}
52+
meta.push(''); // creates final EOL
4353
return meta.join('\n');
4454
}
4555
}

lib/session.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,7 @@ class Session {
369369
` $ ncu-config set branch ${rev}`);
370370
cli.separator();
371371
return true;
372+
// TODO warn if backporting onto master branch
372373
}
373374
}
374375

test/fixtures/data.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ const firstTimerPrivatePR = readJSON('first_timer_pr_with_private_email.json');
7575
const semverMajorPR = readJSON('semver_major_pr.json');
7676
const fixAndRefPR = readJSON('pr_with_fixes_and_refs.json');
7777
const fixCrossPR = readJSON('pr_with_fixes_cross.json');
78+
const backportPR = readJSON('pr_with_backport.json');
7879
const conflictingPR = readJSON('conflicting_pr.json');
7980
const emptyProfilePR = readJSON('empty_profile_pr.json');
8081
const closedPR = readJSON('./closed_pr.json');
@@ -114,6 +115,7 @@ module.exports = {
114115
semverMajorPR,
115116
fixAndRefPR,
116117
fixCrossPR,
118+
backportPR,
117119
conflictingPR,
118120
emptyProfilePR,
119121
readme,
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"createdAt": "2019-10-22T22:42:25Z",
3+
"authorAssociation": "CONTRIBUTOR",
4+
"author": {
5+
"login": "gabrielschulhof",
6+
"email": "[email protected]",
7+
"name": "Gabriel Schulhof"
8+
},
9+
"url": "https://github.com/nodejs/node/pull/30072",
10+
"bodyHTML": "<p>Build the addons for benchmarks in the same way that the addons for<br>\ntests are built.</p>\n<p>PR-URL: <a class='issue-link js-issue-link' data-error-text='Failed to load issue title' data-id='508006081' data-permission-text='Issue title is private' data-url='https://github.com/nodejs/node/issues/29995' data-hovercard-type='pull_request' data-hovercard-url='/nodejs/node/pull/29995/hovercard' href='https://github.com/nodejs/node/pull/29995'>#29995</a><br>\n<span class='issue-keyword tooltipped tooltipped-se' aria-label='This pull request closes issue #1961.'>Fixes</span>: <a class='ssue-link js-issue-link' data-error-text='Failed to load issue title' data-id='507966018' data-permission-text='Issue title is private' data-url='https://github.com/nodejs/build/issues/1961' data-hovercard-type='issue' data-hovercard-url='/nodejs/build/issues/1961/hovercard' href='https://github.com/nodejs/build/issues/1961'>nodejs/build#1961</a><br>\nRefs: <a class='commit-link' href='https://github.com/nodejs/node/commit/53ca0b9ae145c430842bf78e553e3b6cbd2823aa#commitcomment-35494896'><tt>53ca0b9</tt>#commitcomment-35494896</a><br>\nReviewed-By: <a class='user-mention' data-hovercard-type='user' data-hovercard-url='/users/addaleax/hovercard' data-octo-click='hovercard-link-click' data-octo-dimensions='link_type:self' href='https://github.com/addaleax'>@addaleax</a><br>\nReviewed-By: <a class='user-mention' data-hovercard-type='user' data-hovercard-url='/users/Trott/hovercard' data-octo-click='hovercard-link-click' data-octo-dimensions='link_type:self' href='https://github.com/Trott'>@Trott</a><br>\nReviewed-By: <a class='user-mention' data-hovercard-type='user' data-hovercard-url='/users/BethGriggs/hovercard' data-octo-click='hovercard-link-click' data-octo-dimensions='link_type:self' href='https://github.com/BethGriggs'>@BethGriggs</a><br>\nReviewed-By: <a class='user-mention' data-hovercard-type='user' data-hovercard-url='/users/gengjiawen/hovercard' data-octo-click='hovercard-link-click' data-octo-dimensions='link_type:self' href='https://github.com/gengjiawen'>@gengjiawen</a></p>\n\n<h5>Checklist</h5>\n\n<ul class='contains-task-list'>\n<li class='task-list-item'><input type='checkbox' id='' disabled='' class='task-list-item-checkbox' checked=''> <code>make -j4 test</code> (UNIX), or <code>vcbuild test</code> (Windows) passes</li>\n<li class='task-list-item'><input type='checkbox' id='' disabled='' class='task-list-item-checkbox' checked=''> commit message follows <a href='https://github.com/nodejs/node/blob/master/doc/guides/contributing/pull-requests.md#commit-message-guidelines'>commit guidelines</a></li>\n</ul>\n\n<p>We need this PR along with <a class='issue-link js-issue-link' data-error-text='Failed to load issue title' data-id='510938668' data-permission-text='Issue title is private' data-url='https://github.com/nodejs/node/issues/30070' data-hovercard-type='pull_request' data-hovercard-url='/nodejs/node/pull/30070/hovercard' href='https://github.com/nodejs/node/pull/30070'>#30070</a> because <a class='commit-link' href='https://github.com/nodejs/node/commit/53ca0b9ae145c430842bf78e553e3b6cbd2823aa#commitcomment-35494896'><tt>53ca0b9</tt>#commitcomment-35494896</a></p>",
11+
"bodyText": "Build the addons for benchmarks in the same way that the addons for\ntests are built.\nPR-URL: #29995\nFixes: nodejs/build#1961\nRefs: 53ca0b9#commitcomment-35494896\nReviewed-By: @addaleax\nReviewed-By: @Trott\nReviewed-By: @BethGriggs\nReviewed-By: @gengjiawen\n\nChecklist\n\n\n make -j4 test (UNIX), or vcbuild test (Windows) passes\n commit message follows commit guidelines\n\n\nWe need this PR along with #30070 because 53ca0b9#commitcomment-35494896",
12+
"labels": {
13+
"nodes": [
14+
{
15+
"name": "build"
16+
},
17+
{
18+
"name": "v12.x"
19+
}
20+
]
21+
}
22+
}

test/unit/metadata_gen.test.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const MetadataGenerator = require('../../lib/metadata_gen');
44
const {
55
fixAndRefPR,
66
fixCrossPR,
7+
backportPR,
78
allGreenReviewers
89
} = require('../fixtures/data');
910

@@ -15,6 +16,21 @@ const data = {
1516
reviewers: allGreenReviewers
1617
};
1718
const crossData = Object.assign({}, data, { pr: fixCrossPR });
19+
const backportArgv = {
20+
argv: {
21+
owner: 'nodejs',
22+
repo: 'node',
23+
upstream: 'upstream',
24+
branch: 'v12.x-staging',
25+
readme: undefined,
26+
waitTimeSingleApproval: undefined,
27+
waitTimeMultiApproval: undefined,
28+
prid: 30072,
29+
backport: true
30+
}
31+
};
32+
33+
const backportData = Object.assign({}, data, { pr: backportPR }, backportArgv);
1834

1935
const expected = `PR-URL: https://github.com/nodejs/node/pull/16438
2036
Fixes: https://github.com/nodejs/node/issues/16437
@@ -31,6 +47,11 @@ Reviewed-By: Quux User <[email protected]>
3147
Reviewed-By: Baz User <[email protected]>
3248
Reviewed-By: Bar User <[email protected]>
3349
`;
50+
const backportExpected = `PR-URL: https://github.com/nodejs/node/pull/29995
51+
Backport-PR-URL: https://github.com/nodejs/node/pull/30072
52+
Fixes: https://github.com/nodejs/build/issues/1961
53+
Refs: https://github.com/nodejs/node/commit/53ca0b9ae145c430842bf78e553e3b6cbd2823aa#commitcomment-35494896
54+
`;
3455

3556
describe('MetadataGenerator', () => {
3657
it('should generate metadata properly', () => {
@@ -42,4 +63,9 @@ describe('MetadataGenerator', () => {
4263
const results = new MetadataGenerator(crossData).getMetadata();
4364
assert.strictEqual(crossExpected, results);
4465
});
66+
67+
it('should generate correct metadata for a backport', () => {
68+
const backportResults = new MetadataGenerator(backportData).getMetadata();
69+
assert.strictEqual(backportExpected, backportResults);
70+
});
4571
});

0 commit comments

Comments
 (0)