nodegit
Version:
Node.js libgit2 asynchronous native bindings
1,591 lines (1,390 loc) • 54.3 kB
JavaScript
"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) {