').appendTo(row);
+ $('
').appendTo(container);
+ $('
').text(entry.name).appendTo(container);
+ // if (entry.commit) {
+ // $('').text(entry.commit.sha).appendTo(container);
+ // }
+
+ if (entry.remote) {
+ $('').text(entry.remote||"").appendTo(container);
+ if (entry.status.ahead+entry.status.behind > 0) {
+ $(''+
+ ' '+entry.status.ahead+' '+
+ ' '+entry.status.behind+''+
+ '').appendTo(container);
+ }
+ }
+
+ var tools = $('').appendTo(container);
+ if (entry.current) {
+ tools.text('current');
+ } else {
+ $('')
+ .appendTo(tools)
+ .click(function(e) {
+ e.preventDefault();
+ var spinner = utils.addSpinnerOverlay(row).addClass('projects-dialog-spinner-contain');
+ var notification = RED.notify("Are you sure you want to delete the local branch '"+entry.name+"'? This cannot be undone.", {
+ type: "warning",
+ modal: true,
+ fixed: true,
+ buttons: [
+ {
+ text: RED._("common.label.cancel"),
+ click: function() {
+ spinner.remove();
+ notification.close();
+ }
+ },{
+ text: 'Delete branch',
+ click: function() {
+ notification.close();
+ var url = "projects/"+activeProject.name+"/branches/"+entry.name;
+ var options = {
+ url: url,
+ type: "DELETE",
+ responses: {
+ 200: function(data) {
+ row.fadeOut(200,function() {
+ branchList.editableList('removeItem',entry);
+ spinner.remove();
+ });
+ },
+ 400: {
+ 'git_delete_branch_unmerged': function(error) {
+ notification = RED.notify("The local branch '"+entry.name+"' has unmerged changes that will be lost. Are you sure you want to delete it?", {
+ type: "warning",
+ modal: true,
+ fixed: true,
+ buttons: [
+ {
+ text: RED._("common.label.cancel"),
+ click: function() {
+ spinner.remove();
+ notification.close();
+ }
+ },{
+ text: 'Delete unmerged branch',
+ click: function() {
+ options.url += "?force=true";
+ notification.close();
+ utils.sendRequest(options);
+ }
+ }
+ ]
+ });
+ },
+ 'unexpected_error': function(error) {
+ console.log(error);
+ spinner.remove();
+ }
+ },
+ }
+ }
+ utils.sendRequest(options);
+ }
+ }
+
+ ]
+ })
+ })
+ }
+
+ }
+ });
+ $.getJSON("projects/"+activeProject.name+"/branches",function(result) {
+ if (result.branches) {
+ result.branches.sort(function(A,B) {
+ if (A.current) { return -1 }
+ if (B.current) { return 1 }
+ return A.name.localeCompare(B.name);
+ });
+ result.branches.forEach(function(branch) {
+ branchList.editableList('addItem',branch);
+ })
+ }
+ })
+ }
function createRemoteRepositorySection(activeProject,pane) {
- var title = $('').text("Version Control").appendTo(pane);
+ $('').text("Version Control").appendTo(pane);
+
+ createLocalBranchListSection(activeProject,pane);
+
+ var repoContainer = $('').appendTo(pane);
+ var title = $('').text("Git remotes").appendTo(repoContainer);
+
var editRepoButton = $('')
.appendTo(title)
.click(function(evt) {
@@ -958,9 +1082,9 @@ RED.projects.settings = (function() {
$('.projects-dialog-remote-list-entry-delete').show();
remoteListAddButton.show();
});
-
- var repoContainer = $('').appendTo(pane);
- var grTitle = $('').text("Git remotes").appendTo(repoContainer);
+
+
+
row = $('').appendTo(repoContainer);
var remotesList = $('').appendTo(row);
diff --git a/editor/sass/projects.scss b/editor/sass/projects.scss
index 410b3da63..cef18fbfe 100644
--- a/editor/sass/projects.scss
+++ b/editor/sass/projects.scss
@@ -676,3 +676,44 @@
}
}
*/
+
+.projects-dialog-branch-list {
+ .red-ui-editableList-container li:last-child {
+ border-bottom: none;
+ }
+}
+
+.projects-dialog-branch-list-entry {
+ span {
+ display: inline-block;
+ }
+ span:first-child {
+ text-align: center;
+ min-width: 30px;
+ }
+ .branch-name {
+ min-width: 150px;
+ }
+ .branch-remote-name {
+ color: #aaa;
+ font-size: 0.9em;
+ min-width: 150px;
+ }
+ .branch-remote-status {
+ color: #aaa;
+ font-size: 0.9em;
+ }
+ .commit {
+ color: #aaa;
+ font-size: 0.9em;
+ padding: 2px 5px;
+
+ }
+
+ .projects-dialog-branch-list-entry-tools {
+ float: right;
+ margin-right: 20px;
+ font-size: 0.9em;
+ color: #999;
+ }
+}
diff --git a/red/api/editor/projects/index.js b/red/api/editor/projects/index.js
index 33576a0a5..6ce4ab781 100644
--- a/red/api/editor/projects/index.js
+++ b/red/api/editor/projects/index.js
@@ -115,7 +115,7 @@ module.exports = {
})
});
- // Delete project - tbd
+ // Delete project
app.delete("/:id", needsPermission("projects.write"), function(req,res) {
runtime.storage.projects.deleteProject(req.user, req.params.id).then(function() {
res.status(204).end();
@@ -373,6 +373,23 @@ module.exports = {
})
});
+ // Delete a local branch - ?force=true
+ app.delete("/:id/branches/:branchName", needsPermission("projects.write"), function(req, res) {
+ var projectName = req.params.id;
+ var branchName = req.params.branchName;
+ var force = !!req.query.force;
+ runtime.storage.projects.deleteBranch(req.user, projectName, branchName, false, force).then(function(data) {
+ res.status(204).end();
+ })
+ .catch(function(err) {
+ if (err.code) {
+ res.status(400).json({error:err.code, message: err.message});
+ } else {
+ res.status(400).json({error:"unexpected_error", message:err.toString()});
+ }
+ });
+ });
+
// Get a list of remote branches
app.get("/:id/branches/remote", needsPermission("projects.read"), function(req, res) {
var projectName = req.params.id;
@@ -380,7 +397,6 @@ module.exports = {
res.json(data);
})
.catch(function(err) {
- console.log(err.stack);
if (err.code) {
res.status(400).json({error:err.code, message: err.message});
} else {
diff --git a/red/runtime/storage/localfilesystem/projects/Project.js b/red/runtime/storage/localfilesystem/projects/Project.js
index 67aaaadf9..41908d2c9 100644
--- a/red/runtime/storage/localfilesystem/projects/Project.js
+++ b/red/runtime/storage/localfilesystem/projects/Project.js
@@ -234,7 +234,7 @@ Project.prototype.update = function (user, data) {
if (data.git.hasOwnProperty('remotes')) {
var remoteNames = Object.keys(data.git.remotes);
var remotesChanged = false;
- var modifyRemotesPromise = when.resolve();
+ var modifyRemotesPromise = Promise.resolve();
remoteNames.forEach(function(name) {
if (data.git.remotes[name].removed) {
remotesChanged = true;
@@ -348,7 +348,7 @@ Project.prototype.status = function(user) {
}
});
} else {
- fetchPromise = when.resolve();
+ fetchPromise = Promise.resolve();
}
var completeStatus = function(fetchError) {
@@ -461,18 +461,23 @@ Project.prototype.getBranches = function (user, isRemote) {
if (isRemote) {
fetchPromise = self.fetch(user);
} else {
- fetchPromise = when.resolve();
+ fetchPromise = Promise.resolve();
}
return fetchPromise.then(function() {
return gitTools.getBranches(self.path,isRemote);
});
};
+Project.prototype.deleteBranch = function (user, branch, isRemote, force) {
+ // TODO: isRemote==true support
+ // TODO: make sure we don't try to delete active branch
+ return gitTools.deleteBranch(this.path,branch,isRemote, force);
+};
+
Project.prototype.fetch = function(user,remoteName) {
var username;
if (!user) {
username = "_";
- console.log(new Error().stack);
} else {
username = user.username;
}
@@ -484,7 +489,7 @@ Project.prototype.fetch = function(user,remoteName) {
})
} else {
var remotes = Object.keys(this.remotes);
- var promise = when.resolve();
+ var promise = Promise.resolve();
remotes.forEach(function(remote) {
promise = promise.then(function() {
return gitTools.fetch(project.path,remote,authCache.get(project.name,project.remotes[remote].fetch,username))
@@ -661,7 +666,7 @@ function createProject(user, metadata) {
}
var project = metadata.name;
- return when.promise(function(resolve,reject) {
+ return new Promise(function(resolve,reject) {
var projectPath = fspath.join(projectsDir,project);
fs.stat(projectPath, function(err,stat) {
if (!err) {
diff --git a/red/runtime/storage/localfilesystem/projects/git/index.js b/red/runtime/storage/localfilesystem/projects/git/index.js
index 96eef3c3a..615933773 100644
--- a/red/runtime/storage/localfilesystem/projects/git/index.js
+++ b/red/runtime/storage/localfilesystem/projects/git/index.js
@@ -55,10 +55,9 @@ function runGitCommand(args,cwd,env) {
err.code = "git_local_overwrite";
} else if (/CONFLICT/.test(err.stdout)) {
err.code = "git_pull_merge_conflict";
+ } else if (/not fully merged/.test(stderr)) {
+ err.code = "git_delete_branch_unmerged";
}
-
-
-
return reject(err);
}
resolve(stdout);
@@ -276,8 +275,7 @@ function getBranches(cwd, remote) {
if (remote) {
args.push('-r');
}
- //TODO: parse out ahead/behind status (currently m[5] vvv )
- var branchRE = /^([ \*] )(\S+) +(\S+)(?: \[(\S+?)(?:: (.*))?\])? (.*)$/;
+ var branchRE = /^([ \*] )(\S+) +(\S+)(?: \[(\S+?)(?:: (?:ahead (\d+)(?:, )?)?(?:behind (\d+))?)?\])? (.*)$/;
return runGitCommand(args,cwd).then(function(output) {
var branches = [];
var lines = output.split("\n");
@@ -288,10 +286,13 @@ function getBranches(cwd, remote) {
branch = {
name: m[2],
remote: m[4],
- status: m[5],
+ status: {
+ ahead: m[5]||0,
+ behind: m[6]||0,
+ },
commit: {
sha: m[3],
- subject: m[6]
+ subject: m[7]
}
}
if (m[1] === '* ') {
@@ -516,6 +517,19 @@ module.exports = {
args.push(branchName);
return runGitCommand(args,cwd);
},
+ deleteBranch: function(cwd, branchName, isRemote, force) {
+ if (isRemote) {
+ throw new Error("Deleting remote branches not supported");
+ }
+ var args = ['branch'];
+ if (force) {
+ args.push('-D');
+ } else {
+ args.push('-d');
+ }
+ args.push(branchName);
+ return runGitCommand(args, cwd);
+ },
getBranchStatus: getBranchStatus,
addRemote: addRemote,
removeRemote: removeRemote
diff --git a/red/runtime/storage/localfilesystem/projects/index.js b/red/runtime/storage/localfilesystem/projects/index.js
index 15003f4d5..4bf3dca4a 100644
--- a/red/runtime/storage/localfilesystem/projects/index.js
+++ b/red/runtime/storage/localfilesystem/projects/index.js
@@ -212,6 +212,12 @@ function getBranches(user, project,isRemote) {
checkActiveProject(project);
return activeProject.getBranches(user, isRemote);
}
+
+function deleteBranch(user, project, branch, isRemote, force) {
+ checkActiveProject(project);
+ return activeProject.deleteBranch(user, branch, isRemote, force);
+}
+
function setBranch(user, project,branchName,isCreate) {
checkActiveProject(project);
return activeProject.setBranch(branchName,isCreate).then(function() {
@@ -419,6 +425,7 @@ module.exports = {
resolveMerge: resolveMerge,
abortMerge: abortMerge,
getBranches: getBranches,
+ deleteBranch: deleteBranch,
setBranch: setBranch,
getBranchStatus:getBranchStatus,