1
0
mirror of https://github.com/node-red/node-red.git synced 2023-10-10 13:36:53 +02:00

Use flow-diff to resolve merge conflicts

This commit is contained in:
Nick O'Leary 2018-02-13 23:09:51 +00:00
parent 9066cedc29
commit 6191a49ed3
No known key found for this signature in database
GPG Key ID: 4F2157149161A6C9
9 changed files with 422 additions and 250 deletions

View File

@ -131,11 +131,12 @@
var project = RED.projects.getActiveProject(); var project = RED.projects.getActiveProject();
var message = { var message = {
"change-branch":"Change to local branch '"+project.git.branches.local+"'", "change-branch":"Change to local branch '"+project.git.branches.local+"'",
"abort-merge":"Git merge aborted", "merge-abort":"Git merge aborted",
"loaded":"Project '"+msg.project+"' loaded", "loaded":"Project '"+msg.project+"' loaded",
"updated":"Project '"+msg.project+"' updated", "updated":"Project '"+msg.project+"' updated",
"pull":"Project '"+msg.project+"' reloaded", "pull":"Project '"+msg.project+"' reloaded",
"revert": "Project '"+msg.project+"' reloaded" "revert": "Project '"+msg.project+"' reloaded",
"merge-complete":"Git merge completed"
}[msg.action]; }[msg.action];
RED.notify("<p>"+message+"</p>"); RED.notify("<p>"+message+"</p>");
RED.sidebar.info.refresh() RED.sidebar.info.refresh()
@ -219,6 +220,20 @@
} }
] ]
} }
} else if (msg.error === "git_merge_conflict") {
RED.nodes.clear();
RED.sidebar.versionControl.refresh(true);
if (RED.user.hasPermission("projects.write")) {
options.buttons = [
{
text: "Show merge conflicts",
click: function() {
persistentNotifications[notificationId].hideNotification();
RED.sidebar.versionControl.showLocalChanges();
}
}
]
}
} }
} }
if (!persistentNotifications.hasOwnProperty(notificationId)) { if (!persistentNotifications.hasOwnProperty(notificationId)) {

View File

@ -21,17 +21,18 @@ RED.diff = (function() {
RED.keyboard.add("*","ctrl-shift-f 3","core:show-test-flow-diff-3"); RED.keyboard.add("*","ctrl-shift-f 3","core:show-test-flow-diff-3");
} }
function createDiffTable(container) { function createDiffTable(container,CurrentDiff) {
var diffList = $('<ol class="node-dialog-view-diff-diff"></ol>').appendTo(container); var diffList = $('<ol class="node-dialog-view-diff-diff"></ol>').appendTo(container);
diffList.editableList({ diffList.editableList({
addButton: false, addButton: false,
height: "auto",
scrollOnAdd: false, scrollOnAdd: false,
addItem: function(container,i,object) { addItem: function(container,i,object) {
var localDiff = object.diff; var localDiff = object.diff;
var remoteDiff = object.remoteDiff; var remoteDiff = object.remoteDiff;
var tab = object.tab.n; var tab = object.tab.n;
var def = object.def; var def = object.def;
var conflicts = currentDiff.conflicts; var conflicts = CurrentDiff.conflicts;
var tabDiv = $('<div>',{class:"node-diff-tab"}).appendTo(container); var tabDiv = $('<div>',{class:"node-diff-tab"}).appendTo(container);
tabDiv.addClass('collapsed'); tabDiv.addClass('collapsed');
@ -150,10 +151,10 @@ RED.diff = (function() {
} }
div.addClass("node-diff-node-entry-conflict"); div.addClass("node-diff-node-entry-conflict");
} else { } else {
selectState = currentDiff.resolutions[tab.id]; selectState = CurrentDiff.resolutions[tab.id];
} }
// Tab properties row // Tab properties row
createNodeConflictRadioBoxes(tab,div,localNodeDiv,remoteNodeDiv,true,!conflicts[tab.id],selectState); createNodeConflictRadioBoxes(tab,div,localNodeDiv,remoteNodeDiv,true,!conflicts[tab.id],selectState,CurrentDiff);
} }
} }
// var stats = $('<span>',{class:"node-diff-tab-stats"}).appendTo(titleRow); // var stats = $('<span>',{class:"node-diff-tab-stats"}).appendTo(titleRow);
@ -162,14 +163,14 @@ RED.diff = (function() {
var seen = {}; var seen = {};
object.tab.nodes.forEach(function(node) { object.tab.nodes.forEach(function(node) {
seen[node.id] = true; seen[node.id] = true;
createNodeDiffRow(node,flowStats).appendTo(nodesDiv) createNodeDiffRow(node,flowStats,CurrentDiff).appendTo(nodesDiv)
}); });
if (object.newTab) { if (object.newTab) {
localNodeCount = object.newTab.nodes.length; localNodeCount = object.newTab.nodes.length;
object.newTab.nodes.forEach(function(node) { object.newTab.nodes.forEach(function(node) {
if (!seen[node.id]) { if (!seen[node.id]) {
seen[node.id] = true; seen[node.id] = true;
createNodeDiffRow(node,flowStats).appendTo(nodesDiv) createNodeDiffRow(node,flowStats,CurrentDiff).appendTo(nodesDiv)
} }
}); });
} }
@ -177,7 +178,7 @@ RED.diff = (function() {
remoteNodeCount = object.remoteTab.nodes.length; remoteNodeCount = object.remoteTab.nodes.length;
object.remoteTab.nodes.forEach(function(node) { object.remoteTab.nodes.forEach(function(node) {
if (!seen[node.id]) { if (!seen[node.id]) {
createNodeDiffRow(node,flowStats).appendTo(nodesDiv) createNodeDiffRow(node,flowStats,CurrentDiff).appendTo(nodesDiv)
} }
}); });
} }
@ -269,12 +270,12 @@ RED.diff = (function() {
if (flowStats.conflicts > 0) { if (flowStats.conflicts > 0) {
titleRow.addClass("node-diff-node-entry-conflict"); titleRow.addClass("node-diff-node-entry-conflict");
} else { } else {
selectState = currentDiff.resolutions[tab.id]; selectState = CurrentDiff.resolutions[tab.id];
} }
if (tab.id) { if (tab.id) {
var hide = !(flowStats.conflicts > 0 &&(localDiff.deleted[tab.id] || remoteDiff.deleted[tab.id])); var hide = !(flowStats.conflicts > 0 &&(localDiff.deleted[tab.id] || remoteDiff.deleted[tab.id]));
// Tab parent row // Tab parent row
createNodeConflictRadioBoxes(tab,titleRow,localCell,remoteCell, false, hide, selectState); createNodeConflictRadioBoxes(tab,titleRow,localCell,remoteCell, false, hide, selectState, CurrentDiff);
} }
} }
@ -291,11 +292,8 @@ RED.diff = (function() {
var diffHeaders = $('<div class="node-dialog-view-diff-headers"></div>').appendTo(diffPanel); var diffHeaders = $('<div class="node-dialog-view-diff-headers"></div>').appendTo(diffPanel);
if (options.mode === "merge") { if (options.mode === "merge") {
diffPanel.addClass("node-dialog-view-diff-panel-merge"); diffPanel.addClass("node-dialog-view-diff-panel-merge");
var toolbar = $('<div class="node-diff-toolbar">'+
'<span><span id="node-diff-toolbar-resolved-conflicts"></span></span> '+
'</div>').prependTo(diffPanel);
} }
var diffList = createDiffTable(diffPanel); var diffList = createDiffTable(diffPanel, diff);
var localDiff = diff.localDiff; var localDiff = diff.localDiff;
var remoteDiff = diff.remoteDiff; var remoteDiff = diff.remoteDiff;
@ -512,10 +510,10 @@ RED.diff = (function() {
$('<span>',{class:"node-diff-node-label"}).html(nodeLabel).appendTo(contentDiv); $('<span>',{class:"node-diff-node-label"}).html(nodeLabel).appendTo(contentDiv);
return nodeTitleDiv; return nodeTitleDiv;
} }
function createNodeDiffRow(node,stats) { function createNodeDiffRow(node,stats,CurrentDiff) {
var localDiff = currentDiff.localDiff; var localDiff = CurrentDiff.localDiff;
var remoteDiff = currentDiff.remoteDiff; var remoteDiff = CurrentDiff.remoteDiff;
var conflicted = currentDiff.conflicts[node.id]; var conflicted = CurrentDiff.conflicts[node.id];
var hasChanges = false; // exists in original and local/remote but with changes var hasChanges = false; // exists in original and local/remote but with changes
var unChanged = true; // existing in original,local,remote unchanged var unChanged = true; // existing in original,local,remote unchanged
@ -703,10 +701,10 @@ RED.diff = (function() {
} }
div.addClass("node-diff-node-entry-conflict"); div.addClass("node-diff-node-entry-conflict");
} else { } else {
selectState = currentDiff.resolutions[node.id]; selectState = CurrentDiff.resolutions[node.id];
} }
// Node row // Node row
createNodeConflictRadioBoxes(node,div,localNodeDiv,remoteNodeDiv,false,!conflicted,selectState); createNodeConflictRadioBoxes(node,div,localNodeDiv,remoteNodeDiv,false,!conflicted,selectState,CurrentDiff);
row.click(function(evt) { row.click(function(evt) {
$(this).parent().toggleClass('collapsed'); $(this).parent().toggleClass('collapsed');
}); });
@ -982,7 +980,7 @@ RED.diff = (function() {
}); });
return nodePropertiesDiv; return nodePropertiesDiv;
} }
function createNodeConflictRadioBoxes(node,row,localDiv,remoteDiv,propertiesTable,hide,state) { function createNodeConflictRadioBoxes(node,row,localDiv,remoteDiv,propertiesTable,hide,state,diff) {
var safeNodeId = "node-diff-selectbox-"+node.id.replace(/\./g,'-')+(propertiesTable?"-props":""); var safeNodeId = "node-diff-selectbox-"+node.id.replace(/\./g,'-')+(propertiesTable?"-props":"");
var className = ""; var className = "";
if (node.z||propertiesTable) { if (node.z||propertiesTable) {
@ -1019,7 +1017,7 @@ RED.diff = (function() {
row.addClass("node-diff-select-remote"); row.addClass("node-diff-select-remote");
row.removeClass("node-diff-select-local"); row.removeClass("node-diff-select-local");
} }
refreshConflictHeader(); refreshConflictHeader(diff);
} }
var localSelectDiv = $('<label>',{class:"node-diff-selectbox",for:safeNodeId+"-local"}).click(function(e) { e.stopPropagation();}).appendTo(localDiv); var localSelectDiv = $('<label>',{class:"node-diff-selectbox",for:safeNodeId+"-local"}).click(function(e) { e.stopPropagation();}).appendTo(localDiv);
@ -1037,7 +1035,7 @@ RED.diff = (function() {
} }
} }
function refreshConflictHeader() { function refreshConflictHeader(currentDiff) {
var resolutionCount = 0; var resolutionCount = 0;
$(".node-diff-selectbox>input:checked").each(function() { $(".node-diff-selectbox>input:checked").each(function() {
if (currentDiff.conflicts[$(this).data('node-id')]) { if (currentDiff.conflicts[$(this).data('node-id')]) {
@ -1053,6 +1051,7 @@ RED.diff = (function() {
} }
if (conflictCount === resolutionCount) { if (conflictCount === resolutionCount) {
$("#node-diff-view-diff-merge").removeClass('disabled'); $("#node-diff-view-diff-merge").removeClass('disabled');
$("#node-diff-view-resolve-diff").removeClass('disabled');
} }
} }
function getRemoteDiff(callback) { function getRemoteDiff(callback) {
@ -1231,7 +1230,7 @@ RED.diff = (function() {
var localDiff = diff.localDiff; var localDiff = diff.localDiff;
var remoteDiff = diff.remoteDiff; var remoteDiff = diff.remoteDiff;
var conflicts = diff.conflicts; var conflicts = diff.conflicts;
currentDiff = diff; // currentDiff = diff;
var trayOptions = { var trayOptions = {
title: options.title||"Review Changes", //TODO: nls title: options.title||"Review Changes", //TODO: nls
@ -1250,7 +1249,11 @@ RED.diff = (function() {
}, },
open: function(tray) { open: function(tray) {
var trayBody = tray.find('.editor-tray-body'); var trayBody = tray.find('.editor-tray-body');
var diffTable = buildDiffPanel(trayBody,diff,options); var toolbar = $('<div class="node-diff-toolbar">'+
'<span><span id="node-diff-toolbar-resolved-conflicts"></span></span> '+
'</div>').prependTo(trayBody);
var diffContainer = $('<div class="node-diff-container"></div>').appendTo(trayBody);
var diffTable = buildDiffPanel(diffContainer,diff,options);
diffTable.list.hide(); diffTable.list.hide();
if (remoteDiff) { if (remoteDiff) {
$("#node-diff-view-diff-merge").show(); $("#node-diff-view-diff-merge").show();
@ -1262,7 +1265,7 @@ RED.diff = (function() {
} else { } else {
$("#node-diff-view-diff-merge").hide(); $("#node-diff-view-diff-merge").hide();
} }
refreshConflictHeader(); refreshConflictHeader(diff);
// console.log("--------------"); // console.log("--------------");
// console.log(localDiff); // console.log(localDiff);
// console.log(remoteDiff); // console.log(remoteDiff);
@ -1290,8 +1293,8 @@ RED.diff = (function() {
class: "primary disabled", class: "primary disabled",
click: function() { click: function() {
if (!$("#node-diff-view-diff-merge").hasClass('disabled')) { if (!$("#node-diff-view-diff-merge").hasClass('disabled')) {
refreshConflictHeader(); refreshConflictHeader(diff);
mergeDiff(currentDiff); mergeDiff(diff);
RED.tray.close(); RED.tray.close();
} }
} }
@ -1302,7 +1305,7 @@ RED.diff = (function() {
RED.tray.show(trayOptions); RED.tray.show(trayOptions);
} }
function mergeDiff(diff) { function applyDiff(diff) {
var currentConfig = diff.localDiff.currentConfig; var currentConfig = diff.localDiff.currentConfig;
var localDiff = diff.localDiff; var localDiff = diff.localDiff;
var remoteDiff = diff.remoteDiff; var remoteDiff = diff.remoteDiff;
@ -1356,6 +1359,20 @@ RED.diff = (function() {
} }
} }
} }
return {
config: newConfig,
nodeChangedStates: nodeChangedStates,
localChangedStates: localChangedStates
}
}
function mergeDiff(diff) {
var appliedDiff = applyDiff(diff);
var newConfig = appliedDiff.config;
var nodeChangedStates = appliedDiff.nodeChangedStates;
var localChangedStates = appliedDiff.localChangedStates;
var historyEvent = { var historyEvent = {
t:"replace", t:"replace",
config: RED.nodes.createCompleteNodeSet(), config: RED.nodes.createCompleteNodeSet(),
@ -1417,7 +1434,7 @@ RED.diff = (function() {
var trayBody = tray.find('.editor-tray-body'); var trayBody = tray.find('.editor-tray-body');
var diffPanel = $('<div class="node-text-diff"></div>').appendTo(trayBody); var diffPanel = $('<div class="node-text-diff"></div>').appendTo(trayBody);
var codeTable = $("<table>").appendTo(diffPanel); var codeTable = $("<table>",{class:"node-text-diff-content"}).appendTo(diffPanel);
$('<colgroup><col width="50"><col width="50%"><col width="50"><col width="50%"></colgroup>').appendTo(codeTable); $('<colgroup><col width="50"><col width="50%"><col width="50"><col width="50%"></colgroup>').appendTo(codeTable);
var codeBody = $('<tbody>').appendTo(codeTable); var codeBody = $('<tbody>').appendTo(codeTable);
var diffSummary = diffText(textA||"",textB||""); var diffSummary = diffText(textA||"",textB||"");
@ -1681,7 +1698,7 @@ RED.diff = (function() {
files.forEach(function(file) { files.forEach(function(file) {
var hunks = file.hunks; var hunks = file.hunks;
var isBinary = file.binary; var isBinary = file.binary;
var codeTable = $("<table>").appendTo(diffPanel); var codeTable = $("<table>",{class:"node-text-diff-content"}).appendTo(diffPanel);
$('<colgroup><col width="50"><col width="50"><col width="100%"></colgroup>').appendTo(codeTable); $('<colgroup><col width="50"><col width="50"><col width="100%"></colgroup>').appendTo(codeTable);
var codeBody = $('<tbody>').appendTo(codeTable); var codeBody = $('<tbody>').appendTo(codeTable);
@ -1700,49 +1717,145 @@ RED.diff = (function() {
var unresolvedConflicts = 0; var unresolvedConflicts = 0;
var resolvedConflicts = 0; var resolvedConflicts = 0;
var conflictResolutions = {}; var conflictResolutions = {};
if (commitOptions.project.files && commitOptions.project.files.flow === file.file) {
if (commitOptions.unmerged) {
$('<span style="float: right;"><span id="node-diff-toolbar-resolved-conflicts"></span></span>').appendTo(content);
}
// 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();
// e.stopPropagation();
// var projectName = commitOptions.project.name;
// var filename = commitOptions.project.files.flow;
// var commonVersionUrl = "/projects/"+projectName+"/files/"+commitOptions.commonRev+"/"+filename;
// var oldVersionUrl = "/projects/"+projectName+"/files/"+commitOptions.oldRev+"/"+filename;
// var newVersionUrl = "/projects/"+projectName+"/files/"+commitOptions.newRev+"/"+filename;
// var promises = [];
// if (commitOptions.commonRev) {
// var commonVersionUrl = "/projects/"+projectName+"/files/"+commitOptions.commonRev+"/"+filename;
// promises.push($.getJSON(commonVersionUrl));
// } else {
// promises.push($.when(null));
// }
// promises.push($.getJSON(oldVersionUrl));
// promises.push($.getJSON(newVersionUrl));
// $.when.apply($,promises).done(function(commonVersion, oldVersion,newVersion) {
// var commonFlow;
// var oldFlow;
// var newFlow;
// if (commonVersion) {
// try {
// commonFlow = JSON.parse(commonVersion[0].content||"[]");
// } catch(err) {
// console.log("Common Version doesn't contain valid JSON:",commonVersionUrl);
// console.log(err);
// return;
// }
// }
// try {
// oldFlow = JSON.parse(oldVersion[0].content||"[]");
// } catch(err) {
// console.log("Old Version doesn't contain valid JSON:",oldVersionUrl);
// console.log(err);
// return;
// }
// if (!commonFlow) {
// commonFlow = oldFlow;
// }
// try {
// newFlow = JSON.parse(newVersion[0].content||"[]");
// } catch(err) {
// console.log("New Version doesn't contain valid JSON:",newFlow);
// console.log(err);
// return;
// }
// var localDiff = generateDiff(commonFlow,oldFlow);
// var remoteDiff = generateDiff(commonFlow,newFlow);
// var diff = resolveDiffs(localDiff,remoteDiff);
// showDiff(diff,{
// title: filename,
// mode: commitOptions.commonRev?'merge':'view',
// oldRevTitle: commitOptions.oldRevTitle,
// newRevTitle: commitOptions.newRevTitle
// });
// // var flowDiffRow = $("<tr>").insertAfter(diffRow);
// // var content = $('<td colspan="3"></td>').appendTo(flowDiffRow);
// // currentDiff = diff;
// // var diffTable = buildDiffPanel(content,diff,{mode:"view"}).finish();
// });
// })
var diffRow = $('<tr class="node-text-diff-header">').appendTo(codeBody);
var flowDiffContent = $('<td class="flow-diff" colspan="3"></td>').appendTo(diffRow);
if (!commitOptions.unmerged && commitOptions.project.files && commitOptions.project.files.flow === file.file) { var projectName = commitOptions.project.name;
var tools = $('<span style="float: right;" class="button-group"></span>').appendTo(content); var filename = commitOptions.project.files.flow;
$('<button class="editor-button editor-button-small">show flow diff</button>').appendTo(tools).click(function(e) { var commonVersionUrl = "/projects/"+projectName+"/files/"+commitOptions.commonRev+"/"+filename;
e.preventDefault(); var oldVersionUrl = "/projects/"+projectName+"/files/"+commitOptions.oldRev+"/"+filename;
e.stopPropagation(); var newVersionUrl = "/projects/"+projectName+"/files/"+commitOptions.newRev+"/"+filename;
var projectName = commitOptions.project.name; var promises = [$.Deferred(),$.Deferred(),$.Deferred()];
var filename = commitOptions.project.files.flow; if (commitOptions.commonRev) {
var oldVersionUrl = "/projects/"+projectName+"/files/"+commitOptions.oldRev+"/"+filename; var commonVersionUrl = "/projects/"+projectName+"/files/"+commitOptions.commonRev+"/"+filename;
var newVersionUrl = "/projects/"+projectName+"/files/"+commitOptions.newRev+"/"+filename; $.ajax({dataType: "json",url: commonVersionUrl}).then(function(data) { promises[0].resolve(data); }).fail(function() { promises[0].resolve(null);})
$.when($.getJSON(oldVersionUrl),$.getJSON(newVersionUrl)).done(function(oldVersion,newVersion) { } else {
var oldFlow; promises[0].resolve(null);
var newFlow; }
$.ajax({dataType: "json",url: oldVersionUrl}).then(function(data) { promises[1].resolve(data); }).fail(function() { promises[1].resolve({content:"[]"});})
$.ajax({dataType: "json",url: newVersionUrl}).then(function(data) { promises[2].resolve(data); }).fail(function() { promises[2].resolve({content:"[]"});})
$.when.apply($,promises).always(function(commonVersion, oldVersion,newVersion) {
var commonFlow;
var oldFlow;
var newFlow;
if (commonVersion) {
try { try {
oldFlow = JSON.parse(oldVersion[0].content||"[]"); commonFlow = JSON.parse(commonVersion.content||"[]");
} catch(err) { } catch(err) {
console.log("Old Version doesn't contain valid JSON:",oldVersionUrl); console.log("Common Version doesn't contain valid JSON:",commonVersionUrl);
console.log(err); console.log(err);
return; return;
} }
try { }
newFlow = JSON.parse(newVersion[0].content||"[]"); try {
} catch(err) { oldFlow = JSON.parse(oldVersion.content||"[]");
console.log("New Version doesn't contain valid JSON:",newFlow); } catch(err) {
console.log(err); console.log("Old Version doesn't contain valid JSON:",oldVersionUrl);
return; console.log(err);
} return;
var localDiff = generateDiff(oldFlow,oldFlow); }
var remoteDiff = generateDiff(oldFlow,newFlow); if (!commonFlow) {
var diff = resolveDiffs(localDiff,remoteDiff); commonFlow = oldFlow;
showDiff(diff,{ }
title: filename, try {
mode: 'view', newFlow = JSON.parse(newVersion.content||"[]");
oldRevTitle: commitOptions.oldRevTitle, } catch(err) {
newRevTitle: commitOptions.newRevTitle console.log("New Version doesn't contain valid JSON:",newFlow);
}); console.log(err);
// var flowDiffRow = $("<tr>").insertAfter(diffRow); return;
// var content = $('<td colspan="3"></td>').appendTo(flowDiffRow); }
// currentDiff = diff; var localDiff = generateDiff(commonFlow,oldFlow);
// var diffTable = buildDiffPanel(content,diff,{mode:"view"}).finish(); var remoteDiff = generateDiff(commonFlow,newFlow);
commitOptions.currentDiff = resolveDiffs(localDiff,remoteDiff);
var diffTable = buildDiffPanel(flowDiffContent,commitOptions.currentDiff,{
title: filename,
mode: commitOptions.commonRev?'merge':'view',
oldRevTitle: commitOptions.oldRevTitle,
newRevTitle: commitOptions.newRevTitle
}); });
}) diffTable.list.hide();
} refreshConflictHeader(commitOptions.currentDiff);
setTimeout(function() {
diffTable.finish();
diffTable.list.show();
},300);
// var flowDiffRow = $("<tr>").insertAfter(diffRow);
// var content = $('<td colspan="3"></td>').appendTo(flowDiffRow);
// currentDiff = diff;
// var diffTable = buildDiffPanel(content,diff,{mode:"view"}).finish();
});
} else
if (isBinary) { if (isBinary) {
var diffBinaryRow = $('<tr class="node-text-diff-header">').appendTo(codeBody); var diffBinaryRow = $('<tr class="node-text-diff-header">').appendTo(codeBody);
@ -1750,6 +1863,9 @@ RED.diff = (function() {
$('<span></span>').text("Cannot show binary file contents").appendTo(binaryContent); $('<span></span>').text("Cannot show binary file contents").appendTo(binaryContent);
} else { } else {
if (commitOptions.unmerged) {
conflictHeader = $('<span style="float: right;"><span>'+resolvedConflicts+'</span> of <span>'+unresolvedConflicts+'</span> conflicts resolved</span>').appendTo(content);
}
hunks.forEach(function(hunk) { hunks.forEach(function(hunk) {
var diffRow = $('<tr class="node-text-diff-header">').appendTo(codeBody); var diffRow = $('<tr class="node-text-diff-header">').appendTo(codeBody);
var content = $('<td colspan="3"></td>').appendTo(diffRow); var content = $('<td colspan="3"></td>').appendTo(diffRow);
@ -1886,9 +2002,6 @@ RED.diff = (function() {
}); });
}); });
} }
if (commitOptions.unmerged) {
conflictHeader = $('<span style="float: right;"><span>'+resolvedConflicts+'</span> of <span>'+unresolvedConflicts+'</span> conflicts resolved</span>').appendTo(content);
}
}); });
return diffPanel; return diffPanel;
} }
@ -1914,7 +2027,7 @@ RED.diff = (function() {
var trayBody = tray.find('.editor-tray-body'); var trayBody = tray.find('.editor-tray-body');
var diffPanel = $('<div class="node-text-diff"></div>').appendTo(trayBody); var diffPanel = $('<div class="node-text-diff"></div>').appendTo(trayBody);
var codeTable = $("<table>").appendTo(diffPanel); var codeTable = $("<table>",{class:"node-text-diff-content"}).appendTo(diffPanel);
$('<colgroup><col width="50"><col width="50"><col width="100%"></colgroup>').appendTo(codeTable); $('<colgroup><col width="50"><col width="50"><col width="100%"></colgroup>').appendTo(codeTable);
var codeBody = $('<tbody>').appendTo(codeTable); var codeBody = $('<tbody>').appendTo(codeTable);
@ -1957,7 +2070,6 @@ RED.diff = (function() {
} }
} }
var trayOptions = { var trayOptions = {
title: title||"Compare Changes", //TODO: nls title: title||"Compare Changes", //TODO: nls
width: Infinity, width: Infinity,
@ -1996,6 +2108,15 @@ RED.diff = (function() {
class: "primary disabled", class: "primary disabled",
click: function() { click: function() {
if (!$("#node-diff-view-resolve-diff").hasClass('disabled')) { if (!$("#node-diff-view-resolve-diff").hasClass('disabled')) {
if (options.currentDiff) {
// This is a flow file. Need to apply the diff
// and generate the new flow.
var result = applyDiff(options.currentDiff);
currentResolution = {
resolutions:{}
};
currentResolution.resolutions[options.project.files.flow] = JSON.stringify(result.config,"",4);
}
if (options.onresolve) { if (options.onresolve) {
options.onresolve(currentResolution); options.onresolve(currentResolution);
} }

View File

@ -30,7 +30,6 @@ RED.sidebar.versionControl = (function() {
var unmergedContent; var unmergedContent;
var unmergedChangesList; var unmergedChangesList;
var commitButton; var commitButton;
var mergeConflictNotification;
var localChanges; var localChanges;
var localCommitList; var localCommitList;
@ -76,6 +75,11 @@ RED.sidebar.versionControl = (function() {
options.oldRev = "@"; options.oldRev = "@";
options.newRev = ":0"; options.newRev = ":0";
} else { } else {
options.oldRevTitle = "Local";
options.newRevTitle = "Remote";
options.commonRev = ":1";
options.oldRev = ":2";
options.newRev = ":3";
options.onresolve = function(resolution) { options.onresolve = function(resolution) {
utils.sendRequest({ utils.sendRequest({
url: "projects/"+activeProject.name+"/resolve/"+encodeURIComponent(entry.file), url: "projects/"+activeProject.name+"/resolve/"+encodeURIComponent(entry.file),
@ -358,6 +362,7 @@ RED.sidebar.versionControl = (function() {
evt.stopPropagation(); evt.stopPropagation();
var spinner = utils.addSpinnerOverlay(unmergedContent); var spinner = utils.addSpinnerOverlay(unmergedContent);
var activeProject = RED.projects.getActiveProject(); var activeProject = RED.projects.getActiveProject();
RED.deploy.setDeployInflight(true);
utils.sendRequest({ utils.sendRequest({
url: "projects/"+activeProject.name+"/merge", url: "projects/"+activeProject.name+"/merge",
type: "DELETE", type: "DELETE",
@ -375,6 +380,10 @@ RED.sidebar.versionControl = (function() {
} }
}, },
} }
}).always(function() {
setTimeout(function() {
RED.deploy.setDeployInflight(false);
},500);
}); });
}); });
unmergedChangesList = $("<ol>",{style:"position: absolute; top: 30px; bottom: 0; right:0; left:0;"}).appendTo(unmergedContent); unmergedChangesList = $("<ol>",{style:"position: absolute; top: 30px; bottom: 0; right:0; left:0;"}).appendTo(unmergedContent);
@ -496,6 +505,7 @@ RED.sidebar.versionControl = (function() {
evt.preventDefault(); evt.preventDefault();
var spinner = utils.addSpinnerOverlay(submitCommitButton).addClass('projects-dialog-spinner-sidebar'); var spinner = utils.addSpinnerOverlay(submitCommitButton).addClass('projects-dialog-spinner-sidebar');
var activeProject = RED.projects.getActiveProject(); var activeProject = RED.projects.getActiveProject();
RED.deploy.setDeployInflight(true);
utils.sendRequest({ utils.sendRequest({
url: "projects/"+activeProject.name+"/commit", url: "projects/"+activeProject.name+"/commit",
type: "POST", type: "POST",
@ -516,10 +526,11 @@ RED.sidebar.versionControl = (function() {
} }
},{ },{
message:commitMessage.val() message:commitMessage.val()
}); }).always(function() {
setTimeout(function() {
RED.deploy.setDeployInflight(false);
},500);
})
}) })
@ -1108,31 +1119,9 @@ RED.sidebar.versionControl = (function() {
} }
isMerging = !!result.merging; isMerging = !!result.merging;
if (isMerging) { if (isMerging) {
if (!mergeConflictNotification) {
var text = "<p>Automatic merging of changes failed.</p><p>Fix the unmerged conflicts then commit the results.</p>";
var options = {
type: 'error',
fixed: true,
id: 'merge-conflict',
buttons: [
{
text: "Show merge conflicts",
click: function() {
mergeConflictNotification.hideNotification();
RED.sidebar.versionControl.showLocalChanges();
}
}
]
}
mergeConflictNotification = RED.notify(text,options);
}
sidebarContent.addClass("sidebar-version-control-merging"); sidebarContent.addClass("sidebar-version-control-merging");
unmergedContent.show(); unmergedContent.show();
} else { } else {
if (mergeConflictNotification) {
mergeConflictNotification.close();
mergeConflictNotification = null;
}
sidebarContent.removeClass("sidebar-version-control-merging"); sidebarContent.removeClass("sidebar-version-control-merging");
unmergedContent.hide(); unmergedContent.hide();
} }
@ -1307,6 +1296,8 @@ RED.sidebar.versionControl = (function() {
} }
refreshInProgress = false; refreshInProgress = false;
$('.sidebar-version-control-shade').hide(); $('.sidebar-version-control-shade').hide();
}).fail(function() {
refreshInProgress = false;
}); });
} else { } else {
$('.sidebar-version-control-shade').show(); $('.sidebar-version-control-shade').show();

View File

@ -16,17 +16,16 @@
.node-dialog-view-diff-panel { .node-dialog-view-diff-panel {
padding: 5px;
padding-top: 30px;
position: relative;
.red-ui-editableList-container { .red-ui-editableList-container {
border-radius:1px; border-radius:1px;
padding:0; padding:0;
background: #f9f9f9; background: #f9f9f9;
} }
.node-dialog-view-diff-diff { .node-dialog-view-diff-diff {
position: absolute;
top:30px;
bottom:10px;
left:10px;
right:10px;
li { li {
background: #f9f9f9; background: #f9f9f9;
padding: 0px; padding: 0px;
@ -38,21 +37,20 @@
padding: 5px; padding: 5px;
// padding-bottom: 5px; // padding-bottom: 5px;
} }
&.node-dialog-view-diff-panel-merge { }
.node-dialog-view-diff-diff { .node-diff-container {
top: 80px position: absolute;
} top: 40px;
.node-dialog-view-diff-headers { right:0;
top: 55px; bottom: 0;
} left: 0;
overflow-y: scroll;
}
} }
.node-dialog-view-diff-headers { .node-dialog-view-diff-headers {
position: absolute; position: absolute;
left:237px; left:232px;
right:18px; right:12px;
top: 5px; top: 5px;
height: 25px; height: 25px;
div { div {
@ -76,11 +74,6 @@
} }
.node-diff-toolbar { .node-diff-toolbar {
position:absolute;
top:0;
left:0;
right:0;
height: 43px;
box-sizing: border-box; box-sizing: border-box;
color: #666; color: #666;
text-align: right; text-align: right;
@ -555,129 +548,129 @@ ul.node-dialog-configm-deploy-list {
.node-text-diff { .node-text-diff {
height: 100%; height: 100%;
overflow-y:auto; overflow-y:auto;
table {
table.node-text-diff-content {
margin: 10px; margin: 10px;
border: 1px solid $secondary-border-color; border: 1px solid $secondary-border-color;
border-radius: 3px; border-radius: 3px;
table-layout: fixed; table-layout: fixed;
width: calc(100% - 20px); width: calc(100% - 20px);
} td {
td { vertical-align: top;
vertical-align: top; word-wrap: break-word;
word-wrap: break-word;
}
td.lineno {
font-family: monospace;
text-align: right;
color: #aaa;
background: #f6f6f6;
padding: 1px 5px;
}
td.lineno:nth-child(3) {
border-left: 1px solid $secondary-border-color;
}
td.linetext {
font-family: monospace;
white-space: pre-wrap;
padding: 1px 5px;
span.prefix {
width: 30px;
display: inline-block;
text-align: center;
color: #999;
} }
} td.lineno {
td.blank {
background: #f6f6f6;
}
td.added {
background: #eefaee;
}
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;
}
tr.unchanged {
background: #fefefe;
}
tr.start-block {
border-top: 1px solid #f0f0f0;
}
tr.end-block {
border-bottom: 1px solid #f0f0f0;
}
tr.node-text-diff-file-header td {
.filename {
font-family: monospace; font-family: monospace;
text-align: right;
color: #aaa;
background: #f6f6f6;
padding: 1px 5px;
} }
background: #f3f3f3; td.lineno:nth-child(3) {
padding: 5px 10px 5px 0; border-left: 1px solid $secondary-border-color;
color: #333;
cursor: pointer;
i.node-diff-chevron {
width: 30px;
} }
} td.linetext {
tr.node-text-diff-file-header.collapsed { font-family: monospace;
td i.node-diff-chevron { white-space: pre-wrap;
transform: rotate(-90deg); padding: 1px 5px;
span.prefix {
width: 30px;
display: inline-block;
text-align: center;
color: #999;
}
} }
} td.blank {
tr.node-text-diff-commit-header td { background: #f6f6f6;
background: #f3f3f3;
padding: 5px 10px;
color: #333;
h3 {
font-size: 1.4em;
margin: 0;
} }
.commit-summary { td.added {
border-top: 1px solid $secondary-border-color; background: #eefaee;
padding-top: 5px; }
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; color: #999;
} }
.commit-body { tr.unchanged {
margin-bottom:15px; background: #fefefe;
white-space: pre; }
line-height: 1.2em; tr.start-block {
border-top: 1px solid #f0f0f0;
}
tr.end-block {
border-bottom: 1px solid #f0f0f0;
}
tr.node-text-diff-file-header td {
.filename {
font-family: monospace;
}
background: #f3f3f3;
padding: 5px 10px 5px 0;
color: #333;
cursor: pointer;
i.node-diff-chevron {
width: 30px;
}
}
tr.node-text-diff-file-header.collapsed {
td i.node-diff-chevron {
transform: rotate(-90deg);
}
}
tr.node-text-diff-commit-header td {
background: #f3f3f3;
padding: 5px 10px;
color: #333;
h3 {
font-size: 1.4em;
margin: 0;
}
.commit-summary {
border-top: 1px solid $secondary-border-color;
padding-top: 5px;
color: #999;
}
.commit-body {
margin-bottom:15px;
white-space: pre;
line-height: 1.2em;
}
}
tr.node-text-diff-header > td:not(.flow-diff) {
font-family: monospace;
padding: 5px 10px;
text-align: left;
color: #666;
background: #ffd;
height: 30px;
vertical-align: middle;
border-top: 1px solid #f0f0f0;
border-bottom: 1px solid #f0f0f0;
}
tr.node-text-diff-expand td {
cursor: pointer;
&:hover {
background: #ffc;
}
} }
} }
tr.node-text-diff-header td {
font-family: monospace;
padding: 5px 10px;
text-align: left;
color: #666;
background: #ffd;
height: 30px;
vertical-align: middle;
border-top: 1px solid #f0f0f0;
border-bottom: 1px solid #f0f0f0;
}
tr.node-text-diff-expand td {
cursor: pointer;
&:hover {
background: #ffc;
}
}
} }

View File

@ -80,7 +80,7 @@ module.exports = {
}).catch(function(err) { }).catch(function(err) {
log.warn(log._("api.flows.error-save",{message:err.message})); log.warn(log._("api.flows.error-save",{message:err.message}));
log.warn(err.stack); log.warn(err.stack);
res.status(500).json({error:"unexpected_error", message:err.message}); res.status(500).json({error:err.code || "unexpected_error", message:err.message});
}); });
} }
} }

View File

@ -93,7 +93,8 @@
"credentials_load_failed": "<p>Flows stopped as the credentials could not be decrypted.</p><p>The flow credential file is encrypted, but the project's encryption key is missing or invalid.</p>", "credentials_load_failed": "<p>Flows stopped as the credentials could not be decrypted.</p><p>The flow credential file is encrypted, but the project's encryption key is missing or invalid.</p>",
"missing_flow_file": "<p>Project flow file not found.</p><p>The project is not configured with a flow file.</p>", "missing_flow_file": "<p>Project flow file not found.</p><p>The project is not configured with a flow file.</p>",
"project_empty": "<p>The project is empty.</p><p>Do you want to create a default set of project files?<br/>Otherwise, you will have to manually add files to the project outside of the editor.</p>", "project_empty": "<p>The project is empty.</p><p>Do you want to create a default set of project files?<br/>Otherwise, you will have to manually add files to the project outside of the editor.</p>",
"project_not_found": "<p>Project '__project__' not found.</p>" "project_not_found": "<p>Project '__project__' not found.</p>",
"git_merge_conflict": "<p>Automatic merging of changes failed.</p><p>Fix the unmerged conflicts then commit the results.</p>"
}, },
"error": "<strong>Error</strong>: __message__", "error": "<strong>Error</strong>: __message__",

View File

@ -127,7 +127,7 @@ Project.prototype.load = function () {
promises.push(project.loadRemotes()); promises.push(project.loadRemotes());
return when.settle(promises).then(function() { return when.settle(promises).then(function(results) {
return project; return project;
}) })
}); });
@ -218,6 +218,9 @@ Project.prototype.parseRemoteBranch = function (remoteBranch) {
Project.prototype.isEmpty = function () { Project.prototype.isEmpty = function () {
return this.empty; return this.empty;
}; };
Project.prototype.isMerging = function() {
return this.merging;
}
Project.prototype.update = function (user, data) { Project.prototype.update = function (user, data) {
var username; var username;
@ -374,7 +377,13 @@ Project.prototype.unstageFile = function(file) {
return gitTools.unstageFile(this.path,file); return gitTools.unstageFile(this.path,file);
} }
Project.prototype.commit = function(user, options) { Project.prototype.commit = function(user, options) {
return gitTools.commit(this.path,options.message,getGitUser(user)); var self = this;
return gitTools.commit(this.path,options.message,getGitUser(user)).then(function() {
if (self.merging) {
self.merging = false;
return
}
});
} }
Project.prototype.getFileDiff = function(file,type) { Project.prototype.getFileDiff = function(file,type) {
return gitTools.getFileDiff(this.path,file,type); return gitTools.getFileDiff(this.path,file,type);
@ -440,6 +449,21 @@ Project.prototype.status = function(user, includeRemote) {
var result = results[0]; var result = results[0];
if (results[1]) { if (results[1]) {
result.merging = true; result.merging = true;
if (!self.merging) {
self.merging = true;
runtime.events.emit("runtime-event",{
id:"runtime-state",
payload:{
type:"warning",
error:"git_merge_conflict",
project:self.name,
text:"notification.warnings.git_merge_conflict"
},
retain:true}
);
}
} else {
self.merging = false;
} }
self.branches.local = result.branches.local; self.branches.local = result.branches.local;
self.branches.remote = result.branches.remote; self.branches.remote = result.branches.remote;
@ -534,6 +558,11 @@ Project.prototype.pull = function (user,remoteBranchName,setRemote,allowUnrelate
Project.prototype.resolveMerge = function (file,resolutions) { Project.prototype.resolveMerge = function (file,resolutions) {
var filePath = fspath.join(this.path,file); var filePath = fspath.join(this.path,file);
var self = this; var self = this;
if (typeof resolutions === 'string') {
return util.writeFile(filePath, resolutions).then(function() {
return self.stageFile(file);
})
}
return fs.readFile(filePath,"utf8").then(function(content) { return fs.readFile(filePath,"utf8").then(function(content) {
var lines = content.split("\n"); var lines = content.split("\n");
var result = []; var result = [];
@ -573,7 +602,10 @@ Project.prototype.resolveMerge = function (file,resolutions) {
}); });
}; };
Project.prototype.abortMerge = function () { Project.prototype.abortMerge = function () {
return gitTools.abortMerge(this.path); var self = this;
return gitTools.abortMerge(this.path).then(function() {
self.merging = false;
})
}; };
Project.prototype.getBranches = function (user, isRemote) { Project.prototype.getBranches = function (user, isRemote) {

View File

@ -454,18 +454,19 @@ module.exports = {
} else { } else {
promise = runGitCommand(args,cwd) promise = runGitCommand(args,cwd)
} }
return promise.catch(function(err) { return promise;
if (/CONFLICT/.test(err.stdout)) { // .catch(function(err) {
var e = new Error("NLS: pull failed - merge conflict"); // if (/CONFLICT/.test(err.stdout)) {
e.code = "git_pull_merge_conflict"; // var e = new Error("pull failed - merge conflict");
throw e; // e.code = "git_pull_merge_conflict";
} else if (/Please commit your changes or stash/i.test(err.message)) { // throw e;
var e = new Error("NLS: Pull failed - local changes would be overwritten"); // } else if (/Please commit your changes or stash/i.test(err.message)) {
e.code = "git_pull_overwrite"; // var e = new Error("Pull failed - local changes would be overwritten");
throw e; // e.code = "git_pull_overwrite";
} // throw e;
throw err; // }
}); // throw err;
// });
}, },
push: function(cwd,remote,branch,setUpstream, auth) { push: function(cwd,remote,branch,setUpstream, auth) {
var args = ["push"]; var args = ["push"];

View File

@ -212,7 +212,13 @@ function unstageFile(user, project,file) {
} }
function commit(user, project,options) { function commit(user, project,options) {
checkActiveProject(project); checkActiveProject(project);
return activeProject.commit(user, options); var isMerging = activeProject.isMerging();
return activeProject.commit(user, options).then(function() {
// The project was merging, now it isn't. Lets reload.
if (isMerging && !activeProject.isMerging()) {
return reloadActiveProject("merge-complete");
}
})
} }
function getFileDiff(user, project,file,type) { function getFileDiff(user, project,file,type) {
checkActiveProject(project); checkActiveProject(project);
@ -258,7 +264,7 @@ function resolveMerge(user, project,file,resolution) {
function abortMerge(user, project) { function abortMerge(user, project) {
checkActiveProject(project); checkActiveProject(project);
return activeProject.abortMerge().then(function() { return activeProject.abortMerge().then(function() {
return reloadActiveProject("abort-merge") return reloadActiveProject("merge-abort")
}); });
} }
function getBranches(user, project,isRemote) { function getBranches(user, project,isRemote) {
@ -478,6 +484,13 @@ function getFlows() {
error.code = "missing_flow_file"; error.code = "missing_flow_file";
return when.reject(error); return when.reject(error);
} }
if (activeProject.isMerging()) {
log.warn("Project has unmerged changes");
error = new Error("Project has unmerged changes. Cannot load flows");
error.code = "git_merge_conflict";
return when.reject(error);
}
} }
return util.readFile(flowsFullPath,flowsFileBackup,[],'flow'); return util.readFile(flowsFullPath,flowsFileBackup,[],'flow');
} }
@ -486,6 +499,11 @@ function saveFlows(flows) {
if (settings.readOnly) { if (settings.readOnly) {
return when.resolve(); return when.resolve();
} }
if (activeProject && activeProject.isMerging()) {
var error = new Error("Project has unmerged changes. Cannot deploy new flows");
error.code = "git_merge_conflict";
return when.reject(error);
}
try { try {
fs.renameSync(flowsFullPath,flowsFileBackup); fs.renameSync(flowsFullPath,flowsFileBackup);