diff --git a/components/git/land.js b/components/git/land.js index e39ac83d..ec98360a 100644 --- a/components/git/land.js +++ b/components/git/land.js @@ -47,6 +47,11 @@ const landOptions = { describe: 'Prevent adding Fixes and Refs information to commit metadata', default: false, type: 'boolean' + }, + autorebase: { + describe: 'Automatically rebase branches with multiple commits', + default: false, + type: 'boolean' } }; @@ -165,7 +170,8 @@ async function main(state, argv, cli, req, dir) { cli.log('run `git node land --abort` before starting a new session'); return; } - session = new LandingSession(cli, req, dir, argv.prid, argv.backport); + session = new LandingSession(cli, req, dir, argv.prid, argv.backport, + argv.autorebase); const metadata = await getMetadata(session.argv, argv.skipRefs, cli); if (argv.backport) { const split = metadata.metadata.split('\n')[0]; diff --git a/lib/landing_session.js b/lib/landing_session.js index 371e1db2..c94d898c 100644 --- a/lib/landing_session.js +++ b/lib/landing_session.js @@ -16,15 +16,17 @@ const { shortSha } = require('./utils'); const isWindows = process.platform === 'win32'; class LandingSession extends Session { - constructor(cli, req, dir, prid, backport) { + constructor(cli, req, dir, prid, backport, autorebase) { super(cli, dir, prid); this.req = req; this.backport = backport; + this.autorebase = autorebase; } get argv() { const args = super.argv; args.backport = this.backport; + args.autorebase = this.autorebase; return args; } @@ -131,6 +133,17 @@ class LandingSession extends Session { return command; } + makeRebaseSuggestion(subjects) { + const suggestion = this.getRebaseSuggestion(subjects); + this.cli.log('Please run the following commands to complete landing\n\n' + + `$ ${suggestion}\n` + + '$ git node land --continue'); + } + + canAutomaticallyRebase(subjects) { + return subjects.every(line => !line.startsWith('squash!')); + } + async validateLint() { // The linter is currently only run on non-Windows platforms. if (os.platform() === 'win32') { @@ -168,14 +181,34 @@ class LandingSession extends Session { } return this.final(); + } else if (this.autorebase && this.canAutomaticallyRebase(subjects)) { + // Run git rebase in interactive mode with autosquash but without editor + // so that it will perform everything automatically. + cli.log(`There are ${subjects.length} commits in the PR. ` + + 'Attempting autorebase.'); + const { upstream, branch } = this; + const assumeYes = this.cli.assumeYes ? '--yes' : ''; + const msgAmend = `-x "git node land --amend ${assumeYes}"`; + try { + await forceRunAsync('git', + ['rebase', `${upstream}/${branch}`, '-i', '--autosquash', msgAmend], + { + ignoreFailure: false, + spawnArgs: { + shell: true, + env: { ...process.env, GIT_SEQUENCE_EDITOR: ':' } + } + }); + return this.final(); + } catch (e) { + await runAsync('git', ['rebase', '--abort']); + const count = subjects.length; + cli.log(`Couldn't rebase ${count} commits in the PR automatically`); + this.makeRebaseSuggestion(subjects); + } + } else { + this.makeRebaseSuggestion(subjects); } - - const suggestion = this.getRebaseSuggestion(subjects); - - cli.log(`There are ${subjects.length} commits in the PR`); - cli.log('Please run the following commands to complete landing\n\n' + - `$ ${suggestion}\n` + - '$ git node land --continue'); } async apply() {