Skip to content

Commit 0d347f8

Browse files
authored
chore(git-node): avoid dealing with patch files for landing
1 parent a429893 commit 0d347f8

File tree

3 files changed

+93
-53
lines changed

3 files changed

+93
-53
lines changed

lib/landing_session.js

Lines changed: 47 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ class LandingSession extends Session {
3030
this.lint = lint;
3131
this.autorebase = autorebase;
3232
this.fixupAll = fixupAll;
33+
this.expectedCommitShas = [];
3334
}
3435

3536
get argv() {
@@ -44,6 +45,8 @@ class LandingSession extends Session {
4445
async start(metadata) {
4546
const { cli } = this;
4647
this.startLanding();
48+
this.expectedCommitShas =
49+
metadata.data.commits.map(({ commit }) => commit.oid);
4750
const status = metadata.status ? 'should be ready' : 'is not ready';
4851
// NOTE(mmarchini): default answer is yes. If --yes is given, we need to be
4952
// more careful though, and we change the default to the result of our
@@ -78,34 +81,46 @@ class LandingSession extends Session {
7881
}
7982

8083
async downloadAndPatch() {
81-
const { cli, req, repo, owner, prid } = this;
84+
const { cli, repo, owner, prid, expectedCommitShas } = this;
8285

83-
// TODO(joyeecheung): restore previously downloaded patches
8486
cli.startSpinner(`Downloading patch for ${prid}`);
85-
const patch = await req.text(
86-
`https://github.com/${owner}/${repo}/pull/${prid}.patch`);
87-
this.savePatch(patch);
88-
cli.stopSpinner(`Downloaded patch to ${this.patchPath}`);
87+
await runAsync('git', [
88+
'fetch', `https://github.com/${owner}/${repo}.git`,
89+
`refs/pull/${prid}/merge`]);
90+
// We fetched the commit that would result if we used `git merge`.
91+
// ^1 and ^2 refer to the PR base and the PR head, respectively.
92+
const [base, head] = await runAsync('git',
93+
['rev-parse', 'FETCH_HEAD^1', 'FETCH_HEAD^2'],
94+
{ captureStdout: 'lines' });
95+
const commitShas = await runAsync('git',
96+
['rev-list', `${base}..${head}`],
97+
{ captureStdout: 'lines' });
98+
cli.stopSpinner(`Fetched commits as ${shortSha(base)}..${shortSha(head)}`);
8999
cli.separator();
90-
// TODO: check that patches downloaded match metadata.commits
100+
101+
const mismatchedCommits = [
102+
...commitShas.filter((sha) => !expectedCommitShas.includes(sha))
103+
.map((sha) => `Unexpected commit ${sha}`),
104+
...expectedCommitShas.filter((sha) => !commitShas.includes(sha))
105+
.map((sha) => `Missing commit ${sha}`)
106+
].join('\n');
107+
if (mismatchedCommits.length > 0) {
108+
cli.error(`Mismatched commits:\n${mismatchedCommits}`);
109+
process.exit(1);
110+
}
111+
112+
const commitInfo = { base, head, shas: commitShas };
113+
this.saveCommitInfo(commitInfo);
114+
91115
try {
92-
await forceRunAsync('git', ['am', this.patchPath], {
116+
await forceRunAsync('git', ['cherry-pick', `${base}..${head}`], {
93117
ignoreFailure: false
94118
});
95119
} catch (ex) {
96-
const should3Way = await cli.prompt(
97-
'The normal `git am` failed. Do you want to retry with 3-way merge?');
98-
if (should3Way) {
99-
await forceRunAsync('git', ['am', '--abort']);
100-
await runAsync('git', [
101-
'am',
102-
'-3',
103-
this.patchPath
104-
]);
105-
} else {
106-
cli.error('Failed to apply patches');
107-
process.exit(1);
108-
}
120+
await forceRunAsync('git', ['cherry-pick', '--abort']);
121+
122+
cli.error('Failed to apply patches');
123+
process.exit(1);
109124
}
110125

111126
// Check for and maybe assign any unmarked deprecations in the codebase.
@@ -126,7 +141,7 @@ class LandingSession extends Session {
126141
}
127142

128143
cli.ok('Patches applied');
129-
return patch;
144+
return commitInfo;
130145
}
131146

132147
getRebaseSuggestion(subjects) {
@@ -173,21 +188,13 @@ class LandingSession extends Session {
173188
}
174189
}
175190

176-
async tryCompleteLanding(patch) {
191+
async tryCompleteLanding(commitInfo) {
177192
const { cli } = this;
193+
const subjects = await runAsync('git',
194+
['log', '--pretty=format:%s', `${commitInfo.base}..${commitInfo.head}`],
195+
{ captureStdout: 'lines' });
178196

179-
const subjects = patch.match(/Subject: \[PATCH.*?\].*/g);
180-
if (!subjects) {
181-
cli.warn('Cannot get number of commits in the patch. ' +
182-
'It seems to be malformed');
183-
return;
184-
}
185-
186-
// XXX(joyeecheung) we cannot guarantee that no one will put a subject
187-
// line in the commit message but that seems unlikely (some deps update
188-
// might do that).
189-
if (subjects.length === 1) {
190-
// assert(subjects[0].startsWith('Subject: [PATCH]'))
197+
if (commitInfo.shas.length === 1) {
191198
const shouldAmend = await cli.prompt(
192199
'There is only one commit in this PR.\n' +
193200
'do you want to amend the commit message?');
@@ -247,7 +254,7 @@ class LandingSession extends Session {
247254
}
248255
await this.tryResetBranch();
249256

250-
const patch = await this.downloadAndPatch();
257+
const commitInfo = await this.downloadAndPatch();
251258

252259
const cleanLint = await this.validateLint();
253260
if (cleanLint === LINT_RESULTS.FAILED) {
@@ -280,7 +287,7 @@ class LandingSession extends Session {
280287

281288
this.startAmending();
282289

283-
await this.tryCompleteLanding(patch);
290+
await this.tryCompleteLanding(commitInfo);
284291
}
285292

286293
async amend() {
@@ -407,13 +414,13 @@ class LandingSession extends Session {
407414
}
408415
if (this.isApplying()) {
409416
// We're still resolving conflicts.
410-
if (this.amInProgress()) {
411-
cli.log('Looks like you are resolving a `git am` conflict');
417+
if (this.cherryPickInProgress()) {
418+
cli.log('Looks like you are resolving a `git cherry-pick` conflict');
412419
cli.log('Please run `git status` for help');
413420
} else {
414421
// Conflicts has been resolved - amend.
415422
this.startAmending();
416-
return this.tryCompleteLanding(this.patch);
423+
return this.tryCompleteLanding(this.commitInfo);
417424
}
418425
return;
419426
}

lib/run.js

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,24 @@ const { spawn, spawnSync } = require('child_process');
44

55
const IGNORE = '__ignore__';
66

7-
function runAsyncBase(cmd, args, options = {}) {
7+
function runAsyncBase(cmd, args, {
8+
ignoreFailure = true,
9+
spawnArgs,
10+
captureStdout = false
11+
} = {}) {
812
return new Promise((resolve, reject) => {
913
const child = spawn(cmd, args, Object.assign({
1014
cwd: process.cwd(),
11-
stdio: 'inherit'
12-
}, options.spawnArgs));
15+
stdio: captureStdout ? ['inherit', 'pipe', 'inherit'] : 'inherit'
16+
}, spawnArgs));
17+
let stdout;
18+
if (captureStdout) {
19+
stdout = '';
20+
child.stdout.setEncoding('utf8');
21+
child.stdout.on('data', (chunk) => { stdout += chunk; });
22+
}
1323
child.on('close', (code) => {
1424
if (code !== 0) {
15-
const { ignoreFailure = true } = options;
1625
if (ignoreFailure) {
1726
return reject(new Error(IGNORE));
1827
}
@@ -21,7 +30,11 @@ function runAsyncBase(cmd, args, options = {}) {
2130
err.messageOnly = true;
2231
return reject(err);
2332
}
24-
return resolve();
33+
if (captureStdout === 'lines') {
34+
stdout = stdout.split(/\r?\n/g);
35+
if (stdout[stdout.length - 1] === '') stdout.pop();
36+
}
37+
return resolve(stdout);
2538
});
2639
});
2740
}

lib/session.js

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -171,12 +171,12 @@ class Session {
171171
return readFile(this.metadataPath);
172172
}
173173

174-
get patchPath() {
175-
return path.join(this.pullDir, 'patch');
174+
get commitInfoPath() {
175+
return path.join(this.pullDir, 'commit-info');
176176
}
177177

178-
get patch() {
179-
return readFile(this.patchPath);
178+
get commitInfo() {
179+
return readJson(this.commitInfoPath);
180180
}
181181

182182
getMessagePath(rev) {
@@ -196,8 +196,8 @@ class Session {
196196
writeFile(this.metadataPath, status.metadata);
197197
}
198198

199-
savePatch(patch) {
200-
writeFile(this.patchPath, patch);
199+
saveCommitInfo(commitInfo) {
200+
writeJson(this.commitInfoPath, commitInfo);
201201
}
202202

203203
saveMessage(rev, message) {
@@ -218,20 +218,21 @@ class Session {
218218
if (this.session.state === AMENDING) {
219219
return true;
220220
} else if (this.isApplying()) {
221-
return !this.amInProgress();
221+
return !this.cherryPickInProgress();
222222
} else {
223223
return false;
224224
}
225225
}
226226

227227
readyToFinal() {
228-
if (this.amInProgress()) {
228+
if (this.amInProgress() || this.cherryPickInProgress()) {
229229
return false; // git am/rebase in progress
230230
}
231231
return this.session.state === AMENDING;
232232
}
233233

234234
// Refs: https://github.com/git/git/blob/99de064/git-rebase.sh#L208-L228
235+
// XXX: This may be unused at this point?
235236
amInProgress() {
236237
const amPath = path.join(this.gitDir, 'rebase-apply', 'applying');
237238
return fs.existsSync(amPath);
@@ -247,6 +248,11 @@ class Session {
247248
return fs.existsSync(normalRebasePath) || fs.existsSync(mergeRebasePath);
248249
}
249250

251+
cherryPickInProgress() {
252+
const cpPath = path.join(this.gitDir, 'CHERRY_PICK_HEAD');
253+
return fs.existsSync(cpPath);
254+
}
255+
250256
restore() {
251257
const sess = this.session;
252258
if (sess.prid) {
@@ -269,6 +275,19 @@ class Session {
269275
}
270276
}
271277

278+
async tryAbortCherryPick() {
279+
const { cli } = this;
280+
if (!this.cherryPickInProgress()) {
281+
return cli.ok('No git cherry-pick in progress');
282+
}
283+
const shouldAbortCherryPick = await cli.prompt(
284+
'Abort previous git cherry-pick sessions?');
285+
if (shouldAbortCherryPick) {
286+
await forceRunAsync('git', ['cherry-pick', '--abort']);
287+
cli.ok('Aborted previous git cherry-pick sessions');
288+
}
289+
}
290+
272291
async tryAbortRebase() {
273292
const { cli } = this;
274293
if (!this.rebaseInProgress()) {
@@ -284,6 +303,7 @@ class Session {
284303

285304
async tryResetBranch() {
286305
const { cli, upstream, branch } = this;
306+
await this.tryAbortCherryPick();
287307
await this.tryAbortAm();
288308
await this.tryAbortRebase();
289309

0 commit comments

Comments
 (0)