|
|
var Backbone = require('backbone'); |
|
|
var Q = require('q'); |
|
|
|
|
|
var intl = require('../intl'); |
|
|
|
|
|
var AnimationFactory = require('../visuals/animation/animationFactory').AnimationFactory; |
|
|
var AnimationQueue = require('../visuals/animation').AnimationQueue; |
|
|
var TreeCompare = require('../graph/treeCompare'); |
|
|
|
|
|
var Graph = require('../graph'); |
|
|
var Errors = require('../util/errors'); |
|
|
var Main = require('../app'); |
|
|
var Commands = require('../commands'); |
|
|
var GitError = Errors.GitError; |
|
|
var CommandResult = Errors.CommandResult; |
|
|
|
|
|
var ORIGIN_PREFIX = 'o/'; |
|
|
var TAB = ' '; |
|
|
var SHORT_CIRCUIT_CHAIN = 'STAPH'; |
|
|
|
|
|
function catchShortCircuit(err) { |
|
|
if (err !== SHORT_CIRCUIT_CHAIN) { |
|
|
throw err; |
|
|
} |
|
|
} |
|
|
|
|
|
function GitEngine(options) { |
|
|
this.rootCommit = null; |
|
|
this.refs = {}; |
|
|
this.HEAD = null; |
|
|
this.origin = null; |
|
|
this.mode = 'git'; |
|
|
this.localRepo = null; |
|
|
|
|
|
this.branchCollection = options.branches; |
|
|
this.tagCollection = options.tags; |
|
|
this.commitCollection = options.collection; |
|
|
this.gitVisuals = options.gitVisuals; |
|
|
|
|
|
this.eventBaton = options.eventBaton; |
|
|
this.eventBaton.stealBaton('processGitCommand', this.dispatch, this); |
|
|
|
|
|
|
|
|
|
|
|
this.animationFactory = (options.animationFactory) ? |
|
|
options.animationFactory : AnimationFactory; |
|
|
|
|
|
this.initUniqueID(); |
|
|
} |
|
|
|
|
|
GitEngine.prototype.initUniqueID = function() { |
|
|
|
|
|
this.uniqueId = (function() { |
|
|
var n = 0; |
|
|
return function(prepend) { |
|
|
return prepend ? prepend + n++ : n++; |
|
|
}; |
|
|
})(); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.handleModeChange = function(vcs, callback) { |
|
|
if (this.mode === vcs) { |
|
|
|
|
|
callback(); |
|
|
return; |
|
|
} |
|
|
Main.getEvents().trigger('vcsModeChange', {mode: vcs}); |
|
|
var chain = this.setMode(vcs); |
|
|
if (this.origin) { |
|
|
this.origin.setMode(vcs, function() {}); |
|
|
} |
|
|
|
|
|
if (!chain) { |
|
|
callback(); |
|
|
return; |
|
|
} |
|
|
|
|
|
chain.then(callback); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.getIsHg = function() { |
|
|
return this.mode === 'hg'; |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.setMode = function(vcs) { |
|
|
var switchedToHg = (this.mode === 'git' && vcs === 'hg'); |
|
|
this.mode = vcs; |
|
|
if (!switchedToHg) { |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
var deferred = Q.defer(); |
|
|
deferred.resolve(); |
|
|
var chain = deferred.promise; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var neededUpdate = this.updateAllBranchesForHg(); |
|
|
if (neededUpdate) { |
|
|
chain = chain.then(function() { |
|
|
return this.animationFactory.playRefreshAnimationSlow(this.gitVisuals); |
|
|
}.bind(this)); |
|
|
|
|
|
|
|
|
chain = chain.then(function() { |
|
|
var neededPrune = this.pruneTree(); |
|
|
if (!neededPrune) { |
|
|
return; |
|
|
} |
|
|
return this.animationFactory.playRefreshAnimation(this.gitVisuals); |
|
|
}.bind(this)); |
|
|
|
|
|
return chain; |
|
|
} |
|
|
|
|
|
|
|
|
var pruned = this.pruneTree(); |
|
|
if (!pruned) { |
|
|
|
|
|
return; |
|
|
} |
|
|
|
|
|
return this.animationFactory.playRefreshAnimation(this.gitVisuals); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.assignLocalRepo = function(repo) { |
|
|
this.localRepo = repo; |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.defaultInit = function() { |
|
|
var defaultTree = Graph.getDefaultTree(); |
|
|
this.loadTree(defaultTree); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.init = function() { |
|
|
|
|
|
this.rootCommit = this.makeCommit(null, null, {rootCommit: true}); |
|
|
this.commitCollection.add(this.rootCommit); |
|
|
|
|
|
var main = this.makeBranch('main', this.rootCommit); |
|
|
this.HEAD = new Ref({ |
|
|
id: 'HEAD', |
|
|
target: main |
|
|
}); |
|
|
this.refs[this.HEAD.get('id')] = this.HEAD; |
|
|
|
|
|
|
|
|
this.commit(); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.hasOrigin = function() { |
|
|
return !!this.origin; |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.isOrigin = function() { |
|
|
return !!this.localRepo; |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.exportTreeForBranch = function(branchName) { |
|
|
|
|
|
|
|
|
var tree = this.exportTree(); |
|
|
|
|
|
var set = Graph.getUpstreamSet(this, branchName); |
|
|
|
|
|
var commitsToLoop = tree.commits; |
|
|
tree.commits = {}; |
|
|
commitsToLoop.forEach(function(commit, id) { |
|
|
if (set[id]) { |
|
|
|
|
|
tree.commits[id] = commit; |
|
|
} |
|
|
}); |
|
|
|
|
|
var branchesToLoop = tree.branches; |
|
|
tree.branches = {}; |
|
|
branchesToLoop.forEach(function(branch, id) { |
|
|
if (id === branchName) { |
|
|
tree.branches[id] = branch; |
|
|
} |
|
|
}); |
|
|
|
|
|
tree.HEAD.target = branchName; |
|
|
return tree; |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.exportTree = function() { |
|
|
|
|
|
|
|
|
|
|
|
var totalExport = { |
|
|
branches: {}, |
|
|
commits: {}, |
|
|
tags: {}, |
|
|
HEAD: null |
|
|
}; |
|
|
|
|
|
this.branchCollection.toJSON().forEach(function(branch) { |
|
|
branch.target = branch.target.get('id'); |
|
|
delete branch.visBranch; |
|
|
|
|
|
totalExport.branches[branch.id] = branch; |
|
|
}); |
|
|
|
|
|
this.commitCollection.toJSON().forEach(function(commit) { |
|
|
|
|
|
Commit.prototype.constants.circularFields.forEach(function(field) { |
|
|
delete commit[field]; |
|
|
}); |
|
|
|
|
|
|
|
|
commit.parents = (commit.parents || []).map(function(par) { |
|
|
return par.get('id'); |
|
|
}); |
|
|
|
|
|
totalExport.commits[commit.id] = commit; |
|
|
}, this); |
|
|
|
|
|
this.tagCollection.toJSON().forEach(function(tag) { |
|
|
delete tag.visTag; |
|
|
tag.target = tag.target.get('id'); |
|
|
|
|
|
totalExport.tags[tag.id] = tag; |
|
|
}, this); |
|
|
|
|
|
var HEAD = this.HEAD.toJSON(); |
|
|
HEAD.lastTarget = HEAD.lastLastTarget = HEAD.visBranch = HEAD.visTag = undefined; |
|
|
HEAD.target = HEAD.target.get('id'); |
|
|
totalExport.HEAD = HEAD; |
|
|
|
|
|
if (this.hasOrigin()) { |
|
|
totalExport.originTree = this.origin.exportTree(); |
|
|
} |
|
|
|
|
|
return totalExport; |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.printTree = function(tree) { |
|
|
tree = tree || this.exportTree(); |
|
|
TreeCompare.reduceTreeFields([tree]); |
|
|
|
|
|
var str = JSON.stringify(tree); |
|
|
if (/'/.test(str)) { |
|
|
|
|
|
str = escape(str); |
|
|
} |
|
|
return str; |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.printAndCopyTree = function() { |
|
|
window.prompt( |
|
|
intl.str('Copy the tree string below'), |
|
|
this.printTree() |
|
|
); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.loadTree = function(tree) { |
|
|
|
|
|
tree = JSON.parse(JSON.stringify(tree)); |
|
|
|
|
|
|
|
|
this.removeAll(); |
|
|
|
|
|
this.instantiateFromTree(tree); |
|
|
|
|
|
this.reloadGraphics(); |
|
|
this.initUniqueID(); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.loadTreeFromString = function(treeString) { |
|
|
this.loadTree(JSON.parse(unescape(this.crappyUnescape(treeString)))); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.instantiateFromTree = function(tree) { |
|
|
|
|
|
var createdSoFar = {}; |
|
|
|
|
|
Object.values(tree.commits).forEach(function(commitJSON) { |
|
|
var commit = this.getOrMakeRecursive(tree, createdSoFar, commitJSON.id, this.gitVisuals); |
|
|
this.commitCollection.add(commit); |
|
|
}, this); |
|
|
|
|
|
Object.values(tree.branches).forEach(function(branchJSON) { |
|
|
var branch = this.getOrMakeRecursive(tree, createdSoFar, branchJSON.id, this.gitVisuals); |
|
|
|
|
|
this.branchCollection.add(branch, {silent: true}); |
|
|
}, this); |
|
|
|
|
|
Object.values(tree.tags || {}).forEach(function(tagJSON) { |
|
|
var tag = this.getOrMakeRecursive(tree, createdSoFar, tagJSON.id, this.gitVisuals); |
|
|
|
|
|
this.tagCollection.add(tag, {silent: true}); |
|
|
}, this); |
|
|
|
|
|
var HEAD = this.getOrMakeRecursive(tree, createdSoFar, tree.HEAD.id, this.gitVisuals); |
|
|
this.HEAD = HEAD; |
|
|
|
|
|
this.rootCommit = createdSoFar['C0']; |
|
|
if (!this.rootCommit) { |
|
|
throw new Error('Need root commit of C0 for calculations'); |
|
|
} |
|
|
this.refs = createdSoFar; |
|
|
|
|
|
this.gitVisuals.gitReady = false; |
|
|
this.branchCollection.each(function(branch) { |
|
|
this.gitVisuals.addBranch(branch); |
|
|
}, this); |
|
|
this.tagCollection.each(function(tag) { |
|
|
this.gitVisuals.addTag(tag); |
|
|
}, this); |
|
|
|
|
|
if (tree.originTree) { |
|
|
var treeString = JSON.stringify(tree.originTree); |
|
|
|
|
|
|
|
|
this.animationQueue = this.animationQueue || new AnimationQueue({ |
|
|
callback: function() {} |
|
|
}); |
|
|
this.makeOrigin(treeString); |
|
|
} |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.makeOrigin = function(treeString) { |
|
|
if (this.hasOrigin()) { |
|
|
throw new GitError({ |
|
|
msg: intl.str('git-error-origin-exists') |
|
|
}); |
|
|
} |
|
|
treeString = treeString || this.printTree(this.exportTreeForBranch('main')); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var mainVis = this.gitVisuals.getVisualization(); |
|
|
var originVis = mainVis.makeOrigin({ |
|
|
localRepo: this, |
|
|
treeString: treeString |
|
|
}); |
|
|
|
|
|
|
|
|
this.animationQueue.set('promiseBased', true); |
|
|
originVis.customEvents.on('gitEngineReady', function() { |
|
|
this.origin = originVis.gitEngine; |
|
|
originVis.gitEngine.assignLocalRepo(this); |
|
|
this.syncRemoteBranchFills(); |
|
|
|
|
|
|
|
|
this.origin.externalRefresh(); |
|
|
this.animationFactory.playRefreshAnimationAndFinish(this.gitVisuals, this.animationQueue); |
|
|
}, this); |
|
|
|
|
|
var originTree = JSON.parse(unescape(treeString)); |
|
|
|
|
|
|
|
|
Object.keys(originTree.branches).forEach(function(branchName) { |
|
|
var branchJSON = originTree.branches[branchName]; |
|
|
if (this.refs[ORIGIN_PREFIX + branchName]) { |
|
|
|
|
|
return; |
|
|
} |
|
|
|
|
|
var originTarget = this.findCommonAncestorWithRemote( |
|
|
branchJSON.target |
|
|
); |
|
|
|
|
|
|
|
|
var remoteBranch = this.makeBranch( |
|
|
ORIGIN_PREFIX + branchName, |
|
|
this.getCommitFromRef(originTarget) |
|
|
); |
|
|
|
|
|
this.setLocalToTrackRemote(this.refs[branchJSON.id], remoteBranch); |
|
|
}, this); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.makeRemoteBranchIfNeeded = function(branchName) { |
|
|
if (this.doesRefExist(ORIGIN_PREFIX + branchName)) { |
|
|
return; |
|
|
} |
|
|
|
|
|
var source = this.origin.resolveID(branchName); |
|
|
if (source.get('type') !== 'branch') { |
|
|
return; |
|
|
} |
|
|
|
|
|
return this.makeRemoteBranchForRemote(branchName); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.makeBranchIfNeeded = function(branchName) { |
|
|
if (this.doesRefExist(branchName)) { |
|
|
return; |
|
|
} |
|
|
var where = this.findCommonAncestorForRemote( |
|
|
this.getCommitFromRef('HEAD').get('id') |
|
|
); |
|
|
|
|
|
return this.validateAndMakeBranch(branchName, this.getCommitFromRef(where)); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.makeRemoteBranchForRemote = function(branchName) { |
|
|
var target = this.origin.resolveID(branchName).get('target'); |
|
|
var originTarget = this.findCommonAncestorWithRemote( |
|
|
target.get('id') |
|
|
); |
|
|
return this.makeBranch( |
|
|
ORIGIN_PREFIX + branchName, |
|
|
this.getCommitFromRef(originTarget) |
|
|
); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.findCommonAncestorForRemote = function(myTarget) { |
|
|
if (this.origin.refs[myTarget]) { |
|
|
return myTarget; |
|
|
} |
|
|
var parents = this.refs[myTarget].get('parents'); |
|
|
if (parents.length === 1) { |
|
|
|
|
|
myTarget = parents[0].get('id'); |
|
|
|
|
|
return this.findCommonAncestorForRemote(myTarget); |
|
|
} |
|
|
|
|
|
var leftTarget = this.findCommonAncestorForRemote(parents[0].get('id')); |
|
|
var rightTarget = this.findCommonAncestorForRemote(parents[1].get('id')); |
|
|
return this.getCommonAncestor( |
|
|
leftTarget, |
|
|
rightTarget, |
|
|
true |
|
|
).get('id'); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.findCommonAncestorWithRemote = function(originTarget) { |
|
|
if (this.refs[originTarget]) { |
|
|
return originTarget; |
|
|
} |
|
|
|
|
|
|
|
|
var parents = this.origin.refs[originTarget].get('parents'); |
|
|
if (parents.length === 1) { |
|
|
return this.findCommonAncestorWithRemote(parents[0].get('id')); |
|
|
} |
|
|
|
|
|
var leftTarget = this.findCommonAncestorWithRemote(parents[0].get('id')); |
|
|
var rightTarget = this.findCommonAncestorWithRemote(parents[1].get('id')); |
|
|
return this.getCommonAncestor(leftTarget, rightTarget, true ).get('id'); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.makeBranchOnOriginAndTrack = function(branchName, target) { |
|
|
var remoteBranch = this.makeBranch( |
|
|
ORIGIN_PREFIX + branchName, |
|
|
this.getCommitFromRef(target) |
|
|
); |
|
|
|
|
|
if (this.refs[branchName]) { |
|
|
this.setLocalToTrackRemote(this.refs[branchName], remoteBranch); |
|
|
} |
|
|
|
|
|
var originTarget = this.findCommonAncestorForRemote( |
|
|
this.getCommitFromRef(target).get('id') |
|
|
); |
|
|
this.origin.makeBranch( |
|
|
branchName, |
|
|
this.origin.getCommitFromRef(originTarget) |
|
|
); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.setLocalToTrackRemote = function(localBranch, remoteBranch) { |
|
|
localBranch.setRemoteTrackingBranchID(remoteBranch.get('id')); |
|
|
|
|
|
if (!this.command) { |
|
|
|
|
|
return; |
|
|
} |
|
|
|
|
|
var msg = 'local branch "' + |
|
|
localBranch.get('id') + |
|
|
'" set to track remote branch "' + |
|
|
remoteBranch.get('id') + |
|
|
'"'; |
|
|
this.command.addWarning(intl.todo(msg)); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.getOrMakeRecursive = function( |
|
|
tree, |
|
|
createdSoFar, |
|
|
objID, |
|
|
gitVisuals |
|
|
) { |
|
|
if (createdSoFar[objID]) { |
|
|
|
|
|
return createdSoFar[objID]; |
|
|
} |
|
|
|
|
|
var getType = function(tree, id) { |
|
|
if (tree.commits[id]) { |
|
|
return 'commit'; |
|
|
} else if (tree.branches[id]) { |
|
|
return 'branch'; |
|
|
} else if (id == 'HEAD') { |
|
|
return 'HEAD'; |
|
|
} else if (tree.tags[id]) { |
|
|
return 'tag'; |
|
|
} |
|
|
throw new Error("bad type for " + id); |
|
|
}; |
|
|
|
|
|
|
|
|
var type = getType(tree, objID); |
|
|
|
|
|
if (type == 'HEAD') { |
|
|
var headJSON = tree.HEAD; |
|
|
var HEAD = new Ref(Object.assign( |
|
|
tree.HEAD, |
|
|
{ |
|
|
target: this.getOrMakeRecursive(tree, createdSoFar, headJSON.target) |
|
|
} |
|
|
)); |
|
|
createdSoFar[objID] = HEAD; |
|
|
return HEAD; |
|
|
} |
|
|
|
|
|
if (type == 'branch') { |
|
|
var branchJSON = tree.branches[objID]; |
|
|
|
|
|
var branch = new Branch(Object.assign( |
|
|
tree.branches[objID], |
|
|
{ |
|
|
target: this.getOrMakeRecursive(tree, createdSoFar, branchJSON.target) |
|
|
} |
|
|
)); |
|
|
createdSoFar[objID] = branch; |
|
|
return branch; |
|
|
} |
|
|
|
|
|
if (type == 'tag') { |
|
|
var tagJSON = tree.tags[objID]; |
|
|
|
|
|
var tag = new Tag(Object.assign( |
|
|
tree.tags[objID], |
|
|
{ |
|
|
target: this.getOrMakeRecursive(tree, createdSoFar, tagJSON.target) |
|
|
} |
|
|
)); |
|
|
createdSoFar[objID] = tag; |
|
|
return tag; |
|
|
} |
|
|
|
|
|
if (type == 'commit') { |
|
|
|
|
|
var commitJSON = tree.commits[objID]; |
|
|
|
|
|
var parentObjs = commitJSON.parents.map(function(parentID) { |
|
|
return this.getOrMakeRecursive(tree, createdSoFar, parentID); |
|
|
}, this); |
|
|
|
|
|
var commit = new Commit(Object.assign( |
|
|
commitJSON, |
|
|
{ |
|
|
parents: parentObjs, |
|
|
gitVisuals: this.gitVisuals |
|
|
} |
|
|
)); |
|
|
createdSoFar[objID] = commit; |
|
|
return commit; |
|
|
} |
|
|
|
|
|
throw new Error('ruh rho!! unsupported type for ' + objID); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.tearDown = function() { |
|
|
if (this.tornDown) { |
|
|
return; |
|
|
} |
|
|
this.eventBaton.releaseBaton('processGitCommand', this.dispatch, this); |
|
|
this.removeAll(); |
|
|
this.tornDown = true; |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.reloadGraphics = function() { |
|
|
|
|
|
this.gitVisuals.rootCommit = this.refs['C0']; |
|
|
|
|
|
|
|
|
this.gitVisuals.initHeadBranch(); |
|
|
|
|
|
|
|
|
this.gitVisuals.drawTreeFromReload(); |
|
|
|
|
|
this.gitVisuals.refreshTreeHarsh(); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.removeAll = function() { |
|
|
this.branchCollection.reset(); |
|
|
this.tagCollection.reset(); |
|
|
this.commitCollection.reset(); |
|
|
this.refs = {}; |
|
|
this.HEAD = null; |
|
|
this.rootCommit = null; |
|
|
|
|
|
if (this.origin) { |
|
|
|
|
|
this.origin.gitVisuals.getVisualization().tearDown(); |
|
|
delete this.origin; |
|
|
this.gitVisuals.getVisualization().clearOrigin(); |
|
|
} |
|
|
|
|
|
this.gitVisuals.resetAll(); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.getDetachedHead = function() { |
|
|
|
|
|
var target = this.HEAD.get('target'); |
|
|
var targetType = target.get('type'); |
|
|
return targetType !== 'branch'; |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.validateBranchName = function(name) { |
|
|
|
|
|
name = name.replace(///g,"\/"); |
|
|
name = name.replace(/\s/g, ''); |
|
|
|
|
|
|
|
|
if ( |
|
|
!/^(\w+[.\/\-]?)+\w+$/.test(name) || |
|
|
name.search('o/') === 0 |
|
|
) { |
|
|
throw new GitError({ |
|
|
msg: intl.str( |
|
|
'bad-branch-name', |
|
|
{ branch: name } |
|
|
) |
|
|
}); |
|
|
} |
|
|
if (/^[cC]\d+$/.test(name)) { |
|
|
throw new GitError({ |
|
|
msg: intl.str( |
|
|
'bad-branch-name', |
|
|
{ branch: name } |
|
|
) |
|
|
}); |
|
|
} |
|
|
if (/[hH][eE][aA][dD]/.test(name)) { |
|
|
throw new GitError({ |
|
|
msg: intl.str( |
|
|
'bad-branch-name', |
|
|
{ branch: name } |
|
|
) |
|
|
}); |
|
|
} |
|
|
if (name.length > 9) { |
|
|
name = name.slice(0, 9); |
|
|
this.command.addWarning( |
|
|
intl.str( |
|
|
'branch-name-short', |
|
|
{ branch: name } |
|
|
) |
|
|
); |
|
|
} |
|
|
return name; |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.validateAndMakeBranch = function(id, target) { |
|
|
id = this.validateBranchName(id); |
|
|
if (this.doesRefExist(id)) { |
|
|
throw new GitError({ |
|
|
msg: intl.str( |
|
|
'bad-branch-name', |
|
|
{ branch: id } |
|
|
) |
|
|
}); |
|
|
} |
|
|
|
|
|
return this.makeBranch(id, target); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.validateAndMakeTag = function(id, target) { |
|
|
id = this.validateBranchName(id); |
|
|
if (this.refs[id]) { |
|
|
throw new GitError({ |
|
|
msg: intl.str( |
|
|
'bad-tag-name', |
|
|
{ tag: id } |
|
|
) |
|
|
}); |
|
|
} |
|
|
|
|
|
this.makeTag(id, target); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.makeBranch = function(id, target) { |
|
|
if (this.refs[id]) { |
|
|
var err = new Error(); |
|
|
throw new Error('woah already have that ref ' + id + ' ' + err.stack); |
|
|
} |
|
|
|
|
|
var branch = new Branch({ |
|
|
target: target, |
|
|
id: id |
|
|
}); |
|
|
this.branchCollection.add(branch); |
|
|
this.refs[branch.get('id')] = branch; |
|
|
return branch; |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.makeTag = function(id, target) { |
|
|
if (this.refs[id]) { |
|
|
throw new Error('woah already have that'); |
|
|
} |
|
|
|
|
|
var tag = new Tag({ |
|
|
target: target, |
|
|
id: id |
|
|
}); |
|
|
this.tagCollection.add(tag); |
|
|
this.refs[tag.get('id')] = tag; |
|
|
return tag; |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.getHead = function() { |
|
|
return Object.assign({}, this.HEAD); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.getTags = function() { |
|
|
var toReturn = []; |
|
|
this.tagCollection.each(function(tag) { |
|
|
toReturn.push({ |
|
|
id: tag.get('id'), |
|
|
target: tag.get('target'), |
|
|
remote: tag.getIsRemote(), |
|
|
obj: tag |
|
|
}); |
|
|
}, this); |
|
|
return toReturn; |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.getBranches = function() { |
|
|
var toReturn = []; |
|
|
this.branchCollection.each(function(branch) { |
|
|
toReturn.push({ |
|
|
id: branch.get('id'), |
|
|
selected: this.HEAD.get('target') === branch, |
|
|
target: branch.get('target'), |
|
|
remote: branch.getIsRemote(), |
|
|
obj: branch |
|
|
}); |
|
|
}, this); |
|
|
return toReturn; |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.getRemoteBranches = function() { |
|
|
var all = this.getBranches(); |
|
|
return all.filter(function(branchJSON) { |
|
|
return branchJSON.remote === true; |
|
|
}); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.getLocalBranches = function() { |
|
|
var all = this.getBranches(); |
|
|
return all.filter(function(branchJSON) { |
|
|
return branchJSON.remote === false; |
|
|
}); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.printBranchesWithout = function(without) { |
|
|
var commitToBranches = this.getUpstreamBranchSet(); |
|
|
var commitID = this.getCommitFromRef(without).get('id'); |
|
|
|
|
|
var toPrint = commitToBranches[commitID].map(function (branchJSON) { |
|
|
branchJSON.selected = this.HEAD.get('target').get('id') == branchJSON.id; |
|
|
return branchJSON; |
|
|
}, this); |
|
|
this.printBranches(toPrint); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.printBranches = function(branches) { |
|
|
var result = ''; |
|
|
branches.forEach(branch => { |
|
|
result += (branch.selected ? '* ' : '') + this.resolveName(branch.id).split('"')[1] + '\n'; |
|
|
}); |
|
|
throw new CommandResult({ |
|
|
msg: result |
|
|
}); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.printTags = function(tags) { |
|
|
var result = ''; |
|
|
tags.forEach(function (tag) { |
|
|
result += tag.id + '\n'; |
|
|
}); |
|
|
throw new CommandResult({ |
|
|
msg: result |
|
|
}); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.printRemotes = function(options) { |
|
|
var result = ''; |
|
|
if (options.verbose) { |
|
|
result += 'origin (fetch)\n'; |
|
|
result += TAB + 'git@github.com:pcottle/foo.git' + '\n\n'; |
|
|
result += 'origin (push)\n'; |
|
|
result += TAB + 'git@github.com:pcottle/foo.git'; |
|
|
} else { |
|
|
result += 'origin'; |
|
|
} |
|
|
throw new CommandResult({ |
|
|
msg: result |
|
|
}); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.getUniqueID = function() { |
|
|
var id = this.uniqueId('C'); |
|
|
|
|
|
var hasID = function(idToCheck) { |
|
|
|
|
|
|
|
|
if (this.refs[idToCheck]) { |
|
|
return true; |
|
|
} |
|
|
if (this.origin && this.origin.refs[idToCheck]) { |
|
|
return true; |
|
|
} |
|
|
return false; |
|
|
}.bind(this); |
|
|
|
|
|
while (hasID(id)) { |
|
|
id = this.uniqueId('C'); |
|
|
} |
|
|
return id; |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.makeCommit = function(parents, id, options) { |
|
|
|
|
|
|
|
|
|
|
|
if (!id) { |
|
|
id = this.getUniqueID(); |
|
|
} |
|
|
|
|
|
var commit = new Commit(Object.assign({ |
|
|
parents: parents, |
|
|
id: id, |
|
|
gitVisuals: this.gitVisuals |
|
|
}, |
|
|
options || {} |
|
|
)); |
|
|
|
|
|
this.refs[commit.get('id')] = commit; |
|
|
this.commitCollection.add(commit); |
|
|
return commit; |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.revert = function(whichCommits) { |
|
|
|
|
|
var toRevert = whichCommits.map(function(stringRef) { |
|
|
return this.getCommitFromRef(stringRef); |
|
|
}, this); |
|
|
|
|
|
var deferred = Q.defer(); |
|
|
var chain = deferred.promise; |
|
|
var destBranch = this.resolveID('HEAD'); |
|
|
|
|
|
chain = this.animationFactory.highlightEachWithPromise( |
|
|
chain, |
|
|
toRevert, |
|
|
destBranch |
|
|
); |
|
|
|
|
|
var base = this.getCommitFromRef('HEAD'); |
|
|
|
|
|
var chainStep = function(oldCommit) { |
|
|
var newId = this.rebaseAltID(oldCommit.get('id')); |
|
|
var commitMessage = intl.str('git-revert-msg', { |
|
|
oldCommit: this.resolveName(oldCommit), |
|
|
oldMsg: oldCommit.get('commitMessage') |
|
|
}); |
|
|
var newCommit = this.makeCommit([base], newId, { |
|
|
commitMessage: commitMessage |
|
|
}); |
|
|
base = newCommit; |
|
|
|
|
|
return this.animationFactory.playCommitBirthPromiseAnimation( |
|
|
newCommit, |
|
|
this.gitVisuals |
|
|
); |
|
|
}.bind(this); |
|
|
|
|
|
|
|
|
toRevert.forEach(function (commit) { |
|
|
chain = chain.then(function() { |
|
|
return chainStep(commit); |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
chain = chain.then(function() { |
|
|
this.setTargetLocation('HEAD', base); |
|
|
return this.animationFactory.playRefreshAnimation(this.gitVisuals); |
|
|
}.bind(this)); |
|
|
|
|
|
this.animationQueue.thenFinish(chain, deferred); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.reset = function(target) { |
|
|
this.setTargetLocation('HEAD', this.getCommitFromRef(target)); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.setupCherrypickChain = function(toCherrypick) { |
|
|
|
|
|
var deferred = Q.defer(); |
|
|
var chain = deferred.promise; |
|
|
var destinationBranch = this.resolveID('HEAD'); |
|
|
|
|
|
chain = this.animationFactory.highlightEachWithPromise( |
|
|
chain, |
|
|
toCherrypick, |
|
|
destinationBranch |
|
|
); |
|
|
|
|
|
var chainStep = function(commit) { |
|
|
var newCommit = this.cherrypick(commit); |
|
|
return this.animationFactory.playCommitBirthPromiseAnimation( |
|
|
newCommit, |
|
|
this.gitVisuals |
|
|
); |
|
|
}.bind(this); |
|
|
|
|
|
toCherrypick.forEach(function (arg) { |
|
|
chain = chain.then(function() { |
|
|
return chainStep(arg); |
|
|
}); |
|
|
}, this); |
|
|
|
|
|
this.animationQueue.thenFinish(chain, deferred); |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
GitEngine.prototype.checkUpstreamOfSource = function( |
|
|
target, |
|
|
source, |
|
|
targetBranch, |
|
|
sourceBranch, |
|
|
errorMsg |
|
|
) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var upstream = Graph.getUpstreamSet(source, sourceBranch); |
|
|
|
|
|
var targetLocationID = target.getCommitFromRef(targetBranch).get('id'); |
|
|
if (!upstream[targetLocationID]) { |
|
|
throw new GitError({ |
|
|
msg: errorMsg || intl.str('git-error-origin-fetch-no-ff') |
|
|
}); |
|
|
} |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.getTargetGraphDifference = function( |
|
|
target, |
|
|
source, |
|
|
targetBranch, |
|
|
sourceBranch, |
|
|
options |
|
|
) { |
|
|
options = options || {}; |
|
|
sourceBranch = source.resolveID(sourceBranch); |
|
|
|
|
|
var targetSet = Graph.getUpstreamSet(target, targetBranch); |
|
|
var sourceStartCommit = source.getCommitFromRef(sourceBranch); |
|
|
|
|
|
var sourceTree = source.exportTree(); |
|
|
var sourceStartCommitJSON = sourceTree.commits[sourceStartCommit.get('id')]; |
|
|
|
|
|
if (targetSet[sourceStartCommitJSON.id]) { |
|
|
|
|
|
if (options.dontThrowOnNoFetch) { |
|
|
return []; |
|
|
} else { |
|
|
throw new GitError({ |
|
|
msg: intl.str('git-error-origin-fetch-uptodate') |
|
|
}); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
sourceStartCommitJSON.depth = 0; |
|
|
var difference = []; |
|
|
var toExplore = [sourceStartCommitJSON]; |
|
|
|
|
|
var pushParent = function(parentID) { |
|
|
if (targetSet[parentID]) { |
|
|
|
|
|
return; |
|
|
} |
|
|
|
|
|
var parentJSON = sourceTree.commits[parentID]; |
|
|
parentJSON.depth = here.depth + 1; |
|
|
toExplore.push(parentJSON); |
|
|
}; |
|
|
|
|
|
while (toExplore.length) { |
|
|
var here = toExplore.pop(); |
|
|
difference.push(here); |
|
|
here.parents.forEach(pushParent); |
|
|
} |
|
|
|
|
|
|
|
|
var differenceUnique = Graph.getUniqueObjects(difference); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var inOrder = []; |
|
|
var allParentsMade = function(node) { |
|
|
var allParents = true; |
|
|
node.parents.forEach(function(parent) { |
|
|
allParents = allParents && targetSet[parent]; |
|
|
}); |
|
|
return allParents; |
|
|
}; |
|
|
|
|
|
while (differenceUnique.length) { |
|
|
for (var i = 0; i < differenceUnique.length; i++) { |
|
|
if (!allParentsMade(differenceUnique[i])) { |
|
|
|
|
|
|
|
|
continue; |
|
|
} |
|
|
|
|
|
var makeThis = differenceUnique[i]; |
|
|
inOrder.push(makeThis); |
|
|
|
|
|
differenceUnique.splice(i, 1); |
|
|
|
|
|
targetSet[makeThis.id] = true; |
|
|
} |
|
|
} |
|
|
return inOrder; |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.push = function(options) { |
|
|
options = options || {}; |
|
|
|
|
|
if (options.source === "") { |
|
|
|
|
|
this.pushDeleteRemoteBranch( |
|
|
this.refs[ORIGIN_PREFIX + options.destination], |
|
|
this.origin.refs[options.destination] |
|
|
); |
|
|
return; |
|
|
} |
|
|
|
|
|
var sourceBranch = this.resolveID(options.source); |
|
|
if (sourceBranch && sourceBranch.attributes.type === 'tag') { |
|
|
throw new GitError({ |
|
|
msg: intl.todo('Tags are not allowed as sources for pushing'), |
|
|
}); |
|
|
} |
|
|
|
|
|
if (!this.origin.doesRefExist(options.destination)) { |
|
|
console.warn('ref', options.destination); |
|
|
this.makeBranchOnOriginAndTrack( |
|
|
options.destination, |
|
|
this.getCommitFromRef(sourceBranch) |
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
this.animationFactory.playRefreshAnimation(this.origin.gitVisuals); |
|
|
this.animationFactory.playRefreshAnimation(this.gitVisuals); |
|
|
} |
|
|
var branchOnRemote = this.origin.resolveID(options.destination); |
|
|
var sourceLocation = this.resolveID(options.source || 'HEAD'); |
|
|
|
|
|
|
|
|
if (!options.force) { |
|
|
this.checkUpstreamOfSource( |
|
|
this, |
|
|
this.origin, |
|
|
branchOnRemote, |
|
|
sourceLocation, |
|
|
intl.str('git-error-origin-push-no-ff') |
|
|
); |
|
|
} |
|
|
|
|
|
var commitsToMake = this.getTargetGraphDifference( |
|
|
this.origin, |
|
|
this, |
|
|
branchOnRemote, |
|
|
sourceLocation, |
|
|
{ |
|
|
dontThrowOnNoFetch: true, |
|
|
} |
|
|
); |
|
|
if (!commitsToMake.length) { |
|
|
if (!options.force) { |
|
|
|
|
|
|
|
|
throw new GitError({ |
|
|
msg: intl.str('git-error-origin-fetch-uptodate') |
|
|
}); |
|
|
} else { |
|
|
var sourceCommit = this.getCommitFromRef(sourceBranch); |
|
|
var originCommit = this.getCommitFromRef(branchOnRemote); |
|
|
if (sourceCommit.id === originCommit.id) { |
|
|
|
|
|
throw new GitError({ |
|
|
msg: intl.str('git-error-origin-fetch-uptodate') |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
commitsToMake = commitsToMake.filter(function(commitJSON) { |
|
|
return !this.origin.refs[commitJSON.id]; |
|
|
}, this); |
|
|
|
|
|
var makeCommit = function(id, parentIDs) { |
|
|
|
|
|
|
|
|
var parents = parentIDs.map(function(parentID) { |
|
|
return this.origin.refs[parentID]; |
|
|
}, this); |
|
|
return this.origin.makeCommit(parents, id); |
|
|
}.bind(this); |
|
|
|
|
|
|
|
|
var chainStep = function(id, parents) { |
|
|
var newCommit = makeCommit(id, parents); |
|
|
return this.animationFactory.playCommitBirthPromiseAnimation( |
|
|
newCommit, |
|
|
this.origin.gitVisuals |
|
|
); |
|
|
}.bind(this); |
|
|
|
|
|
var deferred = Q.defer(); |
|
|
var chain = deferred.promise; |
|
|
|
|
|
commitsToMake.forEach(function(commitJSON) { |
|
|
chain = chain.then(function() { |
|
|
return this.animationFactory.playHighlightPromiseAnimation( |
|
|
this.refs[commitJSON.id], |
|
|
branchOnRemote |
|
|
); |
|
|
}.bind(this)); |
|
|
|
|
|
chain = chain.then(function() { |
|
|
return chainStep( |
|
|
commitJSON.id, |
|
|
commitJSON.parents |
|
|
); |
|
|
}); |
|
|
}, this); |
|
|
|
|
|
chain = chain.then(function() { |
|
|
var localLocationID = this.getCommitFromRef(sourceLocation).get('id'); |
|
|
var remoteCommit = this.origin.refs[localLocationID]; |
|
|
this.origin.setTargetLocation(branchOnRemote, remoteCommit); |
|
|
|
|
|
this.animationFactory.playRefreshAnimation(this.gitVisuals); |
|
|
return this.animationFactory.playRefreshAnimation(this.origin.gitVisuals); |
|
|
}.bind(this)); |
|
|
|
|
|
|
|
|
chain = chain.then(function() { |
|
|
var localCommit = this.getCommitFromRef(sourceLocation); |
|
|
this.setTargetLocation(this.resolveID(ORIGIN_PREFIX + options.destination), localCommit); |
|
|
return this.animationFactory.playRefreshAnimation(this.gitVisuals); |
|
|
}.bind(this)); |
|
|
|
|
|
if (!options.dontResolvePromise) { |
|
|
this.animationQueue.thenFinish(chain, deferred); |
|
|
} |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.pushDeleteRemoteBranch = function( |
|
|
remoteBranch, |
|
|
branchOnRemote |
|
|
) { |
|
|
if (branchOnRemote.get('id') === 'main') { |
|
|
throw new GitError({ |
|
|
msg: intl.todo('You cannot delete main branch on remote!') |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var id = remoteBranch.get('id'); |
|
|
this.origin.deleteBranch(branchOnRemote); |
|
|
this.deleteBranch(remoteBranch); |
|
|
this.branchCollection.each(function(branch) { |
|
|
if (branch.getRemoteTrackingBranchID() === id) { |
|
|
branch.setRemoteTrackingBranchID(null); |
|
|
} |
|
|
}, this); |
|
|
|
|
|
|
|
|
this.origin.pruneTree(); |
|
|
this.origin.externalRefresh(); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.fetch = function(options) { |
|
|
options = options || {}; |
|
|
var didMakeBranch; |
|
|
|
|
|
|
|
|
|
|
|
if (options.destination && options.source === '') { |
|
|
this.validateAndMakeBranch( |
|
|
options.destination, |
|
|
this.getCommitFromRef('HEAD') |
|
|
); |
|
|
return; |
|
|
} else if (options.destination && options.source) { |
|
|
didMakeBranch = didMakeBranch || this.makeRemoteBranchIfNeeded(options.source); |
|
|
didMakeBranch = didMakeBranch || this.makeBranchIfNeeded(options.destination); |
|
|
options.didMakeBranch = didMakeBranch; |
|
|
|
|
|
return this.fetchCore([{ |
|
|
destination: options.destination, |
|
|
source: options.source |
|
|
}], |
|
|
options |
|
|
); |
|
|
} |
|
|
|
|
|
var allBranchesOnRemote = this.origin.branchCollection.toArray(); |
|
|
var sourceDestPairs = allBranchesOnRemote.map(function(branch) { |
|
|
var branchName = branch.get('id'); |
|
|
didMakeBranch = didMakeBranch || this.makeRemoteBranchIfNeeded(branchName); |
|
|
|
|
|
return { |
|
|
destination: branch.getPrefixedID(), |
|
|
source: branchName |
|
|
}; |
|
|
}, this); |
|
|
options.didMakeBranch = didMakeBranch; |
|
|
return this.fetchCore(sourceDestPairs, options); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.fetchCore = function(sourceDestPairs, options) { |
|
|
|
|
|
|
|
|
|
|
|
sourceDestPairs.forEach(function (pair) { |
|
|
this.checkUpstreamOfSource( |
|
|
this, |
|
|
this.origin, |
|
|
pair.destination, |
|
|
pair.source |
|
|
); |
|
|
}, this); |
|
|
|
|
|
|
|
|
var commitsToMake = []; |
|
|
sourceDestPairs.forEach(function (pair) { |
|
|
commitsToMake = commitsToMake.concat(this.getTargetGraphDifference( |
|
|
this, |
|
|
this.origin, |
|
|
pair.destination, |
|
|
pair.source, |
|
|
Object.assign( |
|
|
{}, |
|
|
options, |
|
|
{dontThrowOnNoFetch: true} |
|
|
) |
|
|
)); |
|
|
}, this); |
|
|
|
|
|
if (!commitsToMake.length && !options.dontThrowOnNoFetch) { |
|
|
throw new GitError({ |
|
|
msg: intl.str('git-error-origin-fetch-uptodate') |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
commitsToMake = Graph.getUniqueObjects(commitsToMake); |
|
|
commitsToMake = Graph.descendSortDepth(commitsToMake); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
commitsToMake = commitsToMake.filter(function(commitJSON) { |
|
|
return !this.refs[commitJSON.id]; |
|
|
}, this); |
|
|
|
|
|
var makeCommit = function(id, parentIDs) { |
|
|
|
|
|
|
|
|
var parents = parentIDs.map(function(parentID) { |
|
|
return this.resolveID(parentID); |
|
|
}, this); |
|
|
return this.makeCommit(parents, id); |
|
|
}.bind(this); |
|
|
|
|
|
|
|
|
var chainStep = function(id, parents) { |
|
|
var newCommit = makeCommit(id, parents); |
|
|
return this.animationFactory.playCommitBirthPromiseAnimation( |
|
|
newCommit, |
|
|
this.gitVisuals |
|
|
); |
|
|
}.bind(this); |
|
|
|
|
|
var deferred = Q.defer(); |
|
|
var chain = deferred.promise; |
|
|
if (options.didMakeBranch) { |
|
|
chain = chain.then(function() { |
|
|
this.animationFactory.playRefreshAnimation(this.origin.gitVisuals); |
|
|
return this.animationFactory.playRefreshAnimation(this.gitVisuals); |
|
|
}.bind(this)); |
|
|
} |
|
|
|
|
|
var originBranchSet = this.origin.getUpstreamBranchSet(); |
|
|
commitsToMake.forEach(function (commitJSON) { |
|
|
|
|
|
|
|
|
var originBranch = originBranchSet[commitJSON.id][0].obj; |
|
|
var localBranch = this.refs[originBranch.getPrefixedID()]; |
|
|
|
|
|
chain = chain.then(function() { |
|
|
return this.animationFactory.playHighlightPromiseAnimation( |
|
|
this.origin.resolveID(commitJSON.id), |
|
|
localBranch |
|
|
); |
|
|
}.bind(this)); |
|
|
|
|
|
chain = chain.then(function() { |
|
|
return chainStep( |
|
|
commitJSON.id, |
|
|
commitJSON.parents |
|
|
); |
|
|
}); |
|
|
}, this); |
|
|
|
|
|
chain = chain.then(function() { |
|
|
|
|
|
sourceDestPairs.forEach(function (pair) { |
|
|
var ours = this.resolveID(pair.destination); |
|
|
var theirCommitID = this.origin.getCommitFromRef(pair.source).get('id'); |
|
|
|
|
|
|
|
|
var localCommit = this.refs[theirCommitID]; |
|
|
this.setTargetLocation(ours, localCommit); |
|
|
}, this); |
|
|
|
|
|
|
|
|
this.animationFactory.playRefreshAnimation(this.origin.gitVisuals); |
|
|
return this.animationFactory.playRefreshAnimation(this.gitVisuals); |
|
|
}.bind(this)); |
|
|
|
|
|
if (!options.dontResolvePromise) { |
|
|
this.animationQueue.thenFinish(chain, deferred); |
|
|
} |
|
|
return { |
|
|
chain: chain, |
|
|
deferred: deferred |
|
|
}; |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.pull = function(options) { |
|
|
options = options || {}; |
|
|
var localBranch = this.getOneBeforeCommit('HEAD'); |
|
|
|
|
|
|
|
|
var pendingFetch = this.fetch({ |
|
|
dontResolvePromise: true, |
|
|
dontThrowOnNoFetch: true, |
|
|
source: options.source, |
|
|
destination: options.destination |
|
|
}); |
|
|
|
|
|
if (!pendingFetch) { |
|
|
|
|
|
return; |
|
|
} |
|
|
|
|
|
var destBranch = this.resolveID(options.destination); |
|
|
|
|
|
if (options.isRebase) { |
|
|
this.pullFinishWithRebase(pendingFetch, localBranch, destBranch); |
|
|
} else { |
|
|
this.pullFinishWithMerge(pendingFetch, localBranch, destBranch); |
|
|
} |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.pullFinishWithRebase = function( |
|
|
pendingFetch, |
|
|
localBranch, |
|
|
remoteBranch |
|
|
) { |
|
|
var chain = pendingFetch.chain; |
|
|
var deferred = pendingFetch.deferred; |
|
|
chain = chain.then(function() { |
|
|
if (this.isUpstreamOf(remoteBranch, localBranch)) { |
|
|
this.command.set('error', new CommandResult({ |
|
|
msg: intl.str('git-result-uptodate') |
|
|
})); |
|
|
throw SHORT_CIRCUIT_CHAIN; |
|
|
} |
|
|
}.bind(this)); |
|
|
|
|
|
|
|
|
|
|
|
chain = chain.then(function() { |
|
|
return this.animationFactory.getDelayedPromise(300); |
|
|
}.bind(this)); |
|
|
|
|
|
chain = chain.then(function() { |
|
|
|
|
|
|
|
|
return this.animationFactory.playHighlightPromiseAnimation( |
|
|
this.getCommitFromRef(remoteBranch), |
|
|
localBranch |
|
|
); |
|
|
}.bind(this)); |
|
|
|
|
|
chain = chain.then(function() { |
|
|
pendingFetch.dontResolvePromise = true; |
|
|
|
|
|
|
|
|
if (this.isUpstreamOf(localBranch, remoteBranch)) { |
|
|
this.setTargetLocation( |
|
|
localBranch, |
|
|
this.getCommitFromRef(remoteBranch) |
|
|
); |
|
|
this.checkout(localBranch); |
|
|
return this.animationFactory.playRefreshAnimation(this.gitVisuals); |
|
|
} |
|
|
|
|
|
try { |
|
|
return this.rebase(remoteBranch, localBranch, pendingFetch); |
|
|
} catch (err) { |
|
|
this.filterError(err); |
|
|
if (err.getMsg() !== intl.str('git-error-rebase-none')) { |
|
|
throw err; |
|
|
} |
|
|
this.setTargetLocation( |
|
|
localBranch, |
|
|
this.getCommitFromRef(remoteBranch) |
|
|
); |
|
|
this.checkout(localBranch); |
|
|
return this.animationFactory.playRefreshAnimation(this.gitVisuals); |
|
|
} |
|
|
}.bind(this)); |
|
|
chain = chain.fail(catchShortCircuit); |
|
|
|
|
|
this.animationQueue.thenFinish(chain, deferred); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.pullFinishWithMerge = function( |
|
|
pendingFetch, |
|
|
localBranch, |
|
|
remoteBranch |
|
|
) { |
|
|
var chain = pendingFetch.chain; |
|
|
var deferred = pendingFetch.deferred; |
|
|
|
|
|
chain = chain.then(function() { |
|
|
if (this.mergeCheck(remoteBranch, localBranch)) { |
|
|
this.command.set('error', new CommandResult({ |
|
|
msg: intl.str('git-result-uptodate') |
|
|
})); |
|
|
throw SHORT_CIRCUIT_CHAIN; |
|
|
} |
|
|
}.bind(this)); |
|
|
|
|
|
|
|
|
|
|
|
chain = chain.then(function() { |
|
|
return this.animationFactory.getDelayedPromise(300); |
|
|
}.bind(this)); |
|
|
|
|
|
chain = chain.then(function() { |
|
|
|
|
|
|
|
|
return this.animationFactory.playHighlightPromiseAnimation( |
|
|
this.getCommitFromRef(remoteBranch), |
|
|
localBranch |
|
|
); |
|
|
}.bind(this)); |
|
|
|
|
|
chain = chain.then(function() { |
|
|
|
|
|
return this.animationFactory.playHighlightPromiseAnimation( |
|
|
this.getCommitFromRef(localBranch), |
|
|
remoteBranch |
|
|
); |
|
|
}.bind(this)); |
|
|
|
|
|
|
|
|
chain = chain.then(function() { |
|
|
return this.animationFactory.getDelayedPromise(700); |
|
|
}.bind(this)); |
|
|
chain = chain.then(function() { |
|
|
var newCommit = this.merge(remoteBranch); |
|
|
if (!newCommit) { |
|
|
|
|
|
return this.animationFactory.playRefreshAnimation(this.gitVisuals); |
|
|
} |
|
|
|
|
|
return this.animationFactory.playCommitBirthPromiseAnimation( |
|
|
newCommit, |
|
|
this.gitVisuals |
|
|
); |
|
|
}.bind(this)); |
|
|
chain = chain.fail(catchShortCircuit); |
|
|
|
|
|
this.animationQueue.thenFinish(chain, deferred); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.fakeTeamwork = function(numToMake, branch) { |
|
|
var makeOriginCommit = function() { |
|
|
var id = this.getUniqueID(); |
|
|
return this.origin.receiveTeamwork(id, branch, this.animationQueue); |
|
|
}.bind(this); |
|
|
|
|
|
var chainStep = function() { |
|
|
var newCommit = makeOriginCommit(); |
|
|
return this.animationFactory.playCommitBirthPromiseAnimation( |
|
|
newCommit, |
|
|
this.origin.gitVisuals |
|
|
); |
|
|
}.bind(this); |
|
|
|
|
|
var deferred = Q.defer(); |
|
|
var chain = deferred.promise; |
|
|
|
|
|
for(var i = 0; i < numToMake; i++) { |
|
|
chain = chain.then(chainStep); |
|
|
} |
|
|
this.animationQueue.thenFinish(chain, deferred); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.receiveTeamwork = function(id, branch, animationQueue) { |
|
|
this.checkout(this.resolveID(branch)); |
|
|
var newCommit = this.makeCommit([this.getCommitFromRef('HEAD')], id); |
|
|
this.setTargetLocation(this.HEAD, newCommit); |
|
|
|
|
|
return newCommit; |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.cherrypick = function(commit) { |
|
|
|
|
|
var id = this.rebaseAltID(commit.get('id')); |
|
|
|
|
|
|
|
|
var newCommit = this.makeCommit([this.getCommitFromRef('HEAD')], id); |
|
|
this.setTargetLocation(this.HEAD, newCommit); |
|
|
|
|
|
return newCommit; |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.commit = function(options) { |
|
|
options = options || {}; |
|
|
var targetCommit = this.getCommitFromRef(this.HEAD); |
|
|
var id = null; |
|
|
|
|
|
|
|
|
if (options.isAmend) { |
|
|
targetCommit = this.resolveID('HEAD~1'); |
|
|
id = this.rebaseAltID(this.getCommitFromRef('HEAD').get('id')); |
|
|
} |
|
|
|
|
|
var newCommit = this.makeCommit([targetCommit], id); |
|
|
if (this.getDetachedHead() && this.mode === 'git') { |
|
|
this.command.addWarning(intl.str('git-warning-detached')); |
|
|
} |
|
|
|
|
|
this.setTargetLocation(this.HEAD, newCommit); |
|
|
return newCommit; |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.resolveNameNoPrefix = function(someRef) { |
|
|
|
|
|
var obj = this.resolveID(someRef); |
|
|
if (obj.get('type') == 'commit') { |
|
|
return obj.get('id'); |
|
|
} |
|
|
if (obj.get('type') == 'branch') { |
|
|
return obj.get('id'); |
|
|
} |
|
|
|
|
|
return this.resolveNameNoPrefix(obj.get('target')); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.resolveName = function(someRef) { |
|
|
|
|
|
var obj = this.resolveID(someRef); |
|
|
if (obj.get('type') == 'commit') { |
|
|
return 'commit ' + obj.get('id'); |
|
|
} |
|
|
if (obj.get('type') == 'branch') { |
|
|
return 'branch "' + obj.get('id') + '"'; |
|
|
} |
|
|
|
|
|
return this.resolveName(obj.get('target')); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.resolveID = function(idOrTarget) { |
|
|
if (idOrTarget === null || idOrTarget === undefined) { |
|
|
var err = new Error(); |
|
|
throw new Error('Don\'t call this with null / undefined: ' + err.stack); |
|
|
} |
|
|
|
|
|
if (typeof idOrTarget !== 'string') { |
|
|
return idOrTarget; |
|
|
} |
|
|
return this.resolveStringRef(idOrTarget); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.resolveRelativeRef = function(commit, relative) { |
|
|
var regex = /([~\^])(\d*)/g; |
|
|
var matches; |
|
|
|
|
|
while (matches = regex.exec(relative)) { |
|
|
var next = commit; |
|
|
var num = matches[2] ? parseInt(matches[2], 10) : 1; |
|
|
|
|
|
if (matches[1] == '^') { |
|
|
next = commit.getParent(num-1); |
|
|
} else { |
|
|
while (next && num--) { |
|
|
next = next.getParent(0); |
|
|
} |
|
|
} |
|
|
|
|
|
if (!next) { |
|
|
var msg = intl.str('git-error-relative-ref', { |
|
|
commit: commit.id, |
|
|
match: matches[0] |
|
|
}); |
|
|
throw new GitError({ |
|
|
msg: msg |
|
|
}); |
|
|
} |
|
|
|
|
|
commit = next; |
|
|
} |
|
|
|
|
|
return commit; |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.doesRefExist = function(ref) { |
|
|
return !!this.refs[ref] |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.resolveStringRef = function(ref) { |
|
|
ref = this.crappyUnescape(ref); |
|
|
|
|
|
if (this.refs[ref]) { |
|
|
return this.refs[ref]; |
|
|
} |
|
|
|
|
|
if (ref.match(/^c\d+'*/) && this.refs[ref.toUpperCase()]) { |
|
|
return this.refs[ref.toUpperCase()]; |
|
|
} |
|
|
|
|
|
|
|
|
var startRef = null; |
|
|
var relative = null; |
|
|
var regex = /^([a-zA-Z0-9]+)(([~\^]\d*)*)$/; |
|
|
var matches = regex.exec(ref); |
|
|
if (matches) { |
|
|
startRef = matches[1]; |
|
|
relative = matches[2]; |
|
|
} else { |
|
|
throw new GitError({ |
|
|
msg: intl.str('git-error-exist', {ref: ref}) |
|
|
}); |
|
|
} |
|
|
|
|
|
if (!this.refs[startRef]) { |
|
|
throw new GitError({ |
|
|
msg: intl.str('git-error-exist', {ref: ref}) |
|
|
}); |
|
|
} |
|
|
var commit = this.getCommitFromRef(startRef); |
|
|
|
|
|
if (relative) { |
|
|
commit = this.resolveRelativeRef( commit, relative ); |
|
|
} |
|
|
|
|
|
return commit; |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.getCommitFromRef = function(ref) { |
|
|
var start = this.resolveID(ref); |
|
|
|
|
|
|
|
|
while (start.get('type') !== 'commit') { |
|
|
start = start.get('target'); |
|
|
} |
|
|
return start; |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.getType = function(ref) { |
|
|
return this.resolveID(ref).get('type'); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.setTargetLocation = function(ref, target) { |
|
|
if (this.getType(ref) == 'commit') { |
|
|
|
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ref = this.getOneBeforeCommit(ref); |
|
|
ref.set('target', target); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.updateBranchesFromSet = function(commitSet) { |
|
|
if (!commitSet) { |
|
|
throw new Error('need commit set here'); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var upstreamSet = this.getUpstreamBranchSet(); |
|
|
|
|
|
var branchesToUpdate = {}; |
|
|
|
|
|
|
|
|
commitSet.forEach(function (val, id) { |
|
|
upstreamSet[id].forEach(function (branchJSON) { |
|
|
branchesToUpdate[branchJSON.id] = true; |
|
|
}); |
|
|
}, this); |
|
|
|
|
|
var branchList = branchesToUpdate.map(function(val, id) { |
|
|
return id; |
|
|
}); |
|
|
return this.updateBranchesForHg(branchList); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.updateAllBranchesForHgAndPlay = function(branchList) { |
|
|
return this.updateBranchesForHg(branchList) && |
|
|
this.animationFactory.playRefreshAnimationSlow(this.gitVisuals); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.updateAllBranchesForHg = function() { |
|
|
var branchList = this.branchCollection.map(function(branch) { |
|
|
return branch.get('id'); |
|
|
}); |
|
|
return this.updateBranchesForHg(branchList); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.syncRemoteBranchFills = function() { |
|
|
this.branchCollection.each(function(branch) { |
|
|
if (!branch.getIsRemote()) { |
|
|
return; |
|
|
} |
|
|
var originBranch = this.origin.refs[branch.getBaseID()]; |
|
|
if (!originBranch.get('visBranch')) { |
|
|
|
|
|
return; |
|
|
} |
|
|
var originFill = originBranch.get('visBranch').get('fill'); |
|
|
branch.get('visBranch').set('fill', originFill); |
|
|
}, this); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.updateBranchesForHg = function(branchList) { |
|
|
var hasUpdated = false; |
|
|
branchList.forEach(function (branchID) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var commitID = this.getCommitFromRef(branchID).get('id'); |
|
|
var altID = this.getBumpedID(commitID); |
|
|
if (!this.refs[altID]) { |
|
|
return; |
|
|
} |
|
|
hasUpdated = true; |
|
|
|
|
|
var lastID; |
|
|
while (this.refs[altID]) { |
|
|
lastID = altID; |
|
|
altID = this.rebaseAltID(altID); |
|
|
} |
|
|
|
|
|
|
|
|
this.setTargetLocation(this.refs[branchID], this.refs[lastID]); |
|
|
}, this); |
|
|
|
|
|
if (!hasUpdated) { |
|
|
return false; |
|
|
} |
|
|
return true; |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.updateCommitParentsForHgRebase = function(commitSet) { |
|
|
var anyChange = false; |
|
|
Object.keys(commitSet).forEach(function(commitID) { |
|
|
var commit = this.refs[commitID]; |
|
|
var thisUpdated = commit.checkForUpdatedParent(this); |
|
|
anyChange = anyChange || thisUpdated; |
|
|
}, this); |
|
|
return anyChange; |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.pruneTreeAndPlay = function() { |
|
|
return this.pruneTree() && |
|
|
this.animationFactory.playRefreshAnimationSlow(this.gitVisuals); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.pruneTree = function(doPrintWarning = true) { |
|
|
var set = this.getUpstreamBranchSet(); |
|
|
|
|
|
var headSet = Graph.getUpstreamSet(this, 'HEAD'); |
|
|
Object.keys(headSet).forEach(function(commitID) { |
|
|
set[commitID] = true; |
|
|
}); |
|
|
Object.keys(this.getUpstreamTagSet()).forEach(commitID => set[commitID] = true); |
|
|
|
|
|
var toDelete = []; |
|
|
this.commitCollection.each(function(commit) { |
|
|
|
|
|
if (!set[commit.get('id')]) { |
|
|
toDelete.push(commit); |
|
|
} |
|
|
}, this); |
|
|
|
|
|
if (!toDelete.length) { |
|
|
|
|
|
|
|
|
return; |
|
|
} |
|
|
if (this.command && doPrintWarning) { |
|
|
this.command.addWarning(intl.str('hg-prune-tree')); |
|
|
} |
|
|
|
|
|
toDelete.forEach(function (commit) { |
|
|
commit.removeFromParents(); |
|
|
this.commitCollection.remove(commit); |
|
|
|
|
|
var ID = commit.get('id'); |
|
|
this.refs[ID] = undefined; |
|
|
delete this.refs[ID]; |
|
|
|
|
|
var visNode = commit.get('visNode'); |
|
|
if (visNode) { |
|
|
visNode.removeAll(); |
|
|
} |
|
|
}, this); |
|
|
|
|
|
return true; |
|
|
}; |
|
|
GitEngine.prototype.getUpstreamBranchSet = function() { |
|
|
return this.getUpstreamCollectionSet(this.branchCollection); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.getUpstreamTagSet = function() { |
|
|
return this.getUpstreamCollectionSet(this.tagCollection); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.getUpstreamCollectionSet = function(collection) { |
|
|
|
|
|
var commitToSet = {}; |
|
|
|
|
|
var inArray = function(arr, id) { |
|
|
var found = false; |
|
|
arr.forEach(function (wrapper) { |
|
|
if (wrapper.id == id) { |
|
|
found = true; |
|
|
} |
|
|
}); |
|
|
|
|
|
return found; |
|
|
}; |
|
|
|
|
|
var bfsSearch = function(commit) { |
|
|
var set = []; |
|
|
var pQueue = [commit]; |
|
|
while (pQueue.length) { |
|
|
var popped = pQueue.pop(); |
|
|
set.push(popped.get('id')); |
|
|
|
|
|
if (popped.get('parents') && popped.get('parents').length) { |
|
|
pQueue = pQueue.concat(popped.get('parents')); |
|
|
} |
|
|
} |
|
|
return set; |
|
|
}; |
|
|
|
|
|
collection.each(function(ref) { |
|
|
var set = bfsSearch(ref.get('target')); |
|
|
set.forEach(function (id) { |
|
|
commitToSet[id] = commitToSet[id] || []; |
|
|
|
|
|
|
|
|
if (!inArray(commitToSet[id], ref.get('id'))) { |
|
|
commitToSet[id].push({ |
|
|
obj: ref, |
|
|
id: ref.get('id') |
|
|
}); |
|
|
} |
|
|
}); |
|
|
}); |
|
|
|
|
|
return commitToSet; |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.getUpstreamHeadSet = function() { |
|
|
var set = Graph.getUpstreamSet(this, 'HEAD'); |
|
|
var including = this.getCommitFromRef('HEAD').get('id'); |
|
|
|
|
|
set[including] = true; |
|
|
return set; |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.getOneBeforeCommit = function(ref) { |
|
|
|
|
|
|
|
|
|
|
|
var start = this.resolveID(ref); |
|
|
if (start === this.HEAD && !this.getDetachedHead()) { |
|
|
start = start.get('target'); |
|
|
} |
|
|
return start; |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.scrapeBaseID = function(id) { |
|
|
var results = /^C(\d+)/.exec(id); |
|
|
|
|
|
if (!results) { |
|
|
throw new Error('regex failed on ' + id); |
|
|
} |
|
|
|
|
|
return 'C' + results[1]; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
GitEngine.prototype.rebaseAltID = function(id) { |
|
|
var newID = this.getBumpedID(id); |
|
|
while (this.refs[newID]) { |
|
|
newID = this.getBumpedID(newID); |
|
|
} |
|
|
return newID; |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.getMostRecentBumpedID = function(id) { |
|
|
var newID = id; |
|
|
var lastID; |
|
|
while (this.refs[newID]) { |
|
|
lastID = newID; |
|
|
newID = this.getBumpedID(newID); |
|
|
} |
|
|
return lastID; |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.getBumpedID = function(id) { |
|
|
|
|
|
|
|
|
var regexMap = [ |
|
|
[/^C(\d+)[']{0,2}$/, function(bits) { |
|
|
|
|
|
return bits[0] + "'"; |
|
|
}], |
|
|
[/^C(\d+)[']{3}$/, function(bits) { |
|
|
|
|
|
return bits[0].slice(0, -3) + "'^4"; |
|
|
}], |
|
|
[/^C(\d+)['][\^](\d+)$/, function(bits) { |
|
|
return 'C' + String(bits[1]) + "'^" + String(Number(bits[2]) + 1); |
|
|
}] |
|
|
]; |
|
|
|
|
|
|
|
|
for (var i = 0; i < regexMap.length; i++) { |
|
|
var regex = regexMap[i][0]; |
|
|
var func = regexMap[i][1]; |
|
|
var results = regex.exec(id); |
|
|
if (results) { |
|
|
return func(results); |
|
|
} |
|
|
} |
|
|
throw new Error('could not modify the id ' + id); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.idSortFunc = function(cA, cB) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var scale = 1000; |
|
|
|
|
|
var regexMap = [ |
|
|
[/^C(\d+)$/, function(bits) { |
|
|
|
|
|
return scale * bits[1]; |
|
|
}], |
|
|
[/^C(\d+)([']+)$/, function(bits) { |
|
|
|
|
|
return scale * bits[1] + bits[2].length; |
|
|
}], |
|
|
[/^C(\d+)['][\^](\d+)$/, function(bits) { |
|
|
return scale * bits[1] + Number(bits[2]); |
|
|
}] |
|
|
]; |
|
|
|
|
|
var getNumToSort = function(id) { |
|
|
for (var i = 0; i < regexMap.length; i++) { |
|
|
var regex = regexMap[i][0]; |
|
|
var func = regexMap[i][1]; |
|
|
var results = regex.exec(id); |
|
|
if (results) { |
|
|
return func(results); |
|
|
} |
|
|
} |
|
|
throw new Error('Could not parse commit ID ' + id); |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return getNumToSort(cA.get('id')) - getNumToSort(cB.get('id')); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.dateSortFunc = function(cA, cB) { |
|
|
|
|
|
|
|
|
return GitEngine.prototype.idSortFunc(cA, cB); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.hgRebase = function(destination, base) { |
|
|
var deferred = Q.defer(); |
|
|
var chain = this.rebase(destination, base, { |
|
|
dontResolvePromise: true, |
|
|
deferred: deferred |
|
|
}); |
|
|
|
|
|
|
|
|
if (!chain) { |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
var commonAncestor = this.getCommonAncestor(destination, base); |
|
|
var baseCommit = this.getCommitFromRef(base); |
|
|
|
|
|
var downstream = this.getDownstreamSet(base); |
|
|
|
|
|
var stopSet = Graph.getUpstreamSet(this, destination); |
|
|
var upstream = this.getUpstreamDiffSetFromSet(stopSet, base); |
|
|
|
|
|
|
|
|
var moreSets = []; |
|
|
Object.keys(upstream).forEach(function(id) { |
|
|
moreSets.push(this.getDownstreamSet(id)); |
|
|
}, this); |
|
|
|
|
|
var mainSet = {}; |
|
|
mainSet[baseCommit.get('id')] = true; |
|
|
[upstream, downstream].concat(moreSets).forEach(function(set) { |
|
|
Object.keys(set).forEach(function(id) { |
|
|
mainSet[id] = true; |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
var branchMap = {}; |
|
|
var upstreamSet = this.getUpstreamBranchSet(); |
|
|
Object.keys(mainSet).forEach(function(commitID) { |
|
|
|
|
|
upstreamSet[commitID].forEach(function(branchJSON) { |
|
|
branchMap[branchJSON.id] = true; |
|
|
}); |
|
|
}); |
|
|
|
|
|
var branchList = Object.keys(branchMap); |
|
|
|
|
|
chain = chain.then(function() { |
|
|
|
|
|
|
|
|
var anyChange = this.updateCommitParentsForHgRebase(mainSet); |
|
|
if (!anyChange) { |
|
|
return; |
|
|
} |
|
|
return this.animationFactory.playRefreshAnimationSlow(this.gitVisuals); |
|
|
}.bind(this)); |
|
|
|
|
|
chain = chain.then(function() { |
|
|
return this.updateAllBranchesForHgAndPlay(branchList); |
|
|
}.bind(this)); |
|
|
|
|
|
chain = chain.then(function() { |
|
|
|
|
|
return this.pruneTreeAndPlay(); |
|
|
}.bind(this)); |
|
|
|
|
|
this.animationQueue.thenFinish(chain, deferred); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.rebase = function(targetSource, currentLocation, options) { |
|
|
|
|
|
if (this.isUpstreamOf(targetSource, currentLocation)) { |
|
|
this.command.setResult(intl.str('git-result-uptodate')); |
|
|
|
|
|
|
|
|
|
|
|
this.checkout(currentLocation); |
|
|
|
|
|
|
|
|
return; |
|
|
} |
|
|
|
|
|
if (this.isUpstreamOf(currentLocation, targetSource)) { |
|
|
|
|
|
this.setTargetLocation(currentLocation, this.getCommitFromRef(targetSource)); |
|
|
|
|
|
|
|
|
this.command.setResult(intl.str('git-result-fastforward')); |
|
|
|
|
|
this.checkout(currentLocation); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var stopSet = Graph.getUpstreamSet(this, targetSource); |
|
|
var toRebaseRough = this.getUpstreamDiffFromSet(stopSet, currentLocation); |
|
|
return this.rebaseFinish(toRebaseRough, stopSet, targetSource, currentLocation, options); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.rebaseOnto = function(targetSource, oldSource, unit, options) { |
|
|
if (this.isUpstreamOf(unit, targetSource)) { |
|
|
this.setTargetLocation(unit, this.getCommitFromRef(targetSource)); |
|
|
this.command.setResult(intl.str('git-result-fastforward')); |
|
|
|
|
|
this.checkout(unit); |
|
|
return; |
|
|
} |
|
|
|
|
|
var stopSet = Graph.getUpstreamSet(this, targetSource); |
|
|
var oldBranchSet = Graph.getUpstreamSet(this, oldSource); |
|
|
var toRebaseRough = this.getUpstreamDiffFromSet(oldBranchSet, unit); |
|
|
return this.rebaseFinish(toRebaseRough, stopSet, targetSource, unit, options); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.getUpstreamDiffSetFromSet = function(stopSet, location) { |
|
|
var set = {}; |
|
|
this.getUpstreamDiffFromSet(stopSet, location).forEach(function (commit) { |
|
|
set[commit.get('id')] = true; |
|
|
}); |
|
|
return set; |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.getUpstreamDiffFromSet = function(stopSet, location) { |
|
|
var result = Graph.bfsFromLocationWithSet(this, location, stopSet); |
|
|
result.sort(this.dateSortFunc); |
|
|
return result; |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.getInteractiveRebaseCommits = function(targetSource, currentLocation) { |
|
|
var stopSet = Graph.getUpstreamSet(this, targetSource); |
|
|
var toRebaseRough = []; |
|
|
|
|
|
|
|
|
var pQueue = [this.getCommitFromRef(currentLocation)]; |
|
|
|
|
|
while (pQueue.length) { |
|
|
var popped = pQueue.pop(); |
|
|
|
|
|
if (stopSet[popped.get('id')]) { |
|
|
continue; |
|
|
} |
|
|
|
|
|
toRebaseRough.push(popped); |
|
|
pQueue = pQueue.concat(popped.get('parents')); |
|
|
pQueue.sort(this.dateSortFunc); |
|
|
} |
|
|
|
|
|
|
|
|
var toRebase = []; |
|
|
toRebaseRough.forEach(function (commit) { |
|
|
if (commit.get('parents').length == 1) { |
|
|
toRebase.push(commit); |
|
|
} |
|
|
}); |
|
|
|
|
|
if (!toRebase.length) { |
|
|
throw new GitError({ |
|
|
msg: intl.str('git-error-rebase-none') |
|
|
}); |
|
|
} |
|
|
|
|
|
return toRebase; |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.rebaseInteractiveTest = function(targetSource, currentLocation, options) { |
|
|
options = options || {}; |
|
|
|
|
|
|
|
|
var toRebase = this.getInteractiveRebaseCommits(targetSource, currentLocation); |
|
|
|
|
|
var rebaseMap = {}; |
|
|
toRebase.forEach(function (commit) { |
|
|
var id = commit.get('id'); |
|
|
rebaseMap[id] = commit; |
|
|
}); |
|
|
|
|
|
var rebaseOrder; |
|
|
if (options['interactiveTest'].length === 0) { |
|
|
|
|
|
|
|
|
rebaseOrder = toRebase; |
|
|
} else { |
|
|
|
|
|
var idsToRebase = options['interactiveTest'][0].split(','); |
|
|
|
|
|
|
|
|
var extraCommits = []; |
|
|
rebaseOrder = []; |
|
|
idsToRebase.forEach(function (id) { |
|
|
if (id in rebaseMap) { |
|
|
rebaseOrder.push(rebaseMap[id]); |
|
|
} else { |
|
|
extraCommits.push(id); |
|
|
} |
|
|
}); |
|
|
|
|
|
if (extraCommits.length > 0) { |
|
|
throw new GitError({ |
|
|
msg: intl.todo('Hey those commits don\'t exist in the set!') |
|
|
}); |
|
|
} |
|
|
} |
|
|
|
|
|
this.rebaseFinish(rebaseOrder, {}, targetSource, currentLocation); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.rebaseInteractive = function(targetSource, currentLocation, options) { |
|
|
options = options || {}; |
|
|
|
|
|
|
|
|
|
|
|
var toRebase = this.getInteractiveRebaseCommits(targetSource, currentLocation); |
|
|
|
|
|
|
|
|
|
|
|
this.animationQueue.set('defer', true); |
|
|
|
|
|
var deferred = Q.defer(); |
|
|
deferred.promise |
|
|
.then(function(userSpecifiedRebase) { |
|
|
|
|
|
if (!userSpecifiedRebase.length) { |
|
|
throw new CommandResult({ |
|
|
msg: intl.str('git-result-nothing') |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
this.rebaseFinish(userSpecifiedRebase, {}, targetSource, currentLocation); |
|
|
}.bind(this)) |
|
|
.fail(function(err) { |
|
|
this.filterError(err); |
|
|
this.command.set('error', err); |
|
|
this.animationQueue.start(); |
|
|
}.bind(this)) |
|
|
.done(); |
|
|
|
|
|
|
|
|
var initialCommitOrdering; |
|
|
if (options.initialCommitOrdering && options.initialCommitOrdering.length > 0) { |
|
|
var rebaseMap = {}; |
|
|
toRebase.forEach(function (commit) { |
|
|
rebaseMap[commit.get('id')] = true; |
|
|
}); |
|
|
|
|
|
|
|
|
initialCommitOrdering = []; |
|
|
options.initialCommitOrdering[0].split(',').forEach(function (id) { |
|
|
if (!rebaseMap[id]) { |
|
|
throw new GitError({ |
|
|
msg: intl.todo('Hey those commits don\'t exist in the set!') |
|
|
}); |
|
|
} |
|
|
initialCommitOrdering.push(id); |
|
|
}); |
|
|
} |
|
|
|
|
|
var InteractiveRebaseView = require('../views/rebaseView').InteractiveRebaseView; |
|
|
|
|
|
new InteractiveRebaseView({ |
|
|
deferred: deferred, |
|
|
toRebase: toRebase, |
|
|
initialCommitOrdering: initialCommitOrdering, |
|
|
aboveAll: options.aboveAll |
|
|
}); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.filterRebaseCommits = function( |
|
|
toRebaseRough, |
|
|
stopSet, |
|
|
options |
|
|
) { |
|
|
var changesAlreadyMade = {}; |
|
|
Object.keys(stopSet).forEach(function(key) { |
|
|
changesAlreadyMade[this.scrapeBaseID(key)] = true; |
|
|
}, this); |
|
|
var uniqueIDs = {}; |
|
|
|
|
|
|
|
|
return toRebaseRough.filter(function(commit) { |
|
|
|
|
|
if (commit.get('parents').length !== 1 && !options.preserveMerges) { |
|
|
return false; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
var baseID = this.scrapeBaseID(commit.get('id')); |
|
|
if (changesAlreadyMade[baseID]) { |
|
|
return false; |
|
|
} |
|
|
|
|
|
|
|
|
if (uniqueIDs[commit.get('id')]) { |
|
|
return false; |
|
|
} |
|
|
|
|
|
uniqueIDs[commit.get('id')] = true; |
|
|
return true; |
|
|
}, this); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.getRebasePreserveMergesParents = function(oldCommit) { |
|
|
var oldParents = oldCommit.get('parents'); |
|
|
return oldParents.map(function(parent) { |
|
|
var oldID = parent.get('id'); |
|
|
var newID = this.getMostRecentBumpedID(oldID); |
|
|
return this.refs[newID]; |
|
|
}, this); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.rebaseFinish = function( |
|
|
toRebaseRough, |
|
|
stopSet, |
|
|
targetSource, |
|
|
currentLocation, |
|
|
options |
|
|
) { |
|
|
options = options || {}; |
|
|
|
|
|
var destinationBranch = this.resolveID(targetSource); |
|
|
var deferred = options.deferred || Q.defer(); |
|
|
var chain = options.chain || deferred.promise; |
|
|
|
|
|
var toRebase = this.filterRebaseCommits(toRebaseRough, stopSet, options); |
|
|
if (!toRebase.length) { |
|
|
throw new GitError({ |
|
|
msg: intl.str('git-error-rebase-none') |
|
|
}); |
|
|
} |
|
|
|
|
|
chain = this.animationFactory.highlightEachWithPromise( |
|
|
chain, |
|
|
toRebase, |
|
|
destinationBranch |
|
|
); |
|
|
|
|
|
|
|
|
var base = this.getCommitFromRef(targetSource); |
|
|
var hasStartedChain = false; |
|
|
|
|
|
var chainStep = function(oldCommit) { |
|
|
var newId = this.rebaseAltID(oldCommit.get('id')); |
|
|
var parents; |
|
|
if (!options.preserveMerges || !hasStartedChain) { |
|
|
|
|
|
parents = [base]; |
|
|
} else { |
|
|
|
|
|
|
|
|
parents = (hasStartedChain) ? |
|
|
this.getRebasePreserveMergesParents(oldCommit) : |
|
|
[base]; |
|
|
} |
|
|
|
|
|
var newCommit = this.makeCommit(parents, newId); |
|
|
base = newCommit; |
|
|
hasStartedChain = true; |
|
|
|
|
|
return this.animationFactory.playCommitBirthPromiseAnimation( |
|
|
newCommit, |
|
|
this.gitVisuals |
|
|
); |
|
|
}.bind(this); |
|
|
|
|
|
|
|
|
toRebase.forEach(function (commit) { |
|
|
chain = chain.then(function() { |
|
|
return chainStep(commit); |
|
|
}); |
|
|
}, this); |
|
|
|
|
|
chain = chain.then(function() { |
|
|
if (this.resolveID(currentLocation).get('type') == 'commit') { |
|
|
|
|
|
|
|
|
this.checkout(base); |
|
|
} else { |
|
|
|
|
|
this.setTargetLocation(currentLocation, base); |
|
|
this.checkout(currentLocation); |
|
|
} |
|
|
return this.animationFactory.playRefreshAnimation(this.gitVisuals); |
|
|
}.bind(this)); |
|
|
|
|
|
if (!options.dontResolvePromise) { |
|
|
this.animationQueue.thenFinish(chain, deferred); |
|
|
} |
|
|
return chain; |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.mergeCheck = function(targetSource, currentLocation) { |
|
|
var sameCommit = this.getCommitFromRef(targetSource) === |
|
|
this.getCommitFromRef(currentLocation); |
|
|
return this.isUpstreamOf(targetSource, currentLocation) || sameCommit; |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.merge = function(targetSource, options) { |
|
|
options = options || {}; |
|
|
var currentLocation = 'HEAD'; |
|
|
|
|
|
|
|
|
if (this.mergeCheck(targetSource, currentLocation)) { |
|
|
throw new CommandResult({ |
|
|
msg: intl.str('git-result-uptodate') |
|
|
}); |
|
|
} |
|
|
|
|
|
if (this.isUpstreamOf(currentLocation, targetSource) && !options.noFF) { |
|
|
|
|
|
this.setTargetLocation(currentLocation, this.getCommitFromRef(targetSource)); |
|
|
|
|
|
this.command.setResult(intl.str('git-result-fastforward')); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
var parent1 = this.getCommitFromRef(currentLocation); |
|
|
var parent2 = this.getCommitFromRef(targetSource); |
|
|
|
|
|
|
|
|
var msg = intl.str( |
|
|
'git-merge-msg', |
|
|
{ |
|
|
target: this.resolveName(targetSource), |
|
|
current: this.resolveName(currentLocation) |
|
|
} |
|
|
); |
|
|
|
|
|
|
|
|
var mergeCommit = this.makeCommit( |
|
|
[parent1, parent2], |
|
|
null, |
|
|
{ |
|
|
commitMessage: msg |
|
|
} |
|
|
); |
|
|
|
|
|
this.setTargetLocation(currentLocation, mergeCommit); |
|
|
return mergeCommit; |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.checkout = function(idOrTarget) { |
|
|
var target = this.resolveID(idOrTarget); |
|
|
if (target.get('id') === 'HEAD') { |
|
|
|
|
|
|
|
|
return; |
|
|
} |
|
|
|
|
|
var type = target.get('type'); |
|
|
|
|
|
if (type === 'branch' && target.getIsRemote()) { |
|
|
target = this.getCommitFromRef(target.get('id')); |
|
|
} |
|
|
|
|
|
if (type !== 'branch' && type !== 'tag' && type !== 'commit') { |
|
|
throw new GitError({ |
|
|
msg: intl.str('git-error-options') |
|
|
}); |
|
|
} |
|
|
if (type === 'tag') { |
|
|
target = target.get('target'); |
|
|
} |
|
|
|
|
|
this.HEAD.set('target', target); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.forceBranch = function(branchName, where) { |
|
|
branchName = this.crappyUnescape(branchName); |
|
|
|
|
|
if (!this.doesRefExist(branchName)) { |
|
|
this.branch(branchName, where); |
|
|
} |
|
|
|
|
|
var branch = this.resolveID(branchName); |
|
|
|
|
|
if (branch.get('type') !== 'branch') { |
|
|
throw new GitError({ |
|
|
msg: intl.str('git-error-options') |
|
|
}); |
|
|
} |
|
|
if (branch.getIsRemote()) { |
|
|
throw new GitError({ |
|
|
msg: intl.str('git-error-remote-branch') |
|
|
}); |
|
|
} |
|
|
|
|
|
var whereCommit = this.getCommitFromRef(where); |
|
|
|
|
|
this.setTargetLocation(branch, whereCommit); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.branch = function(name, ref) { |
|
|
var target = this.getCommitFromRef(ref); |
|
|
var newBranch = this.validateAndMakeBranch(name, target); |
|
|
|
|
|
ref = this.resolveID(ref); |
|
|
if (this.isRemoteBranchRef(ref)) { |
|
|
this.setLocalToTrackRemote(newBranch, ref); |
|
|
} |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.isRemoteBranchRef = function(ref) { |
|
|
var resolved = this.resolveID(ref); |
|
|
if (resolved.get('type') !== 'branch') { |
|
|
return false; |
|
|
} |
|
|
return resolved.getIsRemote(); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.tag = function(name, ref) { |
|
|
var target = this.getCommitFromRef(ref); |
|
|
this.validateAndMakeTag(name, target); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.describe = function(ref) { |
|
|
var startCommit = this.getCommitFromRef(ref); |
|
|
|
|
|
|
|
|
var tagMap = {}; |
|
|
this.tagCollection.toJSON().forEach(function (tag) { |
|
|
tagMap[tag.target.get('id')] = tag.id; |
|
|
}); |
|
|
|
|
|
var pQueue = [startCommit]; |
|
|
var foundTag; |
|
|
var numAway = []; |
|
|
while (pQueue.length) { |
|
|
var popped = pQueue.pop(); |
|
|
var thisID = popped.get('id'); |
|
|
if (tagMap[thisID]) { |
|
|
foundTag = tagMap[thisID]; |
|
|
break; |
|
|
} |
|
|
|
|
|
numAway.push(popped.get('id')); |
|
|
|
|
|
var parents = popped.get('parents'); |
|
|
if (parents && parents.length) { |
|
|
pQueue = pQueue.concat(parents); |
|
|
pQueue.sort(this.dateSortFunc); |
|
|
} |
|
|
} |
|
|
|
|
|
if (!foundTag) { |
|
|
throw new GitError({ |
|
|
msg: intl.todo('Fatal: no tags found upstream') |
|
|
}); |
|
|
} |
|
|
|
|
|
if (numAway.length === 0) { |
|
|
throw new CommandResult({ |
|
|
msg: foundTag |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
throw new CommandResult({ |
|
|
msg: foundTag + '_' + numAway.length + '_g' + startCommit.get('id') |
|
|
}); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.validateAndDeleteBranch = function(name) { |
|
|
|
|
|
var target = this.resolveID(name); |
|
|
|
|
|
if (target.get('type') !== 'branch' || |
|
|
target.get('id') == 'main' || |
|
|
this.HEAD.get('target') === target) { |
|
|
throw new GitError({ |
|
|
msg: intl.str('git-error-branch') |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
var branch = target; |
|
|
|
|
|
if (target.getIsRemote()) { |
|
|
throw new GitError({ |
|
|
msg: intl.str('git-error-remote-branch') |
|
|
}); |
|
|
} |
|
|
this.deleteBranch(branch); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.deleteBranch = function(branch) { |
|
|
this.branchCollection.remove(branch); |
|
|
this.refs[branch.get('id')] = undefined; |
|
|
delete this.refs[branch.get('id')]; |
|
|
|
|
|
|
|
|
if (this.HEAD.get('target') === branch) { |
|
|
this.HEAD.set('target', this.refs['main']); |
|
|
} |
|
|
|
|
|
if (branch.get('visBranch')) { |
|
|
branch.get('visBranch').remove(); |
|
|
} |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.crappyUnescape = function(str) { |
|
|
return str.replace(/'/g, "'").replace(///g, "/"); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.filterError = function(err) { |
|
|
if (!(err instanceof GitError || |
|
|
err instanceof CommandResult)) { |
|
|
throw err; |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
GitEngine.prototype.externalRefresh = function() { |
|
|
this.animationQueue = new AnimationQueue({ |
|
|
callback: function() {} |
|
|
}); |
|
|
this.animationFactory.refreshTree(this.animationQueue, this.gitVisuals); |
|
|
this.animationQueue.start(); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.dispatch = function(command, deferred) { |
|
|
this.command = command; |
|
|
var vcs = command.get('vcs'); |
|
|
var executeCommand = function() { |
|
|
this.dispatchProcess(command, deferred); |
|
|
}.bind(this); |
|
|
|
|
|
|
|
|
this.handleModeChange(vcs, executeCommand); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.dispatchProcess = function(command, deferred) { |
|
|
|
|
|
var whenDone = function() { |
|
|
command.finishWith(deferred); |
|
|
}.bind(this); |
|
|
this.animationQueue = new AnimationQueue({ |
|
|
callback: whenDone |
|
|
}); |
|
|
|
|
|
var vcs = command.get('vcs'); |
|
|
var methodName = command.get('method').replace(/-/g, ''); |
|
|
|
|
|
try { |
|
|
Commands.commands.execute(vcs, methodName, this, this.command); |
|
|
} catch (err) { |
|
|
this.filterError(err); |
|
|
|
|
|
command.set('error', err); |
|
|
deferred.resolve(); |
|
|
return; |
|
|
} |
|
|
|
|
|
var willStartAuto = this.animationQueue.get('defer') || |
|
|
this.animationQueue.get('promiseBased'); |
|
|
|
|
|
|
|
|
if (!this.animationQueue.get('animations').length && !willStartAuto) { |
|
|
this.animationFactory.refreshTree(this.animationQueue, this.gitVisuals); |
|
|
} |
|
|
|
|
|
|
|
|
if (!willStartAuto) { |
|
|
this.animationQueue.start(); |
|
|
} |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.show = function(ref) { |
|
|
var commit = this.getCommitFromRef(ref); |
|
|
|
|
|
throw new CommandResult({ |
|
|
msg: commit.getShowEntry() |
|
|
}); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.status = function() { |
|
|
|
|
|
var lines = []; |
|
|
if (this.getDetachedHead()) { |
|
|
lines.push(intl.str('git-status-detached')); |
|
|
} else { |
|
|
var branchName = this.resolveNameNoPrefix('HEAD'); |
|
|
lines.push(intl.str('git-status-onbranch', {branch: branchName})); |
|
|
} |
|
|
lines.push('Changes to be committed:'); |
|
|
lines.push(''); |
|
|
lines.push(TAB + 'modified: cal/OskiCostume.stl'); |
|
|
lines.push(''); |
|
|
lines.push(intl.str('git-status-readytocommit')); |
|
|
|
|
|
var msg = ''; |
|
|
lines.forEach(function (line) { |
|
|
msg += '# ' + line + '\n'; |
|
|
}); |
|
|
|
|
|
throw new CommandResult({ |
|
|
msg: msg |
|
|
}); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.logWithout = function(ref, omitBranch) { |
|
|
|
|
|
omitBranch = omitBranch.slice(1); |
|
|
this.log(ref, Graph.getUpstreamSet(this, omitBranch)); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.revlist = function(refs) { |
|
|
var range = new RevisionRange(this, refs); |
|
|
|
|
|
|
|
|
var bigLogStr = range.formatRevisions(function(c) { |
|
|
return c.id + '\n'; |
|
|
}); |
|
|
|
|
|
throw new CommandResult({ |
|
|
msg: bigLogStr |
|
|
}); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.log = function(refs) { |
|
|
var range = new RevisionRange(this, refs); |
|
|
|
|
|
|
|
|
var bigLogStr = range.formatRevisions(function(c) { |
|
|
return c.getLogEntry(); |
|
|
}); |
|
|
|
|
|
throw new CommandResult({ |
|
|
msg: bigLogStr |
|
|
}); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.getCommonAncestor = function(ancestor, cousin, dontThrow) { |
|
|
if (this.isUpstreamOf(cousin, ancestor) && !dontThrow) { |
|
|
throw new Error('Don\'t use common ancestor if we are upstream!'); |
|
|
} |
|
|
|
|
|
var upstreamSet = Graph.getUpstreamSet(this, ancestor); |
|
|
|
|
|
|
|
|
var queue = [this.getCommitFromRef(cousin)]; |
|
|
while (queue.length) { |
|
|
var here = queue.pop(); |
|
|
if (upstreamSet[here.get('id')]) { |
|
|
return here; |
|
|
} |
|
|
queue = queue.concat(here.get('parents')); |
|
|
} |
|
|
throw new Error('something has gone very wrong... two nodes aren\'t connected!'); |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.isUpstreamOf = function(child, ancestor) { |
|
|
child = this.getCommitFromRef(child); |
|
|
|
|
|
|
|
|
|
|
|
var upstream = Graph.getUpstreamSet(this, ancestor); |
|
|
return upstream[child.get('id')] !== undefined; |
|
|
}; |
|
|
|
|
|
GitEngine.prototype.getDownstreamSet = function(ancestor) { |
|
|
var commit = this.getCommitFromRef(ancestor); |
|
|
|
|
|
var ancestorID = commit.get('id'); |
|
|
var queue = [commit]; |
|
|
|
|
|
var exploredSet = {}; |
|
|
exploredSet[ancestorID] = true; |
|
|
|
|
|
var addToExplored = function(child) { |
|
|
exploredSet[child.get('id')] = true; |
|
|
queue.push(child); |
|
|
}; |
|
|
|
|
|
while (queue.length) { |
|
|
var here = queue.pop(); |
|
|
var children = here.get('children'); |
|
|
|
|
|
children.forEach(addToExplored); |
|
|
} |
|
|
return exploredSet; |
|
|
}; |
|
|
|
|
|
var Ref = Backbone.Model.extend({ |
|
|
initialize: function() { |
|
|
if (!this.get('target')) { |
|
|
throw new Error('must be initialized with target'); |
|
|
} |
|
|
if (!this.get('id')) { |
|
|
throw new Error('must be given an id'); |
|
|
} |
|
|
this.set('type', 'general ref'); |
|
|
|
|
|
if (this.get('id') == 'HEAD') { |
|
|
this.set('lastLastTarget', null); |
|
|
this.set('lastTarget', this.get('target')); |
|
|
|
|
|
this.on('change:target', this.targetChanged, this); |
|
|
} |
|
|
}, |
|
|
|
|
|
getIsRemote: function() { |
|
|
return false; |
|
|
}, |
|
|
|
|
|
getName: function() { |
|
|
return this.get('id'); |
|
|
}, |
|
|
|
|
|
targetChanged: function(model, targetValue, ev) { |
|
|
|
|
|
|
|
|
|
|
|
this.set('lastLastTarget', this.get('lastTarget')); |
|
|
this.set('lastTarget', targetValue); |
|
|
}, |
|
|
|
|
|
toString: function() { |
|
|
return 'a ' + this.get('type') + 'pointing to ' + String(this.get('target')); |
|
|
} |
|
|
}); |
|
|
|
|
|
var Branch = Ref.extend({ |
|
|
defaults: { |
|
|
visBranch: null, |
|
|
remoteTrackingBranchID: null, |
|
|
remote: false |
|
|
}, |
|
|
|
|
|
initialize: function() { |
|
|
Ref.prototype.initialize.call(this); |
|
|
this.set('type', 'branch'); |
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
setRemoteTrackingBranchID: function(id) { |
|
|
this.set('remoteTrackingBranchID', id); |
|
|
}, |
|
|
|
|
|
getRemoteTrackingBranchID: function() { |
|
|
return this.get('remoteTrackingBranchID'); |
|
|
}, |
|
|
|
|
|
getPrefixedID: function() { |
|
|
if (this.getIsRemote()) { |
|
|
throw new Error('im already remote'); |
|
|
} |
|
|
return ORIGIN_PREFIX + this.get('id'); |
|
|
}, |
|
|
|
|
|
getBaseID: function() { |
|
|
if (!this.getIsRemote()) { |
|
|
throw new Error('im not remote so can\'t get base'); |
|
|
} |
|
|
return this.get('id').replace(ORIGIN_PREFIX, ''); |
|
|
}, |
|
|
|
|
|
getIsRemote: function() { |
|
|
if (typeof this.get('id') !== 'string') { |
|
|
debugger; |
|
|
} |
|
|
return this.get('id').slice(0, 2) === ORIGIN_PREFIX; |
|
|
} |
|
|
}); |
|
|
|
|
|
var Commit = Backbone.Model.extend({ |
|
|
defaults: { |
|
|
type: 'commit', |
|
|
children: null, |
|
|
parents: null, |
|
|
author: 'Peter Cottle', |
|
|
createTime: null, |
|
|
commitMessage: null, |
|
|
visNode: null, |
|
|
gitVisuals: null |
|
|
}, |
|
|
|
|
|
constants: { |
|
|
circularFields: ['gitVisuals', 'visNode', 'children'] |
|
|
}, |
|
|
|
|
|
getLogEntry: function() { |
|
|
return [ |
|
|
'Author: ' + this.get('author'), |
|
|
'Date: ' + this.get('createTime'), |
|
|
'', |
|
|
this.get('commitMessage'), |
|
|
'', |
|
|
'Commit: ' + this.get('id') |
|
|
].join('<br/>') + '\n'; |
|
|
}, |
|
|
|
|
|
getShowEntry: function() { |
|
|
|
|
|
return [ |
|
|
this.getLogEntry().replace('\n', ''), |
|
|
'diff --git a/bigGameResults.html b/bigGameResults.html', |
|
|
'--- bigGameResults.html', |
|
|
'+++ bigGameResults.html', |
|
|
'@@ 13,27 @@ Winner, Score', |
|
|
'- Stanfurd, 14-7', |
|
|
'+ Cal, 21-14' |
|
|
].join('<br/>') + '\n'; |
|
|
}, |
|
|
|
|
|
validateAtInit: function() { |
|
|
if (!this.get('id')) { |
|
|
throw new Error('Need ID!!'); |
|
|
} |
|
|
|
|
|
if (!this.get('createTime')) { |
|
|
this.set('createTime', new Date().toString()); |
|
|
} |
|
|
if (!this.get('commitMessage')) { |
|
|
this.set('commitMessage', intl.str('git-dummy-msg')); |
|
|
} |
|
|
|
|
|
this.set('children', []); |
|
|
|
|
|
|
|
|
if (!this.get('rootCommit')) { |
|
|
if (!this.get('parents') || !this.get('parents').length) { |
|
|
throw new Error('needs parents'); |
|
|
} |
|
|
} |
|
|
}, |
|
|
|
|
|
addNodeToVisuals: function() { |
|
|
var visNode = this.get('gitVisuals').addNode(this.get('id'), this); |
|
|
this.set('visNode', visNode); |
|
|
}, |
|
|
|
|
|
addEdgeToVisuals: function(parent) { |
|
|
this.get('gitVisuals').addEdge(this.get('id'), parent.get('id')); |
|
|
}, |
|
|
|
|
|
getParent: function(parentNum) { |
|
|
if (this && this.attributes && this.attributes.parents) { |
|
|
return this.attributes.parents[parentNum]; |
|
|
} else { |
|
|
return null; |
|
|
} |
|
|
}, |
|
|
|
|
|
removeFromParents: function() { |
|
|
this.get('parents').forEach(function (parent) { |
|
|
parent.removeChild(this); |
|
|
}, this); |
|
|
}, |
|
|
|
|
|
checkForUpdatedParent: function(engine) { |
|
|
var parents = this.get('parents'); |
|
|
if (parents.length > 1) { |
|
|
return; |
|
|
} |
|
|
var parent = parents[0]; |
|
|
var parentID = parent.get('id'); |
|
|
var newestID = engine.getMostRecentBumpedID(parentID); |
|
|
|
|
|
if (parentID === newestID) { |
|
|
|
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
var newParent = engine.refs[newestID]; |
|
|
|
|
|
this.removeFromParents(); |
|
|
this.set('parents', [newParent]); |
|
|
newParent.get('children').push(this); |
|
|
|
|
|
|
|
|
|
|
|
var visNode = this.get('visNode'); |
|
|
if (visNode) { |
|
|
visNode.removeAllEdges(); |
|
|
} |
|
|
|
|
|
var gitVisuals = this.get('gitVisuals'); |
|
|
if (gitVisuals) { |
|
|
gitVisuals.addEdge(this.get('id'), newestID); |
|
|
} |
|
|
|
|
|
return true; |
|
|
}, |
|
|
|
|
|
removeChild: function(childToRemove) { |
|
|
var newChildren = []; |
|
|
this.get('children').forEach(function (child) { |
|
|
if (child !== childToRemove) { |
|
|
newChildren.push(child); |
|
|
} |
|
|
}); |
|
|
this.set('children', newChildren); |
|
|
}, |
|
|
|
|
|
isMainParent: function(parent) { |
|
|
var index = this.get('parents').indexOf(parent); |
|
|
return index === 0; |
|
|
}, |
|
|
|
|
|
initialize: function(options) { |
|
|
this.validateAtInit(); |
|
|
this.addNodeToVisuals(); |
|
|
|
|
|
(this.get('parents') || []).forEach(function (parent) { |
|
|
parent.get('children').push(this); |
|
|
this.addEdgeToVisuals(parent); |
|
|
}, this); |
|
|
} |
|
|
}); |
|
|
|
|
|
var Tag = Ref.extend({ |
|
|
defaults: { |
|
|
visTag: null |
|
|
}, |
|
|
|
|
|
initialize: function() { |
|
|
Ref.prototype.initialize.call(this); |
|
|
this.set('type', 'tag'); |
|
|
} |
|
|
}); |
|
|
|
|
|
function RevisionRange(engine, specifiers) { |
|
|
this.engine = engine; |
|
|
this.tipsToInclude = []; |
|
|
this.tipsToExclude = []; |
|
|
this.includedRefs = {}; |
|
|
this.excludedRefs = {}; |
|
|
this.revisions = []; |
|
|
|
|
|
this.processSpecifiers(specifiers); |
|
|
} |
|
|
|
|
|
var rangeRegex = /^(.*)\.\.(.*)$/; |
|
|
|
|
|
RevisionRange.prototype.processAsRange = function(specifier) { |
|
|
var match = specifier.match(rangeRegex); |
|
|
if(!match) { |
|
|
return false; |
|
|
} |
|
|
this.tipsToExclude.push(match[1]); |
|
|
this.tipsToInclude.push(match[2]); |
|
|
return true; |
|
|
}; |
|
|
|
|
|
RevisionRange.prototype.processAsExclusion = function(specifier) { |
|
|
if(!specifier.startsWith('^')) { |
|
|
return false; |
|
|
} |
|
|
this.tipsToExclude.push(specifier.slice(1)); |
|
|
return true; |
|
|
}; |
|
|
|
|
|
RevisionRange.prototype.processAsInclusion = function(specifier) { |
|
|
this.tipsToInclude.push(specifier); |
|
|
return true; |
|
|
}; |
|
|
|
|
|
RevisionRange.prototype.processSpecifiers = function(specifiers) { |
|
|
var self = this; |
|
|
var processors = [ |
|
|
this.processAsRange, |
|
|
this.processAsExclusion |
|
|
]; |
|
|
|
|
|
specifiers.forEach(function(specifier) { |
|
|
if(!processors.some(function(processor) { return processor.bind(self)(specifier); })) { |
|
|
self.processAsInclusion(specifier); |
|
|
} |
|
|
}); |
|
|
|
|
|
this.tipsToExclude.forEach(function(exclusion) { |
|
|
self.addExcluded(Graph.getUpstreamSet(self.engine, exclusion)); |
|
|
}); |
|
|
|
|
|
this.tipsToInclude.forEach(function(inclusion) { |
|
|
self.addIncluded(Graph.getUpstreamSet(self.engine, inclusion)); |
|
|
}); |
|
|
|
|
|
var includedKeys = Array.from(Object.keys(self.includedRefs)); |
|
|
|
|
|
self.revisions = includedKeys.map(function(revision) { |
|
|
return self.engine.resolveStringRef(revision); |
|
|
}); |
|
|
self.revisions.sort(self.engine.dateSortFunc); |
|
|
self.revisions.reverse(); |
|
|
}; |
|
|
|
|
|
RevisionRange.prototype.isExcluded = function(revision) { |
|
|
return this.excludedRefs.hasOwnProperty(revision); |
|
|
}; |
|
|
|
|
|
RevisionRange.prototype.addExcluded = function(setToExclude) { |
|
|
var self = this; |
|
|
Object.keys(setToExclude).forEach(function(toExclude) { |
|
|
if(!self.isExcluded(toExclude)) { |
|
|
self.excludedRefs[toExclude] = true; |
|
|
} |
|
|
}); |
|
|
}; |
|
|
|
|
|
RevisionRange.prototype.addIncluded = function(setToInclude) { |
|
|
var self = this; |
|
|
Object.keys(setToInclude).forEach(function(toInclude) { |
|
|
if(!self.isExcluded(toInclude)) { |
|
|
self.includedRefs[toInclude] = true; |
|
|
} |
|
|
}); |
|
|
}; |
|
|
|
|
|
RevisionRange.prototype.formatRevisions = function(revisionFormatter) { |
|
|
var output = ""; |
|
|
this.revisions.forEach(function(c) { |
|
|
output += revisionFormatter(c); |
|
|
}); |
|
|
return output; |
|
|
}; |
|
|
|
|
|
exports.GitEngine = GitEngine; |
|
|
exports.Commit = Commit; |
|
|
exports.Branch = Branch; |
|
|
exports.Tag = Tag; |
|
|
exports.Ref = Ref; |
|
|
|