text: const {GitExecutor} = require('./lib/runners/git-executor');
  const {SimpleGitApi} = require('./lib/simple-git-api');
  
  const {Scheduler} = require('./lib/runners/scheduler');
  const {GitLogger} = require('./lib/git-logger');
  const {configurationErrorTask} = require('./lib/tasks/task');
  const {
     asArray,
     filterArray,
     filterPrimitives,
     filterString,
     filterStringOrStringArray,
     filterType,
     getTrailingOptions,
     trailingFunctionArgument,
     trailingOptionsArgument
  } = require('./lib/utils');
  const {applyPatchTask} = require('./lib/tasks/apply-patch')
  const {branchTask, branchLocalTask, deleteBranchesTask, deleteBranchTask} = require('./lib/tasks/branch');
  const {checkIgnoreTask} = require('./lib/tasks/check-ignore');
  const {checkIsRepoTask} = require('./lib/tasks/check-is-repo');
  const {cloneTask, cloneMirrorTask} = require('./lib/tasks/clone');
  const {cleanWithOptionsTask, isCleanOptionsArray} = require('./lib/tasks/clean');
  const {commitTask} = require('./lib/tasks/commit');
  const {diffSummaryTask} = require('./lib/tasks/diff');
  const {fetchTask} = require('./lib/tasks/fetch');
  const {logTask, parseLogOptions} = require('./lib/tasks/log');
  const {moveTask} = require("./lib/tasks/move");
  const {pullTask} = require('./lib/tasks/pull');
  const {pushTagsTask} = require('./lib/tasks/push');
  const {addRemoteTask, getRemotesTask, listRemotesTask, remoteTask, removeRemoteTask} = require('./lib/tasks/remote');
  const {getResetMode, resetTask} = require('./lib/tasks/reset');
  const {stashListTask} = require('./lib/tasks/stash-list');
  const {addSubModuleTask, initSubModuleTask, subModuleTask, updateSubModuleTask} = require('./lib/tasks/sub-module');
  const {addAnnotatedTagTask, addTagTask, tagListTask} = require('./lib/tasks/tag');
  const {straightThroughBufferTask, straightThroughStringTask} = require('./lib/tasks/task');
  
  function Git (options, plugins) {
     this._executor = new GitExecutor(
        options.binary, options.baseDir,
        new Scheduler(options.maxConcurrentProcesses), plugins,
     );
     this._logger = new GitLogger();
  }
  
  (Git.prototype = Object.create(SimpleGitApi.prototype)).constructor = Git;
  
  /**
   * Logging utility for printing out info or error messages to the user
   * @type {GitLogger}
   * @private
   */
  Git.prototype._logger = null;
  
  /**
   * 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._executor.binary = 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._executor.env = name;
     } else {
        (this._executor.env = this._executor.env || {})[name] = value;
     }
  
     return this;
  };
  
  /**
   * List the stash(s) of the local repo
   */
  Git.prototype.stashList = function (options) {
     return this._runTask(
        stashListTask(
           trailingOptionsArgument(arguments) || {},
           filterArray(options) && options || []
        ),
        trailingFunctionArgument(arguments),
     );
  };
  
  function createCloneTask (api, task, repoPath, localPath) {
     if (typeof repoPath !== 'string') {
        return configurationErrorTask(`git.${ api }() requires a string 'repoPath'`);
     }
  
     return task(repoPath, filterType(localPath, filterString), getTrailingOptions(arguments));
  }
  
  
  /**
   * Clone a git repo
   */
  Git.prototype.clone = function () {
     return this._runTask(
        createCloneTask('clone', cloneTask, ...arguments),
        trailingFunctionArgument(arguments),
     );
  };
  
  /**
   * Mirror a git repo
   */
  Git.prototype.mirror = function () {
     return this._runTask(
        createCloneTask('mirror', cloneMirrorTask, ...arguments),
        trailingFunctionArgument(arguments),
     );
  };
  
  /**
   * Moves one or more files to a new destination.
   *
   * @see https://git-scm.com/docs/git-mv
   *
   * @param {string|string[]} from
   * @param {string} to
   */
  Git.prototype.mv = function (from, to) {
     return this._runTask(moveTask(from, to), trailingFunctionArgument(arguments));
  };
  
  /**
   * 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(function () {
        git.tags(function (err, tags) {
           git.checkout(tags.latest, then);
        });
     });
  };
  
  /**
   * 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) {
     const next = trailingFunctionArgument(arguments);
     const messages = [];
  
     if (filterStringOrStringArray(message)) {
        messages.push(...asArray(message));
     } else {
        console.warn('simple-git deprecation notice: git.commit: requires the commit message to be supplied as a string/string[], this will be an error in version 3');
     }
  
     return this._runTask(
        commitTask(
           messages,
           asArray(filterType(files, filterStringOrStringArray, [])),
           [...filterType(options, filterArray, []), ...getTrailingOptions(arguments, 0, true)]
        ),
        next
     );
  };
  
  /**
   * Pull the updated contents of the current repo
   */
  Git.prototype.pull = function (remote, branch, options, then) {
     return this._runTask(
        pullTask(filterType(remote, filterString), filterType(branch, filterString), getTrailingOptions(arguments)),
        trailingFunctionArgument(arguments),
     );
  };
  
  /**
   * 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]
   */
  Git.prototype.fetch = function (remote, branch) {
     return this._runTask(
        fetchTask(filterType(remote, filterString), filterType(branch, filterString), getTrailingOptions(arguments)),
        trailingFunctionArgument(arguments),
     );
  };
  
  /**
   * 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) {
     console.warn('simple-git deprecation notice: git.silent: logging should be configured using the `debug` library / `DEBUG` environment variable, this will be an error in version 3');
     this._logger.silent(!!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) {
     return this._runTask(
        tagListTask(getTrailingOptions(arguments)),
        trailingFunctionArgument(arguments),
     );
  };
  
  /**
   * 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.
   */
  Git.prototype.rebase = function () {
     return this._runTask(
        straightThroughStringTask(['rebase', ...getTrailingOptions(arguments)]),
        trailingFunctionArgument(arguments)
     );
  };
  
  /**
   * Reset a repo
   */
  Git.prototype.reset = function (mode) {
     return this._runTask(
        resetTask(getResetMode(mode), getTrailingOptions(arguments)),
        trailingFunctionArgument(arguments),
     );
  };
  
  /**
   * Revert one or more commits in the local working copy
   */
  Git.prototype.revert = function (commit) {
     const next = trailingFunctionArgument(arguments);
  
     if (typeof commit !== 'string') {
        return this._runTask(
           configurationErrorTask('Commit must be a string'),
           next,
        );
     }
  
     return this._runTask(
        straightThroughStringTask(['revert', ...getTrailingOptions(arguments, 0, true), commit]),
        next
     );
  };
  
  /**
   * Add a lightweight tag to the head of the current branch
   */
  Git.prototype.addTag = function (name) {
     const task = (typeof name === 'string')
        ? addTagTask(name)
        : configurationErrorTask('Git.addTag requires a tag name');
  
     return this._runTask(task, trailingFunctionArgument(arguments));
  };
  
  /**
   * Add an annotated tag to the head of the current branch
   */
  Git.prototype.addAnnotatedTag = function (tagName, tagMessage) {
     return this._runTask(
        addAnnotatedTagTask(tagName, tagMessage),
        trailingFunctionArgument(arguments),
     );
  };
  
  /**
   * 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 first argument.
   */
  Git.prototype.checkout = function () {
     const commands = ['checkout', ...getTrailingOptions(arguments, true)];
     return this._runTask(
        straightThroughStringTask(commands),
        trailingFunctionArgument(arguments),
     );
  };
  
  /**
   * 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], trailingFunctionArgument(arguments));
  };
  
  /**
   * Check out a local branch
   */
  Git.prototype.checkoutLocalBranch = function (branchName, then) {
     return this.checkout(['-b', branchName], trailingFunctionArgument(arguments));
  };
  
  /**
   * Delete a local branch
   */
  Git.prototype.deleteLocalBranch = function (branchName, forceDelete, then) {
     return this._runTask(
        deleteBranchTask(branchName, typeof forceDelete === "boolean" ? forceDelete : false),
        trailingFunctionArgument(arguments),
     );
  };
  
  /**
   * Delete one or more local branches
   */
  Git.prototype.deleteLocalBranches = function (branchNames, forceDelete, then) {
     return this._runTask(
        deleteBranchesTask(branchNames, typeof forceDelete === "boolean" ? forceDelete : false),
        trailingFunctionArgument(arguments),
     );
  };
  
  /**
   * List all branches
   *
   * @param {Object | string[]} [options]
   * @param {Function} [then]
   */
  Git.prototype.branch = function (options, then) {
     return this._runTask(
        branchTask(getTrailingOptions(arguments)),
        trailingFunctionArgument(arguments),
     );
  };
  
  /**
   * Return list of local branches
   *
   * @param {Function} [then]
   */
  Git.prototype.branchLocal = function (then) {
     return this._runTask(
        branchLocalTask(),
        trailingFunctionArgument(arguments),
     );
  };
  
  /**
   * Executes any command against the git binary.
   */
  Git.prototype.raw = function (commands) {
     const createRestCommands = !Array.isArray(commands);
     const command = [].slice.call(createRestCommands ? arguments : commands, 0);
  
     for (let i = 0; i < command.length && createRestCommands; i++) {
        if (!filterPrimitives(command[i])) {
           command.splice(i, command.length - i);
           break;
        }
     }
  
     command.push(
        ...getTrailingOptions(arguments, 0, true),
     );
  
     var next = trailingFunctionArgument(arguments);
  
     if (!command.length) {
        return this._runTask(
           configurationErrorTask('Raw: must supply one or more command to execute'),
           next,
        );
     }
  
     return this._runTask(straightThroughStringTask(command), next);
  };
  
  Git.prototype.submoduleAdd = function (repo, path, then) {
     return this._runTask(
        addSubModuleTask(repo, path),
        trailingFunctionArgument(arguments),
     );
  };
  
  Git.prototype.submoduleUpdate = function (args, then) {
     return this._runTask(
        updateSubModuleTask(getTrailingOptions(arguments, true)),
        trailingFunctionArgument(arguments),
     );
  };
  
  Git.prototype.submoduleInit = function (args, then) {
     return this._runTask(
        initSubModuleTask(getTrailingOptions(arguments, true)),
        trailingFunctionArgument(arguments),
     );
  };
  
  Git.prototype.subModule = function (options, then) {
     return this._runTask(
        subModuleTask(getTrailingOptions(arguments)),
        trailingFunctionArgument(arguments),
     );
  };
  
  Git.prototype.listRemote = function () {
     return this._runTask(
        listRemotesTask(getTrailingOptions(arguments)),
        trailingFunctionArgument(arguments),
     );
  };
  
  /**
   * Adds a remote to the list of remotes.
   */
  Git.prototype.addRemote = function (remoteName, remoteRepo, then) {
     return this._runTask(
        addRemoteTask(remoteName, remoteRepo, getTrailingOptions(arguments)),
        trailingFunctionArgument(arguments),
     );
  };
  
  /**
   * Removes an entry by name from the list of remotes.
   */
  Git.prototype.removeRemote = function (remoteName, then) {
     return this._runTask(
        removeRemoteTask(remoteName),
        trailingFunctionArgument(arguments),
     );
  };
  
  /**
   * Gets the currently available remotes, setting the optional verbose argument to true includes additional
   * detail on the remotes themselves.
   */
  Git.prototype.getRemotes = function (verbose, then) {
     return this._runTask(
        getRemotesTask(verbose === true),
        trailingFunctionArgument(arguments),
     );
  };
  
  /**
   * 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) {
     return this._runTask(
        remoteTask(getTrailingOptions(arguments)),
        trailingFunctionArgument(arguments),
     );
  };
  
  /**
   * 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) {
     const command = getTrailingOptions(arguments);
  
     if (command[0] !== 'tag') {
        command.unshift('tag');
     }
  
     return this._runTask(
        straightThroughStringTask(command),
        trailingFunctionArgument(arguments)
     );
  };
  
  /**
   * Updates repository server info
   *
   * @param {Function} [then]
   */
  Git.prototype.updateServerInfo = function (then) {
     return this._runTask(
        straightThroughStringTask(['update-server-info']),
        trailingFunctionArgument(arguments),
     );
  };
  
  /**
   * 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) {
     const task = pushTagsTask({remote: filterType(remote, filterString)}, getTrailingOptions(arguments));
  
     return this._runTask(task, trailingFunctionArgument(arguments));
  };
  
  /**
   * Removes the named files from source control.
   */
  Git.prototype.rm = function (files) {
     return this._runTask(
        straightThroughStringTask(['rm', '-f', ...asArray(files)]),
        trailingFunctionArgument(arguments)
     );
  };
  
  /**
   * 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
   */
  Git.prototype.rmKeepLocal = function (files) {
     return this._runTask(
        straightThroughStringTask(['rm', '--cached', ...asArray(files)]),
        trailingFunctionArgument(arguments)
     );
  };
  
  /**
   * 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);
  };
  
  Git.prototype.binaryCatFile = function () {
     return this._catFile('buffer', arguments);
  };
  
  Git.prototype._catFile = function (format, args) {
     var handler = trailingFunctionArgument(args);
     var command = ['cat-file'];
     var options = args[0];
  
     if (typeof options === 'string') {
        return this._runTask(
           configurationErrorTask('Git.catFile: options must be supplied as an array of strings'),
           handler,
        );
     }
  
     if (Array.isArray(options)) {
        command.push.apply(command, options);
     }
  
     const task = format === 'buffer'
        ? straightThroughBufferTask(command)
        : straightThroughStringTask(command);
  
     return this._runTask(task, handler);
  };
  
  Git.prototype.diff = function (options, then) {
     const command = ['diff', ...getTrailingOptions(arguments)];
  
     if (typeof options === 'string') {
        command.splice(1, 0, options);
        this._logger.warn('Git#diff: supplying options as a single string is now deprecated, switch to an array of strings');
     }
  
     return this._runTask(
        straightThroughStringTask(command),
        trailingFunctionArgument(arguments),
     );
  };
  
  Git.prototype.diffSummary = function () {
     return this._runTask(
        diffSummaryTask(getTrailingOptions(arguments, 1)),
        trailingFunctionArgument(arguments),
     );
  };
  
  Git.prototype.applyPatch = function (patches) {
     const task = !filterStringOrStringArray(patches)
        ? configurationErrorTask(`git.applyPatch requires one or more string patches as the first argument`)
        : applyPatchTask(asArray(patches), getTrailingOptions([].slice.call(arguments, 1)));
  
     return this._runTask(
        task,
        trailingFunctionArgument(arguments),
     );
  }
  
  Git.prototype.revparse = function () {
     const commands = ['rev-parse', ...getTrailingOptions(arguments, true)];
     return this._runTask(
        straightThroughStringTask(commands, true),
        trailingFunctionArgument(arguments),
     );
  };
  
  /**
   * 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) {
     return this._runTask(
        straightThroughStringTask(['show', ...getTrailingOptions(arguments, 1)]),
        trailingFunctionArgument(arguments)
     );
  };
  
  /**
   */
  Git.prototype.clean = function (mode, options, then) {
     const usingCleanOptionsArray = isCleanOptionsArray(mode);
     const cleanMode = usingCleanOptionsArray && mode.join('') || filterType(mode, filterString) || '';
     const customArgs = getTrailingOptions([].slice.call(arguments, usingCleanOptionsArray ? 1 : 0));
  
     return this._runTask(
        cleanWithOptionsTask(cleanMode, customArgs),
        trailingFunctionArgument(arguments),
     );
  };
  
  Git.prototype.exec = function (then) {
     const task = {
        commands: [],
        format: 'utf-8',
        parser () {
           if (typeof then === 'function') {
              then();
           }
        }
     };
  
     return this._runTask(task);
  };
  
  /**
   * Show commit logs from `HEAD` to the first commit.
   * If provided between `options.from` and `options.to` tags or branch.
   *
   * Additionally you can provide options.file, which is the path to a file in your repository. Then only this file will be considered.
   *
   * To use a custom splitter in the log format, set `options.splitter` to be the string the log should be split on.
   *
   * Options can also be supplied as a standard options object for adding custom properties supported by the git log command.
   * For any other set of options, supply options as an array of strings to be appended to the git log command.
   */
  Git.prototype.log = function (options) {
     const next = trailingFunctionArgument(arguments);
  
     if (filterString(arguments[0]) && filterString(arguments[1])) {
        return this._runTask(
           configurationErrorTask(`git.log(string, string) should be replaced with git.log({ from: string, to: string })`),
           next
        );
     }
  
     const parsedOptions = parseLogOptions(
        trailingOptionsArgument(arguments) || {},
        filterArray(options) && options || []
     );
  
     return this._runTask(
        logTask(parsedOptions.splitter, parsedOptions.fields, parsedOptions.commands),
        next,
     )
  };
  
  /**
   * Clears the queue of pending commands and returns the wrapper instance for chaining.
   *
   * @returns {Git}
   */
  Git.prototype.clearQueue = function () {
     // TODO:
     // this._executor.clear();
     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) {
     return this._runTask(
        checkIgnoreTask(asArray((filterType(pathnames, filterStringOrStringArray, [])))),
        trailingFunctionArgument(arguments),
     );
  };
  
  Git.prototype.checkIsRepo = function (checkType, then) {
     return this._runTask(
        checkIsRepoTask(filterType(checkType, filterString)),
        trailingFunctionArgument(arguments),
     );
  };
  
  module.exports = Git;
  