A big projects update

Includes:

 - change local/remote branches
 - basic support for username/password handling
This commit is contained in:
Nick O'Leary 2017-11-21 23:31:41 +00:00
parent 3745504107
commit 10057de9b3
No known key found for this signature in database
GPG Key ID: 4F2157149161A6C9
19 changed files with 2412 additions and 391 deletions

View File

@ -43,7 +43,9 @@
$(".palette-scroll").removeClass("hide");
$("#palette-search").removeClass("hide");
loadFlows(function() {
RED.projects.refresh();
RED.projects.refresh(function() {
RED.sidebar.info.refresh()
});
var persistentNotifications = {};
RED.comms.subscribe("notification/#",function(topic,msg) {

View File

@ -101,6 +101,14 @@ RED.stack = (function() {
if (entry.onexpand) {
entry.onexpand.call(entry);
}
if (options.singleExpanded) {
entries.forEach(function(e) {
if (e !== entry) {
e.collapse();
}
})
}
icon.addClass("expanded");
entry.container.addClass("palette-category-expanded");
entry.contentWrap.slideDown(200);

View File

@ -1581,10 +1581,10 @@ RED.diff = (function() {
if (Adiff.type === 2) {
cellNo.addClass('blank');
cellLine.addClass('blank');
} else if (Adiff.type === 1) {
} else if (Adiff.type === 4) {
cellNo.addClass('added');
cellLine.addClass('added');
} else if (Adiff.type === 4) {
} else if (Adiff.type === 1) {
cellNo.addClass('removed');
cellLine.addClass('removed');
}
@ -1593,10 +1593,10 @@ RED.diff = (function() {
if (Bdiff.type === 2) {
cellNo.addClass('blank');
cellLine.addClass('blank');
} else if (Bdiff.type === 1) {
} else if (Bdiff.type === 4) {
cellNo.addClass('added');
cellLine.addClass('added');
} else if (Bdiff.type === 4) {
} else if (Bdiff.type === 1) {
cellNo.addClass('removed');
cellLine.addClass('removed');
}
@ -1694,9 +1694,14 @@ RED.diff = (function() {
var isCollapsed = diffFileRow.hasClass("collapsed");
diffFileRow.nextUntil(".node-text-diff-file-header").toggle(!isCollapsed);
})
var label = $('<span></span>').text(file.file).appendTo(content);
var label = $('<span class="filename"></span>').text(file.file).appendTo(content);
if (commitOptions.project.files && commitOptions.project.files.flow === file.file) {
var conflictHeader;
var unresolvedConflicts = 0;
var resolvedConflicts = 0;
var conflictResolutions = {};
if (!commitOptions.unmerged && commitOptions.project.files && commitOptions.project.files.flow === file.file) {
var tools = $('<span style="float: right;" class="button-group"></span>').appendTo(content);
$('<button class="editor-button editor-button-small">show flow diff</button>').appendTo(tools).click(function(e) {
e.preventDefault();
@ -1740,47 +1745,143 @@ RED.diff = (function() {
}
for (var i=0;i<hunks.length;i++) {
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(hunks[i].header).appendTo(content);
var label = $('<span></span>').text(hunk.header).appendTo(content);
var isConflict = hunk.conflict;
var localLine = hunk.localStartLine;
var remoteLine = hunk.remoteStartLine;
if (isConflict) {
unresolvedConflicts++;
}
var localLine = hunks[i].localStartLine;
var remoteLine = hunks[i].remoteStartLine;
for (var j=0;j<hunks[i].lines.length;j++) {
var lineText = hunks[i].lines[j];
hunk.lines.forEach(function(lineText,lineNumber) {
// if (lineText[0] === '\\' || lineText === "") {
// // Comment line - bail out of this hunk
// break;
// }
diffRow = $('<tr>').appendTo(codeBody);
var actualLineNumber = hunk.diffStart + lineNumber;
var isMergeHeader = isConflict && /^..(<<<<<<<|=======$|>>>>>>>)/.test(lineText);
var diffRow = $('<tr>').appendTo(codeBody);
var localLineNo = $('<td class="lineno">').appendTo(diffRow);
var remoteLineNo = $('<td class="lineno">').appendTo(diffRow);
var line = $('<td class="linetext">').appendTo(diffRow);
$('<span class="prefix">').text(lineText[0]).appendTo(line);
$('<span>').text(lineText.substring(1)).appendTo(line);
if (lineText[0] === '+') {
localLineNo.addClass("added");
remoteLineNo.addClass("added");
line.addClass("added");
remoteLineNo.text(remoteLine++);
} else if (lineText[0] === '-') {
localLineNo.addClass("removed");
remoteLineNo.addClass("removed");
line.addClass("removed");
localLineNo.text(localLine++);
var remoteLineNo;
if (!isMergeHeader) {
remoteLineNo = $('<td class="lineno">').appendTo(diffRow);
} else {
line.addClass("unchanged");
if (localLine > 0 && lineText[0] !== '\\' && lineText !== "") {
localLineNo.text(localLine++);
localLineNo.attr('colspan',2);
}
var line = $('<td class="linetext">').appendTo(diffRow);
var prefixStart = 0;
var prefixEnd = 1;
if (isConflict) {
prefixEnd = 2;
}
if (!isMergeHeader) {
var changeMarker = lineText[0];
if (isConflict && !commitOptions.unmerged && changeMarker === ' ') {
changeMarker = lineText[1];
}
if (remoteLine > 0 && lineText[0] !== '\\' && lineText !== "") {
remoteLineNo.text(remoteLine++);
$('<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 {
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;
}
}
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 {
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);
}
});
return diffPanel;
@ -1820,8 +1921,9 @@ RED.diff = (function() {
$('<div style="float: right">').text("Commit "+commit.sha).appendTo(summary);
$('<div>').text((commit.authorName||commit.author)+" - "+options.date).appendTo(summary);
createUnifiedDiffTable(commit.files,options).appendTo(diffPanel);
if (commit.files) {
createUnifiedDiffTable(commit.files,options).appendTo(diffPanel);
}
},
@ -1837,16 +1939,30 @@ RED.diff = (function() {
function showUnifiedDiff(options) {
var diff = options.diff;
var title = options.title;
var files = parseUnifiedDiff(diff);
var currentResolution;
if (options.unmerged) {
options.resolveConflict = function(results) {
currentResolution = results;
if (results.conflicts === results.resolved) {
$("#node-diff-view-resolve-diff").removeClass('disabled');
}
}
}
var trayOptions = {
title: title||"Compare Changes", //TODO: nls
width: Infinity,
overlay: true,
buttons: [
{
text: RED._("common.label.close"),
text: RED._((options.unmerged)?"common.label.cancel":"common.label.close"),
click: function() {
if (options.oncancel) {
options.oncancel();
}
RED.tray.close();
}
}
@ -1858,8 +1974,6 @@ RED.diff = (function() {
var trayBody = tray.find('.editor-tray-body');
var diffPanel = $('<div class="node-text-diff"></div>').appendTo(trayBody);
createUnifiedDiffTable(files,options).appendTo(diffPanel);
},
close: function() {
diffVisible = false;
@ -1868,6 +1982,23 @@ RED.diff = (function() {
}
}
if (options.unmerged) {
trayOptions.buttons.push(
{
id: "node-diff-view-resolve-diff",
text: "Save conflict resolution",
class: "primary disabled",
click: function() {
if (!$("#node-diff-view-resolve-diff").hasClass('disabled')) {
if (options.onresolve) {
options.onresolve(currentResolution);
}
RED.tray.close();
}
}
}
);
}
RED.tray.show(trayOptions);
}
@ -1912,6 +2043,7 @@ RED.diff = (function() {
}
var fileHeader = /^\+\+\+ b\/(.*)\t?/;
var hunkHeader = /^@@ -((\d+)(,(\d+))?) \+((\d+)(,(\d+))?) @@ ?(.*)$/;
var conflictHunkHeader = /^@+ -((\d+)(,(\d+))?) -((\d+)(,(\d+))?) \+((\d+)(,(\d+))?) @+/;
var files = [];
var currentFile;
var hunks = [];
@ -1944,9 +2076,29 @@ RED.diff = (function() {
localLength: hunkLine[4]||1,
remoteStartLine: hunkLine[6],
remoteLength: hunkLine[8]||1,
lines: []
lines: [],
conflict: false
}
} else if (currentHunk) {
continue;
}
hunkLine = conflictHunkHeader.exec(line);
if (hunkLine) {
if (currentHunk) {
currentFile.hunks.push(currentHunk);
}
currentHunk = {
header: line,
localStartLine: hunkLine[2],
localLength: hunkLine[4]||1,
remoteStartLine: hunkLine[6],
remoteLength: hunkLine[8]||1,
diffStart: parseInt(hunkLine[10]),
lines: [],
conflict: true
}
continue;
}
if (currentHunk) {
currentHunk.lines.push(line);
}
}

View File

@ -78,7 +78,7 @@ RED.notify = (function() {
window.clearTimeout(nn.timeoutid);
};
})());
n.timeoutid = window.setTimeout(n.close,timeout||3000);
n.timeoutid = window.setTimeout(n.close,timeout||5000);
}
currentNotifications.push(n);
c+=1;

View File

@ -110,17 +110,13 @@ RED.projects.settings = (function() {
RED.tray.show(trayOptions);
}
function addSpinnerOverlay(container) {
var spinner = $('<div class="projects-dialog-spinner projects-dialog-spinner-sidebar"><img src="red/images/spin.svg"/></div>').appendTo(container);
return spinner;
}
function editDescription(activeProject, container) {
RED.editor.editMarkdown({
title: RED._('sidebar.project.editDescription'),
value: activeProject.description,
complete: function(v) {
container.empty();
var spinner = addSpinnerOverlay(container);
var spinner = utils.addSpinnerOverlay(container);
var done = function(err,res) {
if (err) {
return editDescription(activeProject, container);
@ -176,7 +172,7 @@ RED.projects.settings = (function() {
evt.preventDefault();
var v = input.val();
updateProjectSummary(v, container);
var spinner = addSpinnerOverlay(container);
var spinner = utils.addSpinnerOverlay(container);
var done = function(err,res) {
if (err) {
spinner.remove();
@ -307,7 +303,7 @@ RED.projects.settings = (function() {
complete: function(v) {
try {
var parsed = JSON.parse(v);
var spinner = addSpinnerOverlay(container);
var spinner = utils.addSpinnerOverlay(container);
var done = function(err,res) {
if (err) {
@ -427,9 +423,8 @@ RED.projects.settings = (function() {
var dialogBody;
var filesList;
var selected;
var container = $('<div class="project-file-listing-container"></div>',{style:"position: relative; min-height: 175px; height: 175px;"}).appendTo(row);
var spinner = addSpinnerOverlay(container);
var container = $('<div class="project-file-listing-container"></div>',{style:"position: relative; min-height: 175px; height: 175px;"}).hide().appendTo(row);
var spinner = utils.addSpinnerOverlay(container);
$.getJSON("/projects/"+activeProject.name+"/files",function(result) {
var fileNames = Object.keys(result);
var files = {};
@ -467,7 +462,9 @@ RED.projects.settings = (function() {
createFileSubList(container,files.children,current,done,"height: 175px");
spinner.remove();
});
return container;
}
function createFileSubList(container, files, current, onselect, style) {
style = style || "";
var list = $('<ol>',{class:"projects-dialog-file-list", style:style}).appendTo(container).editableList({
@ -575,7 +572,7 @@ RED.projects.settings = (function() {
// evt.preventDefault();
// var newFlowFile = flowFileInput.val();
// var newCredsFile = credentialsFileInput.val();
// var spinner = addSpinnerOverlay(container);
// var spinner = utils.addSpinnerOverlay(container);
// var done = function(err,res) {
// if (err) {
// spinner.remove();
@ -617,12 +614,12 @@ RED.projects.settings = (function() {
function createFilesSection(activeProject,pane) {
var title = $('<h3></h3>').text("Files").appendTo(pane);
var filesContainer = $('<div class="user-settings-section"></div>').appendTo(pane);
var editButton = $('<button class="editor-button editor-button-small" style="float: right;">edit</button>')
var editFilesButton = $('<button class="editor-button editor-button-small" style="float: right;">edit</button>')
.appendTo(title)
.click(function(evt) {
evt.preventDefault();
formButtons.show();
editButton.hide();
editFilesButton.hide();
flowFileLabelText.hide();
flowFileInput.show();
flowFileInputSearch.show();
@ -646,21 +643,21 @@ RED.projects.settings = (function() {
var flowFileLabelText = $('<span style="display:inline-block; padding: 6px">').text(activeProject.files.flow).appendTo(flowFileLabel);
var flowFileInput = $('<input id="" type="text" style="margin-bottom: 0;width: 100%; border: none;">').val(activeProject.files.flow).hide().appendTo(flowFileLabel);
var flowFileInputSearch = $('<button class="editor-button" style="width: 36px; height: 36px; position: absolute; top: -1px; right: -1px;"><i class="fa fa-folder-open-o"></i></button>')
var flowFileInputSearch = $('<button class="editor-button" style="border-top-right-radius: 4px; border-bottom-right-radius: 4px; width: 36px; height: 34px; position: absolute; top: -1px; right: -1px;"><i class="fa fa-folder-open-o"></i></button>')
.hide()
.appendTo(flowFileLabel)
.click(function(e) {
if ($(this).hasClass('selected')) {
$(this).removeClass('selected');
flowFileLabel.find('.project-file-listing-container').remove();
flowFileLabel.css('height','');
flowFileLabel.find('.project-file-listing-container').slideUp(200,function() {
$(this).remove();
flowFileLabel.css('height','');
});
flowFileLabel.css('color','');
} else {
$(this).addClass('selected');
flowFileLabel.css('height','auto');
flowFileLabel.css('color','inherit');
showProjectFileListing(flowFileLabel,activeProject,flowFileInput.val(),function(result,isDblClick) {
var fileList = showProjectFileListing(flowFileLabel,activeProject,flowFileInput.val(),function(result,isDblClick) {
if (result) {
flowFileInput.val(result);
}
@ -668,7 +665,12 @@ RED.projects.settings = (function() {
$(flowFileInputSearch).click();
}
checkFiles();
})
});
flowFileLabel.css('height','auto');
setTimeout(function() {
fileList.slideDown(200);
},50);
}
})
@ -748,7 +750,7 @@ RED.projects.settings = (function() {
}
checkFiles();
});
var credentialSecretEditButton = $('<button class="editor-button" style="vertical-align: top; width: 36px; margin-bottom: 10px"><i class="fa fa-pencil"></i></button>')
var credentialSecretEditButton = $('<button class="editor-button" style="border-top-right-radius: 4px; border-bottom-right-radius: 4px; vertical-align: top; width: 36px; margin-bottom: 10px"><i class="fa fa-pencil"></i></button>')
.appendTo(credentialSecretButtons)
.click(function(e) {
e.preventDefault();
@ -810,7 +812,7 @@ RED.projects.settings = (function() {
var hideEditForm = function() {
editButton.show();
editFilesButton.show();
formButtons.hide();
flowFileLabelText.show();
flowFileInput.hide();
@ -821,6 +823,11 @@ RED.projects.settings = (function() {
credentialStateLabel.removeClass("uneditable-input");
credentialStateLabel.css('height','');
flowFileInputSearch.removeClass('selected');
flowFileLabel.find('.project-file-listing-container').remove();
flowFileLabel.css('height','');
flowFileLabel.css('color','');
$(".user-settings-row-credentials").hide();
credentialFormRows.hide();
credentialSecretButtons.hide();
@ -831,7 +838,7 @@ RED.projects.settings = (function() {
}
var formButtons = $('<span class="button-group" style="position: relative; float: right; margin-right:0;"></span>').hide().appendTo(filesContainer);
var cancelButton = $('<button class="editor-button">Cancel</button>')
$('<button class="editor-button">Cancel</button>')
.appendTo(formButtons)
.click(function(evt) {
evt.preventDefault();
@ -841,7 +848,7 @@ RED.projects.settings = (function() {
.appendTo(formButtons)
.click(function(evt) {
evt.preventDefault();
var spinner = addSpinnerOverlay(filesContainer);
var spinner = utils.addSpinnerOverlay(filesContainer);
var done = function(err) {
spinner.remove();
if (err) {
@ -927,9 +934,201 @@ RED.projects.settings = (function() {
checkFiles();
updateForm();
}
function createLocalRepositorySection(activeProject,pane) {
var title = $('<h3></h3>').text("Local Repository").appendTo(pane);
var repoContainer = $('<div class="user-settings-section"></div>').appendTo(pane);
var editRepoButton = $('<button class="editor-button editor-button-small" style="float: right;">edit</button>')
.appendTo(title)
.click(function(evt) {
editRepoButton.hide();
localRepoSearch.show();
formButtons.show();
});
var row = $('<div class="user-settings-row"></div>').appendTo(repoContainer);
$('<label for=""></label>').text('Branch').appendTo(row);
var localRepoLabel = $('<div class="uneditable-input" style="padding:0">').appendTo(row);
var hideLocalRepoBranchList = function() {
localRepoSearch.removeClass('selected');
localRepoLabel.css('height','');
localRepoBranchListRow.slideUp(100);
}
var localRepoText = $('<span style="display:inline-block; padding: 6px">').text(activeProject.branches.local).appendTo(localRepoLabel);
var localRepoSearch = $('<button class="editor-button" style="border-top-right-radius: 4px; border-bottom-right-radius: 4px; width: 36px; height: 34px; position: absolute; top: -1px; right: -1px;"><i class="fa fa-code-fork"></i></button>')
.hide()
.appendTo(localRepoLabel)
.click(function(e) {
e.preventDefault();
if ($(this).hasClass('selected')) {
hideLocalRepoBranchList();
} else {
$(this).addClass('selected');
localRepoLabel.css('height','auto');
localRepoBranchListRow.slideDown(100);
localRepoBranchList.refresh("/projects/"+activeProject.name+"/branches");
localRepoBranchList.focus();
}
});
var localRepoBranchListRow = $('<div>').hide().appendTo(localRepoLabel);
var localRepoBranchList = utils.createBranchList({
current: function() {
return activeProject.branches.local
},
placeholder: "Find or create a branch",
container: localRepoBranchListRow,
onselect: function(body) {
localRepoText.text(body.name);
hideLocalRepoBranchList();
}
})
var hideEditForm = function() {
editRepoButton.show();
localRepoSearch.hide();
formButtons.hide();
localRepoBranchListRow.slideUp(100);
localRepoSearch.removeClass('selected');
}
var formButtons = $('<span class="button-group" style="position: relative; float: right; margin-right:0;"></span>').hide().appendTo(repoContainer);
$('<button class="editor-button">Cancel</button>')
.appendTo(formButtons)
.click(function(evt) {
evt.preventDefault();
hideEditForm();
});
var saveButton = $('<button class="editor-button">Save</button>')
.appendTo(formButtons)
.click(function(evt) {
evt.preventDefault();
hideEditForm();
});
var updateForm = function() {
// if (activeProject.settings.credentialSecretInvalid) {
// credentialStateLabel.find(".user-settings-credentials-state-icon").removeClass().addClass("user-settings-credentials-state-icon fa fa-warning");
// credentialStateLabel.find(".user-settings-credentials-state").text("Invalid encryption key");
// } else if (activeProject.settings.credentialsEncrypted) {
// credentialStateLabel.find(".user-settings-credentials-state-icon").removeClass().addClass("user-settings-credentials-state-icon fa fa-lock");
// credentialStateLabel.find(".user-settings-credentials-state").text("Encryption enabled");
// } else {
// credentialStateLabel.find(".user-settings-credentials-state-icon").removeClass().addClass("user-settings-credentials-state-icon fa fa-unlock");
// credentialStateLabel.find(".user-settings-credentials-state").text("Encryption disabled");
// }
// credentialSecretResetButton.toggleClass('disabled',!activeProject.settings.credentialsEncrypted);
// credentialSecretResetButton.prop('disabled',!activeProject.settings.credentialsEncrypted);
}
}
function createRemoteRepositorySection(activeProject,pane) {
var title = $('<h3></h3>').text("Git Remotes").appendTo(pane);
var repoContainer = $('<div class="user-settings-section"></div>').appendTo(pane);
var editRepoButton = $('<button class="editor-button editor-button-small" style="float: right;">edit</button>')
.appendTo(title)
.click(function(evt) {
editRepoButton.hide();
formButtons.show();
});
var row = $('<div class="user-settings-row"></div>').appendTo(repoContainer);
var remotesList = $("<ol>",{style:"height: 320px"}).appendTo(row);
remotesList.editableList({
addButton: "remote",
addItem: function(outer,index,entry) {
var row = $('<div class="user-settings-row"></div>').appendTo(outer);
$('<label for=""></label>').text('Name').appendTo(row);
$('<div class="uneditable-input">').text(entry.name).appendTo(row);
row = $('<div class="user-settings-row"></div>').appendTo(outer);
$('<label for=""></label>').text('Fetch URL').appendTo(row);
$('<div class="uneditable-input">').text(entry.urls.fetch).appendTo(row);
row = $('<div class="user-settings-row"></div>').appendTo(outer);
$('<label for=""></label>').text('Push URL').appendTo(row);
$('<div class="uneditable-input">').text(entry.urls.push).appendTo(row);
}
});
if (activeProject.hasOwnProperty('remotes')) {
for (var name in activeProject.remotes) {
if (activeProject.remotes.hasOwnProperty(name)) {
remotesList.editableList('addItem',{name:name,urls:activeProject.remotes[name]});
}
}
}
// row = $('<div class="user-settings-row"></div>').appendTo(repoContainer);
// if (activeProject.hasOwnProperty('remotes')) {
// $('<label for=""></label>').text('URL').appendTo(row);
// for (var name in activeProject.remotes) {
// if (activeProject.remotes.hasOwnProperty(name)) {
// var repos = activeProject.remotes[name];
// if (repos.fetch === repos.push) {
// $('<div class="uneditable-input">').text(repos.fetch).appendTo(row);
// $('<div class="projects-edit-form-sublabel"><small></small></div>').appendTo(row).find('small').text(name+" fetch/push");
// } else {
// $('<div class="uneditable-input">').text(repos.fetch).appendTo(row);
// $('<div class="projects-edit-form-sublabel"><small></small></div>').appendTo(row).find('small').text(name+" fetch");
// $('<label for=""></label>').appendTo(row);
// $('<div class="uneditable-input">').text(repos.push).appendTo(row);
// $('<div class="projects-edit-form-sublabel"><small></small></div>').appendTo(row).find('small').text(name+" push");
// // $('<span>').text(repos.fetch+" (fetch)").appendTo(repoRow);
// // $('<span>').text(repos.push+" (push)").appendTo(repoRow);
// }
// }
// }
// if (activeProject.branches.hasOwnProperty('remote')) {
// row = $('<div class="user-settings-row"></div>').appendTo(repoContainer);
// $('<label for="" style="text-align: right;box-sizing: border-box; padding-right: 25px;">Branch</label>').appendTo(row);
// $('<div class="uneditable-input">').text(activeProject.branches.remote).appendTo(row);
//
// row = $('<div class="user-settings-row"></div>').appendTo(repoContainer);
// $('<label for="" style="text-align: right;box-sizing: border-box; padding-right: 25px;">Username</label>').appendTo(row);
// $('<div class="uneditable-input">').appendTo(row);
//
// row = $('<div class="user-settings-row"></div>').appendTo(repoContainer);
// $('<label for="" style="text-align: right;box-sizing: border-box; padding-right: 25px;">Password</label>').appendTo(row);
// $('<div class="uneditable-input">').html("&bull; &bull; &bull; &bull; &bull; &bull; &bull; &bull;").appendTo(row);
// }
// } else {
//
//
// }
var hideEditForm = function() {
editRepoButton.show();
formButtons.hide();
}
var formButtons = $('<span class="button-group" style="position: relative; float: right; margin-right:0;"></span>').hide().appendTo(repoContainer);
$('<button class="editor-button">Cancel</button>')
.appendTo(formButtons)
.click(function(evt) {
evt.preventDefault();
hideEditForm();
});
var saveButton = $('<button class="editor-button">Save</button>')
.appendTo(formButtons)
.click(function(evt) {
evt.preventDefault();
hideEditForm();
});
var updateForm = function() { }
}
function createSettingsPane(activeProject) {
var pane = $('<div id="project-settings-tab-settings" class="project-settings-tab-pane node-help"></div>');
createFilesSection(activeProject,pane);
// createLocalRepositorySection(activeProject,pane);
createRemoteRepositorySection(activeProject,pane);
return pane;
}

View File

@ -51,24 +51,61 @@ RED.projects = (function() {
var copyProject;
var projectRepoInput;
var emptyProjectCredentialInput;
var projectRepoUserInput;
var projectRepoPasswordInput;
var projectNameSublabel;
var projectRepoPassphrase;
var projectRepoRemoteName
var projectRepoBranch;
return {
title: "Create a new project", // TODO: NLS
content: function() {
var projectList = null;
var pendingFormValidation = false;
$.getJSON("projects", function(data) {
projectList = {};
data.projects.forEach(function(p) {
projectList[p] = true;
if (pendingFormValidation) {
pendingFormValidation = false;
validateForm();
}
})
});
var container = $('<div class="projects-dialog-screen-create"></div>');
var row;
var validateForm = function() {
var projectName = projectNameInput.val();
var valid = true;
if (!/^[a-zA-Z0-9\-_]+$/.test(projectName)) {
if (projectNameInputChanged) {
projectNameInput.addClass("input-error");
if (projectNameInputChanged) {
if (projectList === null) {
pendingFormValidation = true;
return;
}
valid = false;
} else {
projectNameInput.removeClass("input-error");
projectNameStatus.empty();
if (!/^[a-zA-Z0-9\-_]+$/.test(projectName) || projectList[projectName]) {
projectNameInput.addClass("input-error");
$('<i style="margin-top: 8px;" class="fa fa-exclamation-triangle"></i>').appendTo(projectNameStatus);
projectNameValid = false;
valid = false;
if (projectList[projectName]) {
projectNameSublabel.text("Project already exists");
} else {
projectNameSublabel.text("Must contain only A-Z 0-9 _ -");
}
} else {
projectNameInput.removeClass("input-error");
$('<i style="margin-top: 8px;" class="fa fa-check"></i>').appendTo(projectNameStatus);
projectNameSublabel.text("Must contain only A-Z 0-9 _ -");
projectNameValid = true;
}
projectNameLastChecked = projectName;
}
valid = projectNameValid;
var projectType = $(".projects-dialog-screen-create-type.selected").data('type');
if (projectType === 'copy') {
if (!copyProject) {
@ -76,19 +113,43 @@ RED.projects = (function() {
}
} else if (projectType === 'clone') {
var repo = projectRepoInput.val();
if (repo.trim() === '') {
// TODO: could do more url regex checking...
var validRepo = /^(?:git|ssh|https?|[\d\w\.\-_]+@[\w\.]+):(?:\/\/)?[\w\.@:\/~_-]+\.git(?:\/?|\#[\d\w\.\-_]+?)$/.test(repo);
if (!validRepo) {
if (projectRepoChanged) {
projectRepoInput.addClass("input-error");
}
valid = false;
} else {
projectRepoInput.removeClass("input-error");
}
if (/^(?:ssh|[\d\w\.\-_]+@[\w\.]+):(?:\/\/)?/.test(repo)) {
$(".projects-dialog-screen-create-row-creds").hide();
$(".projects-dialog-screen-create-row-passphrase").show();
} else if (/^https?:\/\//.test(repo)) {
$(".projects-dialog-screen-create-row-creds").show();
$(".projects-dialog-screen-create-row-passphrase").hide();
} else {
$(".projects-dialog-screen-create-row-creds").show();
$(".projects-dialog-screen-create-row-passphrase").hide();
}
} else if (projectType === 'empty') {
projectFlowFileInput.toggleClass("input-error",projectFlowFileInput.val()==='')
valid = valid && projectFlowFileInput.val()!=='';
var flowFile = projectFlowFileInput.val();
if (flowFile === "" || !/\.json$/.test(flowFile)) {
valid = false;
if (!projectFlowFileInput.hasClass("input-error")) {
projectFlowFileInput.addClass("input-error");
projectFlowFileInput.next().empty().append('<i style="margin-top: 8px;" class="fa fa-exclamation-triangle"></i>');
}
} else {
if (projectFlowFileInput.hasClass("input-error")) {
projectFlowFileInput.removeClass("input-error");
projectFlowFileInput.next().empty();
}
}
var encryptionState = $("input[name=projects-encryption-type]:checked").val();
if (encryptionState === 'enabled') {
var encryptionKeyType = $("input[name=projects-encryption-key]:checked").val();
@ -103,7 +164,7 @@ RED.projects = (function() {
row = $('<div class="form-row button-group"></div>').appendTo(container);
var createAsEmpty = $('<button data-type="empty" class="editor-button projects-dialog-screen-create-type toggle selected"><i class="fa fa-archive fa-2x"></i><i style="position: absolute;" class="fa fa-asterisk"></i><br/>Empty Project</button>').appendTo(row);
var createAsCopy = $('<button data-type="copy" class="editor-button projects-dialog-screen-create-type toggle"><i class="fa fa-archive fa-2x"></i><i class="fa fa-long-arrow-right fa-2x"></i><i class="fa fa-archive fa-2x"></i><br/>Copy existing</button>').appendTo(row);
// var createAsCopy = $('<button data-type="copy" class="editor-button projects-dialog-screen-create-type toggle"><i class="fa fa-archive fa-2x"></i><i class="fa fa-long-arrow-right fa-2x"></i><i class="fa fa-archive fa-2x"></i><br/>Copy existing</button>').appendTo(row);
var createAsClone = $('<button data-type="clone" class="editor-button projects-dialog-screen-create-type toggle"><i class="fa fa-git fa-2x"></i><i class="fa fa-arrows-h fa-2x"></i><i class="fa fa-archive fa-2x"></i><br/>Clone repository</button>').appendTo(row);
row.find(".projects-dialog-screen-create-type").click(function(evt) {
evt.preventDefault();
@ -116,24 +177,51 @@ RED.projects = (function() {
row = $('<div class="form-row"></div>').appendTo(container);
$('<label>Project name</label>').appendTo(row);
$('<label for="projects-dialog-screen-create-project-name">Project name</label>').appendTo(row);
var subrow = $('<div style="position:relative;"></div>').appendTo(row);
projectNameInput = $('<input id="projects-dialog-screen-create-project-name" type="text"></input>').appendTo(subrow);
var projectNameStatus = $('<div class="projects-dialog-screen-input-status"></div>').appendTo(subrow);
projectNameInput = $('<input type="text"></input>').appendTo(row);
var projectNameInputChanged = false;
projectNameInput.on("change keyup paste",function() { projectNameInputChanged = true; validateForm(); });
$('<label class="projects-edit-form-sublabel"><small>Must contain only A-Z 0-9 _ -</small></label>').appendTo(row);
var projectNameLastChecked = "";
var projectNameValid;
var checkProjectName;
var autoInsertedName = "";
projectNameInput.on("change keyup paste",function() {
projectNameInputChanged = (projectNameInput.val() !== projectNameLastChecked);
if (checkProjectName) {
clearTimeout(checkProjectName);
} else if (projectNameInputChanged) {
projectNameStatus.empty();
$('<img src="red/images/spin.svg"/>').appendTo(projectNameStatus);
if (projectNameInput.val() === '') {
validateForm();
return;
}
}
checkProjectName = setTimeout(function() {
validateForm();
checkProjectName = null;
},300)
});
projectNameSublabel = $('<label class="projects-edit-form-sublabel"><small>Must contain only A-Z 0-9 _ -</small></label>').appendTo(row).find("small");
// Empty Project
row = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-empty"></div>').appendTo(container);
$('<label>Description</label>').appendTo(row);
projectSummaryInput = $('<input type="text">').appendTo(row);
$('<label for="projects-dialog-screen-create-project-desc">Description</label>').appendTo(row);
projectSummaryInput = $('<input id="projects-dialog-screen-create-project-desc" type="text">').appendTo(row);
$('<label class="projects-edit-form-sublabel"><small>Optional</small></label>').appendTo(row);
row = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-empty"></div>').appendTo(container);
$('<label>Flow file</label>').appendTo(row);
projectFlowFileInput = $('<input type="text">').val("flow.json")
$('<label for="projects-dialog-screen-create-project-file">Flow file</label>').appendTo(row);
subrow = $('<div style="position:relative;"></div>').appendTo(row);
projectFlowFileInput = $('<input id="projects-dialog-screen-create-project-file" type="text">').val("flow.json")
.on("change keyup paste",validateForm)
.appendTo(row);
.appendTo(subrow);
$('<div class="projects-dialog-screen-input-status"></div>').appendTo(subrow);
$('<label class="projects-edit-form-sublabel"><small>*.json</small></label>').appendTo(row);
row = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-empty"></div>').appendTo(container);
@ -185,7 +273,7 @@ RED.projects = (function() {
emptyProjectCredentialInput.on("change keyup paste", validateForm);
row = $('<div class="form-row projects-encryption-disabled-row"></div>').hide().appendTo(credentialsRightBox);
$('<div class="form-tips form-warning" style="padding: 15px; margin: 5px;"><i class="fa fa-warning"></i> The credentials file will not be encrypted and its contents easily read</div>').appendTo(row);
$('<div class="" style="padding: 5px 20px;"><i class="fa fa-warning"></i> The credentials file will not be encrypted and its contents easily read</div>').appendTo(row);
credentialsRightBox.find("input[name=projects-encryption-key]").click(function() {
var val = $(this).val();
@ -195,29 +283,31 @@ RED.projects = (function() {
// Copy Project
row = $('<div class="hide form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-copy"></div>').appendTo(container);
$('<label> Select project to copy</label>').appendTo(row);
var autoInsertedName = "";
createProjectList({
height: "250px",
small: true,
select: function(project) {
copyProject = project;
var projectName = projectNameInput.val();
if (projectName === "" || projectName === autoInsertedName) {
autoInsertedName = project.name+"-copy";
projectNameInput.val(autoInsertedName);
}
validateForm();
}
}).appendTo(row);
// row = $('<div class="hide form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-copy"></div>').appendTo(container);
// $('<label> Select project to copy</label>').appendTo(row);
// createProjectList({
// height: "250px",
// small: true,
// select: function(project) {
// copyProject = project;
// var projectName = projectNameInput.val();
// if (projectName === "" || projectName === autoInsertedName) {
// autoInsertedName = project.name+"-copy";
// projectNameInput.val(autoInsertedName);
// }
// validateForm();
// }
// }).appendTo(row);
// Clone Project
row = $('<div class="hide form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-clone"></div>').appendTo(container);
$('<label>Git repository URL</label>').appendTo(row);
projectRepoInput = $('<input type="text" placeholder="https://git.example.com/path/my-project.git"></input>').appendTo(row);
$('<label for="projects-dialog-screen-create-project-repo">Git repository URL</label>').appendTo(row);
projectRepoInput = $('<input id="projects-dialog-screen-create-project-repo" type="text" placeholder="https://git.example.com/path/my-project.git"></input>').appendTo(row);
$('<label class="projects-edit-form-sublabel"><small>https:// or ssh://</small></label>').appendTo(row);
var projectRepoChanged = false;
projectRepoInput.on("change keyup paste",function() {
projectRepoChanged = true;
var repo = $(this).val();
var m = /\/([^/]+)\.git/.exec(repo);
if (m) {
@ -230,10 +320,35 @@ RED.projects = (function() {
validateForm();
});
// Secret - clone
row = $('<div class="hide form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-clone"></div>').appendTo(container);
$('<label>Credentials encryption key</label>').appendTo(row);
projectSecretInput = $('<input type="text"></input>').appendTo(row);
row = $('<div class="hide form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-clone projects-dialog-screen-create-row-creds"></div>').hide().appendTo(container);
var subrow = $('<div style="width: calc(50% - 10px); display:inline-block;"></div>').appendTo(row);
$('<label for="projects-dialog-screen-create-project-repo-user">Username</label>').appendTo(subrow);
projectRepoUserInput = $('<input id="projects-dialog-screen-create-project-repo-user" type="text"></input>').appendTo(subrow);
subrow = $('<div style="width: calc(50% - 10px); margin-left: 20px; display:inline-block;"></div>').appendTo(row);
$('<label for="projects-dialog-screen-create-project-repo-pass">Password</label>').appendTo(subrow);
projectRepoPasswordInput = $('<input id="projects-dialog-screen-create-project-repo-pass" type="password"></input>').appendTo(subrow);
row = $('<div class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-passphrase"></div>').hide().appendTo(container);
$('<label for="projects-dialog-screen-create-project-repo-passphrase">SSH key passphrase</label>').appendTo(row);
projectRepoPassphrase = $('<input id="projects-dialog-screen-create-project-repo-passphrase" type="password" style="width: calc(100% - 250px);"></input>').appendTo(row);
// row = $('<div style="width: calc(50% - 10px); display:inline-block;" class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-clone"></div>').hide().appendTo(container);
// $('<label for="projects-dialog-screen-create-project-repo-remote-name">Remote name</label>').appendTo(row);
// projectRepoRemoteName = $('<input id="projects-dialog-screen-create-project-repo-remote-name" type="text" style="width: 100%;"></input>').val("origin").appendTo(row);
//
// row = $('<div style="width: calc(50% - 10px); margin-left: 20px; display:inline-block;" class="form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-clone"></div>').hide().appendTo(container);
// $('<label for="projects-dialog-screen-create-project-repo-branch">Branch</label>').appendTo(row);
// projectRepoBranch = $('<input id="projects-dialog-screen-create-project-repo-branch" type="text"></input>').val('master').appendTo(row);
// // Secret - clone
// row = $('<div class="hide form-row projects-dialog-screen-create-row projects-dialog-screen-create-row-clone"></div>').appendTo(container);
// $('<label>Credentials encryption key</label>').appendTo(row);
// projectSecretInput = $('<input type="text"></input>').appendTo(row);
createAsEmpty.click();
@ -280,9 +395,13 @@ RED.projects = (function() {
} else if (projectType === 'copy') {
projectData.copy = copyProject.name;
} else if (projectType === 'clone') {
projectData.credentialSecret = projectSecretInput.val();
// projectData.credentialSecret = projectSecretInput.val();
projectData.remote = {
url: projectRepoInput.val()
// name: projectRepoRemoteName.val()||'origin',
// branch: projectRepoBranch.val()||'master',
url: projectRepoInput.val(),
username: projectRepoUserInput.val(),
password: projectRepoPasswordInput.val()
}
}
@ -304,6 +423,8 @@ RED.projects = (function() {
console.log("git error",error);
},
'git_auth_failed': function(error) {
projectRepoUserInput.addClass("input-error");
projectRepoPasswordInput.addClass("input-error");
// getRepoAuthDetails(req);
console.log("git auth error",error);
},
@ -551,6 +672,115 @@ RED.projects = (function() {
});
}
function createBranchList(options) {
var branchFilterTerm = "";
var branchFilterCreateItem;
var branches = [];
var currentBranch;
var branchPrefix = "";
var container = $('<div class="projects-branch-list">').appendTo(options.container);
var branchFilter = $('<input type="text">').attr('placeholder',options.placeholder).appendTo(container).searchBox({
delay: 200,
change: function() {
branchFilterTerm = $(this).val();
if (/(\.\.|\/\.|[?*[~^: \\]|\/\/|\/.$|\/$)/.test(branchFilterTerm)) {
if (!branchFilterCreateItem.hasClass("input-error")) {
branchFilterCreateItem.addClass("input-error");
branchFilterCreateItem.find("i").addClass("fa-warning").removeClass("fa-code-fork");
}
branchFilterCreateItem.find("span").text("Invalid branch: "+branchPrefix+branchFilterTerm);
} else {
if (branchFilterCreateItem.hasClass("input-error")) {
branchFilterCreateItem.removeClass("input-error");
branchFilterCreateItem.find("i").removeClass("fa-warning").addClass("fa-code-fork");
}
branchFilterCreateItem.find(".sidebar-version-control-branch-list-entry-create-name").text(branchPrefix+branchFilterTerm);
}
branchList.editableList("filter");
}
});
var branchList = $("<ol>",{style:"height: 130px;"}).appendTo(container);
branchList.editableList({
addButton: false,
scrollOnAdd: false,
addItem: function(row,index,entry) {
var container = $('<div class="sidebar-version-control-branch-list-entry">').appendTo(row);
if (typeof entry !== "string") {
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) {
container.addClass("selected");
$('<span class="current"></span>').text(options.currentLabel||"current").appendTo(container);
}
}
container.click(function(evt) {
evt.preventDefault();
if ($(this).hasClass('input-error')) {
return;
}
var body = {};
if (typeof entry !== "string") {
body.name = branchFilter.val();
body.create = true;
if (options.remote) {
body.name = options.remote()+"/"+body.name;
}
} else {
if ($(this).hasClass('selected')) {
body.current = true;
}
body.name = entry;
}
if (options.onselect) {
options.onselect(body);
}
});
},
filter: function(data) {
var isCreateEntry = (typeof data !=="string");
return (isCreateEntry && (branchFilterTerm !== "" && branches.indexOf(branchPrefix+branchFilterTerm) === -1) ) || (!isCreateEntry && data.indexOf(branchPrefix+branchFilterTerm) !== -1);
}
});
return {
refresh: function(url) {
branchFilter.searchBox("value","");
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 = "";
}
$.getJSON(url,function(result) {
branches = result.branches;
result.branches.forEach(function(b) {
branchList.editableList('addItem',b);
});
branchList.editableList('addItem',{});
setTimeout(function() {
spinner.remove();
},Math.max(300-(Date.now() - start),0));
});
},
addItem: function(data) { branchList.editableList('addItem',data) },
filter: function() { branchList.editableList('filter') },
focus: function() { branchFilter.focus() }
}
}
function addSpinnerOverlay(container) {
var spinner = $('<div class="projects-dialog-spinner"><img src="red/images/spin.svg"/></div>').appendTo(container);
return spinner;
}
function init() {
dialog = $('<div id="projects-dialog" class="hide node-red-dialog projects-edit-form"><form class="form-horizontal"></form><div class="projects-dialog-spinner hide"><img src="red/images/spin.svg"/></div></div>')
.appendTo("body")
@ -577,9 +807,13 @@ RED.projects = (function() {
RED.actions.add("core:new-project",RED.projects.newProject);
RED.actions.add("core:open-project",RED.projects.selectProject);
RED.projects.settings.init({sendRequest:sendRequest});
RED.sidebar.versionControl.init({sendRequest:sendRequest});
var projectsAPI = {
sendRequest:sendRequest,
createBranchList:createBranchList,
addSpinnerOverlay:addSpinnerOverlay
};
RED.projects.settings.init(projectsAPI);
RED.sidebar.versionControl.init(projectsAPI);
initScreens();
// initSidebar();
}
@ -589,6 +823,7 @@ RED.projects = (function() {
$.getJSON("projects",function(data) {
if (data.active) {
$.getJSON("projects/"+data.active, function(project) {
console.log(project.branches);
activeProject = project;
// updateProjectSummary();
// updateProjectDescription();

File diff suppressed because it is too large Load Diff

View File

@ -573,8 +573,8 @@
td.lineno {
font-family: monospace;
text-align: right;
color: #999;
background: #fafafa;
color: #aaa;
background: #f6f6f6;
padding: 1px 5px;
}
td.lineno:nth-child(3) {
@ -600,6 +600,23 @@
td.removed {
background: #fadddd;
}
tr.mergeHeader td {
color: #800080;
background: #e5f9ff;
height: 26px;
vertical-align: middle;
}
tr.mergeHeader-separator td {
color: #800080;
background: darken(#e5f9ff, 10%);
height: 0px;
}
tr.mergeHeader-ours td {
border-top: 2px solid darken(#e5f9ff, 10%);
}
tr.mergeHeader-theirs td {
border-bottom: 2px solid darken(#e5f9ff, 10%);
}
td.unchanged {
color: #999;
}
@ -613,7 +630,9 @@
border-bottom: 1px solid #f0f0f0;
}
tr.node-text-diff-file-header td {
font-family: monospace;
.filename {
font-family: monospace;
}
background: #f3f3f3;
padding: 5px 10px 5px 0;
color: #333;

View File

@ -215,6 +215,9 @@
font-size: 13px;
border-radius: 2px;
padding: 0 10px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
&.toggle {
@include workspace-button-toggle;
}
@ -223,6 +226,7 @@
.editor-button-small {
height: 20px;
min-width: 20px;
line-height: 18px;
font-size: 10px;
border-radius: 2px;

View File

@ -243,3 +243,6 @@
background: $shade-color;
z-index: 5;
}
.component-shade {
@include shade
}

View File

@ -20,6 +20,9 @@
}
}
#project-settings-tab-settings {
overflow-y: scroll;
}
.projects-edit-form form {
margin: 0;
.form-row {
@ -28,13 +31,6 @@
color: #555;
width: 100%;
display: block;
font-weight: 500;
&.projects-edit-form-sublabel {
color: #999;
text-align: right;
margin-bottom: -15px;
font-weight: normal;
}
&.projects-edit-form-inline-label {
font-weight: normal;
color: inherit;
@ -51,12 +47,25 @@
}
}
.projects-edit-form-sublabel {
color: #999;
text-align: right;
margin-bottom: -15px;
font-weight: normal;
}
.project-settings-tab-pane {
& * .projects-edit-form-sublabel {
margin-right: 50px;
margin-top: -10px;
margin-bottom: 5px;
}
}
.projects-dialog-spinner {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
top: 1px;
bottom: 1px;
left: 1px;
right: 1px;
text-align: center;
padding: 40px;
background: white;
@ -87,6 +96,14 @@
}
}
&.projects-dialog-spinner-contain {
padding: 0;
img {
width: auto;
height: 100%;
max-height: 50px;
}
}
}
.projects-dialog-screen-start {
@ -196,6 +213,15 @@
background: #fff !important;
color: #666 !important;
}
.projects-dialog-screen-input-status {
text-align: right;
position: absolute;
top: 2px;
right: 8px;
width: 70px;
height: 30px;
color: #999;
}
.sidebar-version-control {
height: 100%;
@ -278,18 +304,22 @@
border-radius: 0;
}
}
.sidebar-version-control-change-container {
position: relative;
height: 50%;
box-sizing: border-box;
border-top: 1px solid $secondary-border-color;
transition: height 0.2s ease-in-out;
&:first-child {
// border-bottom: 1px solid $primary-border-color;
}
}
.sidebar-version-control-change-commit-box {
.sidebar-version-control-merging {
.sidebar-version-control-change-container {
height: 33%;
}
}
.sidebar-version-control-slide-box {
position:absolute;
bottom: 0;
left:0;
@ -299,7 +329,28 @@
background: #f6f6f6;
box-sizing: border-box;
overflow: hidden;
border-top: 1px solid $secondary-border-color;
&.sidebar-version-control-slide-box-top {
z-index: 10;
top: 0px;
left: auto;
width: 100%;
max-width: 280px;
border-left: 1px solid $primary-border-color;
border-right: 1px solid $primary-border-color;
border-bottom: 1px solid $primary-border-color;
box-shadow: 1px 1px 4px rgba(0,0,0,0.2);
color: #666;
background: #f6f6f6;
padding: 10px;
box-sizing: border-box;
}
&.sidebar-version-control-slide-box-bottom {
bottom: 0px;
border-top: 1px solid $secondary-border-color;
}
textarea {
height: 110px;
margin: 10px;
@ -308,12 +359,84 @@
border-radius: 1px;
resize: none;
}
}
.sidebar-version-control-change-commit-toolbar {
padding: 0 20px;
text-align: right;
}
.projects-branch-list {
position: relative;
.red-ui-searchBox-container {
border-top: 1px solid $secondary-border-color;
border-left: 1px solid $secondary-border-color;
border-right: 1px solid $secondary-border-color;
border-top-left-radius: 2px;
border-top-right-radius: 2px;
overflow: hidden;
}
.red-ui-editableList {
border: 1px solid $secondary-border-color;
border-bottom-left-radius: 2px;
border-bottom-right-radius: 2px;
& > .red-ui-editableList-border {
border-radius: 0;
border: none;
}
.red-ui-editableList-container {
padding: 0;
li {
padding: 0;
background: #fff;
}
}
}
}
.uneditable-input .projects-branch-list {
.red-ui-editableList {
border-left: none;
border-bottom: none;
border-right: none;
}
.red-ui-searchBox-container {
border-left: none;
border-right: none;
}
}
.sidebar-version-control-slide-box-header {
margin-bottom: 10px;
}
.sidebar-version-control-slide-box-toolbar {
padding: 0 20px;
text-align: right;
}
.sidebar-version-control-branch-list-entry {
padding: 5px 8px;
color: #666;
cursor: pointer;
&.selected {
border-left-color:#999;
border-right-color:#999;
}
border-left: 2px solid #fff;
border-right: 2px solid #fff;
margin: 0 1px;
i { width: 16px; text-align: center}
&.input-error {
cursor: default;
}
&:not(.input-error):hover {
background: #f3f3f3;
border-left-color:#999;
border-right-color:#999;
}
span {
margin-left: 5px;
}
span.current {
float: right;
font-size: 0.8em;
color: #999;
}
}
.sidebar-version-control-change-entry {
height: 20px;
padding: 5px 10px;
@ -340,6 +463,8 @@
&.node-info-none {
text-align: center;
background: #fefefe;
white-space: normal;
height: auto;
}
}
@ -348,11 +473,18 @@
padding: 5px 10px;
position: relative;
white-space: nowrap;
overflow: hidden;
cursor: pointer;
&:hover {
background: #eee;
}
}
.sidebar-version-control-commit-more {
color: #999;
text-align: center;
padding: 10px;
font-style: italic;
}
.sidebar-version-control-commit-sha {
float: right;
font-family: monospace;
@ -364,6 +496,17 @@
.sidebar-version-control-commit-subject {
color: #666;
}
.sidebar-version-control-commit-refs {
min-height: 22px;
}
.sidebar-version-control-commit-ref {
color: #aaa;
font-size: 0.7em;
border: 1px solid #ccc;
border-radius: 10px;
padding: 2px 5px;
margin-right: 5px;
}
.sidebar-version-control-commit-date {
color: #999;
font-size: 0.85em;
@ -373,18 +516,44 @@
color: #999;
font-size: 0.85em;
}
.sidebar-version-control-commit-head {
}
.sidebar-version-control-change-header {
color: #666;
background: #f6f6f6;
padding: 4px 10px;
height: 30px;
box-sizing: border-box;
border-top: 1px solid $secondary-border-color;
border-bottom: 1px solid $secondary-border-color;
i {
transition: all 0.2s ease-in-out;
}
}
.sidebar-version-control-repo-toolbar {
color: #666;
background: #f6f6f6;
padding: 10px;
box-sizing: border-box;
}
.sidebar-version-control-repo-count {
margin-right: 8px;
display: none;
}
.sidebar-version-control-repo-action {
text-align: left;
width: 100%;
}
.sidebar-version-control-repo-sub-action {
width: calc(50% - 5px);
margin-right: 5px;
&:not(:first-child) {
margin-right: 0;
margin-left: 5px;
}
}
.project-file-listing-container > .red-ui-editableList > .red-ui-editableList-border {
border-radius: 0;
border: none;

View File

@ -38,6 +38,8 @@
label {
display: inline-block;
min-width: 100px;
vertical-align: top;
margin-top: 5px;
input {
vertical-align: top;
padding-bottom: 0;

View File

@ -117,10 +117,27 @@ module.exports = {
// Delete project
});
app.get("/:id/status", function(req,res) {
// Get project metadata
runtime.storage.projects.getStatus(req.params.id).then(function(data) {
if (data) {
res.json(data);
} else {
res.status(404).end();
}
}).catch(function(err) {
console.log(err.stack);
res.status(400).json({error:"unexpected_error", message:err.toString()});
})
});
// Project Files
app.get("/:id/files", function(req,res) {
runtime.storage.projects.getFiles(req.params.id).then(function(data) {
console.log("TODO: REMOVE /:id/files as /:id/status is better!")
res.json(data);
})
.catch(function(err) {
@ -149,7 +166,7 @@ module.exports = {
var file = req.params[0];
runtime.storage.projects.stageFile(projectName,file).then(function(data) {
res.redirect(303,req.baseUrl+"/"+projectName+"/files");
res.redirect(303,req.baseUrl+"/"+projectName+"/status");
})
.catch(function(err) {
console.log(err.stack);
@ -161,7 +178,7 @@ module.exports = {
var files = req.body.files;
runtime.storage.projects.stageFile(projectName,files).then(function(data) {
res.redirect(303,req.baseUrl+"/"+projectName+"/files");
res.redirect(303,req.baseUrl+"/"+projectName+"/status");
})
.catch(function(err) {
console.log(err.stack);
@ -173,7 +190,7 @@ module.exports = {
var projectName = req.params.id;
runtime.storage.projects.commit(projectName,req.body).then(function(data) {
res.redirect(303,req.baseUrl+"/"+projectName+"/files");
res.redirect(303,req.baseUrl+"/"+projectName+"/status");
})
.catch(function(err) {
console.log(err.stack);
@ -186,7 +203,7 @@ module.exports = {
var file = req.params[0];
runtime.storage.projects.unstageFile(projectName,file).then(function(data) {
res.redirect(303,req.baseUrl+"/"+projectName+"/files");
res.redirect(303,req.baseUrl+"/"+projectName+"/status");
})
.catch(function(err) {
console.log(err.stack);
@ -196,7 +213,7 @@ module.exports = {
app.delete("/:id/stage", function(req, res) {
var projectName = req.params.id;
runtime.storage.projects.unstageFile(projectName).then(function(data) {
res.redirect(303,req.baseUrl+"/"+projectName+"/files");
res.redirect(303,req.baseUrl+"/"+projectName+"/status");
})
.catch(function(err) {
console.log(err.stack);
@ -221,13 +238,20 @@ module.exports = {
app.get("/:id/commits", function(req, res) {
var projectName = req.params.id;
var options = {};
var options = {
limit: req.query.limit||20,
before: req.query.before
};
runtime.storage.projects.getCommits(projectName,options).then(function(data) {
res.json(data);
})
.catch(function(err) {
console.log(err.stack);
res.status(400).json({error:"unexpected_error", message:err.toString()});
if (err.code) {
res.status(400).json({error:err.code, message: err.message});
} else {
res.status(400).json({error:"unexpected_error", message:err.toString()});
}
})
});
@ -244,6 +268,137 @@ module.exports = {
})
});
app.post("/:id/push/?*", function(req,res) {
var projectName = req.params.id;
var remoteBranchName = req.params[0]
var setRemote = req.query.u;
runtime.storage.projects.push(projectName,remoteBranchName,setRemote).then(function(data) {
res.status(204).end();
})
.catch(function(err) {
console.log(err.stack);
if (err.code) {
res.status(400).json({error:err.code, message: err.message});
} else {
res.status(400).json({error:"unexpected_error", message:err.toString()});
}
})
});
app.get("/:id/pull/?*", function(req,res) {
var projectName = req.params.id;
var remoteBranchName = req.params[0];
var setRemote = req.query.u;
runtime.storage.projects.pull(projectName,remoteBranchName,setRemote).then(function(data) {
res.status(204).end();
})
.catch(function(err) {
console.log(err.stack);
if (err.code) {
res.status(400).json({error:err.code, message: err.message});
} else {
res.status(400).json({error:"unexpected_error", message:err.toString()});
}
})
});
app.delete("/:id/merge", function(req, res) {
var projectName = req.params.id;
runtime.storage.projects.abortMerge(projectName).then(function(data) {
res.status(204).end();
})
.catch(function(err) {
console.log(err.stack);
if (err.code) {
res.status(400).json({error:err.code, message: err.message});
} else {
res.status(400).json({error:"unexpected_error", message:err.toString()});
}
})
});
app.post("/:id/resolve/*", function(req, res) {
var projectName = req.params.id;
var file = req.params[0];
var resolution = req.body.resolutions;
runtime.storage.projects.resolveMerge(projectName,file,resolution).then(function(data) {
res.status(204).end();
})
.catch(function(err) {
console.log(err.stack);
if (err.code) {
res.status(400).json({error:err.code, message: err.message});
} else {
res.status(400).json({error:"unexpected_error", message:err.toString()});
}
})
});
app.get("/:id/branches", function(req, res) {
var projectName = req.params.id;
runtime.storage.projects.getBranches(projectName,false).then(function(data) {
res.json(data);
})
.catch(function(err) {
console.log(err.stack);
if (err.code) {
res.status(400).json({error:err.code, message: err.message});
} else {
res.status(400).json({error:"unexpected_error", message:err.toString()});
}
})
});
app.get("/:id/branches/remote", function(req, res) {
var projectName = req.params.id;
runtime.storage.projects.getBranches(projectName,true).then(function(data) {
res.json(data);
})
.catch(function(err) {
console.log(err.stack);
if (err.code) {
res.status(400).json({error:err.code, message: err.message});
} else {
res.status(400).json({error:"unexpected_error", message:err.toString()});
}
})
});
app.get("/:id/branches/remote/*/status", function(req, res) {
var projectName = req.params.id;
var branch = req.params[0];
runtime.storage.projects.getBranchStatus(projectName,branch).then(function(data) {
res.json(data);
})
.catch(function(err) {
console.log(err.stack);
if (err.code) {
res.status(400).json({error:err.code, message: err.message});
} else {
res.status(400).json({error:"unexpected_error", message:err.toString()});
}
})
});
app.post("/:id/branches", function(req, res) {
var projectName = req.params.id;
var branchName = req.body.name;
var isCreate = req.body.create;
runtime.storage.projects.setBranch(projectName,branchName,isCreate).then(function(data) {
res.json(data);
})
.catch(function(err) {
console.log(err.stack);
if (err.code) {
res.status(400).json({error:err.code, message: err.message});
} else {
res.status(400).json({error:"unexpected_error", message:err.toString()});
}
})
});
return app;
}
}

View File

@ -49,6 +49,8 @@ Project.prototype.load = function () {
this.credentialSecret = projectSettings.credentialSecret;
this.remote = projectSettings.remote;
// this.paths.flowFile = fspath.join(this.path,"flow.json");
// this.paths.credentialsFile = fspath.join(this.path,"flow_cred.json");
@ -60,15 +62,20 @@ Project.prototype.load = function () {
if (missingFiles.indexOf('package.json') === -1) {
project.paths['package.json'] = fspath.join(project.path,"package.json");
promises.push(fs.readFile(project.paths['package.json'],"utf8").then(function(content) {
project.package = util.parseJSON(content);
if (project.package.hasOwnProperty('node-red')) {
if (project.package['node-red'].hasOwnProperty('settings')) {
project.paths.flowFile = project.package['node-red'].settings.flowFile;
project.paths.credentialsFile = project.package['node-red'].settings.credentialsFile;
try {
project.package = util.parseJSON(content);
if (project.package.hasOwnProperty('node-red')) {
if (project.package['node-red'].hasOwnProperty('settings')) {
project.paths.flowFile = project.package['node-red'].settings.flowFile;
project.paths.credentialsFile = project.package['node-red'].settings.credentialsFile;
}
} else {
// TODO: package.json doesn't have a node-red section
// is that a bad thing?
}
} else {
// TODO: package.json doesn't have a node-red section
// is that a bad thing?
} catch(err) {
// package.json isn't valid JSON... is a merge underway?
project.package = {};
}
}));
} else {
@ -93,6 +100,13 @@ Project.prototype.load = function () {
// project.paths.credentialsFile = fspath.join(project.path,"flow_cred.json");
// }
promises.push(gitTools.getRemotes(project.path).then(function(remotes) {
project.remotes = remotes;
}));
promises.push(gitTools.getBranchInfo(project.path).then(function(branches) {
project.branches = branches;
}));
return when.settle(promises).then(function() {
return project;
})
@ -207,7 +221,24 @@ Project.prototype.getFileDiff = function(file,type) {
return gitTools.getFileDiff(this.path,file,type);
}
Project.prototype.getCommits = function(options) {
return gitTools.getCommits(this.path,options);
var self = this;
var fetchPromise;
if (this.remote) {
options.hasRemote = true;
fetchPromise = gitTools.fetch(this.path)
} else {
fetchPromise = when.resolve();
}
return fetchPromise.then(function() {
return gitTools.getCommits(self.path,options);
}).otherwise(function(e) {
if (e.code === 'git_auth_failed') {
throw e;
}
console.log("Fetch failed");
console.log(e);
return gitTools.getCommits(self.path,options);
});
}
Project.prototype.getCommit = function(sha) {
return gitTools.getCommit(this.path,sha);
@ -220,6 +251,94 @@ Project.prototype.getFile = function (filePath,treeish) {
}
};
Project.prototype.status = function() {
var self = this;
var promises = [
gitTools.getStatus(this.path),
fs.exists(fspath.join(this.path,".git","MERGE_HEAD"))
]
return when.all(promises).then(function(results) {
var result = results[0];
if (results[1]) {
result.merging = true;
}
self.branches.local = result.branches.local;
self.branches.remote = result.branches.remote;
return result;
})
};
Project.prototype.push = function (remoteBranchName,setRemote) {
return gitTools.push(this.path, remoteBranchName, setRemote);
};
Project.prototype.pull = function (remoteBranchName,setRemote) {
if (setRemote) {
return gitTools.setUpstream(this.path, remoteBranchName).then(function() {
return gitTools.pull(this.path);
})
} else {
return gitTools.pull(this.path, remoteBranchName);
}
};
Project.prototype.resolveMerge = function (file,resolutions) {
var filePath = fspath.join(this.path,file);
var self = this;
return fs.readFile(filePath,"utf8").then(function(content) {
var lines = content.split("\n");
var result = [];
var ignoreBlock = false;
var currentBlock;
for (var i=1;i<=lines.length;i++) {
if (resolutions.hasOwnProperty(i)) {
currentBlock = resolutions[i];
if (currentBlock.selection === "A") {
ignoreBlock = false;
} else {
ignoreBlock = true;
}
continue;
}
if (currentBlock) {
if (currentBlock.separator === i) {
if (currentBlock.selection === "A") {
ignoreBlock = true;
} else {
ignoreBlock = false;
}
continue;
} else if (currentBlock.changeEnd === i) {
currentBlock = null;
continue;
} else if (ignoreBlock) {
continue;
}
}
result.push(lines[i-1]);
}
var finalResult = result.join("\n");
return util.writeFile(filePath,finalResult).then(function() {
return self.stageFile(file);
})
});
};
Project.prototype.abortMerge = function () {
return gitTools.abortMerge(this.path);
};
Project.prototype.getBranches = function (remote) {
return gitTools.getBranches(this.path,remote);
};
Project.prototype.setBranch = function (branchName, isCreate) {
var self = this;
return gitTools.checkoutBranch(this.path, branchName, isCreate).then(function() {
return self.load();
})
};
Project.prototype.getBranchStatus = function (branchName) {
return gitTools.getBranchStatus(this.path,branchName);
};
Project.prototype.getFlowFile = function() {
console.log("Project.getFlowFile = ",this.paths.flowFile);
if (this.paths.flowFile) {
@ -243,14 +362,13 @@ Project.prototype.getCredentialsFileBackup = function() {
return getBackupFilename(this.getCredentialsFile());
}
Project.prototype.toJSON = function () {
return {
name: this.name,
summary: this.package.description,
description: this.description,
dependencies: this.package.dependencies,
dependencies: this.package.dependencies||{},
settings: {
credentialsEncrypted: (typeof this.credentialSecret === "string"),
credentialSecretInvalid: this.credentialSecretInvalid
@ -258,7 +376,9 @@ Project.prototype.toJSON = function () {
files: {
flow: this.paths.flowFile,
credentials: this.paths.credentialsFile
}
},
remotes: this.remotes,
branches: this.branches
}
};
@ -281,7 +401,7 @@ function checkProjectExists(project) {
var projectPath = fspath.join(projectsDir,project);
return fs.pathExists(projectPath).then(function(exists) {
if (!exists) {
var e = new Error("NLD: project not found");
var e = new Error("NLS: project not found");
e.code = "project_not_found";
throw e;
}
@ -379,6 +499,7 @@ function createProject(metadata) {
return settings.set('projects',projects);
}).then(function() {
if (metadata.remote) {
console.log(metadata.remote);
return gitTools.clone(metadata.remote,projectPath).then(function(result) {
// Check this is a valid project
// If it is empty
@ -415,8 +536,7 @@ function getProject(name) {
}
currentProject = new Project(name);
return currentProject.load();
})
});
}
function listProjects() {

View File

@ -0,0 +1,86 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var net = require("net");
var fs = require("fs-extra");
var path = require("path");
var os = require("os");
function getListenPath() {
var seed = (0x100000+Math.random()*0x999999).toString(16);
var fn = 'node-red-git-askpass-'+seed+'-sock';
var listenPath;
if (process.platform === 'win32') {
listenPath = '\\\\.\\pipe\\'+getListenPath;
} else {
listenPath = path.join(process.env['XDG_RUNTIME_DIR'] || os.tmpdir(), fn);
}
console.log(listenPath);
return listenPath;
}
var ResponseServer = function(auth) {
return new Promise(function(resolve, reject) {
server = net.createServer(function(connection) {
// Stop accepting new connections
connection.setEncoding('utf8');
var parts = [];
connection.on('data', function(data) {
var m = data.indexOf("\n");
if (m !== -1) {
parts.push(data.substring(0, m));
data = data.substring(m);
var line = parts.join("");
parts = [];
if (line==='Username') {
connection.end(auth.username);
} else if (line === 'Password') {
connection.end(auth.password);
server.close();
} else {
}
}
if (data.length > 0) {
parts.push(data);
}
});
});
var listenPath = getListenPath();
server.listen(listenPath, function(ready) {
resolve({path:listenPath,close:function() { server.close(); }});
});
server.on('close', function() {
console.log("Closing response server");
fs.removeSync(listenPath);
});
server.on('error',function(err) {
console.log("ResponseServer unexpectedError:",err.toString());
server.close();
reject(err);
})
});
}
module.exports = {
ResponseServer: ResponseServer
}

View File

@ -0,0 +1,24 @@
/**
* Copyright JS Foundation and other contributors, http://js.foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
var net = require("net");
var socket = net.connect(process.argv[2], function() {
socket.on('data', function(data) { console.log(data);});
socket.on('end', function() {
});
socket.write((process.argv[3]||"")+"\n", 'utf8');
});
socket.setEncoding('utf8');

View File

@ -17,25 +17,32 @@
var when = require('when');
var exec = require('child_process').exec;
var spawn = require('child_process').spawn;
var authResponseServer = require('./authServer').ResponseServer;
var clone = require('clone');
var path = require("path");
var gitCommand = "git";
var log;
function execCommand(command,args,cwd) {
// function execCommand(command,args,cwd) {
// return when.promise(function(resolve,reject) {
// var fullCommand = command+" "+args.join(" ");
// child = exec(fullCommand, {cwd: cwd, timeout:3000, killSignal: 'SIGTERM'}, function (error, stdout, stderr) {
// if (error) {
// reject(error);
// } else {
// resolve(stdout);
// }
// });
// });
// }
function runGitCommand(args,cwd,env) {
log.trace(gitCommand + JSON.stringify(args));
return when.promise(function(resolve,reject) {
var fullCommand = command+" "+args.join(" ");
child = exec(fullCommand, {cwd: cwd, timeout:3000, killSignal: 'SIGTERM'}, function (error, stdout, stderr) {
if (error) {
reject(error);
} else {
resolve(stdout);
}
});
});
}
function runCommand(command,args,cwd) {
console.log(cwd,command,args);
return when.promise(function(resolve,reject) {
var child = spawn(command, args, {cwd:cwd, detached:true});
args.unshift("credential.helper=")
args.unshift("-c");
var child = spawn(gitCommand, args, {cwd:cwd, detached:true, env:env});
var stdout = "";
var stderr = "";
child.stdout.on('data', function(data) {
@ -49,10 +56,14 @@ function runCommand(command,args,cwd) {
child.on('close', function(code) {
if (code !== 0) {
var err = new Error(stderr);
err.stdout = stdout;
err.stderr = stderr;
if (/fatal: could not read Username/.test(stderr)) {
err.code = "git_auth_failed";
} else if(/HTTP Basic: Access denied/.test(stderr)) {
err.code = "git_auth_failed";
} else if(/Connection refused/.test(stderr)) {
err.code = "git_connection_failed";
} else {
err.code = "git_error";
}
@ -62,9 +73,17 @@ function runCommand(command,args,cwd) {
});
});
}
function isAuthError(err) {
// var lines = err.toString().split("\n");
// lines.forEach(console.log);
function runGitCommandWithAuth(args,cwd,auth) {
return authResponseServer(auth).then(function(rs) {
var commandEnv = clone(process.env);
commandEnv.GIT_ASKPASS = path.join(__dirname,"node-red-ask-pass.sh");
commandEnv.NODE_RED_GIT_NODE_PATH = process.execPath;
commandEnv.NODE_RED_GIT_SOCK_PATH = rs.path;
commandEnv.NODE_RED_GIT_ASKPASS_PATH = path.join(__dirname,"authWriter.js");
return runGitCommand(args,cwd,commandEnv).finally(function() {
rs.close();
});
})
}
function cleanFilename(name) {
@ -85,139 +104,312 @@ function parseFilenames(name) {
}
return result;
}
function getFiles(localRepo) {
function getBranchInfo(localRepo) {
return runGitCommand(["status","--porcelain","-b"],localRepo).then(function(output) {
var lines = output.split("\n");
var unknownDirs = [];
var branchLineRE = /^## (.+?)($|\.\.\.(.+?)($| \[(ahead (\d+))?.*?(behind (\d+))?\]))/m;
var m = branchLineRE.exec(output);
var result = {}; //commits:{}};
if (m) {
result.local = m[1];
if (m[3]) {
result.remote = m[3];
}
// if (m[6] !== undefined) {
// result.commits.ahead = parseInt(m[6]);
// }
// if (m[8] !== undefined) {
// result.commits.behind = parseInt(m[8]);
// }
}
return result;
});
}
function getStatus(localRepo) {
// parseFilename('"test with space"');
// parseFilename('"test with space" -> knownFile.txt');
// parseFilename('"test with space" -> "un -> knownFile.txt"');
var files = {};
return runCommand(gitCommand,["ls-files","--cached","--others","--exclude-standard"],localRepo).then(function(output) {
var lines = output.split("\n");
lines.forEach(function(l) {
if (l==="") {
return;
}
var fullName = cleanFilename(l);
// parseFilename(l);
var parts = fullName.split("/");
var p = files;
var name;
for (var i = 0;i<parts.length-1;i++) {
var name = parts.slice(0,i+1).join("/")+"/";
if (!p.hasOwnProperty(name)) {
p[name] = {
type:"d"
var result = {
files: {},
commits: {},
branches: {}
}
return runGitCommand(['rev-list', 'HEAD', '--count'],localRepo).then(function(count) {
result.commits.total = parseInt(count);
}).then(function() {
return runGitCommand(["ls-files","--cached","--others","--exclude-standard"],localRepo).then(function(output) {
var lines = output.split("\n");
lines.forEach(function(l) {
if (l==="") {
return;
}
var fullName = cleanFilename(l);
// parseFilename(l);
var parts = fullName.split("/");
var p = result.files;
var name;
for (var i = 0;i<parts.length-1;i++) {
var name = parts.slice(0,i+1).join("/")+"/";
if (!p.hasOwnProperty(name)) {
p[name] = {
type:"d"
}
}
}
}
files[fullName] = {
type: /\/$/.test(fullName)?"d":"f"
}
})
return runCommand(gitCommand,["status","--porcelain"],localRepo).then(function(output) {
var lines = output.split("\n");
var unknownDirs = [];
lines.forEach(function(line) {
if (line==="") {
return;
}
if (line[0] === "#") {
return;
}
var status = line.substring(0,2);
var fileName;
var names;
if (status !== '??') {
names = parseFilenames(line.substring(3));
} else {
names = [cleanFilename(line.substring(3))];
}
fileName = names[0];
if (names.length > 1) {
fileName = names[1];
}
// parseFilename(fileName);
if (fileName.charCodeAt(0) === 34) {
fileName = fileName.substring(1,fileName.length-1);
}
if (files.hasOwnProperty(fileName)) {
files[fileName].status = status;
} else {
files[fileName] = {
type: "f",
status: status
};
}
if (names.length > 1) {
files[fileName].oldName = names[0];
}
if (status === "??" && fileName[fileName.length-1] === '/') {
unknownDirs.push(fileName);
result.files[fullName] = {
type: /\/$/.test(fullName)?"d":"f"
}
})
var allFilenames = Object.keys(files);
allFilenames.forEach(function(f) {
var entry = files[f];
if (!entry.hasOwnProperty('status')) {
unknownDirs.forEach(function(uf) {
if (f.startsWith(uf)) {
entry.status = "??"
return runGitCommand(["status","--porcelain","-b"],localRepo).then(function(output) {
var lines = output.split("\n");
var unknownDirs = [];
var branchLineRE = /^## (.+?)($|\.\.\.(.+?)($| \[(ahead (\d+))?.*?(behind (\d+))?\]))/;
lines.forEach(function(line) {
if (line==="") {
return;
}
if (line[0] === "#") {
var m = branchLineRE.exec(line);
if (m) {
result.branches.local = m[1];
if (m[3]) {
result.branches.remote = m[3];
result.commits.ahead = 0;
result.commits.behind = 0;
}
if (m[6] !== undefined) {
result.commits.ahead = parseInt(m[6]);
}
if (m[8] !== undefined) {
result.commits.behind = parseInt(m[8]);
}
}
});
}
return;
}
var status = line.substring(0,2);
var fileName;
var names;
if (status !== '??') {
names = parseFilenames(line.substring(3));
} else {
names = [cleanFilename(line.substring(3))];
}
fileName = names[0];
if (names.length > 1) {
fileName = names[1];
}
// parseFilename(fileName);
if (fileName.charCodeAt(0) === 34) {
fileName = fileName.substring(1,fileName.length-1);
}
if (result.files.hasOwnProperty(fileName)) {
result.files[fileName].status = status;
} else {
result.files[fileName] = {
type: "f",
status: status
};
}
if (names.length > 1) {
result.files[fileName].oldName = names[0];
}
if (status === "??" && fileName[fileName.length-1] === '/') {
unknownDirs.push(fileName);
}
})
var allFilenames = Object.keys(result.files);
allFilenames.forEach(function(f) {
var entry = result.files[f];
if (!entry.hasOwnProperty('status')) {
unknownDirs.forEach(function(uf) {
if (f.startsWith(uf)) {
entry.status = "??"
}
});
}
})
// console.log(files);
return result;
})
// console.log(files);
return files;
})
})
}
function parseLog(log) {
var lines = log.split("\n");
var currentCommit = null;
var currentCommit = {};
var commits = [];
lines.forEach(function(l) {
if (/^sha: /.test(l)) {
if (currentCommit) {
commits.push(currentCommit);
}
if (l === "-----") {
commits.push(currentCommit);
currentCommit = {}
return;
}
var m = /^(.*): (.*)$/.exec(l);
if (m) {
currentCommit[m[1]] = m[2];
if (m[1] === 'refs' && m[2]) {
currentCommit[m[1]] = m[2].split(",").map(function(v) { return v.trim() });
} else {
if (m[1] === 'parents') {
currentCommit[m[1]] = m[2].split(" ");
} else {
currentCommit[m[1]] = m[2];
}
}
}
});
if (currentCommit) {
commits.push(currentCommit);
}
return {commits: commits};
return commits;
}
// function getCommitCounts(cwd, options) {
// var commands = [
// runGitCommand(['rev-list', 'HEAD', '--count'],cwd), // #commits on master
// ];
// if (options.hasRemote) {
// commands.push(runGitCommand(['rev-list', 'master','^origin/master', '--count'],cwd)); // #commits master ahead
// commands.push(runGitCommand(['rev-list', '^master','origin/master', '--count'],cwd)); // #commits master behind
// }
// return when.all(commands).then(function(results) {
// var result = {
// total: parseInt(results[0])
// }
// if (options.hasRemote) {
// result.ahead = parseInt(results[1]);
// result.behind = parseInt(results[2]);
// }
// return result;
// });
// }
function getRemotes(cwd) {
return runGitCommand(['remote','-v'],cwd).then(function(output) {
var result;
if (output.length > 0) {
result = {};
var remoteRE = /^(.+)\t(.+) \((.+)\)$/gm;
var m;
while ((m = remoteRE.exec(output)) !== null) {
result[m[1]] = result[m[1]]||{};
result[m[1]][m[3]] = m[2];
}
}
return result;
})
}
function getBranches(cwd, remote) {
var args = ['branch','--no-color'];
if (remote) {
args.push('-r');
}
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)
});
return {branches:branches};
})
}
function getBranchStatus(cwd,remoteBranch) {
var commands = [
// #commits master ahead
runGitCommand(['rev-list', 'HEAD','^'+remoteBranch, '--count'],cwd),
// #commits master behind
runGitCommand(['rev-list', '^HEAD',remoteBranch, '--count'],cwd)
];
return when.all(commands).then(function(results) {
return {
commits: {
ahead: parseInt(results[0]),
behind: parseInt(results[1])
}
}
})
}
var gitCommand = "git";
module.exports = {
init: function(_settings,_runtime) {
log = _runtime.log
},
initRepo: function(cwd) {
return runCommand(gitCommand,["init"],cwd);
return runGitCommand(["init"],cwd);
},
pull: function(repo, cwd) {
if (repo.url) {
repo = repo.url;
setUpstream: function(cwd,remoteBranch) {
var args = ["branch","--set-upstream-to",remoteBranch];
return runGitCommand(args,cwd);
},
pull: function(cwd,remoteBranch) {
var args = ["pull"];
var m = /^(.*?)\/(.*)$/.exec(remoteBranch);
if (m) {
args.push(m[1]);
args.push(m[2])
}
var args = ["pull",repo,"master"];
return runCommand(gitCommand,args,cwd);
return runGitCommand(args,cwd).otherwise(function(err) {
if (/CONFLICT/.test(err.stdout)) {
var e = new Error("NLS: pull failed - merge conflict");
e.code = "git_pull_merge_conflict";
throw e;
} else if (/Please commit your changes or stash/.test(err.message)) {
var e = new Error("NLS: Pull failed - local changes would be overwritten");
e.code = "git_pull_overwrite";
throw e;
}
throw err;
});
},
clone: function(repo, cwd) {
if (repo.url) {
repo = repo.url;
push: function(cwd,remoteBranch,setUpstream) {
var args = ["push"];
var m = /^(.*?)\/(.*)$/.exec(remoteBranch);
if (m) {
if (setUpstream) {
args.push("-u");
}
args.push(m[1]);
args.push("HEAD:"+m[2]);
} else {
args.push("origin");
}
var args = ["clone",repo,"."];
return runCommand(gitCommand,args,cwd);
args.push("--porcelain");
return runGitCommand(args,cwd).otherwise(function(err) {
if (err.code === 'git_error') {
if (/^!.*non-fast-forward/m.test(err.stdout)) {
err.code = 'git_push_failed';
}
throw err;
}
});
},
getFiles: getFiles,
clone: function(remote, cwd) {
var args = ["clone",remote.url];
if (remote.name) {
args.push("-o");
args.push(remote.name);
}
if (remote.branch) {
args.push("-b");
args.push(remote.branch);
}
args.push(".");
if (remote.hasOwnProperty("username") && remote.hasOwnProperty("password")) {
return runGitCommandWithAuth(args,cwd,remote);
} else {
return runGitCommand(args,cwd);
}
},
getStatus: getStatus,
getFile: function(cwd, filePath, treeish) {
var args = ["show",treeish+":"+filePath];
return runCommand(gitCommand,args,cwd);
return runGitCommand(args,cwd);
},
getFiles: function(cwd) {
return getStatus(cwd).then(function(status) {
return status.files;
})
},
stageFile: function(cwd,file) {
var args = ["add"];
@ -226,18 +418,18 @@ module.exports = {
} else {
args.push(file);
}
return runCommand(gitCommand,args,cwd);
return runGitCommand(args,cwd);
},
unstageFile: function(cwd, file) {
var args = ["reset","--"];
if (file) {
args.push(file);
}
return runCommand(gitCommand,args,cwd);
return runGitCommand(args,cwd);
},
commit: function(cwd, message) {
var args = ["commit","-m",message];
return runCommand(gitCommand,args,cwd);
return runGitCommand(args,cwd);
},
getFileDiff(cwd,file,type) {
var args = ["diff"];
@ -247,14 +439,53 @@ module.exports = {
args.push("--cached");
}
args.push(file);
return runCommand(gitCommand,args,cwd);
return runGitCommand(args,cwd);
},
fetch: function(cwd) {
return runGitCommand(["fetch"],cwd);
},
getCommits: function(cwd,options) {
var args = ["log", "--format=sha: %H%nauthor: %an%ndate: %ct%nsubject: %s","-n 10"];
return runCommand(gitCommand,args,cwd).then(parseLog);
var args = ["log", "--format=sha: %H%nparents: %p%nrefs: %D%nauthor: %an%ndate: %ct%nsubject: %s%n-----"];
var limit = parseInt(options.limit) || 20;
args.push("-n "+limit);
var before = options.before;
if (before) {
args.push(before);
}
var commands = [
runGitCommand(['rev-list', 'HEAD', '--count'],cwd),
runGitCommand(args,cwd).then(parseLog)
];
return when.all(commands).then(function(results) {
var result = results[0];
result.count = results[1].length;
result.before = before;
result.commits = results[1];
return {
count: results[1].length,
commits: results[1],
before: before,
total: parseInt(results[0])
};
})
},
getCommit: function(cwd,sha) {
var args = ["show",sha];
return runCommand(gitCommand,args,cwd);
}
return runGitCommand(args,cwd);
},
abortMerge: function(cwd) {
return runGitCommand(['merge','--abort'],cwd);
},
getRemotes: getRemotes,
getBranches: getBranches,
getBranchInfo: getBranchInfo,
checkoutBranch: function(cwd, branchName, isCreate) {
var args = ['checkout'];
if (isCreate) {
args.push('-b');
}
args.push(branchName);
return runGitCommand(args,cwd);
},
getBranchStatus: getBranchStatus
}

View File

@ -0,0 +1 @@
"$NODE_RED_GIT_NODE_PATH" "$NODE_RED_GIT_ASKPASS_PATH" "$NODE_RED_GIT_SOCK_PATH" $@

View File

@ -36,6 +36,7 @@ function init(_settings, _runtime) {
settings = _settings;
runtime = _runtime;
log = runtime.log;
gitTools.init(_settings, _runtime);
Projects.init(settings,runtime);
@ -121,7 +122,7 @@ function getProject(name) {
function checkActiveProject(project) {
if (!activeProject || activeProject.name !== project) {
//TODO: throw better err
throw new Error("Cannot operate on inactive project");
throw new Error("Cannot operate on inactive project wanted:"+project+" current:"+(activeProject&&activeProject.name));
}
}
function getFiles(project) {
@ -157,7 +158,38 @@ function getFile(project,filePath,sha) {
checkActiveProject(project);
return activeProject.getFile(filePath,sha);
}
function push(project,remoteBranchName,setRemote) {
checkActiveProject(project);
return activeProject.push(remoteBranchName,setRemote);
}
function pull(project,remoteBranchName,setRemote) {
checkActiveProject(project);
return activeProject.pull(remoteBranchName,setRemote).then(reloadActiveProject);
}
function getStatus(project) {
checkActiveProject(project);
return activeProject.status();
}
function resolveMerge(project,file,resolution) {
checkActiveProject(project);
return activeProject.resolveMerge(file,resolution);
}
function abortMerge(project) {
checkActiveProject(project);
return activeProject.abortMerge().then(reloadActiveProject);
}
function getBranches(project,remote) {
checkActiveProject(project);
return activeProject.getBranches(remote);
}
function setBranch(project,branchName,isCreate) {
checkActiveProject(project);
return activeProject.setBranch(branchName,isCreate).then(reloadActiveProject);
}
function getBranchStatus(project,branchName) {
checkActiveProject(project);
return activeProject.getBranchStatus(branchName);
}
function getActiveProject() {
return activeProject;
}
@ -347,6 +379,14 @@ module.exports = {
getFileDiff: getFileDiff,
getCommits: getCommits,
getCommit: getCommit,
push: push,
pull: pull,
getStatus:getStatus,
resolveMerge: resolveMerge,
abortMerge: abortMerge,
getBranches: getBranches,
setBranch: setBranch,
getBranchStatus:getBranchStatus,
getFlows: getFlows,
saveFlows: saveFlows,