1629 lines
51 KiB
JavaScript
1629 lines
51 KiB
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) {
|
|
return _discover(startPath, acrossFs, ceilingDirs).then(function (foundPath) {
|
|
return path.resolve(foundPath);
|
|
});
|
|
};
|
|
|
|
// 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) {
|
|
|
|
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);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* 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) {
|
|
|
|
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);
|
|
});
|
|
});
|
|
};
|
|
|
|
/**
|
|
* 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) {
|
|
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) {
|
|
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);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* 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) {
|
|
var repo = this;
|
|
|
|
return repo.getRemote(remote).then(function (remote) {
|
|
return remote.fetch(null, fetchOptions, "Fetch from " + remote).then(function () {
|
|
return remote.disconnect();
|
|
});
|
|
});
|
|
};
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
Repository.prototype.fetchAll = function (fetchOptions) {
|
|
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());
|
|
});
|
|
};
|
|
|
|
/**
|
|
* @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) {
|
|
var repository = this;
|
|
|
|
return Blob.lookup(repository, oid).then(function (blob) {
|
|
blob.repo = repository;
|
|
return blob;
|
|
});
|
|
};
|
|
|
|
/**
|
|
* 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) {
|
|
return this.getReference(name);
|
|
};
|
|
|
|
/**
|
|
* 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) {
|
|
return this.getReferenceCommit(name);
|
|
};
|
|
|
|
/**
|
|
* Retrieve the commit identified by oid.
|
|
*
|
|
* @async
|
|
* @param {String|Oid} String sha or Oid
|
|
* @return {Commit}
|
|
*/
|
|
Repository.prototype.getCommit = function (oid) {
|
|
var repository = this;
|
|
|
|
return Commit.lookup(repository, oid);
|
|
};
|
|
|
|
/**
|
|
* 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 () {
|
|
var repo = this;
|
|
|
|
return Reference.nameToId(repo, "HEAD").then(function (head) {
|
|
return repo.getCommit(head);
|
|
}).catch(function () {
|
|
return null;
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Retrieve the master branch commit.
|
|
*
|
|
* @async
|
|
* @return {Commit}
|
|
*/
|
|
Repository.prototype.getMasterCommit = function () {
|
|
return this.getBranchCommit("master");
|
|
};
|
|
|
|
/**
|
|
* 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) {
|
|
var repository = this;
|
|
|
|
return Reference.dwim(this, name).then(function (reference) {
|
|
if (reference.isSymbolic()) {
|
|
return reference.resolve().then(function (reference) {
|
|
reference.repo = repository;
|
|
return reference;
|
|
});
|
|
}
|
|
|
|
reference.repo = repository;
|
|
return reference;
|
|
});
|
|
};
|
|
|
|
/**
|
|
* 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) {
|
|
var repository = this;
|
|
|
|
return this.getReference(name).then(function (reference) {
|
|
return repository.getCommit(reference.target());
|
|
});
|
|
};
|
|
|
|
/**
|
|
* 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
|
|
* @return {Remote} The remote object
|
|
*/
|
|
Repository.prototype.getRemote = function (remote) {
|
|
if (remote instanceof NodeGit.Remote) {
|
|
return Promise.resolve(remote);
|
|
}
|
|
|
|
return NodeGit.Remote.lookup(this, remote);
|
|
};
|
|
|
|
/**
|
|
* Lists out the remotes in the given repository.
|
|
*
|
|
* @async
|
|
* @return {Object} Promise object.
|
|
*/
|
|
Repository.prototype.getRemoteNames = function () {
|
|
return Remote.list(this);
|
|
};
|
|
|
|
/**
|
|
* 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 () {
|
|
var names = [];
|
|
var submoduleCallback = function submoduleCallback(submodule, name, payload) {
|
|
names.push(name);
|
|
};
|
|
|
|
return Submodule.foreach(this, submoduleCallback).then(function () {
|
|
return names;
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Retrieve the tag represented by the oid.
|
|
*
|
|
* @async
|
|
* @param {String|Oid} String sha or Oid
|
|
* @return {Tag}
|
|
*/
|
|
Repository.prototype.getTag = function (oid) {
|
|
var repository = this;
|
|
|
|
return Tag.lookup(repository, oid).then(function (reference) {
|
|
reference.repo = repository;
|
|
return reference;
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Retrieve the tag represented by the tag name.
|
|
*
|
|
* @async
|
|
* @param {String} Short or full tag name
|
|
* @return {Tag}
|
|
*/
|
|
Repository.prototype.getTagByName = function (name) {
|
|
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;
|
|
return reference;
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Retrieve the tree represented by the oid.
|
|
*
|
|
* @async
|
|
* @param {String|Oid} String sha or Oid
|
|
* @return {Tree}
|
|
*/
|
|
Repository.prototype.getTree = function (oid) {
|
|
var repository = this;
|
|
|
|
return Tree.lookup(repository, oid).then(function (tree) {
|
|
tree.repo = repository;
|
|
return tree;
|
|
});
|
|
};
|
|
|
|
/**
|
|
* 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 () {
|
|
var repo = this;
|
|
|
|
repo.setIndex(); // clear the index
|
|
|
|
return repo.index();
|
|
};
|
|
|
|
/**
|
|
* 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) {
|
|
return {
|
|
path: p,
|
|
filter: status & NodeGit.Status.STATUS.WT_MODIFIED || status & NodeGit.Status.STATUS.INDEX_MODIFIED
|
|
};
|
|
});
|
|
}, filePaths);
|
|
|
|
return Promise.all(fileFilterPromises).then(function (results) {
|
|
filePaths = fp.flow([fp.filter(function (filterResult) {
|
|
return filterResult.filter;
|
|
}), fp.map(function (filterResult) {
|
|
return filterResult.path;
|
|
})])(results);
|
|
|
|
if (filePaths.length === 0 && origLength > 0) {
|
|
return Promise.reject("Selected staging is only available on modified files.");
|
|
}
|
|
return diff.patches();
|
|
});
|
|
}).then(function (patches) {
|
|
var pathPatches = patches.filter(function (patch) {
|
|
return ~filePaths.indexOf(patch.newFile().path());
|
|
});
|
|
if (pathPatches.length === 0) {
|
|
return Promise.reject("No differences found for this file.");
|
|
}
|
|
|
|
return pathPatches.reduce(function (lastIndexAddPromise, pathPatch) {
|
|
var entry = index.getByPath(pathPatch.newFile().path(), 0);
|
|
|
|
entry.mode = stageNew ? pathPatch.newFile().mode() : pathPatch.oldFile().mode();
|
|
|
|
return lastIndexAddPromise.then(function () {
|
|
return index.add(entry);
|
|
});
|
|
}, Promise.resolve());
|
|
}).then(function () {
|
|
return index.write();
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Stages or unstages line selection of a specified file
|
|
*
|
|
* @async
|
|
* @param {String} filePath The relative path of this file in the repo
|
|
* @param {Array} selectedLines The array of DiffLine objects
|
|
* selected for staging or unstaging
|
|
* @param {Boolean} isStaged Are the selected lines currently staged
|
|
* @return {Number} 0 or an error code
|
|
*/
|
|
Repository.prototype.stageLines = function (filePath, selectedLines, isSelectionStaged, additionalDiffOptions) {
|
|
|
|
var repo = this;
|
|
var index;
|
|
var originalBlob;
|
|
|
|
// The following chain checks if there is a patch with no hunks left for the
|
|
// file, and no filemode changes were done on the file. It is then safe to
|
|
// stage the entire file so the file doesn't show as having unstaged changes
|
|
// in `git status`. Also, check if there are no type changes.
|
|
var lastHunkStagedPromise = function lastHunkStagedPromise(result) {
|
|
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 diff.patches();
|
|
}).then(function (patches) {
|
|
var pathPatch = patches.filter(function (patch) {
|
|
return patch.newFile().path() === filePath;
|
|
});
|
|
var emptyPatch = false;
|
|
if (pathPatch.length > 0) {
|
|
// No hunks, unchanged file mode, and no type changes.
|
|
emptyPatch = pathPatch[0].size() === 0 && pathPatch[0].oldFile().mode() === pathPatch[0].newFile().mode() && !pathPatch[0].isTypeChange();
|
|
}
|
|
if (emptyPatch) {
|
|
return index.addByPath(filePath).then(function () {
|
|
return index.write();
|
|
});
|
|
}
|
|
|
|
return result;
|
|
});
|
|
};
|
|
|
|
return repo.refreshIndex().then(function (indexResult) {
|
|
index = indexResult;
|
|
var pathOid = index.getByPath(filePath).id;
|
|
|
|
return repo.getBlob(pathOid);
|
|
}).then(function (blob) {
|
|
originalBlob = blob;
|
|
|
|
return getPathHunks(repo, index, filePath, isSelectionStaged, additionalDiffOptions);
|
|
}).then(function (hunks) {
|
|
return applySelectedLinesToTarget(originalBlob, selectedLines, hunks, isSelectionStaged);
|
|
}).then(function (newContent) {
|
|
var newContentBuffer = new Buffer(newContent);
|
|
|
|
return repo.createBlobFromBuffer(newContentBuffer);
|
|
}).then(function (newOid) {
|
|
return repo.getBlob(newOid);
|
|
}).then(function (newBlob) {
|
|
var entry = index.getByPath(filePath, 0);
|
|
entry.id = newBlob.id();
|
|
entry.path = filePath;
|
|
entry.fileSize = newBlob.content().length;
|
|
|
|
return index.add(entry);
|
|
}).then(function () {
|
|
return index.write();
|
|
}).then(function (result) {
|
|
if (isSelectionStaged) {
|
|
return result;
|
|
}
|
|
|
|
return lastHunkStagedPromise(result);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Create a new tree builder.
|
|
*
|
|
* @param {Tree} tree
|
|
*/
|
|
Repository.prototype.treeBuilder = function () {
|
|
var builder = TreeBuilder.create(null);
|
|
|
|
builder.root = builder;
|
|
builder.repo = this;
|
|
|
|
return builder;
|
|
}; |