simple-git
Version:
Simple GIT interface for node.js
1,119 lines (986 loc) • 32.6 kB
JavaScript
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];
}