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,