From 27f1d3b704672b5c471957fcdc5c3f5822b819d3 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 8 Dec 2017 16:31:42 +0000 Subject: [PATCH] Add delete local branch option --- editor/js/ui/projectSettings.js | 132 +++++++++++++++++- editor/sass/projects.scss | 41 ++++++ red/api/editor/projects/index.js | 20 ++- .../localfilesystem/projects/Project.js | 17 ++- .../localfilesystem/projects/git/index.js | 28 +++- .../storage/localfilesystem/projects/index.js | 7 + 6 files changed, 226 insertions(+), 19 deletions(-) diff --git a/editor/js/ui/projectSettings.js b/editor/js/ui/projectSettings.js index e1ce6a8ce..88302f279 100644 --- a/editor/js/ui/projectSettings.js +++ b/editor/js/ui/projectSettings.js @@ -947,8 +947,132 @@ RED.projects.settings = (function() { updateForm(); } + function createLocalBranchListSection(activeProject,pane) { + var localBranchContainer = $('
').appendTo(pane); + $('

').text("Branches").appendTo(localBranchContainer); + + var row = $('
').appendTo(localBranchContainer); + + + var branchList = $('
    ').appendTo(row).editableList({ + addButton: false, + scrollOnAdd: false, + addItem: function(row,index,entry) { + var container = $('
    ').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,