Skip to content

Commit 7839c2a

Browse files
committed
v8: add support for backporting multiple commits in one
1 parent 1f0c3e0 commit 7839c2a

File tree

6 files changed

+201
-68
lines changed

6 files changed

+201
-68
lines changed

components/git/v8.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,17 @@ module.exports = {
3131
handler: main
3232
})
3333
.command({
34-
command: 'backport <sha>',
35-
desc: 'Backport a single commit from the V8 repository',
34+
command: 'backport <sha..>',
35+
desc: 'Backport one or more commits from the V8 repository',
3636
handler: main,
3737
builder: (yargs) => {
3838
yargs.option('bump', {
3939
describe: 'Bump V8 embedder version number or patch version',
4040
default: true
41+
}).option('squash', {
42+
describe:
43+
'If multiple commits are backported, squash them into one',
44+
default: false
4145
});
4246
}
4347
})

docs/git-node.md

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -188,19 +188,25 @@ patch if necessary.
188188
If the `git apply` command fails, a patch file will be written in the Node.js
189189
clone directory.
190190

191-
#### `git node v8 backport <sha>`
191+
#### `git node v8 backport <sha..>`
192192

193-
Fetches and applies the patch corresponding to `sha`. Increments the V8
194-
embedder version number or patch version and commits the changes.
195-
If the `git apply` command fails, a patch file will be written in the Node.js
196-
clone directory.
193+
Fetches and applies the patch corresponding to `sha`. Multiple commit SHAs can
194+
be provided to this command. Increments the V8 embedder version number or patch
195+
version and commits the changes for each commit (unless the command is
196+
called with `--squash`). If a patch fails to be applied, the command will pause
197+
and let you fix the conflicts in another terminal.
197198

198199
##### Options
199200

200201
###### `--no-bump`
201202

202203
Set this flag to skip bumping the V8 embedder version number or patch version.
203204

205+
###### `--squash`
206+
207+
Set this flag to squash multiple commits into one. This should only be done if
208+
individual commits would break the build.
209+
204210
#### General options
205211

206212
##### `--node-dir=/path/to/node`

lib/update-v8/backport.js

Lines changed: 174 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,49 @@ const path = require('path');
44

55
const execa = require('execa');
66
const fs = require('fs-extra');
7+
const inquirer = require('inquirer');
78
const Listr = require('listr');
89
const input = require('listr-input');
910

1011
const common = require('./common');
1112

13+
exports.checkOptions = async function checkOptions(options) {
14+
if (options.sha.length > 1 && options.squash) {
15+
const { wantSquash } = await inquirer.prompt([{
16+
type: 'confirm',
17+
name: 'wantSquash',
18+
message: 'Squashing commits should be avoided if possible, because it ' +
19+
'can make git bisection difficult. Only squash commits if they would ' +
20+
'break the build when applied individually. Are you sure?',
21+
default: false
22+
}]);
23+
24+
if (!wantSquash) {
25+
return true;
26+
}
27+
}
28+
};
29+
1230
exports.doBackport = function doBackport(options) {
13-
const todo = [common.getCurrentV8Version(), generatePatch(), applyPatch()];
14-
if (options.bump !== false) {
15-
if (options.nodeMajorVersion < 9) {
16-
todo.push(incrementV8Version());
17-
} else {
18-
todo.push(incrementEmbedderVersion());
31+
const todo = [
32+
common.getCurrentV8Version(),
33+
generatePatches()
34+
];
35+
36+
if (options.squash) {
37+
todo.push(applyPatches());
38+
if (options.bump !== false) {
39+
if (options.nodeMajorVersion < 9) {
40+
todo.push(incrementV8Version());
41+
} else {
42+
todo.push(incrementEmbedderVersion());
43+
}
1944
}
45+
todo.push(commitSquashedBackport());
46+
} else {
47+
todo.push(applyAndCommitPatches());
2048
}
49+
2150
return {
2251
title: 'V8 commit backport',
2352
task: () => {
@@ -26,72 +55,174 @@ exports.doBackport = function doBackport(options) {
2655
};
2756
};
2857

29-
exports.commitBackport = function commitBackport() {
58+
function commitSquashedBackport() {
3059
return {
31-
title: 'Commit patch',
60+
title: 'Commit backport',
3261
task: async(ctx) => {
33-
const messageTitle = `deps: cherry-pick ${ctx.fullSha.substring(
34-
0,
35-
7
36-
)} from upstream V8`;
37-
const indentedMessage = ctx.message.replace(/\n/g, '\n ');
38-
const messageBody =
39-
'Original commit message:\n\n' +
40-
` ${indentedMessage}\n\n` +
41-
`Refs: https://github.com/v8/v8/commit/${ctx.fullSha}`;
42-
62+
const { patches } = ctx;
63+
const messageTitle = formatMessageTitle(patches);
64+
let messageBody;
65+
if (patches.length === 1) {
66+
const [patch] = patches;
67+
messageBody = formatMessageBody(patch, false);
68+
} else {
69+
messageBody = '';
70+
for (const patch of patches) {
71+
const formatted = formatMessageBody(patch, true);
72+
messageBody += formatted + '\n\n';
73+
}
74+
}
4375
await ctx.execGitNode('add', 'deps/v8');
4476
await ctx.execGitNode('commit', '-m', messageTitle, '-m', messageBody);
4577
}
4678
};
4779
};
4880

49-
function generatePatch() {
81+
function commitPatch(patch) {
5082
return {
51-
title: 'Generate patch',
83+
title: 'Commit patch',
5284
task: async(ctx) => {
53-
const sha = ctx.sha;
54-
const fullSha = ctx.fullSha = await ctx.execGitV8('rev-parse', sha);
85+
const messageTitle = formatMessageTitle([patch]);
86+
const messageBody = formatMessageBody(patch, false);
87+
await ctx.execGitNode('add', 'deps/v8');
88+
await ctx.execGitNode('commit', '-m', messageTitle, '-m', messageBody);
89+
}
90+
};
91+
}
92+
93+
function shortSha(sha) {
94+
return sha.substring(0, 7);
95+
}
96+
97+
function formatMessageTitle(patches) {
98+
const action =
99+
patches.some(patch => patch.hadConflicts) ? 'backport' : 'cherry-pick';
100+
if (patches.length === 1) {
101+
return `deps: V8: ${action} ${shortSha(patches[0].sha)}`;
102+
} else if (patches.length === 2) {
103+
return `deps: V8: ${action} ${shortSha(patches[0].sha)} and ${
104+
shortSha(patches[1].sha)
105+
}`;
106+
} else if (patches.length === 3) {
107+
return `deps: V8: ${action} ${shortSha(patches[0].sha)}, ${
108+
shortSha(patches[1].sha)
109+
} and ${shortSha(patches[2].sha)}`;
110+
} else {
111+
return `deps: V8: ${action} ${patches.length} commits`;
112+
}
113+
}
114+
115+
function formatMessageBody(patch, prefixTitle) {
116+
const indentedMessage = patch.message.replace(/\n/g, '\n ');
117+
const body =
118+
'Original commit message:\n\n' +
119+
` ${indentedMessage}\n\n` +
120+
`Refs: https://github.com/v8/v8/commit/${patch.sha}`;
121+
122+
if (prefixTitle) {
123+
const action = patch.hadConflicts ? 'Backport' : 'Cherry-pick';
124+
return `${action} ${shortSha(patch.sha)}.\n` + body;
125+
}
126+
return body;
127+
}
128+
129+
function generatePatches() {
130+
return {
131+
title: 'Generate patches',
132+
task: async(ctx) => {
133+
const shas = ctx.sha;
55134
try {
56-
const [patch, message] = await Promise.all([
57-
ctx.execGitV8('format-patch', '--stdout', `${fullSha}^..${fullSha}`),
58-
ctx.execGitV8('log', '--format=%B', '-n', '1', fullSha)
59-
]);
60-
ctx.patch = patch.stdout;
61-
ctx.message = message.stdout;
135+
const fullShas = await Promise.all(
136+
shas.map(async(sha) => {
137+
const { stdout } = await ctx.execGitV8('rev-parse', sha);
138+
return stdout;
139+
})
140+
);
141+
ctx.patches = await Promise.all(fullShas.map(async(sha) => {
142+
const [patch, message] = await Promise.all([
143+
ctx.execGitV8('format-patch', '--stdout', `${sha}^..${sha}`),
144+
ctx.execGitV8('log', '--format=%B', '-n', '1', sha)
145+
]);
146+
return {
147+
sha,
148+
data: patch.stdout,
149+
message: message.stdout
150+
};
151+
}));
62152
} catch (e) {
63153
throw new Error(e.stderr);
64154
}
65155
}
66156
};
67157
}
68158

69-
function applyPatch() {
159+
function applyPatches() {
70160
return {
71-
title: 'Apply patch to deps/v8',
161+
title: 'Apply patches to deps/v8',
72162
task: async(ctx) => {
73-
const patch = ctx.patch;
74-
try {
75-
await execa('patch',
76-
['-p1', '--merge', '--no-backup-if-mismatch', '--directory=deps/v8'],
77-
{
78-
cwd: ctx.nodeDir,
79-
input: patch
80-
});
81-
} catch (e) {
82-
return input("Resolve merge conflicts and enter 'RESOLVED'", {
83-
validate: value => value === 'RESOLVED'
84-
});
163+
const { patches } = ctx;
164+
for (const patch of patches) {
165+
await applyPatch(ctx, patch);
85166
}
86167
}
87168
};
88169
}
89170

171+
function applyAndCommitPatches() {
172+
return {
173+
title: 'Apply and commit patches to deps/v8',
174+
task: (ctx) => {
175+
return new Listr(ctx.patches.map(applyPatchTask));
176+
}
177+
};
178+
}
179+
180+
function applyPatchTask(patch) {
181+
return {
182+
title: `Commit ${shortSha(patch.sha)}`,
183+
task: (ctx) => {
184+
const todo = [
185+
{
186+
title: 'Apply patch',
187+
task: (ctx) => applyPatch(ctx, patch)
188+
}
189+
];
190+
if (ctx.bump !== false) {
191+
if (ctx.nodeMajorVersion < 9) {
192+
todo.push(incrementV8Version());
193+
} else {
194+
todo.push(incrementEmbedderVersion());
195+
}
196+
}
197+
todo.push(commitPatch(patch));
198+
return new Listr(todo);
199+
}
200+
};
201+
}
202+
203+
async function applyPatch(ctx, patch) {
204+
try {
205+
await execa(
206+
'patch',
207+
['-p1', '--merge', '--no-backup-if-mismatch', '--directory=deps/v8'],
208+
{
209+
cwd: ctx.nodeDir,
210+
input: patch.data
211+
}
212+
);
213+
} catch (e) {
214+
patch.hadConflicts = true;
215+
return input("Resolve merge conflicts and enter 'RESOLVED'", {
216+
validate: value => value.toUpperCase() === 'RESOLVED'
217+
});
218+
}
219+
}
220+
90221
function incrementV8Version() {
91222
return {
92223
title: 'Increment V8 version',
93224
task: async(ctx) => {
94-
const incremented = ctx.currentVersion[3] + 1;
225+
const incremented = ++ctx.currentVersion[3];
95226
const versionHPath = `${ctx.nodeDir}/deps/v8/include/v8-version.h`;
96227
let versionH = await fs.readFile(versionHPath, 'utf8');
97228
versionH = versionH.replace(

lib/update-v8/index.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@ exports.minor = function(options) {
2525
return tasks.run(options);
2626
};
2727

28-
exports.backport = function(options) {
28+
exports.backport = async function(options) {
29+
const shouldStop = await backport.checkOptions(options);
30+
if (shouldStop) return;
2931
const tasks = new Listr(
30-
[updateV8Clone(), backport.doBackport(options), backport.commitBackport()],
32+
[updateV8Clone(), backport.doBackport(options)],
3133
getOptions(options)
3234
);
3335
return tasks.run(options);

0 commit comments

Comments
 (0)