UNPKG

simple-git

Version:

Simple GIT interface for node.js

1,119 lines (986 loc) 32.6 kB
const responses = require('./responses'); const {GitExecutor} = require('./lib/runners/git-executor'); const {Scheduler} = require('./lib/runners/scheduler'); const {GitLogger} = require('./lib/git-logger'); const {adhocExecTask, configurationErrorTask} = require('./lib/tasks/task'); const {NOOP, appendTaskOptions, asArray, filterArray, filterPrimitives, filterString, filterType, folderExists, getTrailingOptions, trailingFunctionArgument, trailingOptionsArgument} = require('./lib/utils'); const {branchTask, branchLocalTask, deleteBranchesTask, deleteBranchTask} = require('./lib/tasks/branch'); const {taskCallback} = require('./lib/task-callback'); const {checkIsRepoTask} = require('./lib/tasks/check-is-repo'); const {cloneTask, cloneMirrorTask} = require('./lib/tasks/clone'); const {addConfigTask, listConfigTask} = require('./lib/tasks/config'); const {cleanWithOptionsTask, isCleanOptionsArray} = require('./lib/tasks/clean'); const {initTask} = require('./lib/tasks/init'); const {mergeTask} = require('./lib/tasks/merge'); const {moveTask} = require("./lib/tasks/move"); const {pullTask} = require('./lib/tasks/pull'); const {pushTagsTask, pushTask} = require('./lib/tasks/push'); const {addRemoteTask, getRemotesTask, listRemotesTask, remoteTask, removeRemoteTask} = require('./lib/tasks/remote'); const {getResetMode, resetTask} = require('./lib/tasks/reset'); const {statusTask} = require('./lib/tasks/status'); const {addSubModuleTask, initSubModuleTask, subModuleTask, updateSubModuleTask} = require('./lib/tasks/sub-module'); const {addAnnotatedTagTask, addTagTask, tagListTask} = require('./lib/tasks/tag'); const {straightThroughStringTask} = require('./lib/tasks/task'); const {parseCheckIgnore} = require('./lib/responses/CheckIgnore'); const ChainedExecutor = Symbol('ChainedExecutor'); /** * Git handling for node. All public functions can be chained and all `then` handlers are optional. * * @param {SimpleGitOptions} options Configuration settings for this instance * * @constructor */ function Git (options) { this._executor = new GitExecutor( options.binary, options.baseDir, new Scheduler(options.maxConcurrentProcesses) ); this._logger = new GitLogger(); } /** * The executor that runs each of the added commands * @type {GitExecutor} * @private */ Git.prototype._executor = null; /** * 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; }; /** * Sets the working directory of the subsequent commands. */ Git.prototype.cwd = function (workingDirectory, then) { const task = (typeof workingDirectory !== 'string') ? configurationErrorTask('Git.cwd: workingDirectory must be supplied as a string') : adhocExecTask(() => { if (!folderExists(workingDirectory)) { throw new Error(`Git.cwd: cannot change to non-directory "${ workingDirectory }"`); } return (this._executor.cwd = workingDirectory); }); return this._runTask(task, trailingFunctionArgument(arguments) || NOOP); }; /** * 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 https://nodejs.org/api/child_process.html#child_process_class_childprocess * @see https://nodejs.org/api/stream.html#stream_class_stream_readable * @param {Function} outputHandler * @returns {Git} */ Git.prototype.outputHandler = function (outputHandler) { this._executor.outputHandler = outputHandler; return this; }; /** * Initialize a git repo * * @param {Boolean} [bare=false] * @param {Function} [then] */ Git.prototype.init = function (bare, then) { return this._runTask( initTask(bare === true, this._executor.cwd, getTrailingOptions(arguments)), trailingFunctionArgument(arguments), ); }; /** * Check the status of the local repo */ Git.prototype.status = function () { return this._runTask( statusTask(getTrailingOptions(arguments)), trailingFunctionArgument(arguments), ); }; /** * List the stash(s) of the local repo * * @param {Object|Array} [options] * @param {Function} [then] */ Git.prototype.stashList = function (options, then) { var handler = trailingFunctionArgument(arguments); var opt = (handler === then ? options : null) || {}; var splitter = opt.splitter || requireResponseHandler('ListLogSummary').SPLITTER; var command = ["stash", "list", "--pretty=format:" + requireResponseHandler('ListLogSummary').START_BOUNDARY + "%H %ai %s%d %aN %ae".replace(/\s+/g, splitter) + requireResponseHandler('ListLogSummary').COMMIT_BOUNDARY ]; if (Array.isArray(opt)) { command = command.concat(opt); } return this._run(command, handler, {parser: Git.responseParser('ListLogSummary', splitter)}); }; /** * Stash the local repo * * @param {Object|Array} [options] * @param {Function} [then] */ Git.prototype.stash = function (options, then) { return this._run( ['stash'].concat(getTrailingOptions(arguments)), 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); }); }); }; /** * Adds one or more files to source control */ Git.prototype.add = function (files) { return this._run( ['add'].concat(files), trailingFunctionArgument(arguments), ); }; /** * 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 command = ['commit']; asArray(message).forEach(function (message) { command.push('-m', message); }); asArray(typeof files === "string" || Array.isArray(files) ? files : []).forEach(cmd => command.push(cmd)); command.push(...getTrailingOptions(arguments, 0, true)); return this._run( command, trailingFunctionArgument(arguments), { parser: Git.responseParser('CommitSummary'), }, ); }; /** * 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) { 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] * @param {Function} [then] */ Git.prototype.fetch = function (remote, branch, then) { const command = ["fetch"].concat(getTrailingOptions(arguments)); if (typeof remote === 'string' && typeof branch === 'string') { command.push(remote, branch); } return this._run( command, trailingFunctionArgument(arguments), { concatStdErr: true, parser: Git.responseParser('FetchSummary'), } ); }; /** * 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._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. * * @param {Object|String[]} [options] * @param {Function} [then] * @returns {Git} */ Git.prototype.rebase = function (options, then) { return this._run( ['rebase'].concat(getTrailingOptions(arguments)), trailingFunctionArgument(arguments) ); }; /** * 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) { return this._runTask( resetTask(getResetMode(mode), getTrailingOptions(arguments)), trailingFunctionArgument(arguments), ); }; /** * 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) { const next = trailingFunctionArgument(arguments); if (typeof commit !== 'string') { return this._runTask( configurationErrorTask('Commit must be a string'), next, ); } return this._run([ 'revert', ...getTrailingOptions(arguments, 0, true), commit ], next); }; /** * Add a lightweight tag to the head of the current branch * * @param {string} name * @param {Function} [then] */ Git.prototype.addTag = function (name, then) { 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 * * @param {string} tagName * @param {string} tagMessage * @param {Function} [then] */ Git.prototype.addAnnotatedTag = function (tagName, tagMessage, then) { 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 `what` parameter. * * @param {string|string[]} what One or more commands to pass to `git checkout` * @param {Function} [then] */ Git.prototype.checkout = function (what, then) { 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), ); }; /** * 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 {boolean} [append=false] optionally append the key/value pair (equivalent of passing `--add` option). * @param {Function} [then] */ Git.prototype.addConfig = function (key, value, append, then) { return this._runTask( addConfigTask(key, value, typeof append === "boolean" ? append : false), trailingFunctionArgument(arguments), ); }; Git.prototype.listConfig = function () { return this._runTask(listConfigTask(), 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._run(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), ); }; /** * 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 {string[]} [options] * @param {Function} [then] */ Git.prototype.mergeFromTo = function (from, to) { if (!(filterString(from) && filterString(to))) { return this._runTask(configurationErrorTask( `Git.mergeFromTo requires that the 'from' and 'to' arguments are supplied as strings` )); } return this._runTask( mergeTask([from, to, ...getTrailingOptions(arguments)]), trailingFunctionArgument(arguments, false), ); }; /** * Runs a merge, `options` can be either an array of arguments * supported by the [`git merge`](https://git-scm.com/docs/git-merge) * or an options object. * * Conflicts during the merge result in an error response, * the response type whether it was an error or success will be a MergeSummary instance. * When successful, the MergeSummary has all detail from a the PullSummary * * @param {Object | string[]} [options] * @param {Function} [then] * @returns {*} * * @see ./responses/MergeSummary.js * @see ./responses/PullSummary.js */ Git.prototype.merge = function () { return this._runTask( mergeTask(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._run(command, trailingFunctionArgument(arguments)); }; /** * Updates repository server info * * @param {Function} [then] */ Git.prototype.updateServerInfo = function (then) { return this._run(["update-server-info"], trailingFunctionArgument(arguments)); }; /** * 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) { const task = pushTask( {remote: filterType(remote, filterString), branch: filterType(branch, filterString)}, getTrailingOptions(arguments), ); return this._runTask(task, 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. * * @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 = 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); } return this._run(command, handler, { format: format }); }; /** * Return repository changes. */ 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._run( ['diff', '--stat=4096', ...getTrailingOptions(arguments, true)], trailingFunctionArgument(arguments), { parser: Git.responseParser('DiffSummary'), } ); }; Git.prototype.revparse = function (options, then) { 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) { var handler = trailingFunctionArgument(arguments) || NOOP; var command = ['show']; if (typeof options === 'string' || Array.isArray(options)) { command = command.concat(options); } return this._run(command, function (err, data) { err ? handler(err) : handler(null, data); }); }; /** */ 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), ); }; /** * Call a simple function at the next step in the chain. * @param {Function} [then] */ 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. * * @param {Object|string[]} [options] * @param {boolean} [options.strictDate=true] Determine whether to use strict ISO date format (default) or not (when set to `false`) * @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 {boolean} [options.multiLine] Optionally include multi-line commit messages * * @param {Function} [then] */ Git.prototype.log = function (options, then) { var handler = trailingFunctionArgument(arguments); var opt = trailingOptionsArgument(arguments) || {}; var splitter = opt.splitter || requireResponseHandler('ListLogSummary').SPLITTER; var format = opt.format || { hash: '%H', date: opt.strictDate === false ? '%ai' : '%aI', message: '%s', refs: '%D', body: opt.multiLine ? '%B' : '%b', author_name: '%aN', author_email: '%ae' }; var rangeOperator = (opt.symmetric !== false) ? '...' : '..'; var fields = Object.keys(format); var formatstr = fields.map(function (k) { return format[k]; }).join(splitter); var suffix = []; var command = ["log", "--pretty=format:" + requireResponseHandler('ListLogSummary').START_BOUNDARY + formatstr + requireResponseHandler('ListLogSummary').COMMIT_BOUNDARY ]; if (filterArray(options)) { command = command.concat(options); opt = {}; } else if (typeof arguments[0] === "string" || typeof arguments[1] === "string") { this._logger.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 + rangeOperator + opt.to); } if (opt.file) { suffix.push("--follow", options.file); } 'splitter n max-count file from to --pretty format symmetric multiLine strictDate'.split(' ').forEach(function (key) { delete opt[key]; }); appendTaskOptions(opt, command); return this._run( command.concat(suffix), handler, { parser: Git.responseParser('ListLogSummary', [splitter, fields]) } ); }; /** * 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) { var handler = trailingFunctionArgument(arguments); var command = ["check-ignore"]; if (handler !== pathnames) { command = command.concat(pathnames); } return this._run(command, function (err, data) { handler && handler(err, !err && parseCheckIgnore(data)); }); }; Git.prototype.checkIsRepo = function (checkType, then) { return this._runTask( checkIsRepoTask(filterType(checkType, filterString)), trailingFunctionArgument(arguments), ); }; Git.prototype._rm = function (_files, options, then) { var files = [].concat(_files); var args = ['rm', options]; args.push.apply(args, files); return this._run(args, trailingFunctionArgument(arguments)); }; /** * 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 {Function} [opt.parser] Optional parser function * @param {number} [opt.onError.exitCode] * @param {string} [opt.onError.stdErr] * * @returns {Git} */ Git.prototype._run = function (command, then, opt) { const task = Object.assign({ concatStdErr: false, onError: undefined, format: 'utf-8', parser (data) { return data; } }, opt || {}, { commands: command, }); return this._runTask(task, then); }; Git.prototype._runTask = function (task, then) { const executor = this[ChainedExecutor] || this._executor.chain(); const promise = executor.push(task); taskCallback( task, promise, then); return Object.create(this, { then: {value: promise.then.bind(promise)}, catch: {value: promise.catch.bind(promise)}, [ChainedExecutor]: {value: executor}, }); }; /** * Handles an exception in the processing of a command. */ Git.fail = function (git, error, handler) { git._logger.error(error); git.clearQueue(); if (typeof handler === 'function') { handler.call(git, error, null); } }; /** * Creates a parser for a task * * @param {string} type * @param {any[]} [args] */ Git.responseParser = function (type, args) { const handler = requireResponseHandler(type); return function (data) { return handler.parse.apply(handler, [data].concat(args === undefined ? [] : args)); }; }; /** * 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) { const err = error instanceof Error ? error : new Error(error); if (typeof callback === 'function') { callback(err); } throw err; }; module.exports = Git; /** * Requires and returns a response handler based on its named type * @param {string} type */ function requireResponseHandler (type) { return responses[type]; }