diff --git a/lib/landing_session.js b/lib/landing_session.js index 0e417c15..9ba7b827 100644 --- a/lib/landing_session.js +++ b/lib/landing_session.js @@ -3,7 +3,7 @@ const path = require('path'); const { - runAsync, runSync, forceRunAsync, exit + runAsync, runSync, forceRunAsync } = require('./run'); const Session = require('./session'); @@ -46,17 +46,10 @@ class LandingSession extends Session { cli.ok(`Aborted \`git node land\` session in ${this.ncuDir}`); } - async apply() { + async downloadAndPatch() { const { cli, req, repo, owner, prid } = this; - if (!this.readyToApply()) { - cli.warn('This session can not proceed to apply patches, ' + - 'run `git node land --abort`'); - return; - } - await this.tryResetBranch(); - - // TODO: restore previously downloaded patches + // TODO(joyeecheung): restore previously downloaded patches cli.startSpinner(`Downloading patch for ${prid}`); const patch = await req.text( `https://github.com/${owner}/${repo}/pull/${prid}.patch`); @@ -81,13 +74,42 @@ class LandingSession extends Session { ]); } else { cli.error('Failed to apply patches'); - exit(); + process.exit(1); } } cli.ok('Patches applied'); + return patch; + } - this.startAmending(); - if (/Subject: \[PATCH\]/.test(patch)) { + getRebaseSuggestion(subjects) { + const { upstream, branch } = this; + let command = `git rebase ${upstream}/${branch} -i`; + command += ' -x "git node land --amend"'; + + const squashes = subjects.filter( + line => line.includes('fixup!') || line.includes('squash!')); + + if (squashes.length !== 0) { + command += ' --autosquash'; + } + + return command; + } + + async suggestAfterPatch(patch) { + const { cli } = this; + const subjects = patch.match(/Subject: \[PATCH.*?\].*/g); + if (!subjects) { + cli.warn('Cannot get number of commits in the patch. ' + + 'It seems to be malformed'); + return; + } + + // XXX(joyeecheung) we cannot guarantee that no one will put a subject + // line in the commit message but that seems unlikely (some deps update + // might do that). + if (subjects.length === 1) { + // assert(subjects[0].startsWith('Subject: [PATCH]')) const shouldAmend = await cli.prompt( 'There is only one commit in this PR.\n' + 'do you want to amend the commit message?'); @@ -101,20 +123,25 @@ class LandingSession extends Session { return this.final(); } - const re = /Subject: \[PATCH 1\/(\d+)\]/; - const match = patch.match(re); - if (!match) { - cli.warn('Cannot get number of commits in the patch. ' + - 'It seems to be malformed'); + const suggestion = this.getRebaseSuggestion(subjects); + + cli.log(`There are ${subjects.length} commits in the PR`); + cli.log('Please run the following command to complete landing\n\n' + + `$ ${suggestion} # put "edit" on every commit that will stay`); + } + + async apply() { + const { cli } = this; + if (!this.readyToApply()) { + cli.warn('This session can not proceed to apply patches, ' + + 'run `git node land --abort`'); return; } - const { upstream, branch } = this; - cli.log( - `There are ${match[1]} commits in the PR.\n` + - `Please run \`git rebase ${upstream}/${branch} -i\`, put "edit" on ` + - 'every commit that\'s gonna stay and use `git node land --amend` to ' + - 'amend the commit messages'); - // TODO: do git rebase automatically? + await this.tryResetBranch(); + + const patch = await this.downloadAndPatch(); + this.startAmending(); + await this.suggestAfterPatch(patch); } getCurrentRev() { @@ -166,17 +193,17 @@ class LandingSession extends Session { cli.log(`Please manually edit ${messageFile}, then run\n` + `\`git commit --amend -F ${messageFile}\` ` + 'to finish amending the message'); - return false; + process.exit(1); // make it work with git rebase -x } async final() { - const { cli } = this; + const { cli, owner, repo, upstream, branch, prid } = this; + if (!this.readyToFinal()) { // check git rebase/am has been done cli.warn('Not yet ready to final'); return; - } - const upstream = this.upstream; - const branch = this.branch; + }; + const notYetPushed = this.getNotYetPushedCommits(); const notYetPushedVerbose = this.getNotYetPushedCommits(true); const validateCommand = path.join( @@ -196,9 +223,11 @@ class LandingSession extends Session { const head = this.getUpstreamHead().slice(0, 7); willBeLanded = `${head}...${willBeLanded}`; } + cli.log('To finish landing:'); cli.log(`1. Run \`git push ${upstream} ${branch}\``); - cli.log(`2. Post in the PR: \`Landed in ${willBeLanded}\``); + const url = `https://github.com/${owner}/${repo}/pull/${prid}`; + cli.log(`2. Post in ${url}: \`Landed in ${willBeLanded}\``); const shouldClean = await cli.prompt('Clean up generated temporary files?'); if (shouldClean) { diff --git a/lib/session.js b/lib/session.js index c74bc1fc..1e001565 100644 --- a/lib/session.js +++ b/lib/session.js @@ -79,6 +79,13 @@ class Session { }); } + // TODO(joyeecheung): more states + // - STARTED (fetching metadata) + // - DOWNLOADING (downloading the patch) + // - PATCHING (git am) + // - AMENDING (git rebase or just amending messages) + // - DONE + startApplying() { this.updateSession({ state: APPLYING