text: (function () {
  
     'use strict';
  
     var debug = require('debug')('simple-git');
     var exists = require('./util/exists');
     var NOOP = function () {};
  
     /**
      * Git handling for node. All public functions can be chained and all `then` handlers are optional.
      *
      * @param {string} baseDir base directory for all processes to run
      *
      * @param {Object} ChildProcess The ChildProcess module
      * @param {Function} Buffer The Buffer implementation to use
      *
      * @constructor
      */
     function Git (baseDir, ChildProcess, Buffer) {
        this._baseDir = baseDir;
        this._runCache = [];
  
        this.ChildProcess = ChildProcess;
        this.Buffer = Buffer;
     }
  
     /**
      * @type {string} The command to use to reference the git binary
      */
     Git.prototype._command = 'git';
  
     /**
      * @type {[key: string]: string} An object of key=value pairs to be passed as environment variables to the
      *                               spawned child process.
      */
     Git.prototype._env = null;
  
     /**
      * @type {Function} An optional handler to use when a child process is created
      */
     Git.prototype._outputHandler = null;
  
     /**
      * @type {boolean} Property showing whether logging will be silenced - defaults to true in a production environment
      */
     Git.prototype._silentLogging = /prod/.test(process.env.NODE_ENV);
  
     /**
      * Sets the path to a custom git binary, should either be `git` when there is an installation of git available on
      * the system path, or a fully qualified path to the executable.
      *
      * @param {string} command
      * @returns {Git}
      */
     Git.prototype.customBinary = function (command) {
        this._command = command;
        return this;
     };
  
     /**
      * Sets an environment variable for the spawned child process, either supply both a name and value as strings or
      * a single object to entirely replace the current environment variables.
      *
      * @param {string|Object} name
      * @param {string} [value]
      * @returns {Git}
      */
     Git.prototype.env = function (name, value) {
        if (arguments.length === 1 && typeof name === 'object') {
           this._env = name;
        }
        else {
           (this._env = this._env || {})[name] = value;
        }
  
        return this;
     };
  
     /**
      * Sets the working directory of the subsequent commands.
      *
      * @param {string} workingDirectory
      * @param {Function} [then]
      * @returns {Git}
      */
     Git.prototype.cwd = function (workingDirectory, then) {
        var git = this;
        var next = Git.trailingFunctionArgument(arguments);
  
        return this.exec(function () {
           git._baseDir = workingDirectory;
           if (!exists(workingDirectory, exists.FOLDER)) {
              Git.exception(git, 'Git.cwd: cannot change to non-directory "' + workingDirectory + '"', next);
           }
           else {
              next && next(null, workingDirectory);
           }
        });
     };
  
     /**
      * Sets a handler function to be called whenever a new child process is created, the handler function will be called
      * with the name of the command being run and the stdout & stderr streams used by the ChildProcess.
      *
      * @example
      * require('simple-git')
      *    .outputHandler(function (command, stdout, stderr) {
      *       stdout.pipe(process.stdout);
      *    })
      *    .checkout('https://github.com/user/repo.git');
      *
      * @see http://nodejs.org/api/child_process.html#child_process_class_childprocess
      * @see http://nodejs.org/api/stream.html#stream_class_stream_readable
      * @param {Function} outputHandler
      * @returns {Git}
      */
     Git.prototype.outputHandler = function (outputHandler) {
        this._outputHandler = outputHandler;
        return this;
     };
  
     /**
      * Initialize a git repo
      *
      * @param {Boolean} [bare=false]
      * @param {Function} [then]
      */
     Git.prototype.init = function (bare, then) {
        var commands = ['init'];
        var next = Git.trailingFunctionArgument(arguments);
  
        if (bare === true) {
           commands.push('--bare');
        }
  
        return this._run(commands, function (err) {
           next && next(err);
        });
     };
  
     /**
      * Check the status of the local repo
      *
      * @param {Function} [then]
      */
     Git.prototype.status = function (then) {
        return this._run(
           ['status', '--porcelain', '-b', '-u'],
           Git._responseHandler(then, 'StatusSummary')
        );
     };
  
     /**
      * List the stash(s) of the local repo
      *
      * @param {Object|Array} [options]
      * @param {Function} [then]
      */
     Git.prototype.stashList = function (options, then) {
        var handler = Git.trailingFunctionArgument(arguments);
        var opt = (handler === then ? options : null) || {};
  
        var splitter = opt.splitter || ';';
        var command = ["stash", "list", "--pretty=format:%H %ai %s%d %aN %ae".replace(/\s+/g, splitter)];
        if (Array.isArray(opt)) {
           command = command.concat(opt);
        }
  
        return this._run(command,
           Git._responseHandler(handler, 'ListLogSummary', splitter)
        );
     };
  
     /**
      * Stash the local repo
      *
      * @param {Object|Array} [options]
      * @param {Function} [then]
      */
     Git.prototype.stash = function (options, then) {
        var handler = Git.trailingFunctionArgument(arguments);
        var command = ["stash"];
  
        if (Array.isArray(options)) {
           command = command.concat(options);
        }
        else {
           Git._appendOptions(command, Git.trailingOptionsArgument(arguments));
        }
  
        return this._run(command, function (err, data) {
           handler && handler(err, !err && data);
        });
     };
  
     /**
      * Clone a git repo
      *
      * @param {string} repoPath
      * @param {string} localPath
      * @param {String[]} [options] Optional array of options to pass through to the clone command
      * @param {Function} [then]
      */
     Git.prototype.clone = function (repoPath, localPath, options, then) {
        var next = Git.trailingFunctionArgument(arguments);
        var command = ['clone'].concat(Git.trailingArrayArgument(arguments));
  
        for (var i = 0, iMax = arguments.length; i < iMax; i++) {
           if (typeof arguments[i] === 'string') {
              command.push(arguments[i]);
           }
        }
  
        return this._run(command, function (err, data) {
           next && next(err, data);
        });
     };
  
     /**
      * Mirror a git repo
      *
      * @param {string} repoPath
      * @param {string} localPath
      * @param {Function} [then]
      */
     Git.prototype.mirror = function (repoPath, localPath, then) {
        return this.clone(repoPath, localPath, ['--mirror'], then);
     };
  
     /**
      * Moves one or more files to a new destination.
      *
      * @see https://git-scm.com/docs/git-mv
      *
      * @param {string|string[]} from
      * @param {string} to
      * @param {Function} [then]
      */
     Git.prototype.mv = function (from, to, then) {
        var handler = Git.trailingFunctionArgument(arguments);
  
        var command = [].concat(from);
        command.unshift('mv', '-v');
        command.push(to);
  
        this._run(command, Git._responseHandler(handler, 'MoveSummary'))
     };
  
     /**
      * Internally uses pull and tags to get the list of tags then checks out the latest tag.
      *
      * @param {Function} [then]
      */
     Git.prototype.checkoutLatestTag = function (then) {
        var git = this;
        return this.pull().tags(function (err, tags) {
           git.checkout(tags.latest, then);
        });
     };
  
     /**
      * Adds one or more files to source control
      *
      * @param {string|string[]} files
      * @param {Function} [then]
      */
     Git.prototype.add = function (files, then) {
        return this._run(['add'].concat(files), function (err, data) {
           then && then(err);
        });
     };
  
     /**
      * Commits changes in the current working directory - when specific file paths are supplied, only changes on those
      * files will be committed.
      *
      * @param {string|string[]} message
      * @param {string|string[]} [files]
      * @param {Object} [options]
      * @param {Function} [then]
      */
     Git.prototype.commit = function (message, files, options, then) {
        var handler = Git.trailingFunctionArgument(arguments);
  
        var command = ['commit'];
  
        [].concat(message).forEach(function (message) {
           command.push('-m', message);
        });
  
        [].push.apply(command, [].concat(typeof files === "string" || Array.isArray(files) ? files : []));
  
        Git._appendOptions(command, Git.trailingOptionsArgument(arguments));
  
        return this._run(
           command,
           Git._responseHandler(handler, 'CommitSummary')
        );
     };
  
     /**
      * Gets a function to be used for logging.
      *
      * @param {string} level
      * @param {string} [message]
      *
      * @returns {Function}
      * @private
      */
     Git.prototype._getLog = function (level, message) {
        var log = this._silentLogging ? function () {
           } : console[level].bind(console);
        if (arguments.length > 1) {
           log(message);
        }
        return log;
     };
  
     /**
      * Pull the updated contents of the current repo
      *
      * @param {string} [remote] When supplied must also include the branch
      * @param {string} [branch] When supplied must also include the remote
      * @param {Object} [options] Optionally include set of options to merge into the command
      * @param {Function} [then]
      */
     Git.prototype.pull = function (remote, branch, options, then) {
        var command = ["pull"];
        var handler = Git.trailingFunctionArgument(arguments);
  
        if (typeof remote === 'string' && typeof branch === 'string') {
           command.push(remote, branch);
        }
  
        Git._appendOptions(command, Git.trailingOptionsArgument(arguments));
  
        return this._run(command, Git._responseHandler(handler, 'PullSummary'));
     };
  
     /**
      * Fetch the updated contents of the current repo.
      *
      * @example
      *   .fetch('upstream', 'master') // fetches from master on remote named upstream
      *   .fetch(function () {}) // runs fetch against default remote and branch and calls function
      *
      * @param {string} [remote]
      * @param {string} [branch]
      * @param {Function} [then]
      */
     Git.prototype.fetch = function (remote, branch, then) {
        var command = ["fetch"];
        var next = Git.trailingFunctionArgument(arguments);
        Git._appendOptions(command, Git.trailingOptionsArgument(arguments));
  
        if (typeof remote === 'string' && typeof branch === 'string') {
           command.push(remote, branch);
        }
  
        if (Array.isArray(remote)) {
           command = command.concat(remote);
        }
  
        return this._run(
           command,
           Git._responseHandler(next, 'FetchSummary'),
           {
              concatStdErr: true
           }
        );
     };
  
     /**
      * Disables/enables the use of the console for printing warnings and errors, by default messages are not shown in
      * a production environment.
      *
      * @param {boolean} silence
      * @returns {Git}
      */
     Git.prototype.silent = function (silence) {
        this._silentLogging = !!silence;
        return this;
     };
  
     /**
      * List all tags. When using git 2.7.0 or above, include an options object with `"--sort": "property-name"` to
      * sort the tags by that property instead of using the default semantic versioning sort.
      *
      * Note, supplying this option when it is not supported by your Git version will cause the operation to fail.
      *
      * @param {Object} [options]
      * @param {Function} [then]
      */
     Git.prototype.tags = function (options, then) {
        var next = Git.trailingFunctionArgument(arguments);
  
        var command = ['-l'];
        Git._appendOptions(command, Git.trailingOptionsArgument(arguments));
  
        var hasCustomSort = command.some(function (option) {
           return /^--sort=/.test(option);
        });
  
        return this.tag(
           command,
           Git._responseHandler(next, 'TagList', [hasCustomSort])
        );
     };
  
     /**
      * Rebases the current working copy. Options can be supplied either as an array of string parameters
      * to be sent to the `git rebase` command, or a standard options object.
      *
      * @param {Object|String[]} [options]
      * @param {Function} [then]
      * @returns {Git}
      */
     Git.prototype.rebase = function (options, then) {
        var handler = Git.trailingFunctionArgument(arguments);
        var command = ['rebase'];
        Git._appendOptions(command, Git.trailingOptionsArgument(arguments));
  
        if (Array.isArray(options)) {
           command.push.apply(command, options);
        }
  
        return this._run(command, function (err, data) {
           handler && handler(err, !err && data);
        })
     };
  
     /**
      * Reset a repo
      *
      * @param {string|string[]} [mode=soft] Either an array of arguments supported by the 'git reset' command, or the
      *                                        string value 'soft' or 'hard' to set the reset mode.
      * @param {Function} [then]
      */
     Git.prototype.reset = function (mode, then) {
        var command = ['reset'];
        var next = Git.trailingFunctionArgument(arguments);
        if (next === mode || typeof mode === 'string' || !mode) {
          var modeStr = ['mixed', 'soft', 'hard'].includes(mode) ? mode : 'soft';
          command.push('--' + modeStr);
        }
        else if (Array.isArray(mode)) {
           command.push.apply(command, mode);
        }
  
        return this._run(command, function (err) {
           next && next(err || null);
        });
     };
  
     /**
      * Revert one or more commits in the local working copy
      *
      * @param {string} commit The commit to revert. Can be any hash, offset (eg: `HEAD~2`) or range (eg: `master~5..master~2`)
      * @param {Object} [options] Optional options object
      * @param {Function} [then]
      */
     Git.prototype.revert = function (commit, options, then) {
        var next = Git.trailingFunctionArgument(arguments);
        var command = ['revert'];
  
        Git._appendOptions(command, Git.trailingOptionsArgument(arguments));
  
        if (typeof commit !== 'string') {
           return this.exec(function () {
              next && next(new TypeError("Commit must be a string"));
           });
        }
  
        command.push(commit);
        return this._run(command, function (err) {
           next && next(err || null);
        });
     };
  
     /**
      * Add a lightweight tag to the head of the current branch
      *
      * @param {string} name
      * @param {Function} [then]
      */
     Git.prototype.addTag = function (name, then) {
        if (typeof name !== "string") {
           return this.exec(function () {
              then && then(new TypeError("Git.addTag requires a tag name"));
           });
        }
  
        return this.tag([name], then);
     };
  
     /**
      * Add an annotated tag to the head of the current branch
      *
      * @param {string} tagName
      * @param {string} tagMessage
      * @param {Function} [then]
      */
     Git.prototype.addAnnotatedTag = function (tagName, tagMessage, then) {
        return this.tag(['-a', '-m', tagMessage, tagName], function (err) {
           then && then(err);
        });
     };
  
     /**
      * Check out a tag or revision, any number of additional arguments can be passed to the `git checkout` command
      * by supplying either a string or array of strings as the `what` parameter.
      *
      * @param {string|string[]} what One or more commands to pass to `git checkout`
      * @param {Function} [then]
      */
     Git.prototype.checkout = function (what, then) {
        var command = ['checkout'];
        command = command.concat(what);
  
        return this._run(command, function (err, data) {
           then && then(err, !err && this._parseCheckout(data));
        });
     };
  
     /**
      * Check out a remote branch
      *
      * @param {string} branchName name of branch
      * @param {string} startPoint (e.g origin/development)
      * @param {Function} [then]
      */
     Git.prototype.checkoutBranch = function (branchName, startPoint, then) {
        return this.checkout(['-b', branchName, startPoint], then);
     };
  
     /**
      * Check out a local branch
      *
      * @param {string} branchName of branch
      * @param {Function} [then]
      */
     Git.prototype.checkoutLocalBranch = function (branchName, then) {
        return this.checkout(['-b', branchName], then);
     };
  
     /**
      * Delete a local branch
      *
      * @param {string} branchName name of branch
      * @param {Function} [then]
      */
     Git.prototype.deleteLocalBranch = function (branchName, then) {
        return this.branch(['-d', branchName], then);
     };
  
     /**
      * List all branches
      *
      * @param {Object} [options]
      * @param {Function} [then]
      */
     Git.prototype.branch = function (options, then) {
        var isDelete, responseHandler;
        var next = Git.trailingFunctionArgument(arguments);
        var command = ['branch'];
        if (Array.isArray(options)) {
           command.push.apply(command, options);
        }
  
        Git._appendOptions(command, Git.trailingOptionsArgument(arguments));
        if (!arguments.length || next === options) {
           command.push('-a', '-v');
        }
  
        isDelete = ['-d', '-D', '--delete'].reduce(function (isDelete, flag) {
           return isDelete || command.indexOf(flag) > 0;
        }, false);
  
        responseHandler = isDelete
           ? Git._responseHandler(next, 'BranchDeleteSummary', false)
           : Git._responseHandler(next, 'BranchSummary');
  
        return this._run(command, responseHandler);
     };
  
     /**
      * Return list of local branches
      *
      * @param {Function} [then]
      */
     Git.prototype.branchLocal = function (then) {
        return this.branch(['-v'], then);
     };
  
     /**
      * Add config to local git instance
      *
      * @param {string} key configuration key (e.g user.name)
      * @param {string} value for the given key (e.g your name)
      * @param {Function} [then]
      */
     Git.prototype.addConfig = function (key, value, then) {
        return this._run(['config', '--local', key, value], function (err, data) {
           then && then(err, !err && data);
        });
     };
  
     /**
      * Executes any command against the git binary.
      *
      * @param {string[]|Object} commands
      * @param {Function} [then]
      *
      * @returns {Git}
      */
     Git.prototype.raw = function (commands, then) {
        var command = [];
        if (Array.isArray(commands)) {
           command = commands.slice(0);
        }
        else {
           Git._appendOptions(command, Git.trailingOptionsArgument(arguments));
        }
  
        var next = Git.trailingFunctionArgument(arguments);
  
        if (!command.length) {
           return this.then(function () {
              next && next(new Error('Raw: must supply one or more command to execute'), null);
           });
        }
  
        return this._run(command, function (err, data) {
           next && next(err, !err && data || null);
        });
     };
  
     /**
      * Add a submodule
      *
      * @param {string} repo
      * @param {string} path
      * @param {Function} [then]
      */
     Git.prototype.submoduleAdd = function (repo, path, then) {
        return this._run(['submodule', 'add', repo, path], function (err) {
           then && then(err);
        });
     };
  
     /**
      * Update submodules
      *
      * @param {string[]} [args]
      * @param {Function} [then]
      */
     Git.prototype.submoduleUpdate = function (args, then) {
        if (typeof args === 'string') {
           this._getLog('warn', 'Git#submoduleUpdate: args should be supplied as an array of individual arguments');
        }
  
        var next = Git.trailingFunctionArgument(arguments);
        var command = (args !== next) ? args : [];
  
        return this.subModule(['update'].concat(command), function (err, args) {
           next && next(err, args);
        });
     };
  
     /**
      * Initialize submodules
      *
      * @param {string[]} [args]
      * @param {Function} [then]
      */
     Git.prototype.submoduleInit = function (args, then) {
        if (typeof args === 'string') {
           this._getLog('warn', 'Git#submoduleInit: args should be supplied as an array of individual arguments');
        }
  
        var next = Git.trailingFunctionArgument(arguments);
        var command = (args !== next) ? args : [];
  
        return this.subModule(['init'].concat(command), function (err, args) {
           next && next(err, args);
        });
     };
  
     /**
      * Call any `git submodule` function with arguments passed as an array of strings.
      *
      * @param {string[]} options
      * @param {Function} [then]
      */
     Git.prototype.subModule = function (options, then) {
        if (!Array.isArray(options)) {
           return this.exec(function () {
              then && then(new TypeError("Git.subModule requires an array of arguments"));
           });
        }
  
        if (options[0] !== 'submodule') {
           options.unshift('submodule');
        }
  
        return this._run(options, function (err, data) {
           then && then(err || null, err ? null : data);
        });
     };
  
     /**
      * List remote
      *
      * @param {string[]} [args]
      * @param {Function} [then]
      */
     Git.prototype.listRemote = function (args, then) {
        var next = Git.trailingFunctionArgument(arguments);
        var data = next === args || args === undefined ? [] : args;
  
        if (typeof data === 'string') {
           this._getLog('warn', 'Git#listRemote: args should be supplied as an array of individual arguments');
        }
  
        return this._run(['ls-remote'].concat(data), function (err, data) {
           next && next(err, data);
        });
     };
  
     /**
      * Adds a remote to the list of remotes.
      *
      * @param {string} remoteName Name of the repository - eg "upstream"
      * @param {string} remoteRepo Fully qualified SSH or HTTP(S) path to the remote repo
      * @param {Function} [then]
      * @returns {*}
      */
     Git.prototype.addRemote = function (remoteName, remoteRepo, then) {
        return this._run(['remote', 'add', remoteName, remoteRepo], function (err) {
           then && then(err);
        });
     };
  
     /**
      * Removes an entry from the list of remotes.
      *
      * @param {string} remoteName Name of the repository - eg "upstream"
      * @param {Function} [then]
      * @returns {*}
      */
     Git.prototype.removeRemote = function (remoteName, then) {
        return this._run(['remote', 'remove', remoteName], function (err) {
           then && then(err);
        });
     };
  
     /**
      * Gets the currently available remotes, setting the optional verbose argument to true includes additional
      * detail on the remotes themselves.
      *
      * @param {boolean} [verbose=false]
      * @param {Function} [then]
      */
     Git.prototype.getRemotes = function (verbose, then) {
        var next = Git.trailingFunctionArgument(arguments);
        var args = verbose === true ? ['-v'] : [];
  
        return this.remote(args, function (err, data) {
           next && next(err, !err && function () {
                 return data.trim().split('\n').reduce(function (remotes, remote) {
                    var detail = remote.trim().split(/\s+/);
                    var name = detail.shift();
  
                    if (!remotes[name]) {
                       remotes[name] = remotes[remotes.length] = {
                          name: name,
                          refs: {}
                       };
                    }
  
                    if (detail.length) {
                       remotes[name].refs[detail.pop().replace(/[^a-z]/g, '')] = detail.pop();
                    }
  
                    return remotes;
                 }, []).slice(0);
              }());
        });
     };
  
     /**
      * Call any `git remote` function with arguments passed as an array of strings.
      *
      * @param {string[]} options
      * @param {Function} [then]
      */
     Git.prototype.remote = function (options, then) {
        if (!Array.isArray(options)) {
           return this.exec(function () {
              then && then(new TypeError("Git.remote requires an array of arguments"));
           });
        }
  
        if (options[0] !== 'remote') {
           options.unshift('remote');
        }
  
        return this._run(options, function (err, data) {
           then && then(err || null, err ? null : data);
        });
     };
  
     /**
      * Merges from one branch to another, equivalent to running `git merge ${from} $[to}`, the `options` argument can
      * either be an array of additional parameters to pass to the command or null / omitted to be ignored.
      *
      * @param {string} from
      * @param {string} to
      * @param {Object} [options]
      * @param {Function} [then]
      */
     Git.prototype.mergeFromTo = function (from, to, options, then) {
        var commands = [
           from,
           to
        ];
        var callback = Git.trailingFunctionArgument(arguments);
  
        if (Array.isArray(options)) {
           commands = commands.concat(options);
        }
  
        return this.merge(commands, callback);
     };
  
     Git.prototype.merge = function (options, then) {
        var self = this;
        var userHandler = Git.trailingFunctionArgument(arguments) || NOOP;
        var mergeHandler = function (err, mergeSummary) {
           if (mergeSummary.failed) {
              Git.fail(self, mergeSummary, userHandler);
           }
           else {
              userHandler(null, mergeSummary);
           }
        };
  
        var command = [];
        Git._appendOptions(command, Git.trailingOptionsArgument(arguments));
        command.push.apply(command, Git.trailingArrayArgument(arguments));
  
        if (command[0] !== 'merge') {
           command.unshift('merge');
        }
  
        if (command.length === 1) {
           return this.exec(function () {
              then && then(new TypeError("Git.merge requires at least one option"));
           });
        }
  
        return this._run(command, Git._responseHandler(mergeHandler, 'MergeSummary'), {
           concatStdErr: true
        });
     };
  
     /**
      * Call any `git tag` function with arguments passed as an array of strings.
      *
      * @param {string[]} options
      * @param {Function} [then]
      */
     Git.prototype.tag = function (options, then) {
        if (!Array.isArray(options)) {
           return this.exec(function () {
              then && then(new TypeError("Git.tag requires an array of arguments"));
           });
        }
  
        if (options[0] !== 'tag') {
           options.unshift('tag');
        }
  
        return this._run(options, function (err, data) {
           then && then(err || null, err ? null : data);
        });
     };
  
     /**
      * Updates repository server info
      *
      * @param {Function} [then]
      */
     Git.prototype.updateServerInfo = function (then) {
        return this._run(["update-server-info"], function (err, data) {
           then && then(err, !err && data);
        });
     };
  
     /**
      * Pushes the current committed changes to a remote, optionally specify the names of the remote and branch to use
      * when pushing. Supply multiple options as an array of strings in the first argument - see examples below.
      *
      * @param {string|string[]} [remote]
      * @param {string} [branch]
      * @param {Function} [then]
      */
     Git.prototype.push = function (remote, branch, then) {
        var command = [];
        var handler = Git.trailingFunctionArgument(arguments);
  
        if (typeof remote === 'string' && typeof branch === 'string') {
           command.push(remote, branch);
        }
  
        if (Array.isArray(remote)) {
           command = command.concat(remote);
        }
  
        Git._appendOptions(command, Git.trailingOptionsArgument(arguments));
  
        if (command[0] !== 'push') {
           command.unshift('push');
        }
  
        return this._run(command, function (err, data) {
           handler && handler(err, !err && data);
        });
     };
  
     /**
      * Pushes the current tag changes to a remote which can be either a URL or named remote. When not specified uses the
      * default configured remote spec.
      *
      * @param {string} [remote]
      * @param {Function} [then]
      */
     Git.prototype.pushTags = function (remote, then) {
        var command = ['push'];
        if (typeof remote === "string") {
           command.push(remote);
        }
        command.push('--tags');
  
        then = typeof arguments[arguments.length - 1] === "function" ? arguments[arguments.length - 1] : null;
  
        return this._run(command, function (err, data) {
           then && then(err, !err && data);
        });
     };
  
     /**
      * Removes the named files from source control.
      *
      * @param {string|string[]} files
      * @param {Function} [then]
      */
     Git.prototype.rm = function (files, then) {
        return this._rm(files, '-f', then);
     };
  
     /**
      * Removes the named files from source control but keeps them on disk rather than deleting them entirely. To
      * completely remove the files, use `rm`.
      *
      * @param {string|string[]} files
      * @param {Function} [then]
      */
     Git.prototype.rmKeepLocal = function (files, then) {
        return this._rm(files, '--cached', then);
     };
  
     /**
      * Returns a list of objects in a tree based on commit hash. Passing in an object hash returns the object's content,
      * size, and type.
      *
      * Passing "-p" will instruct cat-file to determine the object type, and display its formatted contents.
      *
      * @param {string[]} [options]
      * @param {Function} [then]
      */
     Git.prototype.catFile = function (options, then) {
        return this._catFile('utf-8', arguments);
     };
  
     /**
      * Equivalent to `catFile` but will return the native `Buffer` of content from the git command's stdout.
      *
      * @param {string[]} options
      * @param then
      */
     Git.prototype.binaryCatFile = function (options, then) {
        return this._catFile('buffer', arguments);
     };
  
     Git.prototype._catFile = function (format, args) {
        var handler = Git.trailingFunctionArgument(args);
        var command = ['cat-file'];
        var options = args[0];
  
        if (typeof options === 'string') {
           throw new TypeError('Git#catFile: options must be supplied as an array of strings');
        }
        else if (Array.isArray(options)) {
           command.push.apply(command, options);
        }
  
        return this._run(command, function (err, data) {
           handler && handler(err, data);
        }, {
           format: format
        });
     };
  
     /**
      * Return repository changes.
      *
      * @param {string} [options]
      * @param {Function} [then]
      */
     Git.prototype.diff = function (options, then) {
        var command = ['diff'];
  
        if (typeof options === 'string') {
           command[0] += ' ' + options;
           this._getLog('warn',
              'Git#diff: supplying options as a single string is now deprecated, switch to an array of strings');
        }
        else if (Array.isArray(options)) {
           command.push.apply(command, options);
        }
  
        if (typeof arguments[arguments.length - 1] === 'function') {
           then = arguments[arguments.length - 1];
        }
  
        return this._run(command, function (err, data) {
           then && then(err, data);
        });
     };
  
     Git.prototype.diffSummary = function (options, then) {
        var next = Git.trailingFunctionArgument(arguments);
        var command = ['--stat=4096'];
  
        if (options && options !== next) {
           command.push.apply(command, [].concat(options));
        }
  
        return this.diff(command, Git._responseHandler(next, 'DiffSummary'));
     };
  
     /**
      * rev-parse.
      *
      * @param {string|string[]} [options]
      * @param {Function} [then]
      */
     Git.prototype.revparse = function (options, then) {
        var command = ['rev-parse'];
  
        if (typeof options === 'string') {
           command = command + ' ' + options;
           this._getLog('warn',
              'Git#revparse: supplying options as a single string is now deprecated, switch to an array of strings');
        }
        else if (Array.isArray(options)) {
           command.push.apply(command, options);
        }
  
        if (typeof arguments[arguments.length - 1] === 'function') {
           then = arguments[arguments.length - 1];
        }
  
        return this._run(command, function (err, data) {
           then && then(err, data);
        });
     };
  
     /**
      * Show various types of objects, for example the file at a certain commit
      *
      * @param {string} [options]
      * @param {Function} [then]
      */
     Git.prototype.show = function (options, then) {
        var args = [].slice.call(arguments, 0);
        var handler = typeof args[args.length - 1] === "function" ? args.pop() : null;
        var command = ['show'];
        if (typeof options === 'string') {
           command = command + ' ' + options;
           this._getLog('warn',
              'Git#show: supplying options as a single string is now deprecated, switch to an array of strings');
        }
        else if (Array.isArray(options)) {
           command.push.apply(command, options);
        }
  
        return this._run(command, function (err, data) {
           handler && handler(err, !err && data);
        });
     };
  
     /**
      * @param {string} mode Required parameter "n" or "f"
      * @param {Array} options
      * @param {Function} [then]
      */
     Git.prototype.clean = function (mode, options, then) {
        var handler = Git.trailingFunctionArgument(arguments);
  
        if (!/^[nf]$/.test(mode)) {
           return this.exec(function () {
              handler && handler(new TypeError('Git clean mode parameter ("n" or "f") is required'));
           });
        }
  
        var command = ['clean', '-' + mode];
        if (Array.isArray(options)) {
           command = command.concat(options);
        }
  
        if (command.indexOf('-i') > 0) {
           return this.exec(function () {
              handler && handler(new TypeError('Git clean interactive mode is not supported'));
           });
        }
  
        return this._run(command, function (err, data) {
           handler && handler(err, !err && data);
        });
     };
  
     /**
      * Call a simple function at the next step in the chain.
      * @param {Function} [then]
      */
     Git.prototype.exec = function (then) {
        this._run([], function () {
           typeof then === 'function' && then();
        });
        return this;
     };
  
     /**
      * Deprecated means of adding a regular function call at the next step in the chain. Use the replacement
      * Git#exec, the Git#then method will be removed in version 2.x
      *
      * @see exec
      * @deprecated
      */
     Git.prototype.then = function (then) {
        console.warn("\n\
  Git#then is deprecated after version 1.72 and will be removed in version 2.x\n\
  Please switch to using Git#exec to run arbitrary functions as part of the command chain.\n\
  ");
        return this.exec(then);
     };
  
     /**
      * Show commit logs.
      *
      * @param {Object|string[]} [options]
      * @param {string} [options.from] The first commit to include
      * @param {string} [options.to] The most recent commit to include
      * @param {string} [options.file] A single file to include in the result
      *
      * @param {Function} [then]
      */
     Git.prototype.log = function (options, then) {
        var handler = Git.trailingFunctionArgument(arguments);
        var opt = (handler === then ? options : null) || {};
  
        var splitter = opt.splitter || ';';
        var format = opt.format || {
              hash: '%H',
              date: '%ai',
              message: '%s%d',
              author_name: '%aN',
              author_email: '%ae'
           };
  
        var fields = Object.keys(format);
        var formatstr = fields.map(function (k) {
           return format[k];
        }).join(splitter);
        var command = ["log", "--pretty=format:" + formatstr + require('./responses/ListLogSummary').COMMIT_BOUNDARY];
  
        if (Array.isArray(opt)) {
           command = command.concat(opt);
           opt = {};
        }
        else if (typeof arguments[0] === "string" || typeof arguments[1] === "string") {
           this._getLog('warn',
              'Git#log: supplying to or from as strings is now deprecated, switch to an options configuration object');
           opt = {
              from: arguments[0],
              to: arguments[1]
           };
        }
  
        if (opt.n || opt['max-count']) {
           command.push("--max-count=" + (opt.n || opt['max-count']));
        }
  
        if (opt.from && opt.to) {
           command.push(opt.from + "..." + opt.to);
        }
  
        if (opt.file) {
           command.push("--follow", options.file);
        }
  
        'splitter n max-count file from to --pretty format'.split(' ').forEach(function (key) {
           delete opt[key];
        });
  
        Git._appendOptions(command, opt);
  
        return this._run(command, Git._responseHandler(handler, 'ListLogSummary', [splitter, fields]));
     };
  
     /**
      * Clears the queue of pending commands and returns the wrapper instance for chaining.
      *
      * @returns {Git}
      */
     Git.prototype.clearQueue = function () {
        this._runCache.length = 0;
        return this;
     };
  
     /**
      * Check if a pathname or pathnames are excluded by .gitignore
      *
      * @param {string|string[]} pathnames
      * @param {Function} [then]
      */
     Git.prototype.checkIgnore = function (pathnames, then) {
        var handler = Git.trailingFunctionArgument(arguments);
        var command = ["check-ignore"];
  
        if (handler !== pathnames) {
           command = command.concat(pathnames);
        }
  
        return this._run(command, function (err, data) {
           handler && handler(err, !err && this._parseCheckIgnore(data));
        });
     };
  
     /**
      * Validates that the current repo is a Git repo.
      *
      * @param {Function} [then]
      */
     Git.prototype.checkIsRepo = function (then) {
        function onError (exitCode, stdErr, done, fail) {
           if (exitCode === 128 && /Not a git repository/.test(stdErr)) {
              return done(false);
           }
  
           fail(stdErr);
        }
  
        function handler (err, isRepo) {
           then && then(err, String(isRepo).trim() === 'true');
        }
  
        return this._run(['rev-parse', '--is-inside-work-tree'], handler, {onError: onError});
     };
  
     Git.prototype._rm = function (_files, options, then) {
        var files = [].concat(_files);
        var args = ['rm', options];
        args.push.apply(args, files);
  
        return this._run(args, function (err) {
           then && then(err);
        });
     };
  
     Git.prototype._parseCheckout = function (checkout) {
        // TODO
     };
  
     /**
      * Parser for the `check-ignore` command - returns each
      * @param {string} [files]
      * @returns {string[]}
      */
     Git.prototype._parseCheckIgnore = function (files) {
        return files.split(/\n/g).filter(Boolean).map(function (file) {
           return file.trim()
        });
     };
  
     /**
      * Schedules the supplied command to be run, the command should not include the name of the git binary and should
      * be an array of strings passed as the arguments to the git binary.
      *
      * @param {string[]} command
      * @param {Function} then
      * @param {Object} [opt]
      * @param {boolean} [opt.concatStdErr=false] Optionally concatenate stderr output into the stdout
      * @param {boolean} [opt.format="utf-8"] The format to use when reading the content of stdout
      * @param {Function} [opt.onError] Optional error handler for this command - can be used to allow non-clean exits
      *                                  without killing the remaining stack of commands
      * @param {number} [opt.onError.exitCode]
      * @param {string} [opt.onError.stdErr]
      *
      * @returns {Git}
      */
     Git.prototype._run = function (command, then, opt) {
        if (typeof command === "string") {
           command = command.split(" ");
        }
        this._runCache.push([command, then, opt || {}]);
        this._schedule();
  
        return this;
     };
  
     Git.prototype._schedule = function () {
        if (!this._childProcess && this._runCache.length) {
           var git = this;
           var Buffer = git.Buffer;
           var task = git._runCache.shift();
  
           var command = task[0];
           var then = task[1];
           var options = task[2];
  
           debug(command);
  
           var stdOut = [];
           var stdErr = [];
           var spawned = git.ChildProcess.spawn(git._command, command.slice(0), {
              cwd: git._baseDir,
              env: git._env
           });
  
           spawned.stdout.on('data', function (buffer) {
              stdOut.push(buffer);
           });
           spawned.stderr.on('data', function (buffer) {
              stdErr.push(buffer);
           });
  
           spawned.on('error', function (err) {
              stdErr.push(new Buffer(err.stack, 'ascii'));
           });
  
           spawned.on('close', function (exitCode, exitSignal) {
              function done (output) {
                 then.call(git, null, output);
              }
  
              function fail (error) {
                 Git.fail(git, error, then);
              }
  
              delete git._childProcess;
  
              if (exitCode && stdErr.length && options.onError) {
                 options.onError(exitCode, Buffer.concat(stdErr).toString('utf-8'), done, fail);
              }
              else if (exitCode && stdErr.length) {
                 fail(Buffer.concat(stdErr).toString('utf-8'));
              }
              else {
                 if (options.concatStdErr) {
                    [].push.apply(stdOut, stdErr);
                 }
  
                 var stdOutput = Buffer.concat(stdOut);
                 if (options.format !== 'buffer') {
                    stdOutput = stdOutput.toString(options.format || 'utf-8');
                 }
  
                 done(stdOutput);
              }
  
              process.nextTick(git._schedule.bind(git));
           });
  
           git._childProcess = spawned;
  
           if (git._outputHandler) {
              git._outputHandler(command[0], git._childProcess.stdout, git._childProcess.stderr);
           }
        }
     };
  
     /**
      * Handles an exception in the processing of a command.
      */
     Git.fail = function (git, error, handler) {
        git._getLog('error', error);
        git._runCache.length = 0;
        if (typeof handler === 'function') {
           handler.call(git, error, null);
        }
     };
  
     /**
      * Given any number of arguments, returns the last argument if it is a function, otherwise returns null.
      * @returns {Function|null}
      */
     Git.trailingFunctionArgument = function (args) {
        var trailing = args[args.length - 1];
        return (typeof trailing === "function") ? trailing : null;
     };
  
     /**
      * Given any number of arguments, returns the trailing options argument, ignoring a trailing function argument
      * if there is one. When not found, the return value is null.
      * @returns {Object|null}
      */
     Git.trailingOptionsArgument = function (args) {
        var options = args[(args.length - (Git.trailingFunctionArgument(args) ? 2 : 1))];
        return Object.prototype.toString.call(options) === '[object Object]' ? options : null;
     };
  
     /**
      * Given any number of arguments, returns the trailing options array argument, ignoring a trailing function argument
      * if there is one. When not found, the return value is an empty array.
      * @returns {Array}
      */
     Git.trailingArrayArgument = function (args) {
        var options = args[(args.length - (Git.trailingFunctionArgument(args) ? 2 : 1))];
        return Object.prototype.toString.call(options) === '[object Array]' ? options : [];
     };
  
     /**
      * Mutates the supplied command array by merging in properties in the options object. When the
      * value of the item in the options object is a string it will be concatenated to the key as
      * a single `name=value` item, otherwise just the name will be used.
      *
      * @param {string[]} command
      * @param {Object} options
      * @private
      */
     Git._appendOptions = function (command, options) {
        if (options === null) {
           return;
        }
  
        Object.keys(options).forEach(function (key) {
           var value = options[key];
           if (typeof value === 'string') {
              command.push(key + '=' + value);
           }
           else {
              command.push(key);
           }
        });
     };
  
     /**
      * Given the type of response and the callback to receive the parsed response,
      * uses the correct parser and calls back the callback.
      *
      * @param {Function} callback
      * @param {string} type
      * @param {Object[]} [args]
      *
      * @private
      */
     Git._responseHandler = function (callback, type, args) {
        return function (error, data) {
           if (typeof callback !== 'function') {
              return;
           }
  
           if (error) {
              callback(error, null);
              return;
           }
  
           var handler = require('./responses/' + type);
           var result = handler.parse.apply(handler, [data].concat(args === undefined ? [] : args));
  
           callback(null, result);
        };
  
     };
  
     /**
      * Marks the git instance as having had a fatal exception by clearing the pending queue of tasks and
      * logging to the console.
      *
      * @param git
      * @param error
      * @param callback
      */
     Git.exception = function (git, error, callback) {
        git._runCache.length = 0;
        if (typeof callback === 'function') {
           callback(error instanceof Error ? error : new Error(error));
        }
  
        git._getLog('error', error);
     };
  
     module.exports = Git;
  
  }());
  