| @@ -0,0 +1,1592 @@ |
| 1 | +(function () { |
| 2 | + |
| 3 | + 'use strict'; |
| 4 | + |
| 5 | + var debug = require('debug')('simple-git'); |
| 6 | + var deferred = require('./util/deferred'); |
| 7 | + var exists = require('./util/exists'); |
| 8 | + var NOOP = function () {}; |
| 9 | + var responses = require('./responses'); |
| 10 | + |
| 11 | + /** |
| 12 | + * Git handling for node. All public functions can be chained and all `then` handlers are optional. |
| 13 | + * |
| 14 | + * @param {string} baseDir base directory for all processes to run |
| 15 | + * |
| 16 | + * @param {Object} ChildProcess The ChildProcess module |
| 17 | + * @param {Function} Buffer The Buffer implementation to use |
| 18 | + * |
| 19 | + * @constructor |
| 20 | + */ |
| 21 | + function Git (baseDir, ChildProcess, Buffer) { |
| 22 | + this._baseDir = baseDir; |
| 23 | + this._runCache = []; |
| 24 | + |
| 25 | + this.ChildProcess = ChildProcess; |
| 26 | + this.Buffer = Buffer; |
| 27 | + } |
| 28 | + |
| 29 | + /** |
| 30 | + * @type {string} The command to use to reference the git binary |
| 31 | + */ |
| 32 | + Git.prototype._command = 'git'; |
| 33 | + |
| 34 | + /** |
| 35 | + * @type {[key: string]: string} An object of key=value pairs to be passed as environment variables to the |
| 36 | + * spawned child process. |
| 37 | + */ |
| 38 | + Git.prototype._env = null; |
| 39 | + |
| 40 | + /** |
| 41 | + * @type {Function} An optional handler to use when a child process is created |
| 42 | + */ |
| 43 | + Git.prototype._outputHandler = null; |
| 44 | + |
| 45 | + /** |
| 46 | + * @type {boolean} Property showing whether logging will be silenced - defaults to true in a production environment |
| 47 | + */ |
| 48 | + Git.prototype._silentLogging = /prod/.test(process.env.NODE_ENV); |
| 49 | + |
| 50 | + /** |
| 51 | + * Sets the path to a custom git binary, should either be `git` when there is an installation of git available on |
| 52 | + * the system path, or a fully qualified path to the executable. |
| 53 | + * |
| 54 | + * @param {string} command |
| 55 | + * @returns {Git} |
| 56 | + */ |
| 57 | + Git.prototype.customBinary = function (command) { |
| 58 | + this._command = command; |
| 59 | + return this; |
| 60 | + }; |
| 61 | + |
| 62 | + /** |
| 63 | + * Sets an environment variable for the spawned child process, either supply both a name and value as strings or |
| 64 | + * a single object to entirely replace the current environment variables. |
| 65 | + * |
| 66 | + * @param {string|Object} name |
| 67 | + * @param {string} [value] |
| 68 | + * @returns {Git} |
| 69 | + */ |
| 70 | + Git.prototype.env = function (name, value) { |
| 71 | + if (arguments.length === 1 && typeof name === 'object') { |
| 72 | + this._env = name; |
| 73 | + } |
| 74 | + else { |
| 75 | + (this._env = this._env || {})[name] = value; |
| 76 | + } |
| 77 | + |
| 78 | + return this; |
| 79 | + }; |
| 80 | + |
| 81 | + /** |
| 82 | + * Sets the working directory of the subsequent commands. |
| 83 | + * |
| 84 | + * @param {string} workingDirectory |
| 85 | + * @param {Function} [then] |
| 86 | + * @returns {Git} |
| 87 | + */ |
| 88 | + Git.prototype.cwd = function (workingDirectory, then) { |
| 89 | + var git = this; |
| 90 | + var next = Git.trailingFunctionArgument(arguments); |
| 91 | + |
| 92 | + return this.exec(function () { |
| 93 | + git._baseDir = workingDirectory; |
| 94 | + if (!exists(workingDirectory, exists.FOLDER)) { |
| 95 | + Git.exception(git, 'Git.cwd: cannot change to non-directory "' + workingDirectory + '"', next); |
| 96 | + } |
| 97 | + else { |
| 98 | + next && next(null, workingDirectory); |
| 99 | + } |
| 100 | + }); |
| 101 | + }; |
| 102 | + |
| 103 | + /** |
| 104 | + * Sets a handler function to be called whenever a new child process is created, the handler function will be called |
| 105 | + * with the name of the command being run and the stdout & stderr streams used by the ChildProcess. |
| 106 | + * |
| 107 | + * @example |
| 108 | + * require('simple-git') |
| 109 | + * .outputHandler(function (command, stdout, stderr) { |
| 110 | + * stdout.pipe(process.stdout); |
| 111 | + * }) |
| 112 | + * .checkout('https://github.com/user/repo.git'); |
| 113 | + * |
| 114 | + * @see https://nodejs.org/api/child_process.html#child_process_class_childprocess |
| 115 | + * @see https://nodejs.org/api/stream.html#stream_class_stream_readable |
| 116 | + * @param {Function} outputHandler |
| 117 | + * @returns {Git} |
| 118 | + */ |
| 119 | + Git.prototype.outputHandler = function (outputHandler) { |
| 120 | + this._outputHandler = outputHandler; |
| 121 | + return this; |
| 122 | + }; |
| 123 | + |
| 124 | + /** |
| 125 | + * Initialize a git repo |
| 126 | + * |
| 127 | + * @param {Boolean} [bare=false] |
| 128 | + * @param {Function} [then] |
| 129 | + */ |
| 130 | + Git.prototype.init = function (bare, then) { |
| 131 | + var commands = ['init']; |
| 132 | + var next = Git.trailingFunctionArgument(arguments); |
| 133 | + |
| 134 | + if (bare === true) { |
| 135 | + commands.push('--bare'); |
| 136 | + } |
| 137 | + |
| 138 | + return this._run(commands, function (err) { |
| 139 | + next && next(err); |
| 140 | + }); |
| 141 | + }; |
| 142 | + |
| 143 | + /** |
| 144 | + * Check the status of the local repo |
| 145 | + * |
| 146 | + * @param {Function} [then] |
| 147 | + */ |
| 148 | + Git.prototype.status = function (then) { |
| 149 | + return this._run( |
| 150 | + ['status', '--porcelain', '-b', '-u'], |
| 151 | + Git._responseHandler(then, 'StatusSummary') |
| 152 | + ); |
| 153 | + }; |
| 154 | + |
| 155 | + /** |
| 156 | + * List the stash(s) of the local repo |
| 157 | + * |
| 158 | + * @param {Object|Array} [options] |
| 159 | + * @param {Function} [then] |
| 160 | + */ |
| 161 | + Git.prototype.stashList = function (options, then) { |
| 162 | + var handler = Git.trailingFunctionArgument(arguments); |
| 163 | + var opt = (handler === then ? options : null) || {}; |
| 164 | + |
| 165 | + var splitter = opt.splitter || requireResponseHandler('ListLogSummary').SPLITTER; |
| 166 | + var command = ["stash", "list", "--pretty=format:" |
| 167 | + + requireResponseHandler('ListLogSummary').START_BOUNDARY |
| 168 | + + "%H %ai %s%d %aN %ae".replace(/\s+/g, splitter) |
| 169 | + + requireResponseHandler('ListLogSummary').COMMIT_BOUNDARY |
| 170 | + ]; |
| 171 | + |
| 172 | + if (Array.isArray(opt)) { |
| 173 | + command = command.concat(opt); |
| 174 | + } |
| 175 | + |
| 176 | + return this._run(command, |
| 177 | + Git._responseHandler(handler, 'ListLogSummary', splitter) |
| 178 | + ); |
| 179 | + }; |
| 180 | + |
| 181 | + /** |
| 182 | + * Stash the local repo |
| 183 | + * |
| 184 | + * @param {Object|Array} [options] |
| 185 | + * @param {Function} [then] |
| 186 | + */ |
| 187 | + Git.prototype.stash = function (options, then) { |
| 188 | + var command = ['stash']; |
| 189 | + Git._appendOptions(command, Git.trailingOptionsArgument(arguments)); |
| 190 | + command.push.apply(command, Git.trailingArrayArgument(arguments)); |
| 191 | + |
| 192 | + return this._run(command, Git._responseHandler(Git.trailingFunctionArgument(arguments))); |
| 193 | + }; |
| 194 | + |
| 195 | + /** |
| 196 | + * Clone a git repo |
| 197 | + * |
| 198 | + * @param {string} repoPath |
| 199 | + * @param {string} localPath |
| 200 | + * @param {String[]} [options] Optional array of options to pass through to the clone command |
| 201 | + * @param {Function} [then] |
| 202 | + */ |
| 203 | + Git.prototype.clone = function (repoPath, localPath, options, then) { |
| 204 | + var next = Git.trailingFunctionArgument(arguments); |
| 205 | + var command = ['clone'].concat(Git.trailingArrayArgument(arguments)); |
| 206 | + |
| 207 | + for (var i = 0, iMax = arguments.length; i < iMax; i++) { |
| 208 | + if (typeof arguments[i] === 'string') { |
| 209 | + command.push(arguments[i]); |
| 210 | + } |
| 211 | + } |
| 212 | + |
| 213 | + return this._run(command, function (err, data) { |
| 214 | + next && next(err, data); |
| 215 | + }); |
| 216 | + }; |
| 217 | + |
| 218 | + /** |
| 219 | + * Mirror a git repo |
| 220 | + * |
| 221 | + * @param {string} repoPath |
| 222 | + * @param {string} localPath |
| 223 | + * @param {Function} [then] |
| 224 | + */ |
| 225 | + Git.prototype.mirror = function (repoPath, localPath, then) { |
| 226 | + return this.clone(repoPath, localPath, ['--mirror'], then); |
| 227 | + }; |
| 228 | + |
| 229 | + /** |
| 230 | + * Moves one or more files to a new destination. |
| 231 | + * |
| 232 | + * @see https://git-scm.com/docs/git-mv |
| 233 | + * |
| 234 | + * @param {string|string[]} from |
| 235 | + * @param {string} to |
| 236 | + * @param {Function} [then] |
| 237 | + */ |
| 238 | + Git.prototype.mv = function (from, to, then) { |
| 239 | + var handler = Git.trailingFunctionArgument(arguments); |
| 240 | + |
| 241 | + var command = [].concat(from); |
| 242 | + command.unshift('mv', '-v'); |
| 243 | + command.push(to); |
| 244 | + |
| 245 | + this._run(command, Git._responseHandler(handler, 'MoveSummary')) |
| 246 | + }; |
| 247 | + |
| 248 | + /** |
| 249 | + * Internally uses pull and tags to get the list of tags then checks out the latest tag. |
| 250 | + * |
| 251 | + * @param {Function} [then] |
| 252 | + */ |
| 253 | + Git.prototype.checkoutLatestTag = function (then) { |
| 254 | + var git = this; |
| 255 | + return this.pull(function () { |
| 256 | + git.tags(function (err, tags) { |
| 257 | + git.checkout(tags.latest, then); |
| 258 | + }); |
| 259 | + }); |
| 260 | + }; |
| 261 | + |
| 262 | + /** |
| 263 | + * Adds one or more files to source control |
| 264 | + * |
| 265 | + * @param {string|string[]} files |
| 266 | + * @param {Function} [then] |
| 267 | + */ |
| 268 | + Git.prototype.add = function (files, then) { |
| 269 | + return this._run(['add'].concat(files), function (err, data) { |
| 270 | + then && then(err); |
| 271 | + }); |
| 272 | + }; |
| 273 | + |
| 274 | + /** |
| 275 | + * Commits changes in the current working directory - when specific file paths are supplied, only changes on those |
| 276 | + * files will be committed. |
| 277 | + * |
| 278 | + * @param {string|string[]} message |
| 279 | + * @param {string|string[]} [files] |
| 280 | + * @param {Object} [options] |
| 281 | + * @param {Function} [then] |
| 282 | + */ |
| 283 | + Git.prototype.commit = function (message, files, options, then) { |
| 284 | + var handler = Git.trailingFunctionArgument(arguments); |
| 285 | + |
| 286 | + var command = ['commit']; |
| 287 | + |
| 288 | + [].concat(message).forEach(function (message) { |
| 289 | + command.push('-m', message); |
| 290 | + }); |
| 291 | + |
| 292 | + [].push.apply(command, [].concat(typeof files === "string" || Array.isArray(files) ? files : [])); |
| 293 | + |
| 294 | + Git._appendOptions(command, Git.trailingOptionsArgument(arguments)); |
| 295 | + |
| 296 | + return this._run( |
| 297 | + command, |
| 298 | + Git._responseHandler(handler, 'CommitSummary') |
| 299 | + ); |
| 300 | + }; |
| 301 | + |
| 302 | + /** |
| 303 | + * Gets a function to be used for logging. |
| 304 | + * |
| 305 | + * @param {string} level |
| 306 | + * @param {string} [message] |
| 307 | + * |
| 308 | + * @returns {Function} |
| 309 | + * @private |
| 310 | + */ |
| 311 | + Git.prototype._getLog = function (level, message) { |
| 312 | + var log = this._silentLogging ? NOOP : console[level].bind(console); |
| 313 | + if (arguments.length > 1) { |
| 314 | + log(message); |
| 315 | + } |
| 316 | + return log; |
| 317 | + }; |
| 318 | + |
| 319 | + /** |
| 320 | + * Pull the updated contents of the current repo |
| 321 | + * |
| 322 | + * @param {string} [remote] When supplied must also include the branch |
| 323 | + * @param {string} [branch] When supplied must also include the remote |
| 324 | + * @param {Object} [options] Optionally include set of options to merge into the command |
| 325 | + * @param {Function} [then] |
| 326 | + */ |
| 327 | + Git.prototype.pull = function (remote, branch, options, then) { |
| 328 | + var command = ["pull"]; |
| 329 | + var handler = Git.trailingFunctionArgument(arguments); |
| 330 | + |
| 331 | + if (typeof remote === 'string' && typeof branch === 'string') { |
| 332 | + command.push(remote, branch); |
| 333 | + } |
| 334 | + |
| 335 | + Git._appendOptions(command, Git.trailingOptionsArgument(arguments)); |
| 336 | + |
| 337 | + return this._run(command, Git._responseHandler(handler, 'PullSummary')); |
| 338 | + }; |
| 339 | + |
| 340 | + /** |
| 341 | + * Fetch the updated contents of the current repo. |
| 342 | + * |
| 343 | + * @example |
| 344 | + * .fetch('upstream', 'master') // fetches from master on remote named upstream |
| 345 | + * .fetch(function () {}) // runs fetch against default remote and branch and calls function |
| 346 | + * |
| 347 | + * @param {string} [remote] |
| 348 | + * @param {string} [branch] |
| 349 | + * @param {Function} [then] |
| 350 | + */ |
| 351 | + Git.prototype.fetch = function (remote, branch, then) { |
| 352 | + var command = ["fetch"]; |
| 353 | + var next = Git.trailingFunctionArgument(arguments); |
| 354 | + Git._appendOptions(command, Git.trailingOptionsArgument(arguments)); |
| 355 | + |
| 356 | + if (typeof remote === 'string' && typeof branch === 'string') { |
| 357 | + command.push(remote, branch); |
| 358 | + } |
| 359 | + |
| 360 | + if (Array.isArray(remote)) { |
| 361 | + command = command.concat(remote); |
| 362 | + } |
| 363 | + |
| 364 | + return this._run( |
| 365 | + command, |
| 366 | + Git._responseHandler(next, 'FetchSummary'), |
| 367 | + { |
| 368 | + concatStdErr: true |
| 369 | + } |
| 370 | + ); |
| 371 | + }; |
| 372 | + |
| 373 | + /** |
| 374 | + * Disables/enables the use of the console for printing warnings and errors, by default messages are not shown in |
| 375 | + * a production environment. |
| 376 | + * |
| 377 | + * @param {boolean} silence |
| 378 | + * @returns {Git} |
| 379 | + */ |
| 380 | + Git.prototype.silent = function (silence) { |
| 381 | + this._silentLogging = !!silence; |
| 382 | + return this; |
| 383 | + }; |
| 384 | + |
| 385 | + /** |
| 386 | + * List all tags. When using git 2.7.0 or above, include an options object with `"--sort": "property-name"` to |
| 387 | + * sort the tags by that property instead of using the default semantic versioning sort. |
| 388 | + * |
| 389 | + * Note, supplying this option when it is not supported by your Git version will cause the operation to fail. |
| 390 | + * |
| 391 | + * @param {Object} [options] |
| 392 | + * @param {Function} [then] |
| 393 | + */ |
| 394 | + Git.prototype.tags = function (options, then) { |
| 395 | + var next = Git.trailingFunctionArgument(arguments); |
| 396 | + |
| 397 | + var command = ['-l']; |
| 398 | + Git._appendOptions(command, Git.trailingOptionsArgument(arguments)); |
| 399 | + |
| 400 | + var hasCustomSort = command.some(function (option) { |
| 401 | + return /^--sort=/.test(option); |
| 402 | + }); |
| 403 | + |
| 404 | + return this.tag( |
| 405 | + command, |
| 406 | + Git._responseHandler(next, 'TagList', [hasCustomSort]) |
| 407 | + ); |
| 408 | + }; |
| 409 | + |
| 410 | + /** |
| 411 | + * Rebases the current working copy. Options can be supplied either as an array of string parameters |
| 412 | + * to be sent to the `git rebase` command, or a standard options object. |
| 413 | + * |
| 414 | + * @param {Object|String[]} [options] |
| 415 | + * @param {Function} [then] |
| 416 | + * @returns {Git} |
| 417 | + */ |
| 418 | + Git.prototype.rebase = function (options, then) { |
| 419 | + var command = ['rebase']; |
| 420 | + Git._appendOptions(command, Git.trailingOptionsArgument(arguments)); |
| 421 | + command.push.apply(command, Git.trailingArrayArgument(arguments)); |
| 422 | + |
| 423 | + |
| 424 | + return this._run(command, Git._responseHandler(Git.trailingFunctionArgument(arguments))); |
| 425 | + }; |
| 426 | + |
| 427 | + /** |
| 428 | + * Reset a repo |
| 429 | + * |
| 430 | + * @param {string|string[]} [mode=soft] Either an array of arguments supported by the 'git reset' command, or the |
| 431 | + * string value 'soft' or 'hard' to set the reset mode. |
| 432 | + * @param {Function} [then] |
| 433 | + */ |
| 434 | + Git.prototype.reset = function (mode, then) { |
| 435 | + var command = ['reset']; |
| 436 | + var next = Git.trailingFunctionArgument(arguments); |
| 437 | + if (next === mode || typeof mode === 'string' || !mode) { |
| 438 | + var modeStr = ['mixed', 'soft', 'hard'].includes(mode) ? mode : 'soft'; |
| 439 | + command.push('--' + modeStr); |
| 440 | + } |
| 441 | + else if (Array.isArray(mode)) { |
| 442 | + command.push.apply(command, mode); |
| 443 | + } |
| 444 | + |
| 445 | + return this._run(command, function (err) { |
| 446 | + next && next(err || null); |
| 447 | + }); |
| 448 | + }; |
| 449 | + |
| 450 | + /** |
| 451 | + * Revert one or more commits in the local working copy |
| 452 | + * |
| 453 | + * @param {string} commit The commit to revert. Can be any hash, offset (eg: `HEAD~2`) or range (eg: `master~5..master~2`) |
| 454 | + * @param {Object} [options] Optional options object |
| 455 | + * @param {Function} [then] |
| 456 | + */ |
| 457 | + Git.prototype.revert = function (commit, options, then) { |
| 458 | + var next = Git.trailingFunctionArgument(arguments); |
| 459 | + var command = ['revert']; |
| 460 | + |
| 461 | + Git._appendOptions(command, Git.trailingOptionsArgument(arguments)); |
| 462 | + |
| 463 | + if (typeof commit !== 'string') { |
| 464 | + return this.exec(function () { |
| 465 | + next && next(new TypeError("Commit must be a string")); |
| 466 | + }); |
| 467 | + } |
| 468 | + |
| 469 | + command.push(commit); |
| 470 | + return this._run(command, function (err) { |
| 471 | + next && next(err || null); |
| 472 | + }); |
| 473 | + }; |
| 474 | + |
| 475 | + /** |
| 476 | + * Add a lightweight tag to the head of the current branch |
| 477 | + * |
| 478 | + * @param {string} name |
| 479 | + * @param {Function} [then] |
| 480 | + */ |
| 481 | + Git.prototype.addTag = function (name, then) { |
| 482 | + if (typeof name !== "string") { |
| 483 | + return this.exec(function () { |
| 484 | + then && then(new TypeError("Git.addTag requires a tag name")); |
| 485 | + }); |
| 486 | + } |
| 487 | + |
| 488 | + var command = [name]; |
| 489 | + return then ? this.tag(command, then) : this.tag(command); |
| 490 | + }; |
| 491 | + |
| 492 | + /** |
| 493 | + * Add an annotated tag to the head of the current branch |
| 494 | + * |
| 495 | + * @param {string} tagName |
| 496 | + * @param {string} tagMessage |
| 497 | + * @param {Function} [then] |
| 498 | + */ |
| 499 | + Git.prototype.addAnnotatedTag = function (tagName, tagMessage, then) { |
| 500 | + return this.tag(['-a', '-m', tagMessage, tagName], function (err) { |
| 501 | + then && then(err); |
| 502 | + }); |
| 503 | + }; |
| 504 | + |
| 505 | + /** |
| 506 | + * Check out a tag or revision, any number of additional arguments can be passed to the `git checkout` command |
| 507 | + * by supplying either a string or array of strings as the `what` parameter. |
| 508 | + * |
| 509 | + * @param {string|string[]} what One or more commands to pass to `git checkout` |
| 510 | + * @param {Function} [then] |
| 511 | + */ |
| 512 | + Git.prototype.checkout = function (what, then) { |
| 513 | + var command = ['checkout']; |
| 514 | + command = command.concat(what); |
| 515 | + |
| 516 | + return this._run(command, function (err, data) { |
| 517 | + then && then(err, !err && this._parseCheckout(data)); |
| 518 | + }); |
| 519 | + }; |
| 520 | + |
| 521 | + /** |
| 522 | + * Check out a remote branch |
| 523 | + * |
| 524 | + * @param {string} branchName name of branch |
| 525 | + * @param {string} startPoint (e.g origin/development) |
| 526 | + * @param {Function} [then] |
| 527 | + */ |
| 528 | + Git.prototype.checkoutBranch = function (branchName, startPoint, then) { |
| 529 | + return this.checkout(['-b', branchName, startPoint], then); |
| 530 | + }; |
| 531 | + |
| 532 | + /** |
| 533 | + * Check out a local branch |
| 534 | + * |
| 535 | + * @param {string} branchName of branch |
| 536 | + * @param {Function} [then] |
| 537 | + */ |
| 538 | + Git.prototype.checkoutLocalBranch = function (branchName, then) { |
| 539 | + return this.checkout(['-b', branchName], then); |
| 540 | + }; |
| 541 | + |
| 542 | + /** |
| 543 | + * Delete a local branch |
| 544 | + * |
| 545 | + * @param {string} branchName name of branch |
| 546 | + * @param {Function} [then] |
| 547 | + */ |
| 548 | + Git.prototype.deleteLocalBranch = function (branchName, then) { |
| 549 | + return this.branch(['-d', branchName], then); |
| 550 | + }; |
| 551 | + |
| 552 | + /** |
| 553 | + * List all branches |
| 554 | + * |
| 555 | + * @param {Object | string[]} [options] |
| 556 | + * @param {Function} [then] |
| 557 | + */ |
| 558 | + Git.prototype.branch = function (options, then) { |
| 559 | + var isDelete, responseHandler; |
| 560 | + var next = Git.trailingFunctionArgument(arguments); |
| 561 | + var command = ['branch']; |
| 562 | + |
| 563 | + command.push.apply(command, Git.trailingArrayArgument(arguments)); |
| 564 | + Git._appendOptions(command, Git.trailingOptionsArgument(arguments)); |
| 565 | + |
| 566 | + if (!arguments.length || next === options) { |
| 567 | + command.push('-a'); |
| 568 | + } |
| 569 | + |
| 570 | + isDelete = ['-d', '-D', '--delete'].reduce(function (isDelete, flag) { |
| 571 | + return isDelete || command.indexOf(flag) > 0; |
| 572 | + }, false); |
| 573 | + |
| 574 | + if (command.indexOf('-v') < 0) { |
| 575 | + command.splice(1, 0, '-v'); |
| 576 | + } |
| 577 | + |
| 578 | + responseHandler = isDelete |
| 579 | + ? Git._responseHandler(next, 'BranchDeleteSummary', false) |
| 580 | + : Git._responseHandler(next, 'BranchSummary'); |
| 581 | + |
| 582 | + return this._run(command, responseHandler); |
| 583 | + }; |
| 584 | + |
| 585 | + /** |
| 586 | + * Return list of local branches |
| 587 | + * |
| 588 | + * @param {Function} [then] |
| 589 | + */ |
| 590 | + Git.prototype.branchLocal = function (then) { |
| 591 | + return this.branch(['-v'], then); |
| 592 | + }; |
| 593 | + |
| 594 | + /** |
| 595 | + * Add config to local git instance |
| 596 | + * |
| 597 | + * @param {string} key configuration key (e.g user.name) |
| 598 | + * @param {string} value for the given key (e.g your name) |
| 599 | + * @param {Function} [then] |
| 600 | + */ |
| 601 | + Git.prototype.addConfig = function (key, value, then) { |
| 602 | + return this._run(['config', '--local', key, value], function (err, data) { |
| 603 | + then && then(err, !err && data); |
| 604 | + }); |
| 605 | + }; |
| 606 | + |
| 607 | + /** |
| 608 | + * Executes any command against the git binary. |
| 609 | + * |
| 610 | + * @param {string[]|Object} commands |
| 611 | + * @param {Function} [then] |
| 612 | + * |
| 613 | + * @returns {Git} |
| 614 | + */ |
| 615 | + Git.prototype.raw = function (commands, then) { |
| 616 | + var command = []; |
| 617 | + if (Array.isArray(commands)) { |
| 618 | + command = commands.slice(0); |
| 619 | + } |
| 620 | + else { |
| 621 | + Git._appendOptions(command, Git.trailingOptionsArgument(arguments)); |
| 622 | + } |
| 623 | + |
| 624 | + var next = Git.trailingFunctionArgument(arguments); |
| 625 | + |
| 626 | + if (!command.length) { |
| 627 | + return this.exec(function () { |
| 628 | + next && next(new Error('Raw: must supply one or more command to execute'), null); |
| 629 | + }); |
| 630 | + } |
| 631 | + |
| 632 | + return this._run(command, function (err, data) { |
| 633 | + next && next(err, !err && data || null); |
| 634 | + }); |
| 635 | + }; |
| 636 | + |
| 637 | + /** |
| 638 | + * Add a submodule |
| 639 | + * |
| 640 | + * @param {string} repo |
| 641 | + * @param {string} path |
| 642 | + * @param {Function} [then] |
| 643 | + */ |
| 644 | + Git.prototype.submoduleAdd = function (repo, path, then) { |
| 645 | + return this._run(['submodule', 'add', repo, path], function (err) { |
| 646 | + then && then(err); |
| 647 | + }); |
| 648 | + }; |
| 649 | + |
| 650 | + /** |
| 651 | + * Update submodules |
| 652 | + * |
| 653 | + * @param {string[]} [args] |
| 654 | + * @param {Function} [then] |
| 655 | + */ |
| 656 | + Git.prototype.submoduleUpdate = function (args, then) { |
| 657 | + if (typeof args === 'string') { |
| 658 | + this._getLog('warn', 'Git#submoduleUpdate: args should be supplied as an array of individual arguments'); |
| 659 | + } |
| 660 | + |
| 661 | + var next = Git.trailingFunctionArgument(arguments); |
| 662 | + var command = (args !== next) ? args : []; |
| 663 | + |
| 664 | + return this.subModule(['update'].concat(command), function (err, args) { |
| 665 | + next && next(err, args); |
| 666 | + }); |
| 667 | + }; |
| 668 | + |
| 669 | + /** |
| 670 | + * Initialize submodules |
| 671 | + * |
| 672 | + * @param {string[]} [args] |
| 673 | + * @param {Function} [then] |
| 674 | + */ |
| 675 | + Git.prototype.submoduleInit = function (args, then) { |
| 676 | + if (typeof args === 'string') { |
| 677 | + this._getLog('warn', 'Git#submoduleInit: args should be supplied as an array of individual arguments'); |
| 678 | + } |
| 679 | + |
| 680 | + var next = Git.trailingFunctionArgument(arguments); |
| 681 | + var command = (args !== next) ? args : []; |
| 682 | + |
| 683 | + return this.subModule(['init'].concat(command), function (err, args) { |
| 684 | + next && next(err, args); |
| 685 | + }); |
| 686 | + }; |
| 687 | + |
| 688 | + /** |
| 689 | + * Call any `git submodule` function with arguments passed as an array of strings. |
| 690 | + * |
| 691 | + * @param {string[]} options |
| 692 | + * @param {Function} [then] |
| 693 | + */ |
| 694 | + Git.prototype.subModule = function (options, then) { |
| 695 | + if (!Array.isArray(options)) { |
| 696 | + return this.exec(function () { |
| 697 | + then && then(new TypeError("Git.subModule requires an array of arguments")); |
| 698 | + }); |
| 699 | + } |
| 700 | + |
| 701 | + if (options[0] !== 'submodule') { |
| 702 | + options.unshift('submodule'); |
| 703 | + } |
| 704 | + |
| 705 | + return this._run(options, function (err, data) { |
| 706 | + then && then(err || null, err ? null : data); |
| 707 | + }); |
| 708 | + }; |
| 709 | + |
| 710 | + /** |
| 711 | + * List remote |
| 712 | + * |
| 713 | + * @param {string[]} [args] |
| 714 | + * @param {Function} [then] |
| 715 | + */ |
| 716 | + Git.prototype.listRemote = function (args, then) { |
| 717 | + var next = Git.trailingFunctionArgument(arguments); |
| 718 | + var data = next === args || args === undefined ? [] : args; |
| 719 | + |
| 720 | + if (typeof data === 'string') { |
| 721 | + this._getLog('warn', 'Git#listRemote: args should be supplied as an array of individual arguments'); |
| 722 | + } |
| 723 | + |
| 724 | + return this._run(['ls-remote'].concat(data), function (err, data) { |
| 725 | + next && next(err, data); |
| 726 | + }); |
| 727 | + }; |
| 728 | + |
| 729 | + /** |
| 730 | + * Adds a remote to the list of remotes. |
| 731 | + * |
| 732 | + * @param {string} remoteName Name of the repository - eg "upstream" |
| 733 | + * @param {string} remoteRepo Fully qualified SSH or HTTP(S) path to the remote repo |
| 734 | + * @param {Function} [then] |
| 735 | + * @returns {*} |
| 736 | + */ |
| 737 | + Git.prototype.addRemote = function (remoteName, remoteRepo, then) { |
| 738 | + return this._run(['remote', 'add', remoteName, remoteRepo], function (err) { |
| 739 | + then && then(err); |
| 740 | + }); |
| 741 | + }; |
| 742 | + |
| 743 | + /** |
| 744 | + * Removes an entry from the list of remotes. |
| 745 | + * |
| 746 | + * @param {string} remoteName Name of the repository - eg "upstream" |
| 747 | + * @param {Function} [then] |
| 748 | + * @returns {*} |
| 749 | + */ |
| 750 | + Git.prototype.removeRemote = function (remoteName, then) { |
| 751 | + return this._run(['remote', 'remove', remoteName], function (err) { |
| 752 | + then && then(err); |
| 753 | + }); |
| 754 | + }; |
| 755 | + |
| 756 | + /** |
| 757 | + * Gets the currently available remotes, setting the optional verbose argument to true includes additional |
| 758 | + * detail on the remotes themselves. |
| 759 | + * |
| 760 | + * @param {boolean} [verbose=false] |
| 761 | + * @param {Function} [then] |
| 762 | + */ |
| 763 | + Git.prototype.getRemotes = function (verbose, then) { |
| 764 | + var next = Git.trailingFunctionArgument(arguments); |
| 765 | + var args = verbose === true ? ['-v'] : []; |
| 766 | + |
| 767 | + return this.remote(args, function (err, data) { |
| 768 | + next && next(err, !err && function () { |
| 769 | + return data.trim().split('\n').filter(Boolean).reduce(function (remotes, remote) { |
| 770 | + var detail = remote.trim().split(/\s+/); |
| 771 | + var name = detail.shift(); |
| 772 | + |
| 773 | + if (!remotes[name]) { |
| 774 | + remotes[name] = remotes[remotes.length] = { |
| 775 | + name: name, |
| 776 | + refs: {} |
| 777 | + }; |
| 778 | + } |
| 779 | + |
| 780 | + if (detail.length) { |
| 781 | + remotes[name].refs[detail.pop().replace(/[^a-z]/g, '')] = detail.pop(); |
| 782 | + } |
| 783 | + |
| 784 | + return remotes; |
| 785 | + }, []).slice(0); |
| 786 | + }()); |
| 787 | + }); |
| 788 | + }; |
| 789 | + |
| 790 | + /** |
| 791 | + * Call any `git remote` function with arguments passed as an array of strings. |
| 792 | + * |
| 793 | + * @param {string[]} options |
| 794 | + * @param {Function} [then] |
| 795 | + */ |
| 796 | + Git.prototype.remote = function (options, then) { |
| 797 | + if (!Array.isArray(options)) { |
| 798 | + return this.exec(function () { |
| 799 | + then && then(new TypeError("Git.remote requires an array of arguments")); |
| 800 | + }); |
| 801 | + } |
| 802 | + |
| 803 | + if (options[0] !== 'remote') { |
| 804 | + options.unshift('remote'); |
| 805 | + } |
| 806 | + |
| 807 | + return this._run(options, function (err, data) { |
| 808 | + then && then(err || null, err ? null : data); |
| 809 | + }); |
| 810 | + }; |
| 811 | + |
| 812 | + /** |
| 813 | + * Merges from one branch to another, equivalent to running `git merge ${from} $[to}`, the `options` argument can |
| 814 | + * either be an array of additional parameters to pass to the command or null / omitted to be ignored. |
| 815 | + * |
| 816 | + * @param {string} from |
| 817 | + * @param {string} to |
| 818 | + * @param {string[]} [options] |
| 819 | + * @param {Function} [then] |
| 820 | + */ |
| 821 | + Git.prototype.mergeFromTo = function (from, to, options, then) { |
| 822 | + var commands = [ |
| 823 | + from, |
| 824 | + to |
| 825 | + ]; |
| 826 | + var callback = Git.trailingFunctionArgument(arguments); |
| 827 | + |
| 828 | + if (Array.isArray(options)) { |
| 829 | + commands = commands.concat(options); |
| 830 | + } |
| 831 | + |
| 832 | + return this.merge(commands, callback); |
| 833 | + }; |
| 834 | + |
| 835 | + /** |
| 836 | + * Runs a merge, `options` can be either an array of arguments |
| 837 | + * supported by the [`git merge`](https://git-scm.com/docs/git-merge) |
| 838 | + * or an options object. |
| 839 | + * |
| 840 | + * Conflicts during the merge result in an error response, |
| 841 | + * the response type whether it was an error or success will be a MergeSummary instance. |
| 842 | + * When successful, the MergeSummary has all detail from a the PullSummary |
| 843 | + * |
| 844 | + * @param {Object | string[]} [options] |
| 845 | + * @param {Function} [then] |
| 846 | + * @returns {*} |
| 847 | + * |
| 848 | + * @see ./responses/MergeSummary.js |
| 849 | + * @see ./responses/PullSummary.js |
| 850 | + */ |
| 851 | + Git.prototype.merge = function (options, then) { |
| 852 | + var self = this; |
| 853 | + var userHandler = Git.trailingFunctionArgument(arguments) || NOOP; |
| 854 | + var mergeHandler = function (err, mergeSummary) { |
| 855 | + if (!err && mergeSummary.failed) { |
| 856 | + return Git.fail(self, mergeSummary, userHandler); |
| 857 | + } |
| 858 | + |
| 859 | + userHandler(err, mergeSummary); |
| 860 | + }; |
| 861 | + |
| 862 | + var command = []; |
| 863 | + Git._appendOptions(command, Git.trailingOptionsArgument(arguments)); |
| 864 | + command.push.apply(command, Git.trailingArrayArgument(arguments)); |
| 865 | + |
| 866 | + if (command[0] !== 'merge') { |
| 867 | + command.unshift('merge'); |
| 868 | + } |
| 869 | + |
| 870 | + if (command.length === 1) { |
| 871 | + return this.exec(function () { |
| 872 | + then && then(new TypeError("Git.merge requires at least one option")); |
| 873 | + }); |
| 874 | + } |
| 875 | + |
| 876 | + return this._run(command, Git._responseHandler(mergeHandler, 'MergeSummary'), { |
| 877 | + concatStdErr: true |
| 878 | + }); |
| 879 | + }; |
| 880 | + |
| 881 | + /** |
| 882 | + * Call any `git tag` function with arguments passed as an array of strings. |
| 883 | + * |
| 884 | + * @param {string[]} options |
| 885 | + * @param {Function} [then] |
| 886 | + */ |
| 887 | + Git.prototype.tag = function (options, then) { |
| 888 | + var command = []; |
| 889 | + Git._appendOptions(command, Git.trailingOptionsArgument(arguments)); |
| 890 | + command.push.apply(command, Git.trailingArrayArgument(arguments)); |
| 891 | + |
| 892 | + if (command[0] !== 'tag') { |
| 893 | + command.unshift('tag'); |
| 894 | + } |
| 895 | + |
| 896 | + return this._run(command, Git._responseHandler(Git.trailingFunctionArgument(arguments))); |
| 897 | + }; |
| 898 | + |
| 899 | + /** |
| 900 | + * Updates repository server info |
| 901 | + * |
| 902 | + * @param {Function} [then] |
| 903 | + */ |
| 904 | + Git.prototype.updateServerInfo = function (then) { |
| 905 | + return this._run(["update-server-info"], function (err, data) { |
| 906 | + then && then(err, !err && data); |
| 907 | + }); |
| 908 | + }; |
| 909 | + |
| 910 | + /** |
| 911 | + * Pushes the current committed changes to a remote, optionally specify the names of the remote and branch to use |
| 912 | + * when pushing. Supply multiple options as an array of strings in the first argument - see examples below. |
| 913 | + * |
| 914 | + * @param {string|string[]} [remote] |
| 915 | + * @param {string} [branch] |
| 916 | + * @param {Function} [then] |
| 917 | + */ |
| 918 | + Git.prototype.push = function (remote, branch, then) { |
| 919 | + var command = []; |
| 920 | + var handler = Git.trailingFunctionArgument(arguments); |
| 921 | + |
| 922 | + if (typeof remote === 'string' && typeof branch === 'string') { |
| 923 | + command.push(remote, branch); |
| 924 | + } |
| 925 | + |
| 926 | + if (Array.isArray(remote)) { |
| 927 | + command = command.concat(remote); |
| 928 | + } |
| 929 | + |
| 930 | + Git._appendOptions(command, Git.trailingOptionsArgument(arguments)); |
| 931 | + |
| 932 | + if (command[0] !== 'push') { |
| 933 | + command.unshift('push'); |
| 934 | + } |
| 935 | + |
| 936 | + return this._run(command, function (err, data) { |
| 937 | + handler && handler(err, !err && data); |
| 938 | + }); |
| 939 | + }; |
| 940 | + |
| 941 | + /** |
| 942 | + * Pushes the current tag changes to a remote which can be either a URL or named remote. When not specified uses the |
| 943 | + * default configured remote spec. |
| 944 | + * |
| 945 | + * @param {string} [remote] |
| 946 | + * @param {Function} [then] |
| 947 | + */ |
| 948 | + Git.prototype.pushTags = function (remote, then) { |
| 949 | + var command = ['push']; |
| 950 | + if (typeof remote === "string") { |
| 951 | + command.push(remote); |
| 952 | + } |
| 953 | + command.push('--tags'); |
| 954 | + |
| 955 | + then = typeof arguments[arguments.length - 1] === "function" ? arguments[arguments.length - 1] : null; |
| 956 | + |
| 957 | + return this._run(command, function (err, data) { |
| 958 | + then && then(err, !err && data); |
| 959 | + }); |
| 960 | + }; |
| 961 | + |
| 962 | + /** |
| 963 | + * Removes the named files from source control. |
| 964 | + * |
| 965 | + * @param {string|string[]} files |
| 966 | + * @param {Function} [then] |
| 967 | + */ |
| 968 | + Git.prototype.rm = function (files, then) { |
| 969 | + return this._rm(files, '-f', then); |
| 970 | + }; |
| 971 | + |
| 972 | + /** |
| 973 | + * Removes the named files from source control but keeps them on disk rather than deleting them entirely. To |
| 974 | + * completely remove the files, use `rm`. |
| 975 | + * |
| 976 | + * @param {string|string[]} files |
| 977 | + * @param {Function} [then] |
| 978 | + */ |
| 979 | + Git.prototype.rmKeepLocal = function (files, then) { |
| 980 | + return this._rm(files, '--cached', then); |
| 981 | + }; |
| 982 | + |
| 983 | + /** |
| 984 | + * Returns a list of objects in a tree based on commit hash. Passing in an object hash returns the object's content, |
| 985 | + * size, and type. |
| 986 | + * |
| 987 | + * Passing "-p" will instruct cat-file to determine the object type, and display its formatted contents. |
| 988 | + * |
| 989 | + * @param {string[]} [options] |
| 990 | + * @param {Function} [then] |
| 991 | + */ |
| 992 | + Git.prototype.catFile = function (options, then) { |
| 993 | + return this._catFile('utf-8', arguments); |
| 994 | + }; |
| 995 | + |
| 996 | + /** |
| 997 | + * Equivalent to `catFile` but will return the native `Buffer` of content from the git command's stdout. |
| 998 | + * |
| 999 | + * @param {string[]} options |
| 1000 | + * @param then |
| 1001 | + */ |
| 1002 | + Git.prototype.binaryCatFile = function (options, then) { |
| 1003 | + return this._catFile('buffer', arguments); |
| 1004 | + }; |
| 1005 | + |
| 1006 | + Git.prototype._catFile = function (format, args) { |
| 1007 | + var handler = Git.trailingFunctionArgument(args); |
| 1008 | + var command = ['cat-file']; |
| 1009 | + var options = args[0]; |
| 1010 | + |
| 1011 | + if (typeof options === 'string') { |
| 1012 | + throw new TypeError('Git#catFile: options must be supplied as an array of strings'); |
| 1013 | + } |
| 1014 | + else if (Array.isArray(options)) { |
| 1015 | + command.push.apply(command, options); |
| 1016 | + } |
| 1017 | + |
| 1018 | + return this._run(command, function (err, data) { |
| 1019 | + handler && handler(err, data); |
| 1020 | + }, { |
| 1021 | + format: format |
| 1022 | + }); |
| 1023 | + }; |
| 1024 | + |
| 1025 | + /** |
| 1026 | + * Return repository changes. |
| 1027 | + * |
| 1028 | + * @param {string[]} [options] |
| 1029 | + * @param {Function} [then] |
| 1030 | + */ |
| 1031 | + Git.prototype.diff = function (options, then) { |
| 1032 | + var command = ['diff']; |
| 1033 | + |
| 1034 | + if (typeof options === 'string') { |
| 1035 | + command[0] += ' ' + options; |
| 1036 | + this._getLog('warn', |
| 1037 | + 'Git#diff: supplying options as a single string is now deprecated, switch to an array of strings'); |
| 1038 | + } |
| 1039 | + else if (Array.isArray(options)) { |
| 1040 | + command.push.apply(command, options); |
| 1041 | + } |
| 1042 | + |
| 1043 | + if (typeof arguments[arguments.length - 1] === 'function') { |
| 1044 | + then = arguments[arguments.length - 1]; |
| 1045 | + } |
| 1046 | + |
| 1047 | + return this._run(command, function (err, data) { |
| 1048 | + then && then(err, data); |
| 1049 | + }); |
| 1050 | + }; |
| 1051 | + |
| 1052 | + Git.prototype.diffSummary = function (options, then) { |
| 1053 | + var next = Git.trailingFunctionArgument(arguments); |
| 1054 | + var command = ['--stat=4096']; |
| 1055 | + |
| 1056 | + if (options && options !== next) { |
| 1057 | + command.push.apply(command, [].concat(options)); |
| 1058 | + } |
| 1059 | + |
| 1060 | + return this.diff(command, Git._responseHandler(next, 'DiffSummary')); |
| 1061 | + }; |
| 1062 | + |
| 1063 | + /** |
| 1064 | + * Wraps `git rev-parse`. Primarily used to convert friendly commit references (ie branch names) to SHA1 hashes. |
| 1065 | + * |
| 1066 | + * Options should be an array of string options compatible with the `git rev-parse` |
| 1067 | + * |
| 1068 | + * @param {string|string[]} [options] |
| 1069 | + * @param {Function} [then] |
| 1070 | + * |
| 1071 | + * @see https://git-scm.com/docs/git-rev-parse |
| 1072 | + */ |
| 1073 | + Git.prototype.revparse = function (options, then) { |
| 1074 | + var command = ['rev-parse']; |
| 1075 | + |
| 1076 | + if (typeof options === 'string') { |
| 1077 | + command = command + ' ' + options; |
| 1078 | + this._getLog('warn', |
| 1079 | + 'Git#revparse: supplying options as a single string is now deprecated, switch to an array of strings'); |
| 1080 | + } |
| 1081 | + else if (Array.isArray(options)) { |
| 1082 | + command.push.apply(command, options); |
| 1083 | + } |
| 1084 | + |
| 1085 | + if (typeof arguments[arguments.length - 1] === 'function') { |
| 1086 | + then = arguments[arguments.length - 1]; |
| 1087 | + } |
| 1088 | + |
| 1089 | + return this._run(command, function (err, data) { |
| 1090 | + then && then(err, err ? null : String(data).trim()); |
| 1091 | + }); |
| 1092 | + }; |
| 1093 | + |
| 1094 | + /** |
| 1095 | + * Show various types of objects, for example the file at a certain commit |
| 1096 | + * |
| 1097 | + * @param {string[]} [options] |
| 1098 | + * @param {Function} [then] |
| 1099 | + */ |
| 1100 | + Git.prototype.show = function (options, then) { |
| 1101 | + var args = [].slice.call(arguments, 0); |
| 1102 | + var handler = typeof args[args.length - 1] === "function" ? args.pop() : null; |
| 1103 | + var command = ['show']; |
| 1104 | + if (typeof options === 'string') { |
| 1105 | + command = command + ' ' + options; |
| 1106 | + this._getLog('warn', |
| 1107 | + 'Git#show: supplying options as a single string is now deprecated, switch to an array of strings'); |
| 1108 | + } |
| 1109 | + else if (Array.isArray(options)) { |
| 1110 | + command.push.apply(command, options); |
| 1111 | + } |
| 1112 | + |
| 1113 | + return this._run(command, function (err, data) { |
| 1114 | + handler && handler(err, !err && data); |
| 1115 | + }); |
| 1116 | + }; |
| 1117 | + |
| 1118 | + /** |
| 1119 | + * @param {string} mode Required parameter "n" or "f" |
| 1120 | + * @param {string[]} options |
| 1121 | + * @param {Function} [then] |
| 1122 | + */ |
| 1123 | + Git.prototype.clean = function (mode, options, then) { |
| 1124 | + var handler = Git.trailingFunctionArgument(arguments); |
| 1125 | + |
| 1126 | + if (typeof mode !== 'string' || !/[nf]/.test(mode)) { |
| 1127 | + return this.exec(function () { |
| 1128 | + handler && handler(new TypeError('Git clean mode parameter ("n" or "f") is required')); |
| 1129 | + }); |
| 1130 | + } |
| 1131 | + |
| 1132 | + if (/[^dfinqxX]/.test(mode)) { |
| 1133 | + return this.exec(function () { |
| 1134 | + handler && handler(new TypeError('Git clean unknown option found in ' + JSON.stringify(mode))); |
| 1135 | + }); |
| 1136 | + } |
| 1137 | + |
| 1138 | + var command = ['clean', '-' + mode]; |
| 1139 | + if (Array.isArray(options)) { |
| 1140 | + command = command.concat(options); |
| 1141 | + } |
| 1142 | + |
| 1143 | + if (command.some(interactiveMode)) { |
| 1144 | + return this.exec(function () { |
| 1145 | + handler && handler(new TypeError('Git clean interactive mode is not supported')); |
| 1146 | + }); |
| 1147 | + } |
| 1148 | + |
| 1149 | + return this._run(command, function (err, data) { |
| 1150 | + handler && handler(err, !err && data); |
| 1151 | + }); |
| 1152 | + |
| 1153 | + function interactiveMode (option) { |
| 1154 | + if (/^-[^\-]/.test(option)) { |
| 1155 | + return option.indexOf('i') > 0; |
| 1156 | + } |
| 1157 | + |
| 1158 | + return option === '--interactive'; |
| 1159 | + } |
| 1160 | + }; |
| 1161 | + |
| 1162 | + /** |
| 1163 | + * Call a simple function at the next step in the chain. |
| 1164 | + * @param {Function} [then] |
| 1165 | + */ |
| 1166 | + Git.prototype.exec = function (then) { |
| 1167 | + this._run([], function () { |
| 1168 | + typeof then === 'function' && then(); |
| 1169 | + }); |
| 1170 | + return this; |
| 1171 | + }; |
| 1172 | + |
| 1173 | + /** |
| 1174 | + * Deprecated means of adding a regular function call at the next step in the chain. Use the replacement |
| 1175 | + * Git#exec, the Git#then method will be removed in version 2.x |
| 1176 | + * |
| 1177 | + * @see exec |
| 1178 | + * @deprecated |
| 1179 | + */ |
| 1180 | + Git.prototype.then = function (then) { |
| 1181 | + this._getLog( |
| 1182 | + 'error', ` |
| 1183 | +Git#then is deprecated after version 1.72 and will be removed in version 2.x |
| 1184 | +To use promises switch to importing 'simple-git/promise'.`); |
| 1185 | + |
| 1186 | + return this.exec(then); |
| 1187 | + }; |
| 1188 | + |
| 1189 | + /** |
| 1190 | + * Show commit logs from `HEAD` to the first commit. |
| 1191 | + * If provided between `options.from` and `options.to` tags or branch. |
| 1192 | + * |
| 1193 | + * Additionally you can provide options.file, which is the path to a file in your repository. Then only this file will be considered. |
| 1194 | + * |
| 1195 | + * To use a custom splitter in the log format, set `options.splitter` to be the string the log should be split on. |
| 1196 | + * |
| 1197 | + * Options can also be supplied as a standard options object for adding custom properties supported by the git log command. |
| 1198 | + * For any other set of options, supply options as an array of strings to be appended to the git log command. |
| 1199 | + * |
| 1200 | + * @param {Object|string[]} [options] |
| 1201 | + * @param {string} [options.from] The first commit to include |
| 1202 | + * @param {string} [options.to] The most recent commit to include |
| 1203 | + * @param {string} [options.file] A single file to include in the result |
| 1204 | + * @param {boolean} [options.multiLine] Optionally include multi-line commit messages |
| 1205 | + * |
| 1206 | + * @param {Function} [then] |
| 1207 | + */ |
| 1208 | + Git.prototype.log = function (options, then) { |
| 1209 | + var handler = Git.trailingFunctionArgument(arguments); |
| 1210 | + var opt = (handler === then ? options : null) || {}; |
| 1211 | + |
| 1212 | + var splitter = opt.splitter || requireResponseHandler('ListLogSummary').SPLITTER; |
| 1213 | + var format = opt.format || { |
| 1214 | + hash: '%H', |
| 1215 | + date: '%ai', |
| 1216 | + message: '%s', |
| 1217 | + refs: '%D', |
| 1218 | + body: opt.multiLine ? '%B' : '%b', |
| 1219 | + author_name: '%aN', |
| 1220 | + author_email: '%ae' |
| 1221 | + }; |
| 1222 | + var rangeOperator = (opt.symmetric !== false) ? '...' : '..'; |
| 1223 | + |
| 1224 | + var fields = Object.keys(format); |
| 1225 | + var formatstr = fields.map(function (k) { |
| 1226 | + return format[k]; |
| 1227 | + }).join(splitter); |
| 1228 | + var suffix = []; |
| 1229 | + var command = ["log", "--pretty=format:" |
| 1230 | + + requireResponseHandler('ListLogSummary').START_BOUNDARY |
| 1231 | + + formatstr |
| 1232 | + + requireResponseHandler('ListLogSummary').COMMIT_BOUNDARY |
| 1233 | + ]; |
| 1234 | + |
| 1235 | + if (Array.isArray(opt)) { |
| 1236 | + command = command.concat(opt); |
| 1237 | + opt = {}; |
| 1238 | + } |
| 1239 | + else if (typeof arguments[0] === "string" || typeof arguments[1] === "string") { |
| 1240 | + this._getLog('warn', |
| 1241 | + 'Git#log: supplying to or from as strings is now deprecated, switch to an options configuration object'); |
| 1242 | + opt = { |
| 1243 | + from: arguments[0], |
| 1244 | + to: arguments[1] |
| 1245 | + }; |
| 1246 | + } |
| 1247 | + |
| 1248 | + if (opt.n || opt['max-count']) { |
| 1249 | + command.push("--max-count=" + (opt.n || opt['max-count'])); |
| 1250 | + } |
| 1251 | + |
| 1252 | + if (opt.from && opt.to) { |
| 1253 | + command.push(opt.from + rangeOperator + opt.to); |
| 1254 | + } |
| 1255 | + |
| 1256 | + if (opt.file) { |
| 1257 | + suffix.push("--follow", options.file); |
| 1258 | + } |
| 1259 | + |
| 1260 | + 'splitter n max-count file from to --pretty format symmetric multiLine'.split(' ').forEach(function (key) { |
| 1261 | + delete opt[key]; |
| 1262 | + }); |
| 1263 | + |
| 1264 | + Git._appendOptions(command, opt); |
| 1265 | + |
| 1266 | + return this._run( |
| 1267 | + command.concat(suffix), |
| 1268 | + Git._responseHandler(handler, 'ListLogSummary', [splitter, fields]) |
| 1269 | + ); |
| 1270 | + }; |
| 1271 | + |
| 1272 | + /** |
| 1273 | + * Clears the queue of pending commands and returns the wrapper instance for chaining. |
| 1274 | + * |
| 1275 | + * @returns {Git} |
| 1276 | + */ |
| 1277 | + Git.prototype.clearQueue = function () { |
| 1278 | + this._runCache.length = 0; |
| 1279 | + return this; |
| 1280 | + }; |
| 1281 | + |
| 1282 | + /** |
| 1283 | + * Check if a pathname or pathnames are excluded by .gitignore |
| 1284 | + * |
| 1285 | + * @param {string|string[]} pathnames |
| 1286 | + * @param {Function} [then] |
| 1287 | + */ |
| 1288 | + Git.prototype.checkIgnore = function (pathnames, then) { |
| 1289 | + var handler = Git.trailingFunctionArgument(arguments); |
| 1290 | + var command = ["check-ignore"]; |
| 1291 | + |
| 1292 | + if (handler !== pathnames) { |
| 1293 | + command = command.concat(pathnames); |
| 1294 | + } |
| 1295 | + |
| 1296 | + return this._run(command, function (err, data) { |
| 1297 | + handler && handler(err, !err && this._parseCheckIgnore(data)); |
| 1298 | + }); |
| 1299 | + }; |
| 1300 | + |
| 1301 | + /** |
| 1302 | + * Validates that the current repo is a Git repo. |
| 1303 | + * |
| 1304 | + * @param {Function} [then] |
| 1305 | + */ |
| 1306 | + Git.prototype.checkIsRepo = function (then) { |
| 1307 | + function onError (exitCode, stdErr, done, fail) { |
| 1308 | + if (exitCode === 128 && /(Not a git repository|Kein Git-Repository)/i.test(stdErr)) { |
| 1309 | + return done(false); |
| 1310 | + } |
| 1311 | + |
| 1312 | + fail(stdErr); |
| 1313 | + } |
| 1314 | + |
| 1315 | + function handler (err, isRepo) { |
| 1316 | + then && then(err, String(isRepo).trim() === 'true'); |
| 1317 | + } |
| 1318 | + |
| 1319 | + return this._run(['rev-parse', '--is-inside-work-tree'], handler, {onError: onError}); |
| 1320 | + }; |
| 1321 | + |
| 1322 | + Git.prototype._rm = function (_files, options, then) { |
| 1323 | + var files = [].concat(_files); |
| 1324 | + var args = ['rm', options]; |
| 1325 | + args.push.apply(args, files); |
| 1326 | + |
| 1327 | + return this._run(args, function (err) { |
| 1328 | + then && then(err); |
| 1329 | + }); |
| 1330 | + }; |
| 1331 | + |
| 1332 | + Git.prototype._parseCheckout = function (checkout) { |
| 1333 | + // TODO |
| 1334 | + }; |
| 1335 | + |
| 1336 | + /** |
| 1337 | + * Parser for the `check-ignore` command - returns each |
| 1338 | + * @param {string} [files] |
| 1339 | + * @returns {string[]} |
| 1340 | + */ |
| 1341 | + Git.prototype._parseCheckIgnore = function (files) { |
| 1342 | + return files.split(/\n/g).filter(Boolean).map(function (file) { |
| 1343 | + return file.trim() |
| 1344 | + }); |
| 1345 | + }; |
| 1346 | + |
| 1347 | + /** |
| 1348 | + * Schedules the supplied command to be run, the command should not include the name of the git binary and should |
| 1349 | + * be an array of strings passed as the arguments to the git binary. |
| 1350 | + * |
| 1351 | + * @param {string[]} command |
| 1352 | + * @param {Function} then |
| 1353 | + * @param {Object} [opt] |
| 1354 | + * @param {boolean} [opt.concatStdErr=false] Optionally concatenate stderr output into the stdout |
| 1355 | + * @param {boolean} [opt.format="utf-8"] The format to use when reading the content of stdout |
| 1356 | + * @param {Function} [opt.onError] Optional error handler for this command - can be used to allow non-clean exits |
| 1357 | + * without killing the remaining stack of commands |
| 1358 | + * @param {number} [opt.onError.exitCode] |
| 1359 | + * @param {string} [opt.onError.stdErr] |
| 1360 | + * |
| 1361 | + * @returns {Git} |
| 1362 | + */ |
| 1363 | + Git.prototype._run = function (command, then, opt) { |
| 1364 | + if (typeof command === "string") { |
| 1365 | + command = command.split(" "); |
| 1366 | + } |
| 1367 | + this._runCache.push([command, then, opt || {}]); |
| 1368 | + this._schedule(); |
| 1369 | + |
| 1370 | + return this; |
| 1371 | + }; |
| 1372 | + |
| 1373 | + Git.prototype._schedule = function () { |
| 1374 | + if (!this._childProcess && this._runCache.length) { |
| 1375 | + var git = this; |
| 1376 | + var Buffer = git.Buffer; |
| 1377 | + var task = git._runCache.shift(); |
| 1378 | + |
| 1379 | + var command = task[0]; |
| 1380 | + var then = task[1]; |
| 1381 | + var options = task[2]; |
| 1382 | + |
| 1383 | + debug(command); |
| 1384 | + |
| 1385 | + var result = deferred(); |
| 1386 | + |
| 1387 | + var attempted = false; |
| 1388 | + var attemptClose = function attemptClose (e) { |
| 1389 | + |
| 1390 | + // closing when there is content, terminate immediately |
| 1391 | + if (attempted || stdErr.length || stdOut.length) { |
| 1392 | + result.resolve(e); |
| 1393 | + attempted = true; |
| 1394 | + } |
| 1395 | + |
| 1396 | + // first attempt at closing but no content yet, wait briefly for the close/exit that may follow |
| 1397 | + if (!attempted) { |
| 1398 | + attempted = true; |
| 1399 | + setTimeout(attemptClose.bind(this, e), 50); |
| 1400 | + } |
| 1401 | + |
| 1402 | + }; |
| 1403 | + |
| 1404 | + var stdOut = []; |
| 1405 | + var stdErr = []; |
| 1406 | + var spawned = git.ChildProcess.spawn(git._command, command.slice(0), { |
| 1407 | + cwd: git._baseDir, |
| 1408 | + env: git._env, |
| 1409 | + windowsHide: true |
| 1410 | + }); |
| 1411 | + |
| 1412 | + spawned.stdout.on('data', function (buffer) { |
| 1413 | + stdOut.push(buffer); |
| 1414 | + }); |
| 1415 | + |
| 1416 | + spawned.stderr.on('data', function (buffer) { |
| 1417 | + stdErr.push(buffer); |
| 1418 | + }); |
| 1419 | + |
| 1420 | + spawned.on('error', function (err) { |
| 1421 | + stdErr.push(Buffer.from(err.stack, 'ascii')); |
| 1422 | + }); |
| 1423 | + |
| 1424 | + spawned.on('close', attemptClose); |
| 1425 | + spawned.on('exit', attemptClose); |
| 1426 | + |
| 1427 | + result.promise.then(function (exitCode) { |
| 1428 | + function done (output) { |
| 1429 | + then.call(git, null, output); |
| 1430 | + } |
| 1431 | + |
| 1432 | + function fail (error) { |
| 1433 | + Git.fail(git, error, then); |
| 1434 | + } |
| 1435 | + |
| 1436 | + delete git._childProcess; |
| 1437 | + |
| 1438 | + if (exitCode && stdErr.length && options.onError) { |
| 1439 | + options.onError(exitCode, Buffer.concat(stdErr).toString('utf-8'), done, fail); |
| 1440 | + } |
| 1441 | + else if (exitCode && stdErr.length) { |
| 1442 | + fail(Buffer.concat(stdErr).toString('utf-8')); |
| 1443 | + } |
| 1444 | + else { |
| 1445 | + if (options.concatStdErr) { |
| 1446 | + [].push.apply(stdOut, stdErr); |
| 1447 | + } |
| 1448 | + |
| 1449 | + var stdOutput = Buffer.concat(stdOut); |
| 1450 | + if (options.format !== 'buffer') { |
| 1451 | + stdOutput = stdOutput.toString(options.format || 'utf-8'); |
| 1452 | + } |
| 1453 | + |
| 1454 | + done(stdOutput); |
| 1455 | + } |
| 1456 | + |
| 1457 | + process.nextTick(git._schedule.bind(git)); |
| 1458 | + }); |
| 1459 | + |
| 1460 | + git._childProcess = spawned; |
| 1461 | + |
| 1462 | + if (git._outputHandler) { |
| 1463 | + git._outputHandler(command[0], git._childProcess.stdout, git._childProcess.stderr); |
| 1464 | + } |
| 1465 | + } |
| 1466 | + }; |
| 1467 | + |
| 1468 | + /** |
| 1469 | + * Handles an exception in the processing of a command. |
| 1470 | + */ |
| 1471 | + Git.fail = function (git, error, handler) { |
| 1472 | + git._getLog('error', error); |
| 1473 | + git._runCache.length = 0; |
| 1474 | + if (typeof handler === 'function') { |
| 1475 | + handler.call(git, error, null); |
| 1476 | + } |
| 1477 | + }; |
| 1478 | + |
| 1479 | + /** |
| 1480 | + * Given any number of arguments, returns the last argument if it is a function, otherwise returns null. |
| 1481 | + * @returns {Function|null} |
| 1482 | + */ |
| 1483 | + Git.trailingFunctionArgument = function (args) { |
| 1484 | + var trailing = args[args.length - 1]; |
| 1485 | + return (typeof trailing === "function") ? trailing : null; |
| 1486 | + }; |
| 1487 | + |
| 1488 | + /** |
| 1489 | + * Given any number of arguments, returns the trailing options argument, ignoring a trailing function argument |
| 1490 | + * if there is one. When not found, the return value is null. |
| 1491 | + * @returns {Object|null} |
| 1492 | + */ |
| 1493 | + Git.trailingOptionsArgument = function (args) { |
| 1494 | + var options = args[(args.length - (Git.trailingFunctionArgument(args) ? 2 : 1))]; |
| 1495 | + return Object.prototype.toString.call(options) === '[object Object]' ? options : null; |
| 1496 | + }; |
| 1497 | + |
| 1498 | + /** |
| 1499 | + * Given any number of arguments, returns the trailing options array argument, ignoring a trailing function argument |
| 1500 | + * if there is one. When not found, the return value is an empty array. |
| 1501 | + * @returns {Array} |
| 1502 | + */ |
| 1503 | + Git.trailingArrayArgument = function (args) { |
| 1504 | + var options = args[(args.length - (Git.trailingFunctionArgument(args) ? 2 : 1))]; |
| 1505 | + return Object.prototype.toString.call(options) === '[object Array]' ? options : []; |
| 1506 | + }; |
| 1507 | + |
| 1508 | + /** |
| 1509 | + * Mutates the supplied command array by merging in properties in the options object. When the |
| 1510 | + * value of the item in the options object is a string it will be concatenated to the key as |
| 1511 | + * a single `name=value` item, otherwise just the name will be used. |
| 1512 | + * |
| 1513 | + * @param {string[]} command |
| 1514 | + * @param {Object} options |
| 1515 | + * @private |
| 1516 | + */ |
| 1517 | + Git._appendOptions = function (command, options) { |
| 1518 | + if (options === null) { |
| 1519 | + return; |
| 1520 | + } |
| 1521 | + |
| 1522 | + Object.keys(options).forEach(function (key) { |
| 1523 | + var value = options[key]; |
| 1524 | + if (typeof value === 'string') { |
| 1525 | + command.push(key + '=' + value); |
| 1526 | + } |
| 1527 | + else { |
| 1528 | + command.push(key); |
| 1529 | + } |
| 1530 | + }); |
| 1531 | + }; |
| 1532 | + |
| 1533 | + /** |
| 1534 | + * Given the type of response and the callback to receive the parsed response, |
| 1535 | + * uses the correct parser and calls back the callback. |
| 1536 | + * |
| 1537 | + * @param {Function} callback |
| 1538 | + * @param {string} [type] |
| 1539 | + * @param {Object[]} [args] |
| 1540 | + * |
| 1541 | + * @private |
| 1542 | + */ |
| 1543 | + Git._responseHandler = function (callback, type, args) { |
| 1544 | + return function (error, data) { |
| 1545 | + if (typeof callback !== 'function') { |
| 1546 | + return; |
| 1547 | + } |
| 1548 | + |
| 1549 | + if (error) { |
| 1550 | + return callback(error, null); |
| 1551 | + } |
| 1552 | + |
| 1553 | + if (!type) { |
| 1554 | + return callback(null, data); |
| 1555 | + } |
| 1556 | + |
| 1557 | + var handler = requireResponseHandler(type); |
| 1558 | + var result = handler.parse.apply(handler, [data].concat(args === undefined ? [] : args)); |
| 1559 | + |
| 1560 | + callback(null, result); |
| 1561 | + }; |
| 1562 | + |
| 1563 | + }; |
| 1564 | + |
| 1565 | + /** |
| 1566 | + * Marks the git instance as having had a fatal exception by clearing the pending queue of tasks and |
| 1567 | + * logging to the console. |
| 1568 | + * |
| 1569 | + * @param git |
| 1570 | + * @param error |
| 1571 | + * @param callback |
| 1572 | + */ |
| 1573 | + Git.exception = function (git, error, callback) { |
| 1574 | + git._runCache.length = 0; |
| 1575 | + if (typeof callback === 'function') { |
| 1576 | + callback(error instanceof Error ? error : new Error(error)); |
| 1577 | + } |
| 1578 | + |
| 1579 | + git._getLog('error', error); |
| 1580 | + }; |
| 1581 | + |
| 1582 | + module.exports = Git; |
| 1583 | + |
| 1584 | + /** |
| 1585 | + * Requires and returns a response handler based on its named type |
| 1586 | + * @param {string} type |
| 1587 | + */ |
| 1588 | + function requireResponseHandler (type) { |
| 1589 | + return responses[type]; |
| 1590 | + } |
| 1591 | + |
| 1592 | +}()); |