Merge branch "projects"

This commit is contained in:
Hideki Nakamura 2017-12-16 00:33:08 +09:00
commit 9bc41c1709
18 changed files with 690 additions and 306 deletions

View File

@ -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,15 +69,15 @@
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+"'",
"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()
});
@ -213,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:[
@ -283,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();

View File

@ -1678,9 +1678,10 @@ RED.diff = (function() {
function createUnifiedDiffTable(files,commitOptions) {
var diffPanel = $('<div></div>');
console.log(files);
files.forEach(function(file) {
var hunks = file.hunks;
var isBinary = file.binary;
var codeTable = $("<table>").appendTo(diffPanel);
$('<colgroup><col width="50"><col width="50"><col width="100%"></colgroup>').appendTo(codeTable);
var codeBody = $('<tbody>').appendTo(codeTable);
@ -1744,142 +1745,148 @@ RED.diff = (function() {
})
}
if (isBinary) {
var diffBinaryRow = $('<tr class="node-text-diff-header">').appendTo(codeBody);
var binaryContent = $('<td colspan="3"></td>').appendTo(diffBinaryRow);
$('<span></span>').text("Cannot show binary file contents").appendTo(binaryContent);
hunks.forEach(function(hunk) {
var diffRow = $('<tr class="node-text-diff-header">').appendTo(codeBody);
var content = $('<td colspan="3"></td>').appendTo(diffRow);
var label = $('<span></span>').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 = $('<tr>').appendTo(codeBody);
var localLineNo = $('<td class="lineno">').appendTo(diffRow);
var remoteLineNo;
if (!isMergeHeader) {
remoteLineNo = $('<td class="lineno">').appendTo(diffRow);
} else {
localLineNo.attr('colspan',2);
}
var line = $('<td class="linetext">').appendTo(diffRow);
var prefixStart = 0;
var prefixEnd = 1;
} else {
hunks.forEach(function(hunk) {
var diffRow = $('<tr class="node-text-diff-header">').appendTo(codeBody);
var content = $('<td colspan="3"></td>').appendTo(diffRow);
var label = $('<span></span>').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];
}
$('<span class="prefix">').text(changeMarker).appendTo(line);
var handledlLine = false;
if (isConflict && commitOptions.unmerged) {
$('<span class="prefix">').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 = $('<tr>').appendTo(codeBody);
var localLineNo = $('<td class="lineno">').appendTo(diffRow);
var remoteLineNo;
if (!isMergeHeader) {
remoteLineNo = $('<td class="lineno">').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 = $('<td class="linetext">').appendTo(diffRow);
var prefixStart = 0;
var prefixEnd = 1;
if (isConflict) {
prefixEnd = 2;
}
$('<span>').text(lineText.substring(prefixEnd)).appendTo(line);
} else {
diffRow.addClass("mergeHeader");
var isSeparator = /^..(=======$)/.test(lineText);
if (!isSeparator) {
var isOurs = /^..<<<<<<</.test(lineText);
if (isOurs) {
$('<span>').text("<<<<<<< Local Changes").appendTo(line);
hunk.localChangeStart = actualLineNumber;
if (!isMergeHeader) {
var changeMarker = lineText[0];
if (isConflict && !commitOptions.unmerged && changeMarker === ' ') {
changeMarker = lineText[1];
}
$('<span class="prefix">').text(changeMarker).appendTo(line);
var handledlLine = false;
if (isConflict && commitOptions.unmerged) {
$('<span class="prefix">').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;
$('<span>').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"));
$('<button class="editor-button editor-button-small" style="float: right; margin-right: 20px;"><i class="fa fa-angle-double-'+(isOurs?"down":"up")+'"></i> use '+(isOurs?"local":"remote")+' changes</button>')
.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();
$('<span><span>'+resolvedConflicts+'</span> of <span>'+unresolvedConflicts+'</span> conflicts resolved</span>').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++);
}
}
$('<span>').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 = /^..<<<<<<</.test(lineText);
if (isOurs) {
$('<span>').text("<<<<<<< Local Changes").appendTo(line);
hunk.localChangeStart = actualLineNumber;
} else {
hunk.remoteChangeEnd = actualLineNumber;
$('<span>').text(">>>>>>> Remote Changes").appendTo(line);
}
diffRow.addClass("mergeHeader-"+(isOurs?"ours":"theirs"));
$('<button class="editor-button editor-button-small" style="float: right; margin-right: 20px;"><i class="fa fa-angle-double-'+(isOurs?"down":"up")+'"></i> use '+(isOurs?"local":"remote")+' changes</button>')
.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();
$('<span><span>'+resolvedConflicts+'</span> of <span>'+unresolvedConflicts+'</span> conflicts resolved</span>').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 = $('<span style="float: right;"><span>'+resolvedConflicts+'</span> of <span>'+unresolvedConflicts+'</span> conflicts resolved</span>').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<lines.length;i++) {
var line = lines[i];
if (/^diff/.test(line)) {
var diffLine = diffHeader.exec(line);
if (diffLine) {
if (currentHunk) {
currentFile.hunks.push(currentHunk);
files.push(currentFile);
}
currentHunk = null;
currentFile = {
file: null,
file: diffLine[1],
hunks: []
}
} else if (binaryFile.test(line)) {
if (currentFile) {
currentFile.binary = true;
}
} else {
var fileLine = fileHeader.exec(line);
if (fileLine) {
@ -2106,8 +2120,8 @@ RED.diff = (function() {
}
if (currentHunk) {
currentFile.hunks.push(currentHunk);
files.push(currentFile);
}
files.push(currentFile);
return files;
}

View File

@ -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();
}
};
})();

View File

@ -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();
},
@ -947,8 +951,132 @@ RED.projects.settings = (function() {
updateForm();
}
function createLocalBranchListSection(activeProject,pane) {
var localBranchContainer = $('<div class="user-settings-section"></div>').appendTo(pane);
$('<h4></h4>').text("Branches").appendTo(localBranchContainer);
var row = $('<div class="user-settings-row projects-dialog-branch-list"></div>').appendTo(localBranchContainer);
var branchList = $('<ol>').appendTo(row).editableList({
addButton: false,
scrollOnAdd: false,
addItem: function(row,index,entry) {
var container = $('<div class="projects-dialog-branch-list-entry">').appendTo(row);
$('<span><i class="fa fa-code-fork"></i></span>').appendTo(container);
$('<span class="branch-name">').text(entry.name).appendTo(container);
// if (entry.commit) {
// $('<span class="commit">').text(entry.commit.sha).appendTo(container);
// }
if (entry.remote) {
$('<span class="branch-remote-name">').text(entry.remote||"").appendTo(container);
if (entry.status.ahead+entry.status.behind > 0) {
$('<span class="branch-remote-status">'+
'<i class="fa fa-long-arrow-up"></i> <span>'+entry.status.ahead+'</span> '+
'<i class="fa fa-long-arrow-down"></i> <span>'+entry.status.behind+'</span>'+
'</span>').appendTo(container);
}
}
var tools = $('<span class="projects-dialog-branch-list-entry-tools">').appendTo(container);
if (entry.current) {
tools.text('current');
} else {
$('<button class="editor-button editor-button-small">delete</button>')
.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 = $('<h3></h3>').text("Version Control").appendTo(pane);
$('<h3></h3>').text("Version Control").appendTo(pane);
createLocalBranchListSection(activeProject,pane);
var repoContainer = $('<div class="user-settings-section"></div>').appendTo(pane);
var title = $('<h4></h4>').text("Git remotes").appendTo(repoContainer);
var editRepoButton = $('<button class="editor-button editor-button-small" style="float: right;">edit</button>')
.appendTo(title)
.click(function(evt) {
@ -958,9 +1086,9 @@ RED.projects.settings = (function() {
$('.projects-dialog-remote-list-entry-delete').show();
remoteListAddButton.show();
});
var repoContainer = $('<div class="user-settings-section"></div>').appendTo(pane);
var grTitle = $('<h4></h4>').text("Git remotes").appendTo(repoContainer);
row = $('<div class="user-settings-row projects-dialog-remote-list"></div>').appendTo(repoContainer);
var remotesList = $('<ol>').appendTo(row);

View File

@ -615,7 +615,7 @@ RED.projects = (function() {
}
}
]
}
}
}
@ -980,7 +980,6 @@ RED.projects = (function() {
var branchFilterTerm = "";
var branchFilterCreateItem;
var branches = [];
var currentBranch;
var branchPrefix = "";
var container = $('<div class="projects-branch-list">').appendTo(options.container);
@ -1010,15 +1009,15 @@ RED.projects = (function() {
scrollOnAdd: false,
addItem: function(row,index,entry) {
var container = $('<div class="sidebar-version-control-branch-list-entry">').appendTo(row);
if (typeof entry !== "string") {
if (!entry.hasOwnProperty('commit')) {
branchFilterCreateItem = container;
$('<i class="fa fa-code-fork"></i>').appendTo(container);
$('<span>').text("Create branch:").appendTo(container);
$('<div class="sidebar-version-control-branch-list-entry-create-name" style="margin-left: 10px;">').text(entry.name).appendTo(container);
} else {
$('<i class="fa fa-code-fork"></i>').appendTo(container);
$('<span>').text(entry).appendTo(container);
if (currentBranch === entry) {
$('<span>').text(entry.name).appendTo(container);
if (entry.current) {
container.addClass("selected");
$('<span class="current"></span>').text(options.currentLabel||"current").appendTo(container);
}
@ -1029,7 +1028,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) {
@ -1039,7 +1038,7 @@ RED.projects = (function() {
if ($(this).hasClass('selected')) {
body.current = true;
}
body.name = entry;
body.name = entry.name;
}
if (options.onselect) {
options.onselect(body);
@ -1047,7 +1046,7 @@ RED.projects = (function() {
});
},
filter: function(data) {
var isCreateEntry = (typeof data !=="string");
var isCreateEntry = (!data.hasOwnProperty('commit'));
return (
isCreateEntry &&
(
@ -1057,7 +1056,7 @@ RED.projects = (function() {
) ||
(
!isCreateEntry &&
data.indexOf(branchFilterTerm) !== -1
data.name.indexOf(branchFilterTerm) !== -1
);
}
});
@ -1067,14 +1066,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",
@ -1100,7 +1097,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() }
}

View File

@ -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 = $('<i class=""></i>').appendTo(container);
var label = $('<span>').appendTo(container);
var entryLink = $('<a href="#">')
.appendTo(container)
.click(function(e) {
e.preventDefault();
viewFileDiff(entry,state);
});
var label = $('<span>').appendTo(entryLink);
var bg = $('<div class="button-group"></div>').appendTo(row);
var viewDiffButton = $('<button class="editor-button editor-button-small"><i class="fa fa-'+(state==='unmerged'?'columns':'eye')+'"></i></button>')
.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 = $('<div class="sidebar-version-control-change-entry-tools">').appendTo(row);
var bg;
var revertButton;
if (state === 'unstaged') {
bg = $('<span class="button-group" style="margin-right: 5px;"></span>').appendTo(entryTools);
revertButton = $('<button class="editor-button editor-button-small"><i class="fa fa-reply"></i></button>')
.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 = $('<span class="button-group"></span>').appendTo(entryTools);
if (state !== 'unmerged') {
$('<button class="editor-button editor-button-small"><i class="fa fa-'+((state==='unstaged')?"plus":"minus")+'"></i></button>')
.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);
}
@ -571,9 +624,6 @@ RED.sidebar.versionControl = (function() {
$('<div class="sidebar-version-control-slide-box-header"></div>').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 +761,6 @@ RED.sidebar.versionControl = (function() {
var remoteBranchSubRow = $('<div style="height: 0;overflow:hidden; transition: height 0.2s ease-in-out;"></div>').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() {

View File

@ -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%;

View File

@ -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;
}
}
@ -764,3 +770,44 @@ div.projects-dialog-ssh-public-key {
}
}
*/
.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;
}
}

View File

@ -77,6 +77,7 @@
<div id="sidebar-separator"></div>
</div>
<div id="full-shade" class="hide"></div>
<div id="notifications"></div>
<div id="dropTarget"><div data-i18n="[append]workspace.dropFlowHere"><br/><i class="fa fa-download"></i></div></div>

View File

@ -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");

View File

@ -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 = {
@ -115,7 +114,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();
@ -170,6 +169,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;
@ -373,6 +388,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 +412,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 {

View File

@ -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() {

View File

@ -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"
}
}
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}
if (storageModule.sshkeys) {
storageModuleInterface.sshkeys = storageModule.sshkeys;

View File

@ -235,7 +235,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;
@ -330,6 +330,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;
@ -349,7 +357,7 @@ Project.prototype.status = function(user) {
}
});
} else {
fetchPromise = when.resolve();
fetchPromise = Promise.resolve();
}
var completeStatus = function(fetchError) {
@ -462,18 +470,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;
}
@ -485,7 +498,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))
@ -662,7 +675,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) {

View File

@ -23,6 +23,7 @@ var clone = require('clone');
var path = require("path");
var gitCommand = "git";
var gitVersion;
var log;
function runGitCommand(args,cwd,env) {
@ -63,11 +64,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";
}
console.log("===============================================================");
console.log('err:', err);
console.log("===============================================================");
return reject(err);
}
resolve(stdout);
@ -297,17 +296,37 @@ function getRemotes(cwd) {
}
function getBranches(cwd, remote) {
var args = ['branch','--no-color'];
var args = ['branch','-vv','--no-color'];
if (remote) {
args.push('-r');
}
var branchRE = /^([ \*] )(\S+) +(\S+)(?: \[(\S+?)(?:: (?:ahead (\d+)(?:, )?)?(?:behind (\d+))?)?\])? (.*)$/;
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: {
ahead: m[5]||0,
behind: m[6]||0,
},
commit: {
sha: m[3],
subject: m[7]
}
}
if (m[1] === '* ') {
branch.current = true;
}
}
return branch;
}).filter(function(v) { return !!v && v.commit.sha !== '->' });
return {branches:branches};
})
}
@ -340,6 +359,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);
@ -442,6 +470,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)) {
@ -543,6 +575,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

View File

@ -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) {
@ -173,7 +192,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 +203,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);
@ -212,6 +237,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() {
@ -251,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)
@ -334,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}));
}
}
@ -407,6 +442,7 @@ module.exports = {
updateProject: updateProject,
getFiles: getFiles,
getFile: getFile,
revertFile: revertFile,
stageFile: stageFile,
unstageFile: unstageFile,
commit: commit,
@ -419,6 +455,7 @@ module.exports = {
resolveMerge: resolveMerge,
abortMerge: abortMerge,
getBranches: getBranches,
deleteBranch: deleteBranch,
setBranch: setBranch,
getBranchStatus:getBranchStatus,