@ 261105.4
current
import auth from '../../lib/auth.js';
import { parsePRFromURL } from '../../lib/links.js';
import { getMetadata } from '../metadata.js';
import CLI from '../../lib/cli.js';
import Request from '../../lib/request.js';
import { runPromise } from '../../lib/run.js';
import LandingSession from '../../lib/landing_session.js';
import epilogue from './epilogue.js';

export const command = 'land [prid|options]';
export const describe =
  'Manage the current landing session or start a new one for a pull request';

const landActions = {
  apply: {
    describe: 'Apply a patch with the given PR id',
    type: 'number'
  },
  amend: {
    describe: 'Amend the current commit',
    type: 'boolean'
  },
  continue: {
    alias: 'c',
    describe: 'Continue the landing session',
    type: 'boolean'
  },
  final: {
    describe: 'Verify the landed PR and clean up',
    type: 'boolean'
  },
  abort: {
    describe: 'Abort the current landing session',
    type: 'boolean'
  },
  backport: {
    describe: 'Land a backport PR onto a staging branch',
    default: false,
    type: 'boolean'
  },
  'gpg-sign': {
    describe: 'GPG-sign commits, will be passed to the git process',
    alias: 'S'
  },
  autorebase: {
    describe: 'Automatically rebase branches with multiple commits',
    default: false,
    type: 'boolean'
  },
  fixupAll: {
    describe: 'Automatically fixup ("squash") all commits to the first one ' +
      'dismissing other commit messages',
    default: false,
    type: 'boolean'
  },
  oneCommitMax: {
    describe: 'When run in conjunction with the --yes and --autorebase ' +
      'options, will abort the session if trying to land more than one commit',
    default: false,
    type: 'boolean'
  }
};

const landOptions = {
  yes: {
    type: 'boolean',
    default: false,
    describe: 'Assume "yes" as answer to all prompts and run ' +
    'non-interactively. If an undesirable situation occurs, such as a pull ' +
    'request or commit check fails, then git node land will abort.'
  },
  skipRefs: {
    describe: 'Prevent adding Fixes and Refs information to commit metadata',
    default: false,
    type: 'boolean'
  },
  lint: {
    describe: 'Run linter while landing commits',
    default: false,
    type: 'boolean'
  },
  checkCI: {
    describe: 'Query Jenkins CI results when checking the PR',
    default: true,
    type: 'boolean'
  }
};

let yargsInstance;

export function builder(yargs) {
  yargsInstance = yargs;
  return yargs
    .options(Object.assign({}, landOptions, landActions))
    .positional('prid', {
      describe: 'ID or URL of the Pull Request'
    })
    .epilogue(epilogue)
    .example('git node land https://github.com/nodejs/node/pull/12344',
      'Land https://github.com/nodejs/node/pull/12344 in the current directory')
    .example('git node land 12344',
      'Land https://github.com/nodejs/node/pull/12344 in the current directory')
    .example('git node land --abort',
      'Abort the current session')
    .example('git node land --amend',
      'Append metadata to the current commit message')
    .example('git node land --final',
      'Verify the landed PR and clean up')
    .example('git node land --continue',
      'Continue the current landing session');
}

const START = 'start';
const APPLY = 'apply';
const AMEND = 'amend';
const FINAL = 'final';
const CONTINUE = 'continue';
const ABORT = 'abort';

export function handler(argv) {
  if (argv.prid) {
    if (Number.isInteger(argv.prid)) {
      return land(START, argv);
    } else {
      const parsed = parsePRFromURL(argv.prid);
      if (parsed) {
        Object.assign(argv, parsed);
        return land(START, argv);
      }
    }
    yargsInstance.showHelp();
    return;
  }

  const provided = [];
  for (const type of Object.keys(landActions)) {
    if (argv[type]) {
      provided.push(type);
    }
  }

  if (provided.length === 1) {
    return land(provided[0], argv);
  }

  // If the more than one action is provided or no valid action
  // is provided, show help.
  yargsInstance.showHelp();
}

function land(state, argv) {
  const cli = new CLI(process.stderr);
  if (argv.yes) {
    cli.setAssumeYes();
  }
  const dir = process.cwd();

  return runPromise(main(state, argv, cli, dir)).catch((err) => {
    if (cli.spinner.enabled) {
      cli.spinner.fail();
    }
    throw err;
  });
}

async function main(state, argv, cli, dir) {
  const credentials = await auth({
    github: true
  });
  const req = new Request(credentials);
  let session = new LandingSession(cli, req, dir, argv);

  if (state !== AMEND &&
      state !== CONTINUE &&
      await session.warnForWrongBranch()) {
    return;
  }

  if (argv.yes) {
    cli.setAssumeYes();
  }
  try {
    session.restore();
  } catch { // JSON error?
    if (state === ABORT) {
      await session.abort();
      return;
    }
    cli.warn(
      'Failed to detect previous session. ' +
      'please run `git node land --abort`');
    cli.setErrorCode(1);
    return;
  }

  if (state === START) {
    if (session.hasStarted()) {
      cli.warn(
        'Previous `git node land` session for ' +
        `${session.pullName} in progress.`);
      const response = await cli.prompt('Do you want to abort it?', { defaultAnswer: false });
      if (!response) throw new Error('Please run `git node land --abort`');
      await session.abort();
      session = new LandingSession(cli, req, dir, argv);
    }
    const metadata = await getMetadata(session.argv, argv.skipRefs, cli);
    return session.start(metadata);
  } else if (state === APPLY) {
    return session.apply();
  } else if (state === AMEND) {
    return session.amend();
  } else if (state === FINAL) {
    return session.final();
  } else if (state === ABORT) {
    return session.abort();
  } else if (state === CONTINUE) {
    return session.continue();
  }
}