From d00762334782083a1d59bf7cdfda441f40425bfb Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 7 Dec 2017 22:24:57 +0000 Subject: [PATCH 1/6] Return more detailed information on /project/branches api --- editor/js/ui/projects.js | 21 ++++++-------- editor/js/ui/tab-versionControl.js | 6 ---- .../localfilesystem/projects/git/index.js | 28 +++++++++++++++---- 3 files changed, 32 insertions(+), 23 deletions(-) diff --git a/editor/js/ui/projects.js b/editor/js/ui/projects.js index 0493bad0a..903ead70b 100644 --- a/editor/js/ui/projects.js +++ b/editor/js/ui/projects.js @@ -581,7 +581,7 @@ RED.projects = (function() { } } ] - + } } } @@ -852,7 +852,6 @@ RED.projects = (function() { var branchFilterTerm = ""; var branchFilterCreateItem; var branches = []; - var currentBranch; var branchPrefix = ""; var container = $('
').appendTo(options.container); @@ -882,15 +881,15 @@ RED.projects = (function() { scrollOnAdd: false, addItem: function(row,index,entry) { var container = $('
').appendTo(row); - if (typeof entry !== "string") { + if (!entry.hasOwnProperty('commit')) { branchFilterCreateItem = container; $('').appendTo(container); $('').text("Create branch:").appendTo(container); $('
').text(entry.name).appendTo(container); } else { $('').appendTo(container); - $('').text(entry).appendTo(container); - if (currentBranch === entry) { + $('').text(entry.name).appendTo(container); + if (entry.current) { container.addClass("selected"); $('').text(options.currentLabel||"current").appendTo(container); } @@ -901,7 +900,7 @@ RED.projects = (function() { return; } var body = {}; - if (typeof entry !== "string") { + if (!entry.hasOwnProperty('commit')) { body.name = branchFilter.val(); body.create = true; if (options.remote) { @@ -911,7 +910,7 @@ RED.projects = (function() { if ($(this).hasClass('selected')) { body.current = true; } - body.name = entry; + body.name = entry.name; } if (options.onselect) { options.onselect(body); @@ -919,7 +918,7 @@ RED.projects = (function() { }); }, filter: function(data) { - var isCreateEntry = (typeof data !=="string"); + var isCreateEntry = (!data.hasOwnProperty('commit')); return ( isCreateEntry && ( @@ -929,7 +928,7 @@ RED.projects = (function() { ) || ( !isCreateEntry && - data.indexOf(branchFilterTerm) !== -1 + data.name.indexOf(branchFilterTerm) !== -1 ); } }); @@ -939,14 +938,12 @@ RED.projects = (function() { branchList.editableList('empty'); var start = Date.now(); var spinner = addSpinnerOverlay(container).addClass("projects-dialog-spinner-contain"); - currentBranch = options.current(); if (options.remote) { branchPrefix = options.remote()+"/"; } else { branchPrefix = ""; } - sendRequest({ url: url, type: "GET", @@ -972,7 +969,7 @@ RED.projects = (function() { } }) }, - addItem: function(data) { branchList.editableList('addItem',data) }, + // addItem: function(data) { branchList.editableList('addItem',data) }, filter: function() { branchList.editableList('filter') }, focus: function() { branchFilter.focus() } } diff --git a/editor/js/ui/tab-versionControl.js b/editor/js/ui/tab-versionControl.js index eefb7e2ba..0c97ca24c 100644 --- a/editor/js/ui/tab-versionControl.js +++ b/editor/js/ui/tab-versionControl.js @@ -571,9 +571,6 @@ RED.sidebar.versionControl = (function() { $('').text("Change local branch").appendTo(localBranchBox); var localBranchList = utils.createBranchList({ - current: function() { - return RED.projects.getActiveProject().git.branches.local - }, placeholder: "Find or create a branch", container: localBranchBox, onselect: function(body) { @@ -711,9 +708,6 @@ RED.sidebar.versionControl = (function() { var remoteBranchSubRow = $('
').hide().appendTo(remoteBranchRow); var remoteBranchList = utils.createBranchList({ - current: function() { - return RED.projects.getActiveProject().git.branches.remote - }, placeholder: "Find or create a remote branch", currentLabel: "upstream", remote: function() { diff --git a/red/runtime/storage/localfilesystem/projects/git/index.js b/red/runtime/storage/localfilesystem/projects/git/index.js index 2152390e4..96eef3c3a 100644 --- a/red/runtime/storage/localfilesystem/projects/git/index.js +++ b/red/runtime/storage/localfilesystem/projects/git/index.js @@ -272,17 +272,35 @@ function getRemotes(cwd) { } function getBranches(cwd, remote) { - var args = ['branch','--no-color']; + var args = ['branch','-vv','--no-color']; if (remote) { args.push('-r'); } + //TODO: parse out ahead/behind status (currently m[5] vvv ) + var branchRE = /^([ \*] )(\S+) +(\S+)(?: \[(\S+?)(?:: (.*))?\])? (.*)$/; return runGitCommand(args,cwd).then(function(output) { var branches = []; var lines = output.split("\n"); - branches = lines.map(function(l) { return l.substring(2)}) - .filter(function(l) { - return !/HEAD ->/.test(l) && (l.length > 0) - }); + branches = lines.map(function(l) { + var m = branchRE.exec(l); + var branch = null; + if (m) { + branch = { + name: m[2], + remote: m[4], + status: m[5], + commit: { + sha: m[3], + subject: m[6] + } + } + if (m[1] === '* ') { + branch.current = true; + } + } + return branch; + }).filter(function(v) { return !!v && v.commit.sha !== '->' }); + return {branches:branches}; }) } From 27f1d3b704672b5c471957fcdc5c3f5822b819d3 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 8 Dec 2017 16:31:42 +0000 Subject: [PATCH 2/6] 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, From 604e3068b24c32ee3459472264d6efd466d13d80 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Sun, 10 Dec 2017 22:35:57 +0000 Subject: [PATCH 3/6] Add full-screen shade that covers everything but notfications --- editor/js/ui/notifications.js | 10 ++-------- editor/sass/editor.scss | 5 ++++- editor/templates/index.mst | 1 + 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/editor/js/ui/notifications.js b/editor/js/ui/notifications.js index 7a8da7ec5..995ce1ce5 100644 --- a/editor/js/ui/notifications.js +++ b/editor/js/ui/notifications.js @@ -26,10 +26,7 @@ RED.notify = (function() { } if (options.modal) { - $("#header-shade").show(); - $("#editor-shade").show(); - $("#palette-shade").show(); - $(".sidebar-shade").show(); + $("#full-shade").show(); } if (currentNotifications.length > 4) { @@ -77,10 +74,7 @@ RED.notify = (function() { nn.parentNode.removeChild(nn); }); if (options.modal) { - $("#header-shade").hide(); - $("#editor-shade").hide(); - $("#palette-shade").hide(); - $(".sidebar-shade").hide(); + $("#full-shade").hide(); } }; })(); diff --git a/editor/sass/editor.scss b/editor/sass/editor.scss index 7acb20c9a..9d62de15a 100644 --- a/editor/sass/editor.scss +++ b/editor/sass/editor.scss @@ -158,7 +158,10 @@ top: -1px; bottom: -1px; } - +#full-shade { + @include shade; + z-index: 15; +} .dialog-form,#dialog-form, #node-config-dialog-edit-form { height: 100%; diff --git a/editor/templates/index.mst b/editor/templates/index.mst index 502144117..a925341bb 100644 --- a/editor/templates/index.mst +++ b/editor/templates/index.mst @@ -77,6 +77,7 @@
    +

    From bb59cd5742248c4257dd4bc363e1e9f0b7e92556 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 11 Dec 2017 17:05:12 +0000 Subject: [PATCH 4/6] Allow unstaged files to be reverted --- editor/js/main.js | 5 +- editor/js/ui/projectSettings.js | 4 + editor/js/ui/tab-versionControl.js | 205 +++++++++++------- editor/sass/projects.scss | 10 +- red/api/editor/projects/index.js | 16 ++ .../localfilesystem/projects/Project.js | 8 + .../localfilesystem/projects/git/index.js | 4 + .../storage/localfilesystem/projects/index.js | 9 +- 8 files changed, 180 insertions(+), 81 deletions(-) diff --git a/editor/js/main.js b/editor/js/main.js index bd41a2ca4..affa6315f 100644 --- a/editor/js/main.js +++ b/editor/js/main.js @@ -72,8 +72,9 @@ "abort-merge":"Git merge aborted", "loaded":"Project '"+msg.project+"' loaded", "updated":"Project '"+msg.project+"' updated", - "pull":"Project '"+msg.project+"' reloaded" - }[msg.action] + "pull":"Project '"+msg.project+"' reloaded", + "revert": "Project '"+msg.project+"' reloaded" + }[msg.action]; RED.notify(message); RED.sidebar.info.refresh() }); diff --git a/editor/js/ui/projectSettings.js b/editor/js/ui/projectSettings.js index 88302f279..d495b42f6 100644 --- a/editor/js/ui/projectSettings.js +++ b/editor/js/ui/projectSettings.js @@ -134,6 +134,7 @@ RED.projects.settings = (function() { }, 200: function(data) { done(null,data); + RED.sidebar.versionControl.refresh(true); }, 400: { 'unexpected_error': function(error) { @@ -197,6 +198,7 @@ RED.projects.settings = (function() { done(error,null); }, 200: function(data) { + RED.sidebar.versionControl.refresh(true); done(null,data); }, 400: { @@ -326,6 +328,7 @@ RED.projects.settings = (function() { done(error,null); }, 200: function(data) { + RED.sidebar.versionControl.refresh(true); done(null,data); }, 400: { @@ -900,6 +903,7 @@ RED.projects.settings = (function() { }, 200: function(data) { activeProject = data; + RED.sidebar.versionControl.refresh(true); updateForm(); done(); }, diff --git a/editor/js/ui/tab-versionControl.js b/editor/js/ui/tab-versionControl.js index 0c97ca24c..2ed577b45 100644 --- a/editor/js/ui/tab-versionControl.js +++ b/editor/js/ui/tab-versionControl.js @@ -39,7 +39,82 @@ RED.sidebar.versionControl = (function() { var isMerging; - // TODO: DRY projectSummary.js + function viewFileDiff(entry,state) { + var activeProject = RED.projects.getActiveProject(); + var diffTarget = (state === 'staged')?"index":"tree"; + utils.sendRequest({ + url: "projects/"+activeProject.name+"/diff/"+diffTarget+"/"+encodeURIComponent(entry.file), + type: "GET", + responses: { + 0: function(error) { + console.log(error); + // done(error,null); + }, + 200: function(data) { + if (mergeConflictNotification) { + mergeConflictNotification.close(); + mergeConflictNotification = null; + } + var title; + if (state === 'unstaged') { + title = 'Unstaged changes : '+entry.file + } else if (state === 'staged') { + title = 'Staged changes : '+entry.file + } else { + title = 'Resolve conflicts : '+entry.file + } + var options = { + diff: data.diff, + title: title, + unmerged: state === 'unmerged', + project: activeProject + } + if (state == 'unstaged') { + options.oldRevTitle = entry.indexStatus === " "?"HEAD":"Staged"; + options.newRevTitle = "Unstaged"; + options.oldRev = entry.indexStatus === " "?"@":":0"; + options.newRev = "_"; + } else if (state === 'staged') { + options.oldRevTitle = "HEAD"; + options.newRevTitle = "Staged"; + options.oldRev = "@"; + options.newRev = ":0"; + } else { + options.onresolve = function(resolution) { + utils.sendRequest({ + url: "projects/"+activeProject.name+"/resolve/"+encodeURIComponent(entry.file), + type: "POST", + responses: { + 0: function(error) { + console.log(error); + // done(error,null); + }, + 200: function(data) { + refresh(true); + }, + 400: { + 'unexpected_error': function(error) { + console.log(error); + // done(error,null); + } + }, + } + },{resolutions:resolution.resolutions[entry.file]}); + } + } + options.oncancel = showMergeConflictNotification; + RED.diff.showUnifiedDiff(options); + // console.log(data.diff); + }, + 400: { + 'unexpected_error': function(error) { + console.log(error); + // done(error,null); + } + } + } + }) + } function createChangeEntry(row, entry, status, state) { row.addClass("sidebar-version-control-change-entry"); @@ -52,88 +127,67 @@ RED.sidebar.versionControl = (function() { var icon = $('').appendTo(container); - var label = $('').appendTo(container); + var entryLink = $('') + .appendTo(container) + .click(function(e) { + e.preventDefault(); + viewFileDiff(entry,state); + }); + var label = $('').appendTo(entryLink); - var bg = $('
    ').appendTo(row); - var viewDiffButton = $('') - .appendTo(bg) - .click(function(evt) { - evt.preventDefault(); - var activeProject = RED.projects.getActiveProject(); - var diffTarget = (state === 'staged')?"index":"tree"; - utils.sendRequest({ - url: "projects/"+activeProject.name+"/diff/"+diffTarget+"/"+encodeURIComponent(entry.file), - type: "GET", - responses: { - 0: function(error) { - console.log(error); - // done(error,null); - }, - 200: function(data) { - if (mergeConflictNotification) { - mergeConflictNotification.close(); - mergeConflictNotification = null; - } - var title; - if (state === 'unstaged') { - title = 'Unstaged changes : '+entry.file - } else if (state === 'staged') { - title = 'Staged changes : '+entry.file - } else { - title = 'Resolve conflicts : '+entry.file - } - var options = { - diff: data.diff, - title: title, - unmerged: state === 'unmerged', - project: activeProject - } - if (state == 'unstaged') { - options.oldRevTitle = entry.indexStatus === " "?"HEAD":"Staged"; - options.newRevTitle = "Unstaged"; - options.oldRev = entry.indexStatus === " "?"@":":0"; - options.newRev = "_"; - } else if (state === 'staged') { - options.oldRevTitle = "HEAD"; - options.newRevTitle = "Staged"; - options.oldRev = "@"; - options.newRev = ":0"; - } else { - options.onresolve = function(resolution) { - utils.sendRequest({ - url: "projects/"+activeProject.name+"/resolve/"+encodeURIComponent(entry.file), - type: "POST", + var entryTools = $('
    ').appendTo(row); + var bg; + var revertButton; + if (state === 'unstaged') { + bg = $('').appendTo(entryTools); + revertButton = $('') + .appendTo(bg) + .click(function(evt) { + evt.preventDefault(); + var spinner = utils.addSpinnerOverlay(container).addClass('projects-dialog-spinner-contain'); + var notification = RED.notify("Are you sure you want to revert the changes to '"+entry.file+"'? This cannot be undone.", { + type: "warning", + modal: true, + fixed: true, + buttons: [ + { + text: RED._("common.label.cancel"), + click: function() { + spinner.remove(); + notification.close(); + } + },{ + text: 'Revert changes', + click: function() { + notification.close(); + var activeProject = RED.projects.getActiveProject(); + var url = "projects/"+activeProject.name+"/files/_/"+entry.file; + var options = { + url: url, + type: "DELETE", responses: { - 0: function(error) { - console.log(error); - // done(error,null); - }, 200: function(data) { - refresh(true); + spinner.remove(); }, 400: { 'unexpected_error': function(error) { + spinner.remove(); console.log(error); // done(error,null); } - }, + } } - },{resolutions:resolution.resolutions[entry.file]}); + } + utils.sendRequest(options); } } - options.oncancel = showMergeConflictNotification; - RED.diff.showUnifiedDiff(options); - // console.log(data.diff); - }, - 400: { - 'unexpected_error': function(error) { - console.log(error); - // done(error,null); - } - } - } - }) - }) + + ] + }) + + }); + } + bg = $('').appendTo(entryTools); if (state !== 'unmerged') { $('') .appendTo(bg) @@ -203,11 +257,10 @@ RED.sidebar.versionControl = (function() { delete entry.spinner; } - viewDiffButton.attr("disabled",(status === 'D' || status === '?')); - viewDiffButton.find("i") - .toggleClass('fa-eye',!(status === 'D' || status === '?')) - .toggleClass('fa-eye-slash',(status === 'D' || status === '?')) - + if (revertButton) { + revertButton.toggle(status !== '?'); + } + entryLink.toggleClass("disabled",(status === 'D' || status === '?')); } entry["update"+((state==='unstaged')?"Unstaged":"Staged")](entry, status); } diff --git a/editor/sass/projects.scss b/editor/sass/projects.scss index cef18fbfe..5eb47888f 100644 --- a/editor/sass/projects.scss +++ b/editor/sass/projects.scss @@ -444,7 +444,13 @@ span { margin: 0 6px; } - .button-group { + a { + color: currentColor; + &.disabled { + pointer-events: none; + } + } + .sidebar-version-control-change-entry-tools { position: absolute; top: 4px; right: 4px; @@ -455,7 +461,7 @@ } &:hover { - .button-group { + .sidebar-version-control-change-entry-tools { display: block; } } diff --git a/red/api/editor/projects/index.js b/red/api/editor/projects/index.js index 6ce4ab781..65fae6de2 100644 --- a/red/api/editor/projects/index.js +++ b/red/api/editor/projects/index.js @@ -170,6 +170,22 @@ module.exports = { }) }); + // Revert a file + app.delete("/:id/files/_/*", needsPermission("projects.write"), function(req,res) { + var projectId = req.params.id; + var filePath = req.params[0]; + + runtime.storage.projects.revertFile(req.user, projectId,filePath).then(function() { + res.status(204).end(); + }) + .catch(function(err) { + console.log(err.stack); + res.status(400).json({error:"unexpected_error", message:err.toString()}); + }) + }); + + + // Stage a file app.post("/:id/stage/*", needsPermission("projects.write"), function(req,res) { var projectName = req.params.id; diff --git a/red/runtime/storage/localfilesystem/projects/Project.js b/red/runtime/storage/localfilesystem/projects/Project.js index 41908d2c9..20e2df333 100644 --- a/red/runtime/storage/localfilesystem/projects/Project.js +++ b/red/runtime/storage/localfilesystem/projects/Project.js @@ -329,6 +329,14 @@ Project.prototype.getFile = function (filePath,treeish) { return fs.readFile(fspath.join(this.path,filePath),"utf8"); } }; +Project.prototype.revertFile = function (filePath) { + var self = this; + return gitTools.revertFile(this.path, filePath).then(function() { + return self.load(); + }); +}; + + Project.prototype.status = function(user) { var self = this; diff --git a/red/runtime/storage/localfilesystem/projects/git/index.js b/red/runtime/storage/localfilesystem/projects/git/index.js index 615933773..320f508c6 100644 --- a/red/runtime/storage/localfilesystem/projects/git/index.js +++ b/red/runtime/storage/localfilesystem/projects/git/index.js @@ -421,6 +421,10 @@ module.exports = { return status.files; }) }, + revertFile: function(cwd, filePath) { + var args = ["checkout",filePath]; + return runGitCommand(args,cwd); + }, stageFile: function(cwd,file) { var args = ["add"]; if (Array.isArray(file)) { diff --git a/red/runtime/storage/localfilesystem/projects/index.js b/red/runtime/storage/localfilesystem/projects/index.js index 4bf3dca4a..3de6bd36e 100644 --- a/red/runtime/storage/localfilesystem/projects/index.js +++ b/red/runtime/storage/localfilesystem/projects/index.js @@ -173,7 +173,7 @@ function getFileDiff(user, project,file,type) { } function getCommits(user, project,options) { checkActiveProject(project); - return activeProject.getCommits(options); + return activeProject.getCommits(options); } function getCommit(user, project,sha) { checkActiveProject(project); @@ -184,6 +184,12 @@ function getFile(user, project,filePath,sha) { checkActiveProject(project); return activeProject.getFile(filePath,sha); } +function revertFile(user, project,filePath) { + checkActiveProject(project); + return activeProject.revertFile(filePath).then(function() { + return reloadActiveProject("revert"); + }) +} function push(user, project,remoteBranchName,setRemote) { checkActiveProject(project); return activeProject.push(user,remoteBranchName,setRemote); @@ -413,6 +419,7 @@ module.exports = { updateProject: updateProject, getFiles: getFiles, getFile: getFile, + revertFile: revertFile, stageFile: stageFile, unstageFile: unstageFile, commit: commit, From 028d66befc524630d8e230473ca6bbfa75ad8bf9 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 11 Dec 2017 17:05:27 +0000 Subject: [PATCH 5/6] Add suitable message when not displaying binary files --- editor/js/ui/diff.js | 272 +++++++++++++++++++++++-------------------- 1 file changed, 143 insertions(+), 129 deletions(-) diff --git a/editor/js/ui/diff.js b/editor/js/ui/diff.js index 8e9fdbbd3..0d8a5cfbd 100644 --- a/editor/js/ui/diff.js +++ b/editor/js/ui/diff.js @@ -1678,9 +1678,10 @@ RED.diff = (function() { function createUnifiedDiffTable(files,commitOptions) { var diffPanel = $('
    '); + console.log(files); files.forEach(function(file) { var hunks = file.hunks; - + var isBinary = file.binary; var codeTable = $("").appendTo(diffPanel); $('').appendTo(codeTable); var codeBody = $('').appendTo(codeTable); @@ -1744,142 +1745,148 @@ RED.diff = (function() { }) } + if (isBinary) { + var diffBinaryRow = $('').appendTo(codeBody); + var binaryContent = $('').appendTo(diffBinaryRow); + $('').text("Cannot show binary file contents").appendTo(binaryContent); - hunks.forEach(function(hunk) { - var diffRow = $('').appendTo(codeBody); - var content = $('').appendTo(diffRow); - var label = $('').text(hunk.header).appendTo(content); - var isConflict = hunk.conflict; - var localLine = hunk.localStartLine; - var remoteLine = hunk.remoteStartLine; - if (isConflict) { - unresolvedConflicts++; - } - - hunk.lines.forEach(function(lineText,lineNumber) { - // if (lineText[0] === '\\' || lineText === "") { - // // Comment line - bail out of this hunk - // break; - // } - - var actualLineNumber = hunk.diffStart + lineNumber; - var isMergeHeader = isConflict && /^..(<<<<<<<|=======$|>>>>>>>)/.test(lineText); - var diffRow = $('').appendTo(codeBody); - var localLineNo = $('').appendTo(codeBody); + var content = $('').appendTo(diffRow); + var label = $('').text(hunk.header).appendTo(content); + var isConflict = hunk.conflict; + var localLine = hunk.localStartLine; + var remoteLine = hunk.remoteStartLine; if (isConflict) { - prefixEnd = 2; + unresolvedConflicts++; } - if (!isMergeHeader) { - var changeMarker = lineText[0]; - if (isConflict && !commitOptions.unmerged && changeMarker === ' ') { - changeMarker = lineText[1]; - } - $('').text(changeMarker).appendTo(line); - var handledlLine = false; - if (isConflict && commitOptions.unmerged) { - $('').text(lineText[1]).appendTo(line); - if (lineText[0] === '+') { - localLineNo.text(localLine++); - handledlLine = true; - } - if (lineText[1] === '+') { - remoteLineNo.text(remoteLine++); - handledlLine = true; - } + + hunk.lines.forEach(function(lineText,lineNumber) { + // if (lineText[0] === '\\' || lineText === "") { + // // Comment line - bail out of this hunk + // break; + // } + + var actualLineNumber = hunk.diffStart + lineNumber; + var isMergeHeader = isConflict && /^..(<<<<<<<|=======$|>>>>>>>)/.test(lineText); + var diffRow = $('').appendTo(codeBody); + var localLineNo = $('
    ').appendTo(diffRow); - var remoteLineNo; - if (!isMergeHeader) { - remoteLineNo = $('').appendTo(diffRow); - } else { - localLineNo.attr('colspan',2); - } - var line = $('').appendTo(diffRow); - var prefixStart = 0; - var prefixEnd = 1; + } else { + hunks.forEach(function(hunk) { + var diffRow = $('
    ').appendTo(diffRow); + var remoteLineNo; + if (!isMergeHeader) { + remoteLineNo = $('').appendTo(diffRow); } else { - if (lineText[0] === '+' || (isConflict && lineText[1] === '+')) { - localLineNo.addClass("added"); - remoteLineNo.addClass("added"); - line.addClass("added"); - remoteLineNo.text(remoteLine++); - handledlLine = true; - } else if (lineText[0] === '-' || (isConflict && lineText[1] === '-')) { - localLineNo.addClass("removed"); - remoteLineNo.addClass("removed"); - line.addClass("removed"); - localLineNo.text(localLine++); - handledlLine = true; - } + localLineNo.attr('colspan',2); } - if (!handledlLine) { - line.addClass("unchanged"); - if (localLine > 0 && lineText[0] !== '\\' && lineText !== "") { - localLineNo.text(localLine++); - } - if (remoteLine > 0 && lineText[0] !== '\\' && lineText !== "") { - remoteLineNo.text(remoteLine++); - } + var line = $('').appendTo(diffRow); + var prefixStart = 0; + var prefixEnd = 1; + if (isConflict) { + prefixEnd = 2; } - $('').text(lineText.substring(prefixEnd)).appendTo(line); - } else { - diffRow.addClass("mergeHeader"); - var isSeparator = /^..(=======$)/.test(lineText); - if (!isSeparator) { - var isOurs = /^..<<<<<<').text("<<<<<<< Local Changes").appendTo(line); - hunk.localChangeStart = actualLineNumber; + if (!isMergeHeader) { + var changeMarker = lineText[0]; + if (isConflict && !commitOptions.unmerged && changeMarker === ' ') { + changeMarker = lineText[1]; + } + $('').text(changeMarker).appendTo(line); + var handledlLine = false; + if (isConflict && commitOptions.unmerged) { + $('').text(lineText[1]).appendTo(line); + if (lineText[0] === '+') { + localLineNo.text(localLine++); + handledlLine = true; + } + if (lineText[1] === '+') { + remoteLineNo.text(remoteLine++); + handledlLine = true; + } } else { - hunk.remoteChangeEnd = actualLineNumber; - $('').text(">>>>>>> Remote Changes").appendTo(line); - + if (lineText[0] === '+' || (isConflict && lineText[1] === '+')) { + localLineNo.addClass("added"); + remoteLineNo.addClass("added"); + line.addClass("added"); + remoteLineNo.text(remoteLine++); + handledlLine = true; + } else if (lineText[0] === '-' || (isConflict && lineText[1] === '-')) { + localLineNo.addClass("removed"); + remoteLineNo.addClass("removed"); + line.addClass("removed"); + localLineNo.text(localLine++); + handledlLine = true; + } } - diffRow.addClass("mergeHeader-"+(isOurs?"ours":"theirs")); - $('') - .appendTo(line) - .click(function(evt) { - evt.preventDefault(); - resolvedConflicts++; - var addedRows; - var midRow; - if (isOurs) { - addedRows = diffRow.nextUntil(".mergeHeader-separator"); - midRow = addedRows.last().next(); - midRow.nextUntil(".mergeHeader").remove(); - midRow.next().remove(); - } else { - addedRows = diffRow.prevUntil(".mergeHeader-separator"); - midRow = addedRows.last().prev(); - midRow.prevUntil(".mergeHeader").remove(); - midRow.prev().remove(); - } - midRow.remove(); - diffRow.remove(); - addedRows.find(".linetext").addClass('added'); - conflictHeader.empty(); - $(''+resolvedConflicts+' of '+unresolvedConflicts+' conflicts resolved').appendTo(conflictHeader); - - conflictResolutions[file.file] = conflictResolutions[file.file] || {}; - conflictResolutions[file.file][hunk.localChangeStart] = { - changeStart: hunk.localChangeStart, - separator: hunk.changeSeparator, - changeEnd: hunk.remoteChangeEnd, - selection: isOurs?"A":"B" - } - if (commitOptions.resolveConflict) { - commitOptions.resolveConflict({ - conflicts: unresolvedConflicts, - resolved: resolvedConflicts, - resolutions: conflictResolutions - }); - } - }) + if (!handledlLine) { + line.addClass("unchanged"); + if (localLine > 0 && lineText[0] !== '\\' && lineText !== "") { + localLineNo.text(localLine++); + } + if (remoteLine > 0 && lineText[0] !== '\\' && lineText !== "") { + remoteLineNo.text(remoteLine++); + } + } + $('').text(lineText.substring(prefixEnd)).appendTo(line); } else { - hunk.changeSeparator = actualLineNumber; - diffRow.addClass("mergeHeader-separator"); + diffRow.addClass("mergeHeader"); + var isSeparator = /^..(=======$)/.test(lineText); + if (!isSeparator) { + var isOurs = /^..<<<<<<').text("<<<<<<< Local Changes").appendTo(line); + hunk.localChangeStart = actualLineNumber; + } else { + hunk.remoteChangeEnd = actualLineNumber; + $('').text(">>>>>>> Remote Changes").appendTo(line); + + } + diffRow.addClass("mergeHeader-"+(isOurs?"ours":"theirs")); + $('') + .appendTo(line) + .click(function(evt) { + evt.preventDefault(); + resolvedConflicts++; + var addedRows; + var midRow; + if (isOurs) { + addedRows = diffRow.nextUntil(".mergeHeader-separator"); + midRow = addedRows.last().next(); + midRow.nextUntil(".mergeHeader").remove(); + midRow.next().remove(); + } else { + addedRows = diffRow.prevUntil(".mergeHeader-separator"); + midRow = addedRows.last().prev(); + midRow.prevUntil(".mergeHeader").remove(); + midRow.prev().remove(); + } + midRow.remove(); + diffRow.remove(); + addedRows.find(".linetext").addClass('added'); + conflictHeader.empty(); + $(''+resolvedConflicts+' of '+unresolvedConflicts+' conflicts resolved').appendTo(conflictHeader); + + conflictResolutions[file.file] = conflictResolutions[file.file] || {}; + conflictResolutions[file.file][hunk.localChangeStart] = { + changeStart: hunk.localChangeStart, + separator: hunk.changeSeparator, + changeEnd: hunk.remoteChangeEnd, + selection: isOurs?"A":"B" + } + if (commitOptions.resolveConflict) { + commitOptions.resolveConflict({ + conflicts: unresolvedConflicts, + resolved: resolvedConflicts, + resolutions: conflictResolutions + }); + } + }) + } else { + hunk.changeSeparator = actualLineNumber; + diffRow.addClass("mergeHeader-separator"); + } } - } + }); }); - }); + } if (commitOptions.unmerged) { conflictHeader = $(''+resolvedConflicts+' of '+unresolvedConflicts+' conflicts resolved').appendTo(content); } @@ -2041,7 +2048,9 @@ RED.diff = (function() { } else { lines = diff.split("\n"); } + var diffHeader = /^diff --git a\/(.*) b\/(.*)$/; var fileHeader = /^\+\+\+ b\/(.*)\t?/; + var binaryFile = /^Binary files /; var hunkHeader = /^@@ -((\d+)(,(\d+))?) \+((\d+)(,(\d+))?) @@ ?(.*)$/; var conflictHunkHeader = /^@+ -((\d+)(,(\d+))?) -((\d+)(,(\d+))?) \+((\d+)(,(\d+))?) @+/; var files = []; @@ -2050,16 +2059,21 @@ RED.diff = (function() { var currentHunk; for (var i=0;i Date: Wed, 13 Dec 2017 09:44:48 +0000 Subject: [PATCH 6/6] Add projects editorTheme flag to disable the feature --- editor/js/main.js | 32 +++++--- red/api/editor/index.js | 8 +- red/api/editor/projects/index.js | 1 - red/api/editor/theme.js | 6 ++ red/runtime/locales/en-US/runtime.json | 12 ++- red/runtime/settings.js | 4 +- red/runtime/storage/index.js | 6 +- .../localfilesystem/projects/git/index.js | 10 +++ .../storage/localfilesystem/projects/index.js | 75 ++++++++++++------- 9 files changed, 106 insertions(+), 48 deletions(-) diff --git a/editor/js/main.js b/editor/js/main.js index affa6315f..4573d57aa 100644 --- a/editor/js/main.js +++ b/editor/js/main.js @@ -43,9 +43,13 @@ $(".palette-scroll").removeClass("hide"); $("#palette-search").removeClass("hide"); loadFlows(function() { - RED.projects.refresh(function() { + if (RED.settings.theme("projects.enabled",true)) { + RED.projects.refresh(function() { + RED.sidebar.info.refresh() + }); + } else { RED.sidebar.info.refresh() - }); + } var persistentNotifications = {}; RED.comms.subscribe("notification/#",function(topic,msg) { @@ -65,7 +69,6 @@ RED.view.redraw(true); RED.projects.refresh(function() { loadFlows(function() { - console.log(msg); var project = RED.projects.getActiveProject(); var message = { "change-branch":"Change to local branch '"+project.git.branches.local+"'", @@ -214,12 +217,13 @@ function loadEditor() { var menuOptions = []; - - menuOptions.push({id:"menu-item-projects-menu",label:"NLS: Projects",options:[ - {id:"menu-item-projects-new",label:"New...",disabled:false,onselect:"core:new-project"}, - {id:"menu-item-projects-open",label:"Open...",disabled:false,onselect:"core:open-project"}, - {id:"menu-item-projects-delete",label:"Delete...",disabled:false,onselect:"core:delete-project"}, - ]}); + if (RED.settings.theme("projects.enabled",true)) { + menuOptions.push({id:"menu-item-projects-menu",label:"NLS: Projects",options:[ + {id:"menu-item-projects-new",label:"New...",disabled:false,onselect:"core:new-project"}, + {id:"menu-item-projects-open",label:"Open...",disabled:false,onselect:"core:open-project"}, + {id:"menu-item-projects-delete",label:"Delete...",disabled:false,onselect:"core:delete-project"}, + ]}); + } menuOptions.push({id:"menu-item-view-menu",label:RED._("menu.label.view.view"),options:[ @@ -284,10 +288,18 @@ RED.palette.init(); if (RED.settings.theme('palette.editable') !== false) { RED.palette.editor.init(); + } else { + console.log("Palette editor disabled"); } RED.sidebar.init(); - RED.projects.init(); + + if (RED.settings.theme("projects.enabled",true)) { + RED.projects.init(); + } else { + console.log("Palette editor disabled"); + } + RED.subflow.init(); RED.workspaces.init(); RED.clipboard.init(); diff --git a/red/api/editor/index.js b/red/api/editor/index.js index b9f4d539a..4e26ef82f 100644 --- a/red/api/editor/index.js +++ b/red/api/editor/index.js @@ -68,9 +68,11 @@ module.exports = { editorApp.use("/",ui.editorResources); //Projects - var projects = require("./projects"); - projects.init(runtime); - editorApp.use("/projects",projects.app()); + if (runtime.storage.projects) { + var projects = require("./projects"); + projects.init(runtime); + editorApp.use("/projects",projects.app()); + } // Locales var locales = require("./locales"); diff --git a/red/api/editor/projects/index.js b/red/api/editor/projects/index.js index 65fae6de2..cd9d1b935 100644 --- a/red/api/editor/projects/index.js +++ b/red/api/editor/projects/index.js @@ -31,7 +31,6 @@ module.exports = { // List all projects app.get("/", needsPermission("projects.read"), function(req,res) { - console.log(req.user); runtime.storage.projects.listProjects(req.user, req.user).then(function(list) { var active = runtime.storage.projects.getActiveProject(req.user); var response = { diff --git a/red/api/editor/theme.js b/red/api/editor/theme.js index b34266646..6699e7cd7 100644 --- a/red/api/editor/theme.js +++ b/red/api/editor/theme.js @@ -182,6 +182,12 @@ module.exports = { if (theme.hasOwnProperty("palette")) { themeSettings.palette = theme.palette; } + + if (theme.hasOwnProperty("projects")) { + themeSettings.projects = theme.projects; + } + + return themeApp; }, context: function() { diff --git a/red/runtime/locales/en-US/runtime.json b/red/runtime/locales/en-US/runtime.json index 9853a2b3f..597ad6bbe 100644 --- a/red/runtime/locales/en-US/runtime.json +++ b/red/runtime/locales/en-US/runtime.json @@ -133,13 +133,19 @@ "localfilesystem": { "user-dir": "User directory : __path__", "flows-file": "Flows file : __path__", - "changing-project": "Setting active project : __project__", - "active-project": "Active project : __project__", "create": "Creating new __type__ file", "empty": "Existing __type__ file is empty", "invalid": "Existing __type__ file is not valid json", "restore": "Restoring __type__ file backup : __path__", - "restore-fail": "Restoring __type__ file backup failed : __message__" + "restore-fail": "Restoring __type__ file backup failed : __message__", + "projects": { + "changing-project": "Setting active project : __project__", + "active-project": "Active project : __project__", + "no-active-project": "No active project : using default flows file", + "disabled": "Projects disabled : editorTheme.projects.enabled=false", + "git-not-found": "Projects disabled : git command not found", + "git-version-old": "Projects disabled : git __version__ too old" + } } } } diff --git a/red/runtime/settings.js b/red/runtime/settings.js index dd27dd3a5..bc51f98b8 100644 --- a/red/runtime/settings.js +++ b/red/runtime/settings.js @@ -56,7 +56,7 @@ var persistentSettings = { return storage.getSettings().then(function(_settings) { globalSettings = _settings; if (globalSettings) { - userSettings = globalSettings.users || {}; + userSettings = globalSettings.users || {}; } else { userSettings = {}; @@ -177,11 +177,9 @@ var persistentSettings = { userSettings[username] = settings; try { assert.deepEqual(current,settings); - console.log("skip the save"); return when.resolve(); } catch(err) { globalSettings.users = userSettings; - console.log("saving"); return storage.saveSettings(globalSettings); } } diff --git a/red/runtime/storage/index.js b/red/runtime/storage/index.js index f60862fe7..65b2f8bbf 100644 --- a/red/runtime/storage/index.js +++ b/red/runtime/storage/index.js @@ -54,8 +54,10 @@ var storageModuleInterface = { } catch (e) { return when.reject(e); } - if (storageModule.projects) { - storageModuleInterface.projects = storageModule.projects; + if (runtime.settings.hasOwnProperty("editorTheme") && runtime.settings.editorTheme.hasOwnProperty("projects")) { + if (storageModule.projects) { + storageModuleInterface.projects = storageModule.projects; + } } return storageModule.init(runtime.settings,runtime); }, diff --git a/red/runtime/storage/localfilesystem/projects/git/index.js b/red/runtime/storage/localfilesystem/projects/git/index.js index 320f508c6..a706b410e 100644 --- a/red/runtime/storage/localfilesystem/projects/git/index.js +++ b/red/runtime/storage/localfilesystem/projects/git/index.js @@ -22,6 +22,7 @@ var clone = require('clone'); var path = require("path"); var gitCommand = "git"; +var gitVersion; var log; function runGitCommand(args,cwd,env) { @@ -334,6 +335,15 @@ function removeRemote(cwd,name) { module.exports = { init: function(_settings,_runtime) { log = _runtime.log + return new Promise(function(resolve,reject) { + runGitCommand(["--version"]).then(function(output) { + var m = / (\d\S+)/.exec(output); + gitVersion = m[1]; + resolve(gitVersion); + }).catch(function(err) { + resolve(null); + }); + }); }, initRepo: function(cwd) { return runGitCommand(["init"],cwd); diff --git a/red/runtime/storage/localfilesystem/projects/index.js b/red/runtime/storage/localfilesystem/projects/index.js index 3de6bd36e..2c24a48af 100644 --- a/red/runtime/storage/localfilesystem/projects/index.js +++ b/red/runtime/storage/localfilesystem/projects/index.js @@ -29,6 +29,9 @@ var Projects = require("./Project"); var settings; var runtime; +var projectsEnabled; +var projectLogMessages = []; + var projectsDir; var activeProject @@ -36,11 +39,15 @@ function init(_settings, _runtime) { settings = _settings; runtime = _runtime; log = runtime.log; - gitTools.init(_settings, _runtime); - Projects.init(settings,runtime); - - projectsDir = fspath.join(settings.userDir,"projects"); + try { + if (settings.editorTheme.projects.enabled === false) { + projectLogMessages.push(log._("storage.localfilesystem.projects.disabled")) + projectsEnabled = false; + } + } catch(err) { + projectsEnabled = true; + } if (settings.flowFile) { flowsFile = settings.flowFile; @@ -73,27 +80,39 @@ function init(_settings, _runtime) { credentialsFile = fspath.join(settings.userDir,ffBase+"_cred"+ffExt); credentialsFileBackup = getBackupFilename(credentialsFile) - if (!settings.readOnly) { - return fs.ensureDir(projectsDir) - //TODO: this is accessing settings from storage directly as settings - // has not yet been initialised. That isn't ideal - can this be deferred? - .then(storageSettings.getSettings) - .then(function(globalSettings) { - if (!globalSettings.projects) { - // TODO: Migration Case - console.log("TODO: Migration from single file to project"); - globalSettings.projects = { - activeProject: "", - projects: {} - } - return storageSettings.saveSettings(globalSettings); - } else { - activeProject = globalSettings.projects.activeProject; + var setupProjectsPromise; + + if (projectsEnabled) { + return gitTools.init(_settings, _runtime).then(function(gitVersion) { + if (!gitVersion) { + projectLogMessages.push(log._("storage.localfilesystem.projects.git-not-found")) + projectsEnabled = false; + } else { + Projects.init(settings,runtime); + projectsDir = fspath.join(settings.userDir,"projects"); + if (!settings.readOnly) { + return fs.ensureDir(projectsDir) + //TODO: this is accessing settings from storage directly as settings + // has not yet been initialised. That isn't ideal - can this be deferred? + .then(storageSettings.getSettings) + .then(function(globalSettings) { + if (!globalSettings.projects) { + // TODO: Migration Case + console.log("TODO: Migration from single file to project"); + globalSettings.projects = { + activeProject: "", + projects: {} + } + return storageSettings.saveSettings(globalSettings); + } else { + activeProject = globalSettings.projects.activeProject; + } + }); } - }); - } else { - return when.resolve(); + } + }); } + return Promise.resolve(); } function getUserGitSettings(user) { @@ -263,9 +282,8 @@ function setActiveProject(user, projectName) { var globalProjectSettings = settings.get("projects"); globalProjectSettings.activeProject = project.name; return settings.set("projects",globalProjectSettings).then(function() { - log.info(log._("storage.localfilesystem.changing-project",{project:activeProject||"none"})); + log.info(log._("storage.localfilesystem.projects.changing-project",{project:activeProject||"none"})); log.info(log._("storage.localfilesystem.flows-file",{path:flowsFullPath})); - // console.log("Updated file targets to"); // console.log(flowsFullPath) // console.log(credentialsFile) @@ -346,11 +364,16 @@ function getFlows() { log.info(log._("storage.localfilesystem.user-dir",{path:settings.userDir})); if (activeProject) { return loadProject(activeProject).then(function() { - log.info(log._("storage.localfilesystem.active-project",{project:activeProject.name||"none"})); + log.info(log._("storage.localfilesystem.projects.active-project",{project:activeProject.name||"none"})); log.info(log._("storage.localfilesystem.flows-file",{path:flowsFullPath})); return getFlows(); }); } else { + if (projectsEnabled) { + log.warn(log._("storage.localfilesystem.projects.no-active-project")) + } else { + projectLogMessages.forEach(log.warn); + } log.info(log._("storage.localfilesystem.flows-file",{path:flowsFullPath})); } }