Source: index.js

/**
 * @copyright Copyright 2016 Kevin Locke <kevin@kevinlocke.name>
 * @license MIT
 */

'use strict';

const { execFile } = require('child_process');

/** Options for {@link gitBranchIs}.
 *
 * @typedef {{
 *   cwd: (?string|undefined),
 *   gitArgs: (Array|undefined),
 *   gitDir: (?string|undefined),
 *   gitPath: (string|undefined)
 * }} GitBranchIsOptions
 * @property {?string=} cwd Current working directory where the branch name is
 * tested.
 * @property {Array=} gitArgs Extra arguments to pass to git.
 * @property {?string=} gitDir Path to the repository (i.e.
 * <code>--git-dir=</code> option to <code>git</code>).
 * @property {string=} gitPath Git binary name or path to use (default:
 * <code>'git'</code>).
 */
const GitBranchIsOptions = {
  cwd: '',
  gitArgs: [],
  gitDir: '',
  gitPath: 'git',
};

/** Checks that the current branch of a git repository has a given name.
 *
 * @param {string|function(string)} branchNameOrTest Expected name of
 * current branch or a test function to apply to the branch name.
 * @param {?GitBranchIsOptions=} options Options.
 * @param {?function(Error, boolean=)=} callback Callback function called
 * with the return value of <code>branchNameOrTest</code> if it is a function,
 * or the result of identity checking <code>branchNameOrTest</code> to the
 * current branch name.
 * @returns {Promise|undefined} If <code>callback</code> is not given, a
 * <code>Promise</code> with the return value of <code>branchNameOrTest</code>
 * if it is a function, or the result of identity checking
 * <code>branchNameOrTest</code> to the current branch name.
 */
function gitBranchIs(branchNameOrTest, options, callback) {
  if (!callback && typeof options === 'function') {
    callback = options;
    options = undefined;
  }

  if (!callback) {
    return new Promise((resolve, reject) => {
      gitBranchIs(branchNameOrTest, options, (err, result) => {
        if (err) { reject(err); } else { resolve(result); }
      });
    });
  }

  if (typeof callback !== 'function') {
    throw new TypeError('callback must be a function');
  }

  if (options !== undefined && typeof options !== 'object') {
    process.nextTick(callback, new TypeError('options must be an object'));
    return undefined;
  }

  gitBranchIs.getBranch(options, (err, currentBranch) => {
    if (err) {
      callback(err);
      return;
    }

    let result;
    try {
      result = currentBranch === branchNameOrTest
        || (typeof branchNameOrTest === 'function'
         && branchNameOrTest(currentBranch));
    } catch (errTest) {
      callback(errTest);
      return;
    }

    callback(null, result); // eslint-disable-line unicorn/no-null
  });
  return undefined;
}

/** Gets the name of the current (i.e. checked out) branch of a git repository.
 *
 * @param {?GitBranchIsOptions=} options Options.
 * @param {?function(Error, string=)=} callback Callback function called
 * with the current branch name, empty string if not on a branch, or
 * <code>Error</code> if there was an error determining the branch name.
 * @returns {Promise|undefined} If <code>callback</code> is not given, a
 * <code>Promise</code> with the current branch name, empty string if not on a
 * branch, or <code>Error</code> if there was an error determining the branch
 * name.
 */
gitBranchIs.getBranch = function getBranch(options, callback) {
  if (!callback && typeof options === 'function') {
    callback = options;
    options = undefined;
  }

  if (!callback) {
    return new Promise((resolve, reject) => {
      getBranch(options, (err, result) => {
        if (err) { reject(err); } else { resolve(result); }
      });
    });
  }

  if (typeof callback !== 'function') {
    throw new TypeError('callback must be a function');
  }

  if (options && typeof options !== 'object') {
    process.nextTick(callback, new TypeError('options must be an Object'));
    return undefined;
  }

  const combinedOpts = {
    ...GitBranchIsOptions,
    ...options,
  };
  const gitArgs = combinedOpts.gitArgs
    ? Array.prototype.slice.call(combinedOpts.gitArgs, 0)
    : [];
  if (combinedOpts.gitDir) {
    gitArgs.unshift(`--git-dir=${combinedOpts.gitDir}`);
  }
  // Note: --quiet causes symbolic-ref to exit with code 1 and no error
  // instead of code 128 and "ref %s is not a symbolic ref" when not on a
  // branch.
  gitArgs.push('symbolic-ref', '--quiet', '--short', 'HEAD');

  try {
    execFile(
      combinedOpts.gitPath,
      gitArgs,
      { cwd: combinedOpts.cwd },
      (errExec, stdout, stderr) => {
        if (errExec) {
          if (errExec.code === 1 && !stdout && !stderr) {
            // Not on a branch
            callback(null, ''); // eslint-disable-line unicorn/no-null
          } else {
            callback(errExec);
          }

          return;
        }

        // Note:  ASCII space and control characters are forbidden in names
        // https://www.kernel.org/pub/software/scm/git/docs/git-check-ref-format.html
        callback(null, stdout.trimEnd()); // eslint-disable-line unicorn/no-null
      },
    );
  } catch (errExec) {
    process.nextTick(callback, errExec);
    return undefined;
  }

  return undefined;
};

module.exports = gitBranchIs;