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

Merge pull request #2035 from node-red/simple-git

Add option for simplified git workglow
This commit is contained in:
Nick O'Leary 2020-09-28 11:39:17 +01:00 committed by GitHub
commit 54dc98a90b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 161 additions and 67 deletions

View File

@ -137,6 +137,7 @@ module.exports = {
req.body.hasOwnProperty('description') || req.body.hasOwnProperty('description') ||
req.body.hasOwnProperty('dependencies')|| req.body.hasOwnProperty('dependencies')||
req.body.hasOwnProperty('summary') || req.body.hasOwnProperty('summary') ||
req.body.hasOwnProperty('version') ||
req.body.hasOwnProperty('files') || req.body.hasOwnProperty('files') ||
req.body.hasOwnProperty('git')) { req.body.hasOwnProperty('git')) {
runtimeAPI.projects.updateProject(opts).then(function() { runtimeAPI.projects.updateProject(opts).then(function() {

View File

@ -43,6 +43,10 @@ module.exports = {
rejectHandler: function(req,res,err) { rejectHandler: function(req,res,err) {
//TODO: why this when errorHandler also?! //TODO: why this when errorHandler also?!
log.audit({event: "api.error",error:err.code||"unexpected_error",message:err.message||err.toString()},req); log.audit({event: "api.error",error:err.code||"unexpected_error",message:err.message||err.toString()},req);
if (!err.code) {
// by definition, an unexpected_error to log
log.error(err);
}
var response = { var response = {
code: err.code||"unexpected_error", code: err.code||"unexpected_error",
message: err.message||err.toString() message: err.message||err.toString()

View File

@ -721,6 +721,12 @@
"committerTip": "Leave blank to use system default", "committerTip": "Leave blank to use system default",
"userName": "Username", "userName": "Username",
"email": "Email", "email": "Email",
"workflow": "Workflow",
"workfowTip": "Choose your preferred git workflow",
"workflowManual": "Manual",
"workflowManualTip": "All changes must be manually committed under the 'history' sidebar",
"workflowAuto": "Automatic",
"workflowAutoTip": "Changes are committed automatically with every deploy",
"sshKeys": "SSH Keys", "sshKeys": "SSH Keys",
"sshKeysTip": "Allows you to create secure connections to remote git repositories.", "sshKeysTip": "Allows you to create secure connections to remote git repositories.",
"add": "add key", "add": "add key",

View File

@ -166,34 +166,42 @@ RED.projects.settings = (function() {
description.find(".red-ui-text-bidi-aware").contents().filter(function() { return this.nodeType === 3 && this.textContent.trim() !== "" }).wrap( "<span></span>" ); description.find(".red-ui-text-bidi-aware").contents().filter(function() { return this.nodeType === 3 && this.textContent.trim() !== "" }).wrap( "<span></span>" );
} }
function editSummary(activeProject, summary, container) { function editSummary(activeProject, summary, container, version, versionContainer) {
var editButton = container.prev(); var editButton = container.prev();
editButton.hide(); editButton.hide();
container.empty(); container.empty();
versionContainer.empty();
var bg = $('<span class="button-row" style="position: relative; float: right; margin-right:0;"></span>').appendTo(container); var bg = $('<span class="button-row" style="position: relative; float: right; margin-right:0;"></span>').appendTo(container);
var input = $('<input type="text" style="width: calc(100% - 150px); margin-right: 10px;">').val(summary||"").appendTo(container); var input = $('<input type="text" style="width: calc(100% - 150px); margin-right: 10px;">').val(summary||"").appendTo(container);
var versionInput = $('<input type="text" style="width: calc(100% - 150px); margin-right: 10px;">').val(version||"").appendTo(versionContainer);
$('<button class="red-ui-button">' + RED._("common.label.cancel") + '</button>') $('<button class="red-ui-button">' + RED._("common.label.cancel") + '</button>')
.appendTo(bg) .appendTo(bg)
.on("click", function(evt) { .on("click", function(evt) {
evt.preventDefault(); evt.preventDefault();
updateProjectSummary(activeProject.summary, container); updateProjectSummary(activeProject.summary, container);
updateProjectVersion(activeProject.version, versionContainer);
editButton.show(); editButton.show();
}); });
$('<button class="red-ui-button">' + RED._("common.label.save") + '</button>') $('<button class="red-ui-button">' + RED._("common.label.save") + '</button>')
.appendTo(bg) .appendTo(bg)
.on("click", function(evt) { .on("click", function(evt) {
evt.preventDefault(); evt.preventDefault();
var v = input.val(); var newSummary = input.val();
updateProjectSummary(v, container); var newVersion = versionInput.val();
var spinner = utils.addSpinnerOverlay(container); updateProjectSummary(newSummary, container);
updateProjectVersion(newVersion, versionContainer);
var spinner = utils.addSpinnerOverlay(container).addClass('red-ui-component-spinner-contain');
var done = function(err,res) { var done = function(err,res) {
if (err) { if (err) {
spinner.remove(); spinner.remove();
return editSummary(activeProject, summary, container); return editSummary(activeProject, summary, container, version, versionContainer);
} }
activeProject.summary = v; activeProject.summary = newSummary;
activeProject.version = newVersion;
spinner.remove(); spinner.remove();
updateProjectSummary(activeProject.summary, container); updateProjectSummary(activeProject.summary, container);
updateProjectVersion(activeProject.version, versionContainer);
editButton.show(); editButton.show();
} }
utils.sendRequest({ utils.sendRequest({
@ -214,31 +222,39 @@ RED.projects.settings = (function() {
} }
}, },
} }
},{summary:v}); },{summary:newSummary, version: newVersion});
}); });
} }
function updateProjectSummary(summary, container) { function updateProjectSummary(summary, container) {
container.empty(); container.empty();
if (summary) { if (summary) {
container.text(summary).removeClass('node-info-node'); container.text(summary).removeClass('red-ui-help-info-none');
} else { } else {
container.text(RED._("sidebar.project.noSummaryAvailable")).addClass('red-ui-help-info-none'); container.text(RED._("sidebar.project.noSummaryAvailable")).addClass('red-ui-help-info-none');
} }
} }
function updateProjectVersion(version, container) {
container.empty();
if (version) {
container.text(version);
}
}
function createMainPane(activeProject) { function createMainPane(activeProject) {
var pane = $('<div id="red-ui-project-settings-tab-main" class="red-ui-project-settings-tab-pane red-ui-help"></div>'); var pane = $('<div id="red-ui-project-settings-tab-main" class="red-ui-project-settings-tab-pane red-ui-help"></div>');
$('<h1>').text(activeProject.name).appendTo(pane); $('<h1>').text(activeProject.name).appendTo(pane);
var summary = $('<div style="position: relative">').appendTo(pane); var summary = $('<div style="position: relative">').appendTo(pane);
var summaryContent = $('<div></div>').appendTo(summary); var summaryContent = $('<div></div>').appendTo(summary);
var versionContent = $('<div></div>').addClass('red-ui-help-info-none').appendTo(summary);
updateProjectSummary(activeProject.summary, summaryContent); updateProjectSummary(activeProject.summary, summaryContent);
updateProjectVersion(activeProject.version, versionContent);
if (RED.user.hasPermission("projects.write")) { if (RED.user.hasPermission("projects.write")) {
$('<button class="red-ui-button red-ui-button-small" style="float: right;">' + RED._('sidebar.project.editDescription') + '</button>') $('<button class="red-ui-button red-ui-button-small" style="float: right;">' + RED._('sidebar.project.editDescription') + '</button>')
.prependTo(summary) .prependTo(summary)
.on("click", function(evt) { .on("click", function(evt) {
evt.preventDefault(); evt.preventDefault();
editSummary(activeProject, activeProject.summary, summaryContent); editSummary(activeProject, activeProject.summary, summaryContent, activeProject.version, versionContent);
}); });
} }
$('<hr>').appendTo(pane); $('<hr>').appendTo(pane);
@ -1573,8 +1589,6 @@ RED.projects.settings = (function() {
updateForm(); updateForm();
} }
function createSettingsPane(activeProject) { function createSettingsPane(activeProject) {
var pane = $('<div id="red-ui-project-settings-tab-settings" class="red-ui-project-settings-tab-pane red-ui-help"></div>'); var pane = $('<div id="red-ui-project-settings-tab-settings" class="red-ui-project-settings-tab-pane red-ui-help"></div>');
createFilesSection(activeProject,pane); createFilesSection(activeProject,pane);

View File

@ -38,13 +38,34 @@ RED.projects.userSettings = (function() {
$('<label for="user-settings-gitconfig-email"></label>').text(RED._("editor:sidebar.project.userSettings.email")).appendTo(row); $('<label for="user-settings-gitconfig-email"></label>').text(RED._("editor:sidebar.project.userSettings.email")).appendTo(row);
gitEmailInput = $('<input type="text" id="user-settings-gitconfig-email">').appendTo(row); gitEmailInput = $('<input type="text" id="user-settings-gitconfig-email">').appendTo(row);
gitEmailInput.val(currentGitSettings.user.email||""); gitEmailInput.val(currentGitSettings.user.email||"");
}
function createWorkflowSection(pane) {
var currentGitSettings = RED.settings.get('git') || {};
currentGitSettings.workflow = currentGitSettings.workflow || {};
currentGitSettings.workflow.mode = currentGitSettings.workflow.mode || "manual";
var title = $('<h3></h3>').text(RED._("editor:sidebar.project.userSettings.workflow")).appendTo(pane);
var workflowContainer = $('<div class="red-ui-settings-section"></div>').appendTo(pane);
$('<div class="red-ui-settings-section-description"></div>').appendTo(workflowContainer).text(RED._("editor:sidebar.project.userSettings.workfowTip"));
var row = $('<div class="red-ui-settings-row"></div>').appendTo(workflowContainer);
$('<label><input type="radio" name="user-setting-gitworkflow" value="manual"> <div style="margin-left: 3px; display: inline-block"><div data-i18n="editor:sidebar.project.userSettings.workflowManual"></div><div style="color:#aaa;" data-i18n="editor:sidebar.project.userSettings.workflowManualTip"></div></div></label>').appendTo(row);
row = $('<div class="red-ui-settings-row"></div>').appendTo(workflowContainer);
$('<label><input type="radio" name="user-setting-gitworkflow" value="auto"> <div style="margin-left: 3px; display: inline-block"><div data-i18n="editor:sidebar.project.userSettings.workflowAuto"></div><div style="color:#aaa;" data-i18n="editor:sidebar.project.userSettings.workflowAutoTip"></div></div></label>').appendTo(row);
workflowContainer.find('[name="user-setting-gitworkflow"][type="radio"][value="'+currentGitSettings.workflow.mode+'"]').prop('checked',true)
} }
function createSSHKeySection(pane) { function createSSHKeySection(pane) {
var title = $('<h3></h3>').text(RED._("editor:sidebar.project.userSettings.sshKeys")).appendTo(pane);
var container = $('<div class="red-ui-settings-section"></div>').appendTo(pane); var container = $('<div class="red-ui-settings-section"></div>').appendTo(pane);
var popover; var popover;
var title = $('<h3></h3>').text(RED._("editor:sidebar.project.userSettings.sshKeys")).appendTo(container);
var subtitle = $('<div class="red-ui-settings-section-description"></div>').appendTo(container).text(RED._("editor:sidebar.project.userSettings.sshKeysTip")); var subtitle = $('<div class="red-ui-settings-section-description"></div>').appendTo(container).text(RED._("editor:sidebar.project.userSettings.sshKeysTip"));
var addKeyButton = $('<button id="user-settings-gitconfig-add-key" class="red-ui-button red-ui-button-small" style="float: right; margin-right: 10px;">'+RED._("editor:sidebar.project.userSettings.add")+'</button>') var addKeyButton = $('<button id="user-settings-gitconfig-add-key" class="red-ui-button red-ui-button-small" style="float: right; margin-right: 10px;">'+RED._("editor:sidebar.project.userSettings.add")+'</button>')
@ -391,6 +412,7 @@ RED.projects.userSettings = (function() {
function createSettingsPane(activeProject) { function createSettingsPane(activeProject) {
var pane = $('<div id="red-ui-settings-tab-gitconfig" class="project-settings-tab-pane red-ui-help"></div>'); var pane = $('<div id="red-ui-settings-tab-gitconfig" class="project-settings-tab-pane red-ui-help"></div>');
createGitUserSection(pane); createGitUserSection(pane);
createWorkflowSection(pane);
createSSHKeySection(pane); createSSHKeySection(pane);
return pane; return pane;
} }
@ -407,6 +429,9 @@ RED.projects.userSettings = (function() {
currentGitSettings.user = currentGitSettings.user || {}; currentGitSettings.user = currentGitSettings.user || {};
currentGitSettings.user.name = gitUsernameInput.val(); currentGitSettings.user.name = gitUsernameInput.val();
currentGitSettings.user.email = gitEmailInput.val(); currentGitSettings.user.email = gitEmailInput.val();
currentGitSettings.workflow = currentGitSettings.workflow || {};
currentGitSettings.workflow.mode = $('[name="user-setting-gitworkflow"][type="radio"]:checked').val()
RED.settings.set('git', currentGitSettings); RED.settings.set('git', currentGitSettings);
} }
}); });

View File

@ -293,14 +293,20 @@ RED.sidebar.versionControl = (function() {
if (activeProject) { if (activeProject) {
// TODO: this is a full refresh of the files - should be able to // TODO: this is a full refresh of the files - should be able to
// just do an incremental refresh // just do an incremental refresh
allChanges = {};
unstagedChangesList.editableList('empty');
stagedChangesList.editableList('empty');
unmergedChangesList.editableList('empty');
$.getJSON("projects/"+activeProject.name+"/status",function(result) { var workflowMode = ((RED.settings.get('git') || {}).workflow || {}).mode || "manual";
refreshFiles(result); if (workflowMode === 'auto') {
}); refresh(true);
} else {
allChanges = {};
unstagedChangesList.editableList('empty');
stagedChangesList.editableList('empty');
unmergedChangesList.editableList('empty');
$.getJSON("projects/"+activeProject.name+"/status",function(result) {
refreshFiles(result);
});
}
} }
}); });
RED.events.on("login",function() { RED.events.on("login",function() {

View File

@ -79,7 +79,7 @@
display: table; display: table;
clear: both; clear: both;
} }
.uneditable-input, input, textarea { .uneditable-input, input[type="text"],input[type="password"], textarea {
width: calc(100% - 150px); width: calc(100% - 150px);
} }
textarea { textarea {

View File

@ -88,7 +88,7 @@ var api = module.exports = {
return reject(err); return reject(err);
} }
} }
apiPromise = runtime.nodes.setFlows(flows.flows,flows.credentials,deploymentType); apiPromise = runtime.nodes.setFlows(flows.flows,flows.credentials,deploymentType,null,null,opts.user);
} }
apiPromise.then(function(flowId) { apiPromise.then(function(flowId) {
return resolve({rev:flowId}); return resolve({rev:flowId});
@ -98,7 +98,7 @@ var api = module.exports = {
return reject(err); return reject(err);
}); });
}); });
}); });
}, },
/** /**
@ -114,7 +114,7 @@ var api = module.exports = {
return mutex.runExclusive(function() { return mutex.runExclusive(function() {
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
var flow = opts.flow; var flow = opts.flow;
runtime.nodes.addFlow(flow).then(function (id) { runtime.nodes.addFlow(flow,opts.user).then(function (id) {
runtime.log.audit({event: "flow.add", id: id}, opts.req); runtime.log.audit({event: "flow.add", id: id}, opts.req);
return resolve(id); return resolve(id);
}).catch(function (err) { }).catch(function (err) {
@ -170,7 +170,7 @@ var api = module.exports = {
var flow = opts.flow; var flow = opts.flow;
var id = opts.id; var id = opts.id;
try { try {
runtime.nodes.updateFlow(id, flow).then(function () { runtime.nodes.updateFlow(id, flow, opts.user).then(function () {
runtime.log.audit({event: "flow.update", id: id}, opts.req); runtime.log.audit({event: "flow.update", id: id}, opts.req);
return resolve(id); return resolve(id);
}).catch(function (err) { }).catch(function (err) {
@ -216,7 +216,7 @@ var api = module.exports = {
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
var id = opts.id; var id = opts.id;
try { try {
runtime.nodes.removeFlow(id).then(function () { runtime.nodes.removeFlow(id, opts.user).then(function () {
runtime.log.audit({event: "flow.remove", id: id}, opts.req); runtime.log.audit({event: "flow.remove", id: id}, opts.req);
return resolve(); return resolve();
}).catch(function (err) { }).catch(function (err) {

View File

@ -115,7 +115,7 @@ function load(forceStart) {
* type - full/nodes/flows/load (default full) * type - full/nodes/flows/load (default full)
* muteLog - don't emit the standard log messages (used for individual flow api) * muteLog - don't emit the standard log messages (used for individual flow api)
*/ */
function setFlows(_config,_credentials,type,muteLog,forceStart) { function setFlows(_config,_credentials,type,muteLog,forceStart,user) {
if (typeof _credentials === "string") { if (typeof _credentials === "string") {
type = _credentials; type = _credentials;
_credentials = null; _credentials = null;
@ -186,7 +186,7 @@ function setFlows(_config,_credentials,type,muteLog,forceStart) {
credentialsDirty:credsDirty, credentialsDirty:credsDirty,
credentials: creds credentials: creds
} }
return storage.saveFlows(saveConfig); return storage.saveFlows(saveConfig, user);
}); });
} }
@ -481,7 +481,7 @@ function updateMissingTypes() {
} }
} }
function addFlow(flow) { function addFlow(flow, user) {
var i,node; var i,node;
if (!flow.hasOwnProperty('nodes')) { if (!flow.hasOwnProperty('nodes')) {
throw new Error('missing nodes property'); throw new Error('missing nodes property');
@ -531,7 +531,7 @@ function addFlow(flow) {
var newConfig = clone(activeConfig.flows); var newConfig = clone(activeConfig.flows);
newConfig = newConfig.concat(nodes); newConfig = newConfig.concat(nodes);
return setFlows(newConfig,null,'flows',true).then(function() { return setFlows(newConfig, null, 'flows', true, null, user).then(function() {
log.info(log._("nodes.flows.added-flow",{label:(flow.label?flow.label+" ":"")+"["+flow.id+"]"})); log.info(log._("nodes.flows.added-flow",{label:(flow.label?flow.label+" ":"")+"["+flow.id+"]"}));
return flow.id; return flow.id;
}); });
@ -607,7 +607,7 @@ function getFlow(id) {
return result; return result;
} }
function updateFlow(id,newFlow) { function updateFlow(id,newFlow, user) {
var label = id; var label = id;
if (id !== 'global') { if (id !== 'global') {
if (!activeFlowConfig.flows[id]) { if (!activeFlowConfig.flows[id]) {
@ -662,12 +662,12 @@ function updateFlow(id,newFlow) {
} }
newConfig = newConfig.concat(nodes); newConfig = newConfig.concat(nodes);
return setFlows(newConfig,null,'flows',true).then(function() { return setFlows(newConfig, null, 'flows', true, null, user).then(function() {
log.info(log._("nodes.flows.updated-flow",{label:(label?label+" ":"")+"["+id+"]"})); log.info(log._("nodes.flows.updated-flow",{label:(label?label+" ":"")+"["+id+"]"}));
}) })
} }
function removeFlow(id) { function removeFlow(id, user) {
if (id === 'global') { if (id === 'global') {
// TODO: nls + error code // TODO: nls + error code
throw new Error('not allowed to remove global'); throw new Error('not allowed to remove global');
@ -684,7 +684,7 @@ function removeFlow(id) {
return node.z !== id && node.id !== id; return node.z !== id && node.id !== id;
}); });
return setFlows(newConfig,null,'flows',true).then(function() { return setFlows(newConfig, null, 'flows', true, null, user).then(function() {
log.info(log._("nodes.flows.removed-flow",{label:(flow.label?flow.label+" ":"")+"["+flow.id+"]"})); log.info(log._("nodes.flows.removed-flow",{label:(flow.label?flow.label+" ":"")+"["+flow.id+"]"}));
}); });
} }

View File

@ -82,7 +82,7 @@ var storageModuleInterface = {
}) })
}); });
}, },
saveFlows: function(config) { saveFlows: function(config, user) {
var flows = config.flows; var flows = config.flows;
var credentials = config.credentials; var credentials = config.credentials;
var credentialSavePromise; var credentialSavePromise;
@ -94,7 +94,7 @@ var storageModuleInterface = {
delete config.credentialsDirty; delete config.credentialsDirty;
return credentialSavePromise.then(function() { return credentialSavePromise.then(function() {
return storageModule.saveFlows(flows).then(function() { return storageModule.saveFlows(flows, user).then(function() {
return crypto.createHash('md5').update(JSON.stringify(config.flows)).digest("hex"); return crypto.createHash('md5').update(JSON.stringify(config.flows)).digest("hex");
}) })
}); });

View File

@ -40,16 +40,22 @@ function getSSHKeyUsername(userObj) {
} }
return username; return username;
} }
function getGitUser(user) {
function getUserGitSettings(user) {
var username; var username;
if (!user) { if (!user) {
username = "_"; username = "_";
} else { } else {
username = user.username; username = user.username;
} }
var userSettings = settings.getUserSettings(username); var userSettings = settings.getUserSettings(username)||{};
if (userSettings && userSettings.git) { return userSettings.git;
return userSettings.git.user; }
function getGitUser(user) {
var gitSettings = getUserGitSettings(user);
if (gitSettings) {
return gitSettings.user;
} }
return null; return null;
} }
@ -172,7 +178,7 @@ Project.prototype.initialise = function(user,data) {
} }
} }
return when.all(promises).then(function() { return Promise.all(promises).then(function() {
return gitTools.stageFile(project.path,files); return gitTools.stageFile(project.path,files);
}).then(function() { }).then(function() {
return gitTools.commit(project.path,"Create project files",getGitUser(user)); return gitTools.commit(project.path,"Create project files",getGitUser(user));
@ -343,6 +349,10 @@ Project.prototype.update = function (user, data) {
savePackage = true; savePackage = true;
this.package.description = data.summary; this.package.description = data.summary;
} }
if (data.hasOwnProperty('version')) {
savePackage = true;
this.package.version = data.version;
}
if (data.hasOwnProperty('git')) { if (data.hasOwnProperty('git')) {
if (data.git.hasOwnProperty('user')) { if (data.git.hasOwnProperty('user')) {
@ -390,11 +400,15 @@ Project.prototype.update = function (user, data) {
if (saveSettings) { if (saveSettings) {
promises.push(settings.set("projects",globalProjectSettings)); promises.push(settings.set("projects",globalProjectSettings));
} }
var modifiedFiles = [];
if (saveREADME) { if (saveREADME) {
promises.push(util.writeFile(fspath.join(this.path,this.paths['README.md']), this.description)); promises.push(util.writeFile(fspath.join(this.path,this.paths['README.md']), this.description));
modifiedFiles.push('README.md');
} }
if (savePackage) { if (savePackage) {
promises.push(fs.readFile(fspath.join(project.path,project.paths['package.json']),"utf8").then(content => { promises.push(fs.readFile(fspath.join(this.path,this.paths['package.json']),"utf8").then(content => {
var currentPackage = {}; var currentPackage = {};
try { try {
currentPackage = util.parseJSON(content); currentPackage = util.parseJSON(content);
@ -403,12 +417,22 @@ Project.prototype.update = function (user, data) {
this.package = Object.assign(currentPackage,this.package); this.package = Object.assign(currentPackage,this.package);
return util.writeFile(fspath.join(project.path,this.paths['package.json']), JSON.stringify(this.package,"",4)); return util.writeFile(fspath.join(project.path,this.paths['package.json']), JSON.stringify(this.package,"",4));
})); }));
modifiedFiles.push('package.json');
} }
return when.settle(promises).then(res => { return when.settle(promises).then(function(res) {
var gitSettings = getUserGitSettings(user) || {};
var workflowMode = (gitSettings.workflow||{}).mode || "manual";
if (workflowMode === 'auto') {
return project.stageFile(modifiedFiles.map(f => project.paths[f])).then(() => {
return project.commit(user,{message:"Update "+modifiedFiles.join(", ")})
})
}
}).then(res => {
if (reloadProject) { if (reloadProject) {
return this.load() return this.load()
} }
}).then(() => { return { }).then(function() {
return {
flowFilesChanged: flowFilesChanged, flowFilesChanged: flowFilesChanged,
credentialSecretChanged: credentialSecretChanged credentialSecretChanged: credentialSecretChanged
}}) }})
@ -809,6 +833,7 @@ Project.prototype.export = function () {
return { return {
name: this.name, name: this.name,
summary: this.package.description, summary: this.package.description,
version: this.package.version,
description: this.description, description: this.description,
dependencies: this.package.dependencies||{}, dependencies: this.package.dependencies||{},
empty: this.empty, empty: this.empty,
@ -910,7 +935,7 @@ function createDefaultProject(user, project) {
} }
} }
return when.all(promises).then(function() { return Promise.all(promises).then(function() {
return gitTools.stageFile(projectPath,files); return gitTools.stageFile(projectPath,files);
}).then(function() { }).then(function() {
return gitTools.commit(projectPath,"Create project",getGitUser(user)); return gitTools.commit(projectPath,"Create project",getGitUser(user));

View File

@ -190,7 +190,13 @@ function listProjects() {
} }
function getUserGitSettings(user) { function getUserGitSettings(user) {
var userSettings = settings.getUserSettings(user)||{}; var username;
if (!user) {
username = "_";
} else {
username = user.username;
}
var userSettings = settings.getUserSettings(username)||{};
return userSettings.git; return userSettings.git;
} }
@ -362,7 +368,6 @@ function reloadActiveProject(action) {
}); });
} }
function createProject(user, metadata) { function createProject(user, metadata) {
// var userSettings = getUserGitSettings(user);
if (metadata.files && metadata.migrateFiles) { if (metadata.files && metadata.migrateFiles) {
// We expect there to be no active project in this scenario // We expect there to be no active project in this scenario
if (activeProject) { if (activeProject) {
@ -549,7 +554,7 @@ function getFlows() {
}); });
} }
function saveFlows(flows) { function saveFlows(flows, user) {
if (settings.readOnly) { if (settings.readOnly) {
return when.resolve(); return when.resolve();
} }
@ -569,7 +574,15 @@ function saveFlows(flows) {
} else { } else {
flowData = JSON.stringify(flows); flowData = JSON.stringify(flows);
} }
return util.writeFile(flowsFullPath, flowData, flowsFileBackup); return util.writeFile(flowsFullPath, flowData, flowsFileBackup).then(() => {
var gitSettings = getUserGitSettings(user) || {};
var workflowMode = (gitSettings.workflow||{}).mode || "manual";
if (activeProject && workflowMode === 'auto') {
return activeProject.stageFile([flowsFullPath, credentialsFile]).then(() => {
return activeProject.commit(user,{message:"Update flow files"})
})
}
});
} }
function getCredentials() { function getCredentials() {

View File

@ -45,7 +45,7 @@ describe('storage/localfilesystem', function() {
}); });
it('should initialise the user directory',function(done) { it('should initialise the user directory',function(done) {
localfilesystem.init({userDir:userDir}, mockRuntime).then(function() { localfilesystem.init({userDir:userDir,getUserSettings: () => {{}}}, mockRuntime).then(function() {
fs.existsSync(path.join(userDir,"lib")).should.be.true(); fs.existsSync(path.join(userDir,"lib")).should.be.true();
fs.existsSync(path.join(userDir,"lib",'flows')).should.be.true(); fs.existsSync(path.join(userDir,"lib",'flows')).should.be.true();
done(); done();
@ -60,7 +60,7 @@ describe('storage/localfilesystem', function() {
process.env.NODE_RED_HOME = path.join(userDir,"NRH"); process.env.NODE_RED_HOME = path.join(userDir,"NRH");
fs.mkdirSync(process.env.NODE_RED_HOME); fs.mkdirSync(process.env.NODE_RED_HOME);
fs.writeFileSync(path.join(process.env.NODE_RED_HOME,".config.json"),"{}","utf8"); fs.writeFileSync(path.join(process.env.NODE_RED_HOME,".config.json"),"{}","utf8");
var settings = {}; var settings = {getUserSettings: () => {{}}};
localfilesystem.init(settings, mockRuntime).then(function() { localfilesystem.init(settings, mockRuntime).then(function() {
try { try {
fs.existsSync(path.join(process.env.NODE_RED_HOME,"lib")).should.be.true(); fs.existsSync(path.join(process.env.NODE_RED_HOME,"lib")).should.be.true();
@ -85,7 +85,7 @@ describe('storage/localfilesystem', function() {
fs.mkdirSync(process.env.HOMEPATH); fs.mkdirSync(process.env.HOMEPATH);
fs.mkdirSync(path.join(process.env.HOMEPATH,".node-red")); fs.mkdirSync(path.join(process.env.HOMEPATH,".node-red"));
fs.writeFileSync(path.join(process.env.HOMEPATH,".node-red",".config.json"),"{}","utf8"); fs.writeFileSync(path.join(process.env.HOMEPATH,".node-red",".config.json"),"{}","utf8");
var settings = {}; var settings = {getUserSettings: () => {{}}};
localfilesystem.init(settings, mockRuntime).then(function() { localfilesystem.init(settings, mockRuntime).then(function() {
try { try {
fs.existsSync(path.join(process.env.HOMEPATH,".node-red","lib")).should.be.true(); fs.existsSync(path.join(process.env.HOMEPATH,".node-red","lib")).should.be.true();
@ -112,7 +112,7 @@ describe('storage/localfilesystem', function() {
process.env.HOMEPATH = path.join(userDir,"HOMEPATH"); process.env.HOMEPATH = path.join(userDir,"HOMEPATH");
fs.mkdirSync(process.env.HOME); fs.mkdirSync(process.env.HOME);
var settings = {}; var settings = {getUserSettings: () => {{}}};
localfilesystem.init(settings, mockRuntime).then(function() { localfilesystem.init(settings, mockRuntime).then(function() {
try { try {
fs.existsSync(path.join(process.env.HOME,".node-red","lib")).should.be.true(); fs.existsSync(path.join(process.env.HOME,".node-red","lib")).should.be.true();
@ -142,7 +142,7 @@ describe('storage/localfilesystem', function() {
process.env.USERPROFILE = path.join(userDir,"USERPROFILE"); process.env.USERPROFILE = path.join(userDir,"USERPROFILE");
fs.mkdirSync(process.env.USERPROFILE); fs.mkdirSync(process.env.USERPROFILE);
var settings = {}; var settings = {getUserSettings: () => {{}}};
localfilesystem.init(settings, mockRuntime).then(function() { localfilesystem.init(settings, mockRuntime).then(function() {
try { try {
fs.existsSync(path.join(process.env.USERPROFILE,".node-red","lib")).should.be.true(); fs.existsSync(path.join(process.env.USERPROFILE,".node-red","lib")).should.be.true();
@ -163,7 +163,7 @@ describe('storage/localfilesystem', function() {
}); });
it('should handle missing flow file',function(done) { it('should handle missing flow file',function(done) {
localfilesystem.init({userDir:userDir}, mockRuntime).then(function() { localfilesystem.init({userDir:userDir,getUserSettings: () => {{}}}, mockRuntime).then(function() {
var flowFile = 'flows_'+require('os').hostname()+'.json'; var flowFile = 'flows_'+require('os').hostname()+'.json';
var flowFilePath = path.join(userDir,flowFile); var flowFilePath = path.join(userDir,flowFile);
fs.existsSync(flowFilePath).should.be.false(); fs.existsSync(flowFilePath).should.be.false();
@ -179,7 +179,7 @@ describe('storage/localfilesystem', function() {
}); });
it('should handle empty flow file, no backup',function(done) { it('should handle empty flow file, no backup',function(done) {
localfilesystem.init({userDir:userDir}, mockRuntime).then(function() { localfilesystem.init({userDir:userDir,getUserSettings: () => {{}}}, mockRuntime).then(function() {
var flowFile = 'flows_'+require('os').hostname()+'.json'; var flowFile = 'flows_'+require('os').hostname()+'.json';
var flowFilePath = path.join(userDir,flowFile); var flowFilePath = path.join(userDir,flowFile);
var flowFileBackupPath = path.join(userDir,"."+flowFile+".backup"); var flowFileBackupPath = path.join(userDir,"."+flowFile+".backup");
@ -197,7 +197,7 @@ describe('storage/localfilesystem', function() {
}); });
it('should handle empty flow file, restores backup',function(done) { it('should handle empty flow file, restores backup',function(done) {
localfilesystem.init({userDir:userDir}, mockRuntime).then(function() { localfilesystem.init({userDir:userDir,getUserSettings: () => {{}}}, mockRuntime).then(function() {
var flowFile = 'flows_'+require('os').hostname()+'.json'; var flowFile = 'flows_'+require('os').hostname()+'.json';
var flowFilePath = path.join(userDir,flowFile); var flowFilePath = path.join(userDir,flowFile);
var flowFileBackupPath = path.join(userDir,"."+flowFile+".backup"); var flowFileBackupPath = path.join(userDir,"."+flowFile+".backup");
@ -220,7 +220,7 @@ describe('storage/localfilesystem', function() {
}); });
it('should save flows to the default file',function(done) { it('should save flows to the default file',function(done) {
localfilesystem.init({userDir:userDir}, mockRuntime).then(function() { localfilesystem.init({userDir:userDir,getUserSettings: () => {{}}}, mockRuntime).then(function() {
var flowFile = 'flows_'+require('os').hostname()+'.json'; var flowFile = 'flows_'+require('os').hostname()+'.json';
var flowFilePath = path.join(userDir,flowFile); var flowFilePath = path.join(userDir,flowFile);
var flowFileBackupPath = path.join(userDir,"."+flowFile+".backup"); var flowFileBackupPath = path.join(userDir,"."+flowFile+".backup");
@ -249,7 +249,7 @@ describe('storage/localfilesystem', function() {
var flowFile = 'test.json'; var flowFile = 'test.json';
var flowFilePath = path.join(userDir,flowFile); var flowFilePath = path.join(userDir,flowFile);
localfilesystem.init({userDir:userDir, flowFile:flowFilePath}, mockRuntime).then(function() { localfilesystem.init({userDir:userDir, flowFile:flowFilePath,getUserSettings: () => {{}}}, mockRuntime).then(function() {
fs.existsSync(defaultFlowFilePath).should.be.false(); fs.existsSync(defaultFlowFilePath).should.be.false();
fs.existsSync(flowFilePath).should.be.false(); fs.existsSync(flowFilePath).should.be.false();
@ -273,7 +273,7 @@ describe('storage/localfilesystem', function() {
it('should format the flows file when flowFilePretty specified',function(done) { it('should format the flows file when flowFilePretty specified',function(done) {
var flowFile = 'test.json'; var flowFile = 'test.json';
var flowFilePath = path.join(userDir,flowFile); var flowFilePath = path.join(userDir,flowFile);
localfilesystem.init({userDir:userDir, flowFile:flowFilePath,flowFilePretty:true}, mockRuntime).then(function() { localfilesystem.init({userDir:userDir, flowFile:flowFilePath,flowFilePretty:true,getUserSettings: () => {{}}}, mockRuntime).then(function() {
localfilesystem.saveFlows(testFlow).then(function() { localfilesystem.saveFlows(testFlow).then(function() {
var content = fs.readFileSync(flowFilePath,"utf8"); var content = fs.readFileSync(flowFilePath,"utf8");
content.split("\n").length.should.be.above(1); content.split("\n").length.should.be.above(1);
@ -294,7 +294,7 @@ describe('storage/localfilesystem', function() {
it('should fsync the flows file',function(done) { it('should fsync the flows file',function(done) {
var flowFile = 'test.json'; var flowFile = 'test.json';
var flowFilePath = path.join(userDir,flowFile); var flowFilePath = path.join(userDir,flowFile);
localfilesystem.init({editorTheme:{projects:{enabled:false}},userDir:userDir, flowFile:flowFilePath}, mockRuntime).then(function() { localfilesystem.init({editorTheme:{projects:{enabled:false}},userDir:userDir, flowFile:flowFilePath,getUserSettings: () => {{}}}, mockRuntime).then(function() {
sinon.spy(fs,"fsync"); sinon.spy(fs,"fsync");
localfilesystem.saveFlows(testFlow).then(function() { localfilesystem.saveFlows(testFlow).then(function() {
fs.fsync.callCount.should.be.greaterThan(0); fs.fsync.callCount.should.be.greaterThan(0);
@ -312,7 +312,7 @@ describe('storage/localfilesystem', function() {
it('should log fsync errors and continue',function(done) { it('should log fsync errors and continue',function(done) {
var flowFile = 'test.json'; var flowFile = 'test.json';
var flowFilePath = path.join(userDir,flowFile); var flowFilePath = path.join(userDir,flowFile);
localfilesystem.init({userDir:userDir, flowFile:flowFilePath}, mockRuntime).then(function() { localfilesystem.init({userDir:userDir, flowFile:flowFilePath,getUserSettings: () => {{}}}, mockRuntime).then(function() {
sinon.stub(fs,"fsync", function(fd, cb) { sinon.stub(fs,"fsync", function(fd, cb) {
cb(new Error()); cb(new Error());
}); });
@ -338,7 +338,7 @@ describe('storage/localfilesystem', function() {
var flowFilePath = path.join(userDir,flowFile); var flowFilePath = path.join(userDir,flowFile);
var flowFileBackupPath = path.join(userDir,"."+flowFile+".backup"); var flowFileBackupPath = path.join(userDir,"."+flowFile+".backup");
localfilesystem.init({userDir:userDir, flowFile:flowFilePath}, mockRuntime).then(function() { localfilesystem.init({userDir:userDir, flowFile:flowFilePath,getUserSettings: () => {{}}}, mockRuntime).then(function() {
fs.existsSync(defaultFlowFilePath).should.be.false(); fs.existsSync(defaultFlowFilePath).should.be.false();
fs.existsSync(flowFilePath).should.be.false(); fs.existsSync(flowFilePath).should.be.false();
fs.existsSync(flowFileBackupPath).should.be.false(); fs.existsSync(flowFileBackupPath).should.be.false();
@ -378,7 +378,7 @@ describe('storage/localfilesystem', function() {
var flowFile = 'test.json'; var flowFile = 'test.json';
var flowFilePath = path.join(userDir,flowFile); var flowFilePath = path.join(userDir,flowFile);
var credFile = path.join(userDir,"test_cred.json"); var credFile = path.join(userDir,"test_cred.json");
localfilesystem.init({userDir:userDir, flowFile:flowFilePath}, mockRuntime).then(function() { localfilesystem.init({userDir:userDir, flowFile:flowFilePath,getUserSettings: () => {{}}}, mockRuntime).then(function() {
fs.existsSync(credFile).should.be.false(); fs.existsSync(credFile).should.be.false();
localfilesystem.getCredentials().then(function(creds) { localfilesystem.getCredentials().then(function(creds) {
@ -397,7 +397,7 @@ describe('storage/localfilesystem', function() {
var flowFilePath = path.join(userDir,flowFile); var flowFilePath = path.join(userDir,flowFile);
var credFile = path.join(userDir,"test_cred.json"); var credFile = path.join(userDir,"test_cred.json");
localfilesystem.init({userDir:userDir, flowFile:flowFilePath}, mockRuntime).then(function() { localfilesystem.init({userDir:userDir, flowFile:flowFilePath,getUserSettings: () => {{}}}, mockRuntime).then(function() {
fs.existsSync(credFile).should.be.false(); fs.existsSync(credFile).should.be.false();
@ -426,7 +426,7 @@ describe('storage/localfilesystem', function() {
var credFile = path.join(userDir,"test_cred.json"); var credFile = path.join(userDir,"test_cred.json");
var credFileBackup = path.join(userDir,".test_cred.json.backup"); var credFileBackup = path.join(userDir,".test_cred.json.backup");
localfilesystem.init({userDir:userDir, flowFile:flowFilePath}, mockRuntime).then(function() { localfilesystem.init({userDir:userDir, flowFile:flowFilePath,getUserSettings: () => {{}}}, mockRuntime).then(function() {
fs.writeFileSync(credFile,"{}","utf8"); fs.writeFileSync(credFile,"{}","utf8");
@ -452,7 +452,7 @@ describe('storage/localfilesystem', function() {
var flowFilePath = path.join(userDir,flowFile); var flowFilePath = path.join(userDir,flowFile);
var credFile = path.join(userDir,"test_cred.json"); var credFile = path.join(userDir,"test_cred.json");
localfilesystem.init({userDir:userDir, flowFile:flowFilePath, flowFilePretty:true}, mockRuntime).then(function() { localfilesystem.init({userDir:userDir, flowFile:flowFilePath, flowFilePretty:true,getUserSettings: () => {{}}}, mockRuntime).then(function() {
fs.existsSync(credFile).should.be.false(); fs.existsSync(credFile).should.be.false();