Skip to content

Commit 03da45f

Browse files
committed
[wip] git node land
1 parent 59b121f commit 03da45f

File tree

10 files changed

+443
-90
lines changed

10 files changed

+443
-90
lines changed

bin/git-node

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
const CMD = process.argv[2];
55
const path = require('path');
6-
const { run } = require('../lib/run');
6+
const { runAsync } = require('../lib/run');
77
const fs = require('fs');
88

99
if (!CMD) {
@@ -18,4 +18,4 @@ if (!fs.existsSync(script)) {
1818
process.exit(1);
1919
}
2020

21-
run(script, process.argv.slice(3));
21+
runAsync(script, process.argv.slice(3));
File renamed without changes.

components/git/git-node-help

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
1-
#!/usr/bin/env bash
1+
#!/usr/bin/env node
22

3-
echo "
4-
1) To land single a commit:
5-
git node just <PRID>
6-
7-
2) To land multiple commits:
8-
git node apply <PRID>
9-
git rebase -i upstream/master # edit every commit that's gonna stay
10-
# on each stay
11-
git node ammend <PRID>
12-
git rebase --continue
13-
# when the rebase is done
14-
git node final"
3+
console.log(`
4+
Usage:
5+
git node land <PRID> # start a landing session
6+
git node land --apply # pull and apply patches
7+
git rebase -i upstream/master # edit every commit that's gonna stay
8+
git node land --amend # regenerate commit messages in HEAD
9+
git rebase --continue
10+
git node land --final # verify all the messages
11+
`);

components/git/git-node-just

Lines changed: 0 additions & 43 deletions
This file was deleted.

components/git/git-node-land

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
#!/usr/bin/env node
2+
3+
const getMetadata = require('../metadata');
4+
const CLI = require('../../lib/cli');
5+
const cli = new CLI(process.stderr);
6+
const Request = require('../../lib/request');
7+
const req = new Request();
8+
const { runPromise, runAsync, runSync } = require('../../lib/run');
9+
const Session = require('../../lib/landing_session');
10+
const dir = process.cwd();
11+
const args = process.argv.slice(2);
12+
13+
const START = 'START';
14+
const APPLY = 'APPLY';
15+
const AMEND = 'AMEND';
16+
const FINAL = 'FINAL';
17+
const CONTINUE = 'CONTINUE';
18+
const ABORT = 'ABORT';
19+
20+
const states = [
21+
[START, (args) => !isNaN(parseInt(args[0]))],
22+
[CONTINUE, (args) => args[0] === '--continue'],
23+
[APPLY, (args) => args[0] === '--apply'],
24+
[AMEND, (args) => args[0] === '--amend'],
25+
[FINAL, (args) => args[0] === '--final'],
26+
[ABORT, (args) => args[0] === '--abort']
27+
];
28+
29+
const result = states.filter(([state, pred]) => pred(args));
30+
if (result.length) {
31+
runPromise(main(result[0][0], args).catch((err) => {
32+
if (cli.spinner.enabled) {
33+
cli.spinner.fail();
34+
}
35+
throw err;
36+
}));
37+
} else {
38+
cli.error('Usage: `git node land <PRID>`');
39+
process.exit(1);
40+
}
41+
42+
async function main(state, args) {
43+
if (state === START) {
44+
let session = Session.restore(dir);
45+
if (session.hasStarted()) {
46+
cli.warn(
47+
'Previous `git node land` session for ' +
48+
`${session.pullName} in progress.`);
49+
cli.log('run `git node land --abort` before starting a new session');
50+
return;
51+
}
52+
session = new Session(dir, parseInt(args[0]));
53+
await start(session);
54+
} else if (state === APPLY) {
55+
const session = Session.restore(dir);
56+
await apply(session);
57+
} else if (state === AMEND) {
58+
const session = Session.restore(dir);
59+
await amend(session);
60+
} else if (state === FINAL) {
61+
const session = Session.restore(dir);
62+
await final(session);
63+
} else if (state === ABORT) {
64+
const session = Session.restore(dir);
65+
session.abort();
66+
} else if (state === CONTINUE) {
67+
const session = Session.restore(dir);
68+
await continueSession(session);
69+
}
70+
}
71+
72+
async function start(session) {
73+
session.start();
74+
const { repo, owner, prid } = session;
75+
const result = await getMetadata({ repo, owner, prid }, cli);
76+
77+
const status = result.status ? 'should be ready' : 'is not ready';
78+
const response = await cli.prompt(
79+
`This PR ${status} to land, do you want to continue?`);
80+
if (response) {
81+
session.saveMetadata(status);
82+
session.startApplying();
83+
return apply(session);
84+
} else {
85+
session.abort();
86+
cli.log('Landing session aborted');
87+
process.exit();
88+
}
89+
}
90+
91+
async function apply(session) {
92+
if (!session.readyToApply()) {
93+
cli.warn('This session can not proceed to apply patches, ' +
94+
'run `git node land --abort`');
95+
return;
96+
}
97+
98+
if (session.hasAM()) {
99+
const shouldAbortAm = await cli.prompt('Abort previous git am sessions?');
100+
if (shouldAbortAm) {
101+
await runAsync('git', ['am', '--abort']);
102+
}
103+
}
104+
105+
const { repo, owner, prid } = session;
106+
// TODO: restore previously downloaded patches
107+
cli.startSpinner(`Downloading patch for ${prid}`);
108+
const patch = await req.promise({
109+
url: `https://github.com/nodejs/${owner}/${repo}/${prid}.patch`
110+
});
111+
session.savePatch(patch);
112+
cli.stopSpinner(`Downloaded patch to ${session.patchPath}`);
113+
114+
// TODO: check that patches downloaded match metadata.commits
115+
await runAsync('git', ['am', '--whitespace=fix', session.patchPath]);
116+
cli.ok('Patches applied');
117+
118+
session.startAmending();
119+
if (/Subject: \[PATCH\]/.test(patch)) {
120+
const shouldAmend = await cli.prompt(
121+
'There is only one patch to apply.\n' +
122+
'do you want to amend the commit message?');
123+
if (shouldAmend) {
124+
const canFinal = await amend(session);
125+
if (canFinal) {
126+
return final(session);
127+
}
128+
}
129+
}
130+
}
131+
132+
async function amend(session) {
133+
if (!session.readyToAmend()) {
134+
cli.warn('Not yet ready to amend, run `git node land --abort`');
135+
return;
136+
}
137+
138+
const rev = runSync('git', ['rev-parse', 'HEAD']);
139+
const original = runSync('git', ['show', rev, '-s', '--format=%B']);
140+
const metadata = session.metadata.split('\n');
141+
const amended = original.split('\n');
142+
if (amended[amended.length - 1] !== '\n') {
143+
amended.push('\n');
144+
}
145+
146+
for (const line of metadata) {
147+
if (original.includes(line)) {
148+
cli.warn(`Found ${line}, skipping..`);
149+
} else {
150+
amended.push(line);
151+
}
152+
}
153+
154+
const message = amended.join('\n') + '\n';
155+
const messageFile = session.saveMessage(rev, message);
156+
cli.separator('New Message');
157+
cli.log(message);
158+
const takeMessage = await cli.prompt('Use this message?');
159+
if (takeMessage) {
160+
await runAsync('git', ['commit', '--amend', '-F', messageFile]);
161+
session.markAsAmended(rev);
162+
return true;
163+
}
164+
165+
cli.log(`Please manually edit ${messageFile}, then run ` +
166+
`\`git commit --amend -F ${messageFile}\` to finish amending the message`);
167+
return false;
168+
};
169+
170+
async function final(session) {
171+
if (!session.readyToFinal()) { // check git rebase/am has been done
172+
cli.warn('Not yet ready to final');
173+
return;
174+
}
175+
176+
const upstream = session.upstream;
177+
const branch = session.branch;
178+
const notYetPushed = runSync('git',
179+
['rev-list', `${upstream}/${branch}...HEAD`]).split('\n');
180+
await runAsync('core-validate-commit', notYetPushed);
181+
cli.log('This session is ready to be completed.');
182+
cli.log(`run \`git push ${upstream} ${branch}\` to finish landing`);
183+
const shouldClean = await cli.prompt('Clean up generated temporary files?');
184+
if (shouldClean) {
185+
session.cleanFiles();
186+
}
187+
}
188+
189+
async function continueSession(session) {
190+
if (session.readyToFinal()) {
191+
return final(session);
192+
}
193+
if (session.readyToAmend()) {
194+
return amend(session);
195+
}
196+
if (session.readyToApply()) {
197+
return apply(session);
198+
}
199+
if (session.hasStarted()) {
200+
return apply(session);
201+
}
202+
cli.log(
203+
'Please run `git node land <PRID> to start a landing session`');
204+
}

lib/config.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
'use strict';
2+
3+
const path = require('path');
4+
const os = require('os');
5+
const { readJson } = require('./file');
6+
7+
exports.getConfig = function(dir, home) {
8+
const ncurcPath = path.join(home || os.homedir(), '.ncurc');
9+
const ncurc = readJson(ncurcPath);
10+
11+
const ncuDir = exports.getNcuDir(dir || process.cwd());
12+
const configPath = path.join(ncuDir, 'config');
13+
let config = readJson(configPath);
14+
return Object.assign(ncurc, config);
15+
};
16+
17+
exports.getNcuDir = function(dir) {
18+
return path.join(dir, '.ncu');
19+
};

lib/file.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
'use strict';
2+
3+
const fs = require('fs');
4+
5+
exports.writeFile = function(file, content) {
6+
fs.writeFileSync(file, content, 'utf8');
7+
};
8+
9+
exports.writeJson = function(file, obj) {
10+
exports.writeFile(file, JSON.stringify(obj, null, 2));
11+
};
12+
13+
exports.readFile = function(file) {
14+
if (fs.existsSync(file)) {
15+
return fs.readFileSync(file, 'utf8');
16+
}
17+
return '';
18+
};
19+
20+
exports.readJson = function(file) {
21+
const content = exports.readFile(file);
22+
if (content) {
23+
return JSON.parse(content);
24+
}
25+
return {};
26+
};

0 commit comments

Comments
 (0)