UNPKG

nodegit

Version:

Node.js libgit2 asynchronous native bindings

1,591 lines (1,390 loc) 54.3 kB
"use strict"; var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); function _toArray(arr) { return Array.isArray(arr) ? arr : Array.from(arr); } var fse = require("fs-extra"); var fp = require("lodash/fp"); var NodeGit = require("../"); var Blob = NodeGit.Blob; var Checkout = NodeGit.Checkout; var Commit = NodeGit.Commit; var normalizeOptions = NodeGit.Utils.normalizeOptions; var shallowClone = NodeGit.Utils.shallowClone; var path = require("path"); var Filter = NodeGit.Filter; var FilterList = NodeGit.FilterList; var Reference = NodeGit.Reference; var Remote = NodeGit.Remote; var Repository = NodeGit.Repository; var Revwalk = NodeGit.Revwalk; var Status = NodeGit.Status; var StatusFile = NodeGit.StatusFile; var StatusList = NodeGit.StatusList; var Submodule = NodeGit.Submodule; var Tag = NodeGit.Tag; var Tree = NodeGit.Tree; var TreeBuilder = NodeGit.Treebuilder; var _discover = Repository.discover; var _initExt = Repository.initExt; var _fetchheadForeach = Repository.prototype.fetchheadForeach; var _mergeheadForeach = Repository.prototype.mergeheadForeach; function applySelectedLinesToTarget(originalContent, newLines, pathHunks, isStaged, reverse) { // 43: ascii code for '+' // 45: ascii code for '-' var lineTypes = { ADDED: !reverse ? 43 : 45, DELETED: !reverse ? 45 : 43 }; var newContent = ""; var oldIndex = 0; var linesPromises = []; var oldLines = originalContent.toString().split("\n"); // if no selected lines were sent, return the original content if (!newLines || newLines.length === 0) { return originalContent; } function lineEqualsFirstNewLine(hunkLine) { return hunkLine.oldLineno() === newLines[0].oldLineno() && hunkLine.newLineno() === newLines[0].newLineno(); } function processSelectedLine(hunkLine) { // if this hunk line is a selected line find the selected line var newLine = newLines.filter(function (nLine) { return hunkLine.oldLineno() === nLine.oldLineno() && hunkLine.newLineno() === nLine.newLineno(); }); if (hunkLine.content().indexOf("\\ No newline at end of file") !== -1) { return false; } // determine what to add to the new content if (isStaged && newLine && newLine.length > 0 || !isStaged && (!newLine || newLine.length === 0)) { if (hunkLine.origin() !== lineTypes.ADDED) { newContent += hunkLine.content(); } if (isStaged && hunkLine.origin() !== lineTypes.DELETED || !isStaged && hunkLine.origin() !== lineTypes.ADDED) { oldIndex++; } } else { switch (hunkLine.origin()) { case lineTypes.ADDED: newContent += hunkLine.content(); if (isStaged) { oldIndex++; } break; case lineTypes.DELETED: if (!isStaged) { oldIndex++; } break; default: newContent += oldLines[oldIndex++]; if (oldIndex < oldLines.length) { newContent += "\n"; } break; } } } // find the affected hunk pathHunks.forEach(function (pathHunk) { linesPromises.push(pathHunk.lines()); }); return Promise.all(linesPromises).then(function (results) { for (var i = 0; i < results.length && newContent.length < 1; i++) { var hunkStart = isStaged || reverse ? pathHunks[i].newStart() : pathHunks[i].oldStart(); var lines = results[i]; if (lines.filter(lineEqualsFirstNewLine).length > 0) { // add content that is before the hunk while (hunkStart > oldIndex + 1) { newContent += oldLines[oldIndex++] + "\n"; } // modify the lines of the hunk according to the selection lines.forEach(processSelectedLine); // add the rest of the file while (oldLines.length > oldIndex) { newContent += oldLines[oldIndex++] + (oldLines.length > oldIndex ? "\n" : ""); } } } return newContent; }); } function getPathHunks(repo, index, filePath, isStaged, additionalDiffOptions) { var diffOptions = additionalDiffOptions ? { flags: additionalDiffOptions } : undefined; return Promise.resolve().then(function () { if (isStaged) { return repo.getHeadCommit().then(function getTreeFromCommit(commit) { return commit.getTree(); }).then(function getDiffFromTree(tree) { return NodeGit.Diff.treeToIndex(repo, tree, index, diffOptions); }); } return NodeGit.Diff.indexToWorkdir(repo, index, { flags: NodeGit.Diff.OPTION.SHOW_UNTRACKED_CONTENT | NodeGit.Diff.OPTION.RECURSE_UNTRACKED_DIRS | (additionalDiffOptions || 0) }); }).then(function (diff) { return NodeGit.Status.file(repo, filePath).then(function (status) { if (!(status & NodeGit.Status.STATUS.WT_MODIFIED) && !(status & NodeGit.Status.STATUS.INDEX_MODIFIED)) { return Promise.reject("Selected staging is only available on modified files."); } return diff.patches(); }); }).then(function (patches) { var pathPatch = patches.filter(function (patch) { return patch.newFile().path() === filePath; }); if (pathPatch.length !== 1) { return Promise.reject("No differences found for this file."); } return pathPatch[0].hunks(); }); } function getReflogMessageForCommit(commit) { var parentCount = commit.parentcount(); var summary = commit.summary(); var commitType; if (parentCount >= 2) { commitType = " (merge)"; } else if (parentCount == 0) { commitType = " (initial)"; } else { commitType = ""; } return "commit" + commitType + ": " + summary; } /** * Goes through a rebase's rebase operations and commits them if there are * no merge conflicts * * @param {Repository} repository The repository that the rebase is being * performed in * @param {Rebase} rebase The current rebase being performed * @param {Signature} signature Identity of the one performing the rebase * @param {Function} beforeNextFn Callback to be called before each * invocation of next(). If the callback * returns a promise, the next() will be * called when the promise resolves. * @param {Function} beforeFinishFn Callback called before the invocation * of finish(). If the callback returns a * promise, finish() will be called when the * promise resolves. This callback will be * provided a detailed overview of the rebase * @return {Int|Index} An error code for an unsuccesful rebase or an index for * a rebase with conflicts */ function performRebase(repository, rebase, signature, beforeNextFn, beforeFinishFn) { var beforeNextFnResult; /* In the case of FF merges and a beforeFinishFn, this will fail * when looking for 'rewritten' so we need to handle that case. */ function readRebaseMetadataFile(fileName, continueOnError) { return fse.readFile(path.join(repository.path(), "rebase-merge", fileName), { encoding: "utf8" }).then(fp.trim).catch(function (err) { if (continueOnError) { return null; } throw err; }); } function calcHeadName(input) { return input.replace(/refs\/heads\/(.*)/, "$1"); } function getPromise() { return rebase.next().then(function () { return repository.refreshIndex(); }).then(function (index) { if (index.hasConflicts()) { throw index; } return rebase.commit(null, signature); }).then(function () { return performRebase(repository, rebase, signature, beforeNextFn, beforeFinishFn); }).catch(function (error) { if (error && error.errno === NodeGit.Error.CODE.ITEROVER) { var calcRewritten = fp.cond([[fp.isEmpty, fp.constant(null)], [fp.stubTrue, fp.flow([fp.split("\n"), fp.map(fp.split(" "))])]]); var beforeFinishFnPromise = !beforeFinishFn ? Promise.resolve() : Promise.all([readRebaseMetadataFile("onto_name"), readRebaseMetadataFile("onto"), readRebaseMetadataFile("head-name").then(calcHeadName), readRebaseMetadataFile("orig-head"), readRebaseMetadataFile("rewritten", true).then(calcRewritten)]).then(function (_ref) { var _ref2 = _slicedToArray(_ref, 5), ontoName = _ref2[0], ontoSha = _ref2[1], originalHeadName = _ref2[2], originalHeadSha = _ref2[3], rewritten = _ref2[4]; return beforeFinishFn({ ontoName: ontoName, ontoSha: ontoSha, originalHeadName: originalHeadName, originalHeadSha: originalHeadSha, rebase: rebase, rewritten: rewritten }); }); return beforeFinishFnPromise.then(function () { return rebase.finish(signature); }); } else { throw error; } }); } if (beforeNextFn) { beforeNextFnResult = beforeNextFn(rebase); // if beforeNextFn returns a promise, chain the promise return Promise.resolve(beforeNextFnResult).then(getPromise); } return getPromise(); } /** * Creates a branch with the passed in name pointing to the commit * * @async * @param {String} startPath The base path where the lookup starts. * @param {Number} acrossFs If non-zero, then the lookup will not stop when a filesystem device change is detected while exploring parent directories. * @param {String} ceilingDirs A list of absolute symbolic link free paths. the search will stop if any of these paths are hit. This may be set to null * @return {String} Path of the git repository */ Repository.discover = function (startPath, acrossFs, ceilingDirs, callback) { return _discover(startPath, acrossFs, ceilingDirs).then(function (foundPath) { foundPath = path.resolve(foundPath); if (typeof callback === "function") { callback(null, foundPath); } return foundPath; }, callback); }; // Override Repository.initExt to normalize initoptions Repository.initExt = function (repo_path, opts) { opts = normalizeOptions(opts, NodeGit.RepositoryInitOptions); return _initExt(repo_path, opts); }; Repository.getReferences = function (repo, type, refNamesOnly) { return repo.getReferences().then(function (refList) { var filteredRefList = refList.filter(function (reference) { return type === Reference.TYPE.ALL || reference.type === type; }); if (refNamesOnly) { return filteredRefList.map(function (reference) { return reference.name(); }); } return filteredRefList; }); }; /** * This will set the HEAD to point to the local branch and then attempt * to update the index and working tree to match the content of the * latest commit on that branch * * @async * @param {String|Reference} branch the branch to checkout * @param {Object|CheckoutOptions} opts the options to use for the checkout */ Repository.prototype.checkoutBranch = function (branch, opts) { var repo = this; return repo.getReference(branch).then(function (ref) { if (!ref.isBranch()) { return false; } return repo.checkoutRef(ref, opts); }); }; /** * This will set the HEAD to point to the reference and then attempt * to update the index and working tree to match the content of the * latest commit on that reference * * @async * @param {Reference} reference the reference to checkout * @param {Object|CheckoutOptions} opts the options to use for the checkout */ Repository.prototype.checkoutRef = function (reference, opts) { var repo = this; opts = opts || {}; opts.checkoutStrategy = opts.checkoutStrategy || NodeGit.Checkout.STRATEGY.SAFE | NodeGit.Checkout.STRATEGY.RECREATE_MISSING; return repo.getReferenceCommit(reference.name()).then(function (commit) { return commit.getTree(); }).then(function (tree) { return Checkout.tree(repo, tree, opts); }).then(function () { var name = reference.name(); return repo.setHead(name); }); }; /** * Continues an existing rebase * * @async * @param {Signature} signature Identity of the one performing the rebase * @param {Function} beforeNextFn Callback to be called before each step * of the rebase. If the callback returns a * promise, the rebase will resume when the * promise resolves. The rebase object is * is passed to the callback. * @param {Function} beforeFinishFn Callback called before the invocation * of finish(). If the callback returns a * promise, finish() will be called when the * promise resolves. This callback will be * provided a detailed overview of the rebase * @param {RebaseOptions} rebaseOptions Options to initialize the rebase object * with * @return {Oid|Index} A commit id for a succesful merge or an index for a * rebase with conflicts */ Repository.prototype.continueRebase = function (signature, beforeNextFn, beforeFinishFn, rebaseOptions) { var repo = this; var rebase = void 0; var promiseChain = Promise.resolve(); if (!signature) { promiseChain = promiseChain.then(function () { return repo.defaultSignature(); }).then(function (signatureResult) { signature = signatureResult; }); } return promiseChain.then(function () { return repo.refreshIndex(); }).then(function (index) { if (index.hasConflicts()) { throw index; } return NodeGit.Rebase.open(repo, rebaseOptions); }).then(function (_rebase) { rebase = _rebase; return rebase.commit(null, signature).catch(function (e) { // If the first commit on continueRebase is a // "patch already applied" error, // interpret that as an explicit "skip commit" // and ignore the error. var errno = fp.get(["errno"], e); if (errno === NodeGit.Error.CODE.EAPPLIED) { return; } throw e; }); }).then(function () { return performRebase(repo, rebase, signature, beforeNextFn, beforeFinishFn); }).then(function (error) { if (error) { throw error; } return repo.getBranchCommit("HEAD"); }); }; /** * Creates a branch with the passed in name pointing to the commit * * @async * @param {String} name Branch name, e.g. "master" * @param {Commit|String|Oid} commit The commit the branch will point to * @param {Boolean} force Overwrite branch if it exists * @return {Reference} */ Repository.prototype.createBranch = function (name, commit, force) { var repo = this; if (commit instanceof Commit) { return NodeGit.Branch.create(repo, name, commit, force ? 1 : 0); } else { return repo.getCommit(commit).then(function (commit) { return NodeGit.Branch.create(repo, name, commit, force ? 1 : 0); }); } }; /** * Create a blob from a buffer * * @async * @param {Buffer} buffer * @return {Oid} */ Repository.prototype.createBlobFromBuffer = function (buffer) { return Blob.createFromBuffer(this, buffer, buffer.length); }; /** * Create a commit * * @async * @param {String} updateRef * @param {Signature} author * @param {Signature} committer * @param {String} message * @param {Oid|String} Tree * @param {Array} parents * @return {Oid} The oid of the commit */ Repository.prototype.createCommit = function (updateRef, author, committer, message, tree, parents, callback) { var repo = this; var promises = []; parents = parents || []; promises.push(repo.getTree(tree)); parents.forEach(function (parent) { promises.push(repo.getCommit(parent)); }); return Promise.all(promises).then(function (results) { tree = results[0]; // Get the normalized values for our input into the function var parentsLength = parents.length; parents = []; for (var i = 0; i < parentsLength; i++) { parents.push(results[i + 1]); } return Commit.create(repo, updateRef, author, committer, null /* use default message encoding */ , message, tree, parents.length, parents); }).then(function (commit) { if (typeof callback === "function") { callback(null, commit); } return commit; }, callback); }; /** * Create a commit * * @async * @param {Signature} author * @param {Signature} committer * @param {String} message * @param {Oid|String} treeOid * @param {Array} parents * @return {String} The content of the commit object * as a string */ Repository.prototype.createCommitBuffer = function (author, committer, message, treeOid, parents) { var repo = this; var promises = (parents || []).reduce(function (acc, parent) { acc.push(repo.getCommit(parent)); return acc; }, [repo.getTree(treeOid)]); return Promise.all(promises).then(function (_ref3) { var _ref4 = _toArray(_ref3), tree = _ref4[0], parentCommits = _ref4.slice(1); return Commit.createBuffer(repo, author, committer, null /* use default message encoding */ , message, tree, parentCommits.length, parentCommits); }); }; /** * Create a commit that is digitally signed * * @async * @param {String} updateRef * @param {Signature} author * @param {Signature} committer * @param {String} message * @param {Tree|Oid|String} Tree * @param {Array} parents * @param {Function} onSignature Callback to be called with string to be signed * @return {Oid} The oid of the commit */ Repository.prototype.createCommitWithSignature = function (updateRef, author, committer, message, tree, parents, onSignature) { var repo = this; var promises = []; var commitContent; var skippedSigning; parents = parents || []; promises.push(repo.getTree(tree)); parents.forEach(function (parent) { promises.push(repo.getCommit(parent)); }); var createCommitPromise = Promise.all(promises).then(function (results) { tree = results[0]; // Get the normalized values for our input into the function var parentsLength = parents.length; parents = []; for (var i = 0; i < parentsLength; i++) { parents.push(results[i + 1]); } return Commit.createBuffer(repo, author, committer, null /* use default message encoding */ , message, tree, parents.length, parents); }).then(function (commitContentResult) { commitContent = commitContentResult; if (!commitContent.endsWith("\n")) { commitContent += "\n"; } return onSignature(commitContent); }).then(function (_ref5) { var code = _ref5.code, field = _ref5.field, signedData = _ref5.signedData; switch (code) { case NodeGit.Error.CODE.OK: return Commit.createWithSignature(repo, commitContent, signedData, field); case NodeGit.Error.CODE.PASSTHROUGH: skippedSigning = true; return Commit.create(repo, updateRef, author, committer, null /* use default message encoding */ , message, tree, parents.length, parents); default: { var error = new Error("Repository.prototype.createCommitWithSignature " + ("threw with error code " + code)); error.errno = code; throw error; } } }); if (!updateRef) { return createCommitPromise; } return createCommitPromise.then(function (commitOid) { if (skippedSigning) { return commitOid; } return repo.getCommit(commitOid).then(function (commitResult) { return Reference.updateTerminal(repo, updateRef, commitOid, getReflogMessageForCommit(commitResult), committer); }).then(function () { return commitOid; }); }); }; /** * Creates a new commit on HEAD from the list of passed in files * * @async * @param {Array} filesToAdd * @param {Signature} author * @param {Signature} committer * @param {String} message * @return {Oid} The oid of the new commit */ Repository.prototype.createCommitOnHead = function (filesToAdd, author, committer, message, callback) { var repo = this; return repo.refreshIndex().then(function (index) { if (!filesToAdd) { filesToAdd = []; } return filesToAdd.reduce(function (lastFilePromise, filePath) { return lastFilePromise.then(function () { return index.addByPath(filePath); }); }, Promise.resolve()).then(function () { return index.write(); }).then(function () { return index.writeTree(); }); }).then(function (treeOid) { return repo.getHeadCommit().then(function (parent) { if (parent !== null) { // To handle a fresh repo with no commits parent = [parent]; } return repo.createCommit("HEAD", author, committer, message, treeOid, parent); }); }, callback); }; /** * Creates a new lightweight tag * * @async * @param {String|Oid} String sha or Oid * @param {String} name the name of the tag * @return {Reference} */ Repository.prototype.createLightweightTag = function (oid, name, callback) { var repository = this; return Commit.lookup(repository, oid).then(function (commit) { // Final argument is `force` which overwrites any previous tag return Tag.createLightweight(repository, name, commit, 0); }).then(function () { return Reference.lookup(repository, "refs/tags/" + name); }); }; /** * Instantiate a new revision walker for browsing the Repository"s history. * See also `Commit.prototype.history()` * * @return {Revwalk} */ Repository.prototype.createRevWalk = function () { return Revwalk.create(this); }; /** * Creates a new annotated tag * * @async * @param {String|Oid} String sha or Oid * @param {String} name the name of the tag * @param {String} message the description that will be attached to the * annotated tag * @return {Tag} */ Repository.prototype.createTag = function (oid, name, message, callback) { var repository = this; var signature = null; return repository.defaultSignature().then(function (signatureResult) { signature = signatureResult; return Commit.lookup(repository, oid); }).then(function (commit) { // Final argument is `force` which overwrites any previous tag return Tag.create(repository, name, commit, signature, message, 0); }).then(function (tagOid) { return repository.getTag(tagOid, callback); }); }; /** * Gets the default signature for the default user and now timestamp * * @async * @return {Signature} */ Repository.prototype.defaultSignature = function () { return NodeGit.Signature.default(this).then(function (result) { if (!result || !result.name()) { result = NodeGit.Signature.now("unknown", "unknown@example.com"); } return result; }).catch(function () { return NodeGit.Signature.now("unknown", "unknown@example.com"); }); }; /** * Deletes a tag from a repository by the tag name. * * @async * @param {String} Short or full tag name */ Repository.prototype.deleteTagByName = function (name) { var repository = this; name = ~name.indexOf("refs/tags/") ? name.substr(10) : name; return Tag.delete(repository, name); }; /** * Discard line selection of a specified file. * Assumes selected lines are unstaged. * * @async * @param {String} filePath The relative path of this file in the repo * @param {Array} selectedLines The array of DiffLine objects * selected for discarding * @return {Number} 0 or an error code */ Repository.prototype.discardLines = function (filePath, selectedLines, additionalDiffOptions) { var repo = this; var fullFilePath = path.join(repo.workdir(), filePath); var index; var originalContent; var filterList; return repo.refreshIndex().then(function (indexResult) { index = indexResult; return FilterList.load(repo, null, filePath, Filter.MODE.CLEAN, Filter.FLAG.DEFAULT); }).then(function (_filterList) { filterList = _filterList; if (filterList) { return filterList.applyToFile(repo, filePath); } return fse.readFile(fullFilePath, "utf8"); }).then(function (content) { originalContent = content; return getPathHunks(repo, index, filePath, false, additionalDiffOptions); }).then(function (hunks) { return applySelectedLinesToTarget(originalContent, selectedLines, hunks, false, true); }).then(function (newContent) { return FilterList.load(repo, null, filePath, Filter.MODE.SMUDGE, Filter.FLAG.DEFAULT).then(function (_filterList) { filterList = _filterList; if (filterList) { /* jshint ignore:start */ // We need the constructor for the check in NodeGit's C++ layer // to accept an object, and this seems to be a nice way to do it return filterList.applyToData(new String(newContent)); /* jshint ignore:end */ } return newContent; }); }).then(function (filteredContent) { return fse.writeFile(fullFilePath, filteredContent); }); }; /** * Fetches from a remote * * @async * @param {String|Remote} remote * @param {Object|FetchOptions} fetchOptions Options for the fetch, includes * callbacks for fetching */ Repository.prototype.fetch = function (remote, fetchOptions, callback) { var repo = this; function finallyFn(error) { if (typeof callback === "function") { callback(error); } } return repo.getRemote(remote).then(function (remote) { return remote.fetch(null, fetchOptions, "Fetch from " + remote).then(function () { return remote.disconnect(); }); }).then(finallyFn).catch(function (error) { finallyFn(error); throw error; }); }; /** * Fetches from all remotes. This is done in series due to deadlocking issues * with fetching from many remotes that can happen. * * @async * @param {Object|FetchOptions} fetchOptions Options for the fetch, includes * callbacks for fetching * @param {Function} callback */ Repository.prototype.fetchAll = function (fetchOptions, callback) { var repo = this; function createCallbackWrapper(fn, remote) { return function () { var args = Array.prototype.slice.call(arguments); args.push(remote); return fn.apply(this, args); }.bind(this); } fetchOptions = fetchOptions || {}; var remoteCallbacks = fetchOptions.callbacks || {}; var credentials = remoteCallbacks.credentials; var certificateCheck = remoteCallbacks.certificateCheck; var transferProgress = remoteCallbacks.transferProgress; return repo.getRemoteNames().then(function (remotes) { return remotes.reduce(function (fetchPromise, remote) { var wrappedFetchOptions = shallowClone(fetchOptions); var wrappedRemoteCallbacks = shallowClone(remoteCallbacks); if (credentials) { wrappedRemoteCallbacks.credentials = createCallbackWrapper(credentials, remote); } if (certificateCheck) { wrappedRemoteCallbacks.certificateCheck = createCallbackWrapper(certificateCheck, remote); } if (transferProgress) { wrappedRemoteCallbacks.transferProgress = createCallbackWrapper(transferProgress, remote); } wrappedFetchOptions.callbacks = wrappedRemoteCallbacks; return fetchPromise.then(function () { return repo.fetch(remote, wrappedFetchOptions); }); }, Promise.resolve()); }).then(function () { if (typeof callback === "function") { callback(); } }); }; /** * @async * @param {FetchheadForeachCb} callback The callback function to be called on * each entry */ Repository.prototype.fetchheadForeach = function (callback) { return _fetchheadForeach.call(this, callback, null); }; /** * Retrieve the blob represented by the oid. * * @async * @param {String|Oid} String sha or Oid * @return {Blob} */ Repository.prototype.getBlob = function (oid, callback) { var repository = this; return Blob.lookup(repository, oid).then(function (blob) { blob.repo = repository; if (typeof callback === "function") { callback(null, blob); } return blob; }, callback); }; /** * Look up a branch. Alias for `getReference` * * @async * @param {String|Reference} name Ref name, e.g. "master", "refs/heads/master" * or Branch Ref * @return {Reference} */ Repository.prototype.getBranch = function (name, callback) { return this.getReference(name, callback); }; /** * Look up a branch's most recent commit. Alias to `getReferenceCommit` * * @async * @param {String|Reference} name Ref name, e.g. "master", "refs/heads/master" * or Branch Ref * @return {Commit} */ Repository.prototype.getBranchCommit = function (name, callback) { return this.getReferenceCommit(name, callback); }; /** * Retrieve the commit identified by oid. * * @async * @param {String|Oid} String sha or Oid * @return {Commit} */ Repository.prototype.getCommit = function (oid, callback) { var repository = this; return Commit.lookup(repository, oid).then(function (commit) { if (typeof callback === "function") { callback(null, commit); } return commit; }, callback); }; /** * Gets the branch that HEAD currently points to * Is an alias to head() * * @async * @return {Reference} */ Repository.prototype.getCurrentBranch = function () { return this.head(); }; /** * Retrieve the commit that HEAD is currently pointing to * * @async * @return {Commit} */ Repository.prototype.getHeadCommit = function (callback) { var repo = this; return Reference.nameToId(repo, "HEAD").then(function (head) { return repo.getCommit(head, callback); }).catch(function () { return null; }); }; /** * Retrieve the master branch commit. * * @async * @return {Commit} */ Repository.prototype.getMasterCommit = function (callback) { return this.getBranchCommit("master", callback); }; /** * Lookup the reference with the given name. * * @async * @param {String|Reference} name Ref name, e.g. "master", "refs/heads/master" * or Branch Ref * @return {Reference} */ Repository.prototype.getReference = function (name, callback) { var repository = this; return Reference.dwim(this, name).then(function (reference) { if (reference.isSymbolic()) { return reference.resolve().then(function (reference) { reference.repo = repository; if (typeof callback === "function") { callback(null, reference); } return reference; }, callback); } else { reference.repo = repository; if (typeof callback === "function") { callback(null, reference); } return reference; } }, callback); }; /** * Look up a refs's commit. * * @async * @param {String|Reference} name Ref name, e.g. "master", "refs/heads/master" * or Branch Ref * @return {Commit} */ Repository.prototype.getReferenceCommit = function (name, callback) { var repository = this; return this.getReference(name).then(function (reference) { return repository.getCommit(reference.target()).then(function (commit) { if (typeof callback === "function") { callback(null, commit); } return commit; }); }, callback); }; /** * Lookup reference names for a repository. * * @async * @param {Reference.TYPE} type Type of reference to look up * @return {Array<String>} */ Repository.prototype.getReferenceNames = function (type) { return Repository.getReferences(this, type, true); }; /** * Lookup references for a repository. * * @async * @param {Reference.TYPE} type Type of reference to look up * @return {Array<Reference>} */ /** * Gets a remote from the repo * * @async * @param {String|Remote} remote * @param {Function} callback * @return {Remote} The remote object */ Repository.prototype.getRemote = function (remote, callback) { if (remote instanceof NodeGit.Remote) { return Promise.resolve(remote).then(function (remoteObj) { if (typeof callback === "function") { callback(null, remoteObj); } return remoteObj; }, callback); } return NodeGit.Remote.lookup(this, remote).then(function (remoteObj) { if (typeof callback === "function") { callback(null, remoteObj); } return remoteObj; }, callback); }; /** * Lists out the remotes in the given repository. * * @async * @param {Function} Optional callback * @return {Object} Promise object. */ Repository.prototype.getRemoteNames = function (callback) { return Remote.list(this).then(function (remotes) { if (typeof callback === "function") { callback(null, remotes); } return remotes; }, callback); }; /** * Get the status of a repo to it's working directory * * @async * @param {obj} opts * @return {Array<StatusFile>} */ Repository.prototype.getStatus = function (opts) { var statuses = []; var statusCallback = function statusCallback(path, status) { statuses.push(new StatusFile({ path: path, status: status })); }; if (!opts) { opts = { flags: Status.OPT.INCLUDE_UNTRACKED | Status.OPT.RECURSE_UNTRACKED_DIRS }; } return Status.foreachExt(this, opts, statusCallback).then(function () { return statuses; }); }; /** * Get extended statuses of a repo to it's working directory. Status entries * have `status`, `headToIndex` delta, and `indexToWorkdir` deltas * * @async * @param {obj} opts * @return {Array<StatusFile>} */ Repository.prototype.getStatusExt = function (opts) { var statuses = []; if (!opts) { opts = { flags: Status.OPT.INCLUDE_UNTRACKED | Status.OPT.RECURSE_UNTRACKED_DIRS | Status.OPT.RENAMES_INDEX_TO_WORKDIR | Status.OPT.RENAMES_HEAD_TO_INDEX | Status.OPT.RENAMES_FROM_REWRITES }; } return StatusList.create(this, opts).then(function (list) { for (var i = 0; i < list.entrycount(); i++) { var entry = Status.byIndex(list, i); statuses.push(new StatusFile({ entry: entry })); } return statuses; }); }; /** * Get the names of the submodules in the repository. * * @async * @return {Array<String>} */ Repository.prototype.getSubmoduleNames = function (callback) { var names = []; var submoduleCallback = function submoduleCallback(submodule, name, payload) { names.push(name); }; return Submodule.foreach(this, submoduleCallback).then(function () { if (typeof callback === "function") { callback(null, names); } return names; }); }; /** * Retrieve the tag represented by the oid. * * @async * @param {String|Oid} String sha or Oid * @return {Tag} */ Repository.prototype.getTag = function (oid, callback) { var repository = this; return Tag.lookup(repository, oid).then(function (reference) { reference.repo = repository; if (typeof callback === "function") { callback(null, reference); } return reference; }, callback); }; /** * Retrieve the tag represented by the tag name. * * @async * @param {String} Short or full tag name * @return {Tag} */ Repository.prototype.getTagByName = function (name, callback) { var repo = this; name = ~name.indexOf("refs/tags/") ? name : "refs/tags/" + name; return Reference.nameToId(repo, name).then(function (oid) { return Tag.lookup(repo, oid).then(function (reference) { reference.repo = repo; if (typeof callback === "function") { callback(null, reference); } return reference; }); }, callback); }; /** * Retrieve the tree represented by the oid. * * @async * @param {String|Oid} String sha or Oid * @return {Tree} */ Repository.prototype.getTree = function (oid, callback) { var repository = this; return Tree.lookup(repository, oid).then(function (tree) { tree.repo = repository; if (typeof callback === "function") { callback(null, tree); } return tree; }, callback); }; /** * Returns true if the repository is in the APPLY_MAILBOX or * APPLY_MAILBOX_OR_REBASE state. * @return {Boolean} */ Repository.prototype.isApplyingMailbox = function () { var state = this.state(); return state === NodeGit.Repository.STATE.APPLY_MAILBOX || state === NodeGit.Repository.STATE.APPLY_MAILBOX_OR_REBASE; }; /** * Returns true if the repository is in the BISECT state. * @return {Boolean} */ Repository.prototype.isBisecting = function () { return this.state() === NodeGit.Repository.STATE.BISECT; }; /** * Returns true if the repository is in the CHERRYPICK state. * @return {Boolean} */ Repository.prototype.isCherrypicking = function () { return this.state() === NodeGit.Repository.STATE.CHERRYPICK; }; /** * Returns true if the repository is in the default NONE state. * @return {Boolean} */ Repository.prototype.isDefaultState = function () { return this.state() === NodeGit.Repository.STATE.NONE; }; /** * Returns true if the repository is in the MERGE state. * @return {Boolean} */ Repository.prototype.isMerging = function () { return this.state() === NodeGit.Repository.STATE.MERGE; }; /** * Returns true if the repository is in the REBASE, REBASE_INTERACTIVE, or * REBASE_MERGE state. * @return {Boolean} */ Repository.prototype.isRebasing = function () { var state = this.state(); return state === NodeGit.Repository.STATE.REBASE || state === NodeGit.Repository.STATE.REBASE_INTERACTIVE || state === NodeGit.Repository.STATE.REBASE_MERGE; }; /** * Returns true if the repository is in the REVERT state. * @return {Boolean} */ Repository.prototype.isReverting = function () { return this.state() === NodeGit.Repository.STATE.REVERT; }; /** * Rebases a branch onto another branch * * @async * @param {String} branch * @param {String} upstream * @param {String} onto * @param {Signature} signature Identity of the one performing the rebase * @param {Function} beforeNextFn Callback to be called before each step * of the rebase. If the callback returns a * promise, the rebase will resume when the * promise resolves. The rebase object is * is passed to the callback. * @param {Function} beforeFinishFn Callback called before the invocation * of finish(). If the callback returns a * promise, finish() will be called when the * promise resolves. This callback will be * provided a detailed overview of the rebase * @param {RebaseOptions} rebaseOptions Options to initialize the rebase object * with * @return {Oid|Index} A commit id for a succesful merge or an index for a * rebase with conflicts */ Repository.prototype.rebaseBranches = function (branch, upstream, onto, signature, beforeNextFn, beforeFinishFn, rebaseOptions) { var repo = this; var branchCommit = void 0; var upstreamCommit = void 0; var ontoCommit = void 0; var mergeOptions = (rebaseOptions || {}).mergeOptions; var promiseChain = Promise.resolve(); if (!signature) { promiseChain = promiseChain.then(function () { return repo.defaultSignature(); }).then(function (signatureResult) { signature = signatureResult; }); } return Promise.all([repo.getReference(branch), upstream ? repo.getReference(upstream) : null, onto ? repo.getReference(onto) : null]).then(function (refs) { return Promise.all([NodeGit.AnnotatedCommit.fromRef(repo, refs[0]), upstream ? NodeGit.AnnotatedCommit.fromRef(repo, refs[1]) : null, onto ? NodeGit.AnnotatedCommit.fromRef(repo, refs[2]) : null]); }).then(function (annotatedCommits) { branchCommit = annotatedCommits[0]; upstreamCommit = annotatedCommits[1]; ontoCommit = annotatedCommits[2]; return NodeGit.Merge.base(repo, branchCommit.id(), upstreamCommit.id()); }).then(function (oid) { if (oid.toString() === branchCommit.id().toString()) { // we just need to fast-forward return repo.mergeBranches(branch, upstream, null, null, mergeOptions).then(function () { // checkout 'branch' to match the behavior of rebase return repo.checkoutBranch(branch); }); } else if (oid.toString() === upstreamCommit.id().toString()) { // 'branch' is already on top of 'upstream' // checkout 'branch' to match the behavior of rebase return repo.checkoutBranch(branch); } return NodeGit.Rebase.init(repo, branchCommit, upstreamCommit, ontoCommit, rebaseOptions).then(function (rebase) { return performRebase(repo, rebase, signature, beforeNextFn, beforeFinishFn); }).then(function (error) { if (error) { throw error; } }); }).then(function () { return repo.getBranchCommit("HEAD"); }); }; /** * Grabs a fresh copy of the index from the repository. Invalidates * all previously grabbed indexes * * @async * @return {Index} */ Repository.prototype.refreshIndex = function (callback) { var repo = this; repo.setIndex(); // clear the index return repo.index().then(function (index) { if (typeof callback === "function") { callback(null, index); } return index; }, callback); }; /** * Merge a branch onto another branch * * @async * @param {String|Reference} to * @param {String|Reference} from * @param {Signature} signature * @param {Merge.PREFERENCE} mergePreference * @param {MergeOptions} mergeOptions * @param {MergeBranchOptions} mergeBranchOptions * @return {Oid|Index} A commit id for a succesful merge or an index for a * merge with conflicts */ Repository.prototype.mergeBranches = function (to, from, signature, mergePreference, mergeOptions, mergeBranchOptions) { var repo = this; var fromBranch = void 0; var toBranch = void 0; // Support old parameter `processMergeMessageCallback` var isOldOptionParameter = typeof mergeBranchOptions === "function"; if (isOldOptionParameter) { console.error("DeprecationWarning: Repository#mergeBranches parameter " + "processMergeMessageCallback, use MergeBranchOptions"); } var processMergeMessageCallback = mergeBranchOptions && (isOldOptionParameter ? mergeBranchOptions : mergeBranchOptions.processMergeMessageCallback) || function (message) { return message; }; var signingCallback = mergeBranchOptions && mergeBranchOptions.signingCb; mergePreference = mergePreference || NodeGit.Merge.PREFERENCE.NONE; mergeOptions = normalizeOptions(mergeOptions, NodeGit.MergeOptions); var promiseChain = Promise.resolve(); if (!signature) { promiseChain = promiseChain.then(function () { return repo.defaultSignature(); }).then(function (signatureResult) { signature = signatureResult; }); } return promiseChain.then(function () { return Promise.all([repo.getBranch(to), repo.getBranch(from)]); }).then(function (objects) { toBranch = objects[0]; fromBranch = objects[1]; return Promise.all([repo.getBranchCommit(toBranch), repo.getBranchCommit(fromBranch)]); }).then(function (branchCommits) { var toCommitOid = branchCommits[0].toString(); var fromCommitOid = branchCommits[1].toString(); return NodeGit.Merge.base(repo, toCommitOid, fromCommitOid).then(function (baseCommit) { if (baseCommit.toString() == fromCommitOid) { // The commit we're merging to is already in our history. // nothing to do so just return the commit the branch is on return toCommitOid; } else if (baseCommit.toString() == toCommitOid && mergePreference !== NodeGit.Merge.PREFERENCE.NO_FASTFORWARD) { // fast forward var message = "Fast forward branch " + toBranch.shorthand() + " to branch " + fromBranch.shorthand(); return branchCommits[1].getTree().then(function (tree) { if (toBranch.isHead()) { // Checkout the tree if we're on the branch var opts = { checkoutStrategy: NodeGit.Checkout.STRATEGY.SAFE | NodeGit.Checkout.STRATEGY.RECREATE_MISSING }; return NodeGit.Checkout.tree(repo, tree, opts); } }).then(function () { return toBranch.setTarget(fromCommitOid, message).then(function () { return fromCommitOid; }); }); } else if (mergePreference !== NodeGit.Merge.PREFERENCE.FASTFORWARD_ONLY) { var updateHead; // We have to merge. Lets do it! return NodeGit.Reference.lookup(repo, "HEAD").then(function (headRef) { return headRef.resolve(); }).then(function (headRef) { updateHead = !!headRef && headRef.name() === toBranch.name(); return NodeGit.Merge.commits(repo, toCommitOid, fromCommitOid, mergeOptions); }).then(function (index) { // if we have conflicts then throw the index if (index.hasConflicts()) { throw index; } // No conflicts so just go ahead with the merge return index.writeTreeTo(repo); }).then(function (oid) { var mergeDecorator; if (fromBranch.isTag()) { mergeDecorator = "tag"; } else if (fromBranch.isRemote()) { mergeDecorator = "remote-tracking branch"; } else { mergeDecorator = "branch"; } var message = "Merge " + mergeDecorator + " '" + fromBranch.shorthand() + "'"; // https://github.com/git/git/blob/master/builtin/fmt-merge-msg.c#L456-L459 if (toBranch.shorthand() !== "master") { message += " into " + toBranch.shorthand(); } return Promise.all([oid, processMergeMessageCallback(message)]); }).then(function (_ref6) { var _ref7 = _slicedToArray(_ref6, 2), oid = _ref7[0], message = _ref7[1]; if (signingCallback) { return repo.createCommitWithSignature(toBranch.name(), signature, signature, message, oid, [toCommitOid, fromCommitOid], signingCallback); } return repo.createCommit(toBranch.name(), signature, signature, message, oid, [toCommitOid, fromCommitOid]); }).then(function (commit) { // we've updated the checked out branch, so make sure we update // head so that our index isn't messed up if (updateHead) { return repo.getBranch(to).then(function (branch) { return repo.getBranchCommit(branch); }).then(function (branchCommit) { return branchCommit.getTree(); }).then(function (tree) { var opts = { checkoutStrategy: NodeGit.Checkout.STRATEGY.SAFE | NodeGit.Checkout.STRATEGY.RECREATE_MISSING }; return NodeGit.Checkout.tree(repo, tree, opts); }).then(function () { return commit; }); } else { return commit; } }); } else { // A non fast-forwardable merge with ff-only return toCommitOid; } }); }); }; /** * @async * @param {MergeheadForeachCb} callback The callback function to be called on * each entry */ Repository.prototype.mergeheadForeach = function (callback) { return _mergeheadForeach.call(this, callback, null); }; /** * Stages or unstages line selection of a specified file * * @async * @param {String|Array} filePath The relative path of this file in the repo * @param {Boolean} stageNew Set to stage new filemode. Unset to unstage. * @return {Number} 0 or an error code */ Repository.prototype.stageFilemode = function (filePath, stageNew, additionalDiffOptions) { var repo = this; var index; var diffOptions = additionalDiffOptions ? { flags: additionalDiffOptions } : undefined; var diffPromise = stageNew ? NodeGit.Diff.indexToWorkdir(repo, index, { flags: NodeGit.Diff.OPTION.SHOW_UNTRACKED_CONTENT | NodeGit.Diff.OPTION.RECURSE_UNTRACKED_DIRS | (additionalDiffOptions || 0) }) : repo.getHeadCommit().then(function getTreeFromCommit(commit) { return commit.getTree(); }).then(function getDiffFromTree(tree) { return NodeGit.Diff.treeToIndex(repo, tree, index, diffOptions); }); var filePaths = filePath instanceof Array ? filePath : [filePath]; var indexLock = repo.path().replace(".git/", "") + ".git/index.lock"; return fse.remove(indexLock).then(function () { return repo.refreshIndex(); }).then(function (indexResult) { index = indexResult; }).then(function () { return diffPromise; }).then(function (diff) { var origLength = filePaths.length; var fileFilterPromises = fp.map(function (p) { return NodeGit.Status.file(repo, p).then(function (status) {