Skip to content

Commit 38dac57

Browse files
committed
feat: implement autorebase for PRs with multiple commits
1 parent de6d1e2 commit 38dac57

File tree

2 files changed

+48
-9
lines changed

2 files changed

+48
-9
lines changed

components/git/land.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ const landOptions = {
4747
describe: 'Prevent adding Fixes and Refs information to commit metadata',
4848
default: false,
4949
type: 'boolean'
50+
},
51+
autorebase: {
52+
describe: 'Automatically rebase branches with multiple commits',
53+
default: false,
54+
type: 'boolean'
5055
}
5156
};
5257

@@ -165,7 +170,8 @@ async function main(state, argv, cli, req, dir) {
165170
cli.log('run `git node land --abort` before starting a new session');
166171
return;
167172
}
168-
session = new LandingSession(cli, req, dir, argv.prid, argv.backport);
173+
session = new LandingSession(cli, req, dir, argv.prid, argv.backport,
174+
argv.autorebase);
169175
const metadata = await getMetadata(session.argv, argv.skipRefs, cli);
170176
if (argv.backport) {
171177
const split = metadata.metadata.split('\n')[0];

lib/landing_session.js

+41-8
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,17 @@ const { shortSha } = require('./utils');
1616
const isWindows = process.platform === 'win32';
1717

1818
class LandingSession extends Session {
19-
constructor(cli, req, dir, prid, backport) {
19+
constructor(cli, req, dir, prid, backport, autorebase) {
2020
super(cli, dir, prid);
2121
this.req = req;
2222
this.backport = backport;
23+
this.autorebase = autorebase;
2324
}
2425

2526
get argv() {
2627
const args = super.argv;
2728
args.backport = this.backport;
29+
args.autorebase = this.autorebase;
2830
return args;
2931
}
3032

@@ -131,6 +133,17 @@ class LandingSession extends Session {
131133
return command;
132134
}
133135

136+
makeRebaseSuggestion(subjects) {
137+
const suggestion = this.getRebaseSuggestion(subjects);
138+
this.cli.log('Please run the following commands to complete landing\n\n' +
139+
`$ ${suggestion}\n` +
140+
'$ git node land --continue');
141+
}
142+
143+
canAutomaticallyRebase(subjects) {
144+
return subjects.every(line => !line.startsWith('squash!'));
145+
}
146+
134147
async validateLint() {
135148
// The linter is currently only run on non-Windows platforms.
136149
if (os.platform() === 'win32') {
@@ -168,14 +181,34 @@ class LandingSession extends Session {
168181
}
169182

170183
return this.final();
184+
} else if (this.autorebase && this.canAutomaticallyRebase(subjects)) {
185+
// Run git rebase in interactive mode with autosquash but without editor
186+
// so that it will perform everything automatically.
187+
cli.log(`There are ${subjects.length} commits in the PR. ` +
188+
'Attempting autorebase.');
189+
const { upstream, branch } = this;
190+
const assumeYes = this.cli.assumeYes ? '--yes' : '';
191+
const msgAmend = `-x "git node land --amend ${assumeYes}"`;
192+
try {
193+
await forceRunAsync('git',
194+
['rebase', `${upstream}/${branch}`, '-i', '--autosquash', msgAmend],
195+
{
196+
ignoreFailure: false,
197+
spawnArgs: {
198+
shell: true,
199+
env: { ...process.env, GIT_SEQUENCE_EDITOR: ':' }
200+
}
201+
});
202+
return this.final();
203+
} catch (e) {
204+
await runAsync('git', ['rebase', '--abort']);
205+
const count = subjects.length;
206+
cli.log(`Couldn't rebase ${count} commits in the PR automatically`);
207+
this.makeRebaseSuggestion(subjects);
208+
}
209+
} else {
210+
this.makeRebaseSuggestion(subjects);
171211
}
172-
173-
const suggestion = this.getRebaseSuggestion(subjects);
174-
175-
cli.log(`There are ${subjects.length} commits in the PR`);
176-
cli.log('Please run the following commands to complete landing\n\n' +
177-
`$ ${suggestion}\n` +
178-
'$ git node land --continue');
179212
}
180213

181214
async apply() {

0 commit comments

Comments
 (0)