diff --git a/editor/js/main.js b/editor/js/main.js index da3d7af3e..9baa14d47 100644 --- a/editor/js/main.js +++ b/editor/js/main.js @@ -68,7 +68,7 @@ console.log(msg); var project = RED.projects.getActiveProject(); var message = { - "change-branch":"Change to local branch '"+project.branches.local+"'", + "change-branch":"Change to local branch '"+project.git.branches.local+"'", "abort-merge":"Git merge aborted", "loaded":"Project '"+msg.project+"' loaded", "updated":"Project '"+msg.project+"' updated", diff --git a/editor/js/ui/common/editableList.js b/editor/js/ui/common/editableList.js index c13788f2f..79be3cfcb 100644 --- a/editor/js/ui/common/editableList.js +++ b/editor/js/ui/common/editableList.js @@ -75,7 +75,7 @@ addLabel = 'add'; } } - $(' '+addLabel+'') + $(' '+addLabel+'') .appendTo(this.topContainer) .click(function(evt) { evt.preventDefault(); diff --git a/editor/js/ui/projectSettings.js b/editor/js/ui/projectSettings.js index ea12a112e..0c1ca4ba2 100644 --- a/editor/js/ui/projectSettings.js +++ b/editor/js/ui/projectSettings.js @@ -60,6 +60,7 @@ RED.projects.settings = (function() { }, open: function(tray) { var project = RED.projects.getActiveProject(); + var trayBody = tray.find('.editor-tray-body'); var settingsContent = $('
').appendTo(trayBody); var tabContainer = $('
',{id:"user-settings-tabs-container"}).appendTo(settingsContent); @@ -148,7 +149,12 @@ RED.projects.settings = (function() { } function updateProjectDescription(activeProject, container) { container.empty(); - var desc = marked(activeProject.description||""); + var desc; + if (activeProject.description) { + desc = marked(activeProject.description); + } else { + desc = ''+'No description available'+''; + } var description = addTargetToExternalLinks($(''+desc+'')).appendTo(container); description.find(".bidiAware").contents().filter(function() { return this.nodeType === 3 && this.textContent.trim() !== "" }).wrap( "" ); } @@ -280,7 +286,7 @@ RED.projects.settings = (function() { } } if (unknownCount > 0) { - depsList.editableList('addItem',{index:1, label:"Unknown Dependencies"}); // TODO: nls + depsList.editableList('addItem',{index:1, label:"Unlisted dependencies"}); // TODO: nls } if (unusedCount > 0) { depsList.editableList('addItem',{index:3, label:"Unused dependencies"}); // TODO: nls @@ -418,15 +424,18 @@ RED.projects.settings = (function() { } - function showProjectFileListing(row,activeProject,current,done) { + function showProjectFileListing(row,activeProject,current,filter,done) { var dialog; var dialogBody; var filesList; var selected; var container = $('
',{style:"position: relative; min-height: 175px; height: 175px;"}).hide().appendTo(row); var spinner = utils.addSpinnerOverlay(container); - $.getJSON("/projects/"+activeProject.name+"/files",function(result) { + $.getJSON("projects/"+activeProject.name+"/files",function(result) { var fileNames = Object.keys(result); + fileNames = fileNames.filter(function(fn) { + return !result[fn].status || !/D/.test(result[fn].status); + }) var files = {}; fileNames.sort(); fileNames.forEach(function(file) { @@ -459,13 +468,13 @@ RED.projects.settings = (function() { return result; } var files = sortFiles("",files,""); - createFileSubList(container,files.children,current,done,"height: 175px"); + createFileSubList(container,files.children,current,filter,done,"height: 175px"); spinner.remove(); }); return container; } - function createFileSubList(container, files, current, onselect, style) { + function createFileSubList(container, files, current, filter, onselect, style) { style = style || ""; var list = $('
    ',{class:"projects-dialog-file-list", style:style}).appendTo(container).editableList({ addButton: false, @@ -481,7 +490,7 @@ RED.projects.settings = (function() { } else { children.hide(); } - createFileSubList(children,entry.children,current,onselect); + createFileSubList(children,entry.children,current,filter,onselect); header.addClass("selectable"); header.click(function(e) { if ($(this).hasClass("expanded")) { @@ -507,19 +516,23 @@ RED.projects.settings = (function() { header.addClass("projects-dialog-file-list-entry-file-type-git"); } $(' ').appendTo(header); - header.addClass("selectable"); - if (entry.path === current) { - header.addClass("selected"); + if (filter.test(entry.name)) { + header.addClass("selectable"); + if (entry.path === current) { + header.addClass("selected"); + } + header.click(function(e) { + $(".projects-dialog-file-list-entry.selected").removeClass("selected"); + $(this).addClass("selected"); + onselect(entry.path); + }) + header.dblclick(function(e) { + e.preventDefault(); + onselect(entry.path,true); + }) + } else { + header.addClass("unselectable"); } - header.click(function(e) { - $(".projects-dialog-file-list-entry.selected").removeClass("selected"); - $(this).addClass("selected"); - onselect(entry.path); - }) - header.dblclick(function(e) { - e.preventDefault(); - onselect(entry.path,true); - }) } $('').text(entry.name).appendTo(header); } @@ -657,7 +670,7 @@ RED.projects.settings = (function() { } else { $(this).addClass('selected'); flowFileLabel.css('color','inherit'); - var fileList = showProjectFileListing(flowFileLabel,activeProject,flowFileInput.val(),function(result,isDblClick) { + var fileList = showProjectFileListing(flowFileLabel,activeProject,flowFileInput.val(), /.*\.json$/,function(result,isDblClick) { if (result) { flowFileInput.val(result); } @@ -887,7 +900,6 @@ RED.projects.settings = (function() { }, 200: function(data) { activeProject = data; - console.log("updating form"); updateForm(); done(); }, @@ -935,191 +947,218 @@ RED.projects.settings = (function() { updateForm(); } - function createLocalRepositorySection(activeProject,pane) { - var title = $('

    ').text("Local Repository").appendTo(pane); - var repoContainer = $('
    ').appendTo(pane); - var editRepoButton = $('') - .appendTo(title) - .click(function(evt) { - editRepoButton.hide(); - localRepoSearch.show(); - formButtons.show(); - }); - - var row = $('
    ').appendTo(repoContainer); - $('').text('Branch').appendTo(row); - var localRepoLabel = $('
    ').appendTo(row); - - var hideLocalRepoBranchList = function() { - localRepoSearch.removeClass('selected'); - localRepoLabel.css('height',''); - localRepoBranchListRow.slideUp(100); - } - var localRepoText = $('').text(activeProject.branches.local).appendTo(localRepoLabel); - var localRepoSearch = $('') - .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 = $('
    ').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 = $('').hide().appendTo(repoContainer); - $('') - .appendTo(formButtons) - .click(function(evt) { - evt.preventDefault(); - hideEditForm(); - }); - var saveButton = $('') - .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 = $('

    ').text("Git Remotes").appendTo(pane); - var repoContainer = $('').appendTo(pane); + var title = $('

    ').text("Version Control").appendTo(pane); var editRepoButton = $('') .appendTo(title) .click(function(evt) { editRepoButton.hide(); formButtons.show(); + + $('.projects-dialog-remote-list-entry-delete').show(); + remoteListAddButton.show(); + + gitUsernameLabel.hide(); + gitUsernameInput.show(); + gitEmailLabel.hide(); + gitEmailInput.show(); }); + var repoContainer = $('').appendTo(pane); + var subtitle = $('

    ').text("Committer Details").appendTo(repoContainer); + $('
    ').appendTo(subtitle).find('small').text("Leave blank to use system default"); + var row = $('').appendTo(repoContainer); + $('').text('Username').appendTo(row); + var gitUsernameLabel = $('
    ').appendTo(row); + var gitUsernameInput = $('').hide().appendTo(row); + + row = $('').appendTo(repoContainer); + $('').text('Email').appendTo(row); + var gitEmailLabel = $('
    ').appendTo(row); + var gitEmailInput = $('').hide().appendTo(row); + + if (activeProject.git.user) { + gitUsernameLabel.text(activeProject.git.user.name); + gitUsernameInput.val(activeProject.git.user.name); + + gitEmailLabel.text(activeProject.git.user.email); + gitEmailInput.val(activeProject.git.user.email); + } - var remotesList = $("
      ",{style:"height: 320px"}).appendTo(row); + var grTitle = $('

      ').text("Git remotes").appendTo(repoContainer); + + + row = $('').appendTo(repoContainer); + var remotesList = $('
        ').appendTo(row); remotesList.editableList({ - addButton: "remote", + addButton: 'add remote repository', + height: 'auto', addItem: function(outer,index,entry) { - var row = $('').appendTo(outer); - $('').text('Name').appendTo(row); - $('
        ').text(entry.name).appendTo(row); - row = $('').appendTo(outer); - $('').text('Fetch URL').appendTo(row); - $('
        ').text(entry.urls.fetch).appendTo(row); - row = $('').appendTo(outer); - $('').text('Push URL').appendTo(row); - $('
        ').text(entry.urls.push).appendTo(row); + + var header = $('
        ').appendTo(outer); + entry.header = $('').text(entry.name||"Add new remote").appendTo(header); + var body = $('
        ').appendTo(outer); + entry.body = body; + if (entry.name) { + entry.removeButton = $('') + .hide() + .appendTo(header) + .click(function(e) { + entry.removed = true; + body.fadeOut(100); + entry.header.css("text-decoration","line-through") + entry.header.css("font-style","italic") + $(this).hide(); + }); + if (entry.urls.fetch === entry.urls.push) { + row = $('').appendTo(body); + $('').text('URL').appendTo(row); + $('
        ').text(entry.urls.fetch).appendTo(row); + } else { + row = $('').appendTo(body); + $('').text('Fetch URL').appendTo(row); + $('
        ').text(entry.urls.fetch).appendTo(row); + row = $('').appendTo(body); + $('').text('Push URL').appendTo(row); + $('
        ').text(entry.urls.push).appendTo(row); + } + } else { + row = $('').appendTo(body); + $('').text('Remote name').appendTo(row); + entry.nameInput = $('').appendTo(row); + + row = $('').appendTo(body); + var fetchLabel = $('').text('URL').appendTo(row); + entry.urlInput = $('').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 = $('').appendTo(repoContainer); - - // if (activeProject.hasOwnProperty('remotes')) { - // $('').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) { - // $('
        ').text(repos.fetch).appendTo(row); - // $('
        ').appendTo(row).find('small').text(name+" fetch/push"); - // } else { - // $('
        ').text(repos.fetch).appendTo(row); - // $('
        ').appendTo(row).find('small').text(name+" fetch"); - // $('').appendTo(row); - // $('
        ').text(repos.push).appendTo(row); - // $('
        ').appendTo(row).find('small').text(name+" push"); - // // $('').text(repos.fetch+" (fetch)").appendTo(repoRow); - // // $('').text(repos.push+" (push)").appendTo(repoRow); - // } - // } - // } - // if (activeProject.branches.hasOwnProperty('remote')) { - // row = $('').appendTo(repoContainer); - // $('').appendTo(row); - // $('
        ').text(activeProject.branches.remote).appendTo(row); - // - // row = $('').appendTo(repoContainer); - // $('').appendTo(row); - // $('
        ').appendTo(row); - // - // row = $('').appendTo(repoContainer); - // $('').appendTo(row); - // $('
        ').html("• • • • • • • •").appendTo(row); - // } - // } else { - // - // - // } + var remoteListAddButton = row.find(".red-ui-editableList-addButton").hide(); var hideEditForm = function() { editRepoButton.show(); formButtons.hide(); + $('.projects-dialog-remote-list-entry-delete').hide(); + remoteListAddButton.hide(); + + gitUsernameLabel.show(); + gitUsernameInput.hide(); + gitEmailLabel.show(); + gitEmailInput.hide(); + } - var formButtons = $('').hide().appendTo(repoContainer); + var formButtons = $('') + .hide().appendTo(repoContainer); $('') .appendTo(formButtons) .click(function(evt) { evt.preventDefault(); + + var items = remotesList.editableList('items'); + items.each(function() { + var data = $(this).data('data'); + if (!data.name) { + remotesList.editableList('removeItem',data); + } else if (data.removed) { + delete data.removed; + data.body.show(); + data.header.css("text-decoration",""); + data.header.css("font-style",""); + } + }) + + hideEditForm(); }); var saveButton = $('') .appendTo(formButtons) .click(function(evt) { evt.preventDefault(); - hideEditForm(); + var spinner = utils.addSpinnerOverlay(repoContainer); + + var body = { + user: { + name: gitUsernameInput.val(), + email: gitEmailInput.val() + }, + remotes: {} + } + + var items = remotesList.editableList('items'); + items.each(function() { + var data = $(this).data('data'); + if (!data.name) { + body.remotes[data.nameInput.val()] = { + url: data.urlInput.val() + }; + remotesList.editableList('removeItem',data); + } else if (data.removed) { + body.remotes[data.name] = { + removed: true + }; + delete data.removed; + data.body.show(); + data.header.css("text-decoration",""); + data.header.css("font-style",""); + } + }) + + var done = function(err) { + spinner.remove(); + if (err) { + console.log(err); + return; + } + hideEditForm(); + } + var payload = { git: body }; + + // console.log(JSON.stringify(payload,null,4)); + RED.deploy.setDeployInflight(true); + utils.sendRequest({ + url: "projects/"+activeProject.name, + type: "PUT", + responses: { + 0: function(error) { + done(error); + }, + 200: function(data) { + activeProject.git.remotes = data.git.remotes; + activeProject.git.user = data.git.user; + if (activeProject.git.user) { + gitUsernameLabel.text(activeProject.git.user.name); + gitUsernameInput.val(activeProject.git.user.name); + gitEmailLabel.text(activeProject.git.user.email); + gitEmailInput.val(activeProject.git.user.email); + } + + updateForm(); + done(); + }, + 400: { + 'unexpected_error': function(error) { + console.log(error); + done(error); + } + }, + } + },payload); }); - var updateForm = function() { } + var updateForm = function() { + remotesList.editableList('empty'); + if (activeProject.git.hasOwnProperty('remotes')) { + for (var name in activeProject.git.remotes) { + if (activeProject.git.remotes.hasOwnProperty(name)) { + remotesList.editableList('addItem',{name:name,urls:activeProject.git.remotes[name]}); + } + } + } + } + updateForm(); } diff --git a/editor/js/ui/projects.js b/editor/js/ui/projects.js index 745c5c503..a25116349 100644 --- a/editor/js/ui/projects.js +++ b/editor/js/ui/projects.js @@ -397,12 +397,14 @@ RED.projects = (function() { projectData.copy = copyProject.name; } else if (projectType === 'clone') { // projectData.credentialSecret = projectSecretInput.val(); - projectData.remote = { - // name: projectRepoRemoteName.val()||'origin', - // branch: projectRepoBranch.val()||'master', - url: projectRepoInput.val(), - username: projectRepoUserInput.val(), - password: projectRepoPasswordInput.val() + projectData.git = { + remotes: { + 'origin': { + url: projectRepoInput.val(), + username: projectRepoUserInput.val(), + password: projectRepoPasswordInput.val() + } + } } } @@ -632,7 +634,7 @@ RED.projects = (function() { function sendRequest(options,body) { // dialogBody.hide(); - console.log(options.url); + console.log(options.url,body); if (options.requireCleanWorkspace && RED.nodes.dirty()) { var message = 'You have undeployed changes that will be lost. Do you want to continue?'; @@ -698,7 +700,7 @@ RED.projects = (function() { resultCallbackArgs = {error:responses.statusText}; return; } else if (options.handleAuthFail !== false && xhr.responseJSON.error === 'git_auth_failed') { - var url = activeProject.remotes.origin.fetch; + var url = activeProject.git.remotes.origin.fetch; var message = $('
        '+ '
        Authentication required for repository:
        '+ '
        '+url+'
        '+ @@ -723,6 +725,11 @@ RED.projects = (function() { var username = $('#projects-user-auth-username').val(); var password = $('#projects-user-auth-password').val(); body = body || {}; + var authBody = {git:{remotes:{}}}; + authBody.git.remotes[options.remote||'origin'] = { + username: username, + password: password + }; var done = function(err) { if (err) { console.log("Failed to update auth"); @@ -749,14 +756,7 @@ RED.projects = (function() { } }, } - },{ - remote: { - origin: { - username: username, - password: password - } - } - }); + },authBody); } } ] @@ -858,7 +858,17 @@ RED.projects = (function() { }, filter: function(data) { var isCreateEntry = (typeof data !=="string"); - return (isCreateEntry && (branchFilterTerm !== "" && branches.indexOf(branchPrefix+branchFilterTerm) === -1) ) || (!isCreateEntry && data.indexOf(branchPrefix+branchFilterTerm) !== -1); + return ( + isCreateEntry && + ( + branchFilterTerm !== "" && + branches.indexOf(branchPrefix+branchFilterTerm) === -1 + ) + ) || + ( + !isCreateEntry && + data.indexOf(branchFilterTerm) !== -1 + ); } }); return { diff --git a/editor/js/ui/tab-versionControl.js b/editor/js/ui/tab-versionControl.js index 96c0d76bf..1790e5859 100644 --- a/editor/js/ui/tab-versionControl.js +++ b/editor/js/ui/tab-versionControl.js @@ -226,7 +226,7 @@ RED.sidebar.versionControl = (function() { stagedChangesList.editableList('empty'); unmergedChangesList.editableList('empty'); - $.getJSON("/projects/"+activeProject.name+"/status",function(result) { + $.getJSON("projects/"+activeProject.name+"/status",function(result) { refreshFiles(result); }); } @@ -471,7 +471,7 @@ RED.sidebar.versionControl = (function() { localCommitListShade.show(); $(this).addClass('selected'); var activeProject = RED.projects.getActiveProject(); - localBranchList.refresh("/projects/"+activeProject.name+"/branches"); + localBranchList.refresh("projects/"+activeProject.name+"/branches"); localBranchBox.show(); setTimeout(function() { localBranchBox.css("height","215px"); @@ -523,7 +523,7 @@ RED.sidebar.versionControl = (function() { row.click(function(e) { var activeProject = RED.projects.getActiveProject(); if (activeProject) { - $.getJSON("/projects/"+activeProject.name+"/commits/"+entry.sha,function(result) { + $.getJSON("projects/"+activeProject.name+"/commits/"+entry.sha,function(result) { result.project = activeProject; result.parents = entry.parents; result.oldRev = entry.sha+"~1"; @@ -572,7 +572,7 @@ RED.sidebar.versionControl = (function() { var localBranchList = utils.createBranchList({ current: function() { - return RED.projects.getActiveProject().branches.local + return RED.projects.getActiveProject().git.branches.local }, placeholder: "Find or create a branch", container: localBranchBox, @@ -653,7 +653,7 @@ RED.sidebar.versionControl = (function() { } else { $(this).addClass('selected'); var activeProject = RED.projects.getActiveProject(); - remoteBranchList.refresh("/projects/"+activeProject.name+"/branches/remote"); + remoteBranchList.refresh("projects/"+activeProject.name+"/branches/remote"); remoteBranchSubRow.show(); setTimeout(function() { remoteBranchSubRow.height(180); @@ -676,7 +676,7 @@ RED.sidebar.versionControl = (function() { var activeProject = RED.projects.getActiveProject(); var spinner = utils.addSpinnerOverlay(remoteBox).addClass("projects-dialog-spinner-contain"); utils.sendRequest({ - url: "/projects/"+activeProject.name+"/branches/remote", + url: "projects/"+activeProject.name+"/branches/remote", type: "GET", responses: { 0: function(error) { @@ -706,13 +706,13 @@ RED.sidebar.versionControl = (function() { var remoteBranchSubRow = $('
        ').hide().appendTo(remoteBranchRow); var remoteBranchList = utils.createBranchList({ current: function() { - return RED.projects.getActiveProject().branches.remote + return RED.projects.getActiveProject().git.branches.remote }, placeholder: "Find or create a remote branch", currentLabel: "upstream", remote: function() { var project = RED.projects.getActiveProject(); - var remotes = Object.keys(project.remotes); + var remotes = Object.keys(project.git.remotes); return remotes[0]; }, container: remoteBranchSubRow, @@ -721,12 +721,12 @@ RED.sidebar.versionControl = (function() { $("#sidebar-version-control-repo-toolbar-set-upstream").prop('disabled',false); $("#sidebar-version-control-remote-branch").text(body.name+(body.create?" *":"")); var activeProject = RED.projects.getActiveProject(); - if (activeProject.branches.remote === body.name) { - delete activeProject.branches.remoteAlt; + if (activeProject.git.branches.remote === body.name) { + delete activeProject.git.branches.remoteAlt; } else { - activeProject.branches.remoteAlt = body.name; + activeProject.git.branches.remoteAlt = body.name; } - $("#sidebar-version-control-repo-toolbar-set-upstream-row").toggle(!!activeProject.branches.remoteAlt); + $("#sidebar-version-control-repo-toolbar-set-upstream-row").toggle(!!activeProject.git.branches.remoteAlt); closeRemoteBranchBox(function() { if (!body.create) { var start = Date.now(); @@ -738,7 +738,7 @@ RED.sidebar.versionControl = (function() { },Math.max(400-(Date.now() - start),0)); }) } else { - if (!activeProject.branches.remote) { + if (!activeProject.git.branches.remote) { $('#sidebar-version-control-repo-toolbar-message').text("The created branch will be set as the tracked upstream branch."); $("#sidebar-version-control-repo-toolbar-set-upstream").prop('checked',true); $("#sidebar-version-control-repo-toolbar-set-upstream").prop('disabled',true); @@ -762,8 +762,8 @@ RED.sidebar.versionControl = (function() { var spinner = utils.addSpinnerOverlay(remoteBox).addClass("projects-dialog-spinner-contain"); var activeProject = RED.projects.getActiveProject(); var url = "projects/"+activeProject.name+"/push"; - if (activeProject.branches.remoteAlt) { - url+="/"+activeProject.branches.remoteAlt; + if (activeProject.git.branches.remoteAlt) { + url+="/"+activeProject.git.branches.remoteAlt; } if ($("#sidebar-version-control-repo-toolbar-set-upstream").prop('checked')) { url+="?u=true" @@ -803,8 +803,8 @@ RED.sidebar.versionControl = (function() { var spinner = utils.addSpinnerOverlay(remoteBox).addClass("projects-dialog-spinner-contain"); var activeProject = RED.projects.getActiveProject(); var url = "projects/"+activeProject.name+"/pull"; - if (activeProject.branches.remoteAlt) { - url+="/"+activeProject.branches.remoteAlt; + if (activeProject.git.branches.remoteAlt) { + url+="/"+activeProject.git.branches.remoteAlt; } if ($("#sidebar-version-control-repo-toolbar-set-upstream").prop('checked')) { url+="?u=true" @@ -965,7 +965,7 @@ RED.sidebar.versionControl = (function() { localCommitList.editableList('empty'); var activeProject = RED.projects.getActiveProject(); if (activeProject) { - getCommits("/projects/"+activeProject.name+"/commits",localCommitList,localCommitList.parent()); + getCommits("projects/"+activeProject.name+"/commits",localCommitList,localCommitList.parent()); } } // function refreshRemoteCommits() { @@ -973,7 +973,7 @@ RED.sidebar.versionControl = (function() { // var spinner = utils.addSpinnerOverlay(remoteCommitList); // var activeProject = RED.projects.getActiveProject(); // if (activeProject) { - // getCommits("/projects/"+activeProject.name+"/commits/origin",remoteCommitList,remoteCommitList.parent()); + // getCommits("projects/"+activeProject.name+"/commits/origin",remoteCommitList,remoteCommitList.parent()); // } // } @@ -1127,7 +1127,7 @@ RED.sidebar.versionControl = (function() { var activeProject = RED.projects.getActiveProject(); if (activeProject) { - $.getJSON("/projects/"+activeProject.name+"/status",function(result) { + $.getJSON("projects/"+activeProject.name+"/status",function(result) { refreshFiles(result); $('#sidebar-version-control-local-branch').text(result.branches.local); @@ -1136,7 +1136,7 @@ RED.sidebar.versionControl = (function() { var commitsAhead = result.commits.ahead || 0; var commitsBehind = result.commits.behind || 0; - if (activeProject.hasOwnProperty('remotes')) { + if (activeProject.git.hasOwnProperty('remotes')) { if (result.branches.hasOwnProperty("remoteError")) { $("#sidebar-version-control-repo-status-auth-issue").show(); $("#sidebar-version-control-repo-status-stats").hide(); diff --git a/editor/js/ui/view.js b/editor/js/ui/view.js index 7358c1419..cc36df2ec 100644 --- a/editor/js/ui/view.js +++ b/editor/js/ui/view.js @@ -2766,7 +2766,7 @@ RED.view = (function() { if (v === undefined) { return gridSize; } else { - gridSize = v; + gridSize = Math.max(5,v); updateGrid(); } } diff --git a/editor/sass/jquery.scss b/editor/sass/jquery.scss index 749e2cd59..2dcf5589b 100644 --- a/editor/sass/jquery.scss +++ b/editor/sass/jquery.scss @@ -79,6 +79,7 @@ font-size: 14px; padding: 6px 14px; margin-right: 8px; + border-radius: 2px; color: $editor-button-color; background: $editor-button-background; diff --git a/editor/sass/projects.scss b/editor/sass/projects.scss index 0ea0f0a20..e34d52af9 100644 --- a/editor/sass/projects.scss +++ b/editor/sass/projects.scss @@ -180,7 +180,6 @@ border-right-color: #aaa; } } - i { color: #ccc; font-size: 2em; @@ -592,6 +591,9 @@ border-right-color:#999; } } + &.unselectable { + color: #ccc; + } i { color: #999; @@ -627,3 +629,47 @@ } } .projects-dialog-file-list-entry-file-type-git { color: #999 } + +.projects-dialog-remote-list { + .red-ui-editableList-container { + padding: 0; + li { + padding: 0; + border: none; + border-radius: 4px; + overflow: hidden; + } + } +} + +.projects-dialog-remote-list-entry-header { + padding: 8px 10px; + background: #f6f6f6; + margin-bottom: 8px; +} +.projects-dialog-remote-list-entry-delete { + float: right; +} + +/* +.expandable-list-entry { + .exandable-list-entry-header { + padding: 15px 0; + cursor: pointer; + &:hover { + background: #f3f3f3; + } + i { + width: 16px; + text-align: center; + } + .fa-angle-right { + color: #333; + transition: all 0.2s ease-in-out; + } + } + &.expanded .fa-angle-right { + transform: rotate(90deg); + } +} +*/ diff --git a/editor/sass/userSettings.scss b/editor/sass/userSettings.scss index 61c1f9ab8..b51186508 100644 --- a/editor/sass/userSettings.scss +++ b/editor/sass/userSettings.scss @@ -54,6 +54,10 @@ input[type='number'] { width: 60px; } + h4 { + margin-top: 20px; + margin-bottom: 10px; + } } #user-settings-tab-view { diff --git a/red/api/editor/projects/index.js b/red/api/editor/projects/index.js index 59d9b3691..8faab61df 100644 --- a/red/api/editor/projects/index.js +++ b/red/api/editor/projects/index.js @@ -17,6 +17,7 @@ var express = require("express"); var runtime; var settings; +var needsPermission = require("../../auth").needsPermission; module.exports = { init: function(_runtime) { @@ -29,9 +30,10 @@ module.exports = { // Projects // List all projects - app.get("/", function(req,res) { - runtime.storage.projects.listProjects().then(function(list) { - var active = runtime.storage.projects.getActiveProject(); + app.get("/", needsPermission("projects.read"), function(req,res) { + console.log(req.user); + runtime.storage.projects.listProjects(req.user, req.user).then(function(list) { + var active = runtime.storage.projects.getActiveProject(req.user); var response = { active: active.name, projects: list @@ -48,8 +50,8 @@ module.exports = { }); // Create project - app.post("/", function(req,res) { - runtime.storage.projects.createProject(req.body).then(function(data) { + app.post("/", needsPermission("projects.write"), function(req,res) { + runtime.storage.projects.createProject(req.user, req.body).then(function(data) { res.json(data); }).catch(function(err) { console.log(err.stack); @@ -62,12 +64,12 @@ module.exports = { }); // Update a project - app.put("/:id", function(req,res) { + app.put("/:id", needsPermission("projects.write"), function(req,res) { //TODO: validate the payload properly if (req.body.active) { - var currentProject = runtime.storage.projects.getActiveProject(); + var currentProject = runtime.storage.projects.getActiveProject(req.user); if (req.params.id !== currentProject.name) { - runtime.storage.projects.setActiveProject(req.params.id).then(function() { + runtime.storage.projects.setActiveProject(req.user, req.params.id).then(function() { res.redirect(303,req.baseUrl + '/'); }).catch(function(err) { if (err.code) { @@ -84,8 +86,8 @@ module.exports = { req.body.hasOwnProperty('dependencies')|| req.body.hasOwnProperty('summary') || req.body.hasOwnProperty('files') || - req.body.hasOwnProperty('remote')) { - runtime.storage.projects.updateProject(req.params.id, req.body).then(function() { + req.body.hasOwnProperty('git')) { + runtime.storage.projects.updateProject(req.user, req.params.id, req.body).then(function() { res.redirect(303,req.baseUrl + '/'+ req.params.id); }).catch(function(err) { if (err.code) { @@ -100,8 +102,8 @@ module.exports = { }); // Get project metadata - app.get("/:id", function(req,res) { - runtime.storage.projects.getProject(req.params.id).then(function(data) { + app.get("/:id", needsPermission("projects.read"), function(req,res) { + runtime.storage.projects.getProject(req.user, req.params.id).then(function(data) { if (data) { res.json(data); } else { @@ -114,13 +116,13 @@ module.exports = { }); // Delete project - tbd - app.delete("/:id", function(req,res) { + app.delete("/:id", needsPermission("projects.write"), function(req,res) { }); // Get project status - files, commit counts, branch info - app.get("/:id/status", function(req,res) { - runtime.storage.projects.getStatus(req.params.id).then(function(data) { + app.get("/:id/status", needsPermission("projects.read"), function(req,res) { + runtime.storage.projects.getStatus(req.user, req.params.id).then(function(data) { if (data) { res.json(data); } else { @@ -134,8 +136,8 @@ module.exports = { // Project file listing - app.get("/:id/files", function(req,res) { - runtime.storage.projects.getFiles(req.params.id).then(function(data) { + app.get("/:id/files", needsPermission("projects.read"), function(req,res) { + runtime.storage.projects.getFiles(req.user, req.params.id).then(function(data) { console.log("TODO: REMOVE /:id/files as /:id/status is better!") res.json(data); }) @@ -147,12 +149,12 @@ module.exports = { // Get file content in a given tree (index/stage) - app.get("/:id/files/:treeish/*", function(req,res) { + app.get("/:id/files/:treeish/*", needsPermission("projects.read"), function(req,res) { var projectId = req.params.id; var treeish = req.params.treeish; var filePath = req.params[0]; - runtime.storage.projects.getFile(projectId,filePath,treeish).then(function(data) { + runtime.storage.projects.getFile(req.user, projectId,filePath,treeish).then(function(data) { res.json({content:data}); }) .catch(function(err) { @@ -162,11 +164,11 @@ module.exports = { }); // Stage a file - app.post("/:id/stage/*", function(req,res) { + app.post("/:id/stage/*", needsPermission("projects.write"), function(req,res) { var projectName = req.params.id; var file = req.params[0]; - runtime.storage.projects.stageFile(projectName,file).then(function(data) { + runtime.storage.projects.stageFile(req.user, projectName,file).then(function(data) { res.redirect(303,req.baseUrl+"/"+projectName+"/status"); }) .catch(function(err) { @@ -176,11 +178,11 @@ module.exports = { }); // Stage multiple files - app.post("/:id/stage", function(req,res) { + app.post("/:id/stage", needsPermission("projects.write"), function(req,res) { var projectName = req.params.id; var files = req.body.files; - runtime.storage.projects.stageFile(projectName,files).then(function(data) { + runtime.storage.projects.stageFile(req.user, projectName,files).then(function(data) { res.redirect(303,req.baseUrl+"/"+projectName+"/status"); }) .catch(function(err) { @@ -190,10 +192,10 @@ module.exports = { }); // Commit changes - app.post("/:id/commit", function(req,res) { + app.post("/:id/commit", needsPermission("projects.write"), function(req,res) { var projectName = req.params.id; - runtime.storage.projects.commit(projectName,req.body).then(function(data) { + runtime.storage.projects.commit(req.user, projectName,req.body).then(function(data) { res.redirect(303,req.baseUrl+"/"+projectName+"/status"); }) .catch(function(err) { @@ -203,11 +205,11 @@ module.exports = { }); // Unstage a file - app.delete("/:id/stage/*", function(req,res) { + app.delete("/:id/stage/*", needsPermission("projects.write"), function(req,res) { var projectName = req.params.id; var file = req.params[0]; - runtime.storage.projects.unstageFile(projectName,file).then(function(data) { + runtime.storage.projects.unstageFile(req.user, projectName,file).then(function(data) { res.redirect(303,req.baseUrl+"/"+projectName+"/status"); }) .catch(function(err) { @@ -217,9 +219,9 @@ module.exports = { }); // Unstage multiple files - app.delete("/:id/stage", function(req, res) { + app.delete("/:id/stage", needsPermission("projects.write"), function(req, res) { var projectName = req.params.id; - runtime.storage.projects.unstageFile(projectName).then(function(data) { + runtime.storage.projects.unstageFile(req.user, projectName).then(function(data) { res.redirect(303,req.baseUrl+"/"+projectName+"/status"); }) .catch(function(err) { @@ -229,11 +231,11 @@ module.exports = { }); // Get a file diff - app.get("/:id/diff/:type/*", function(req,res) { + app.get("/:id/diff/:type/*", needsPermission("projects.read"), function(req,res) { var projectName = req.params.id; var type = req.params.type; var file = req.params[0]; - runtime.storage.projects.getFileDiff(projectName,file,type).then(function(data) { + runtime.storage.projects.getFileDiff(req.user, projectName,file,type).then(function(data) { res.json({ diff: data }) @@ -245,13 +247,13 @@ module.exports = { }); // Get a list of commits - app.get("/:id/commits", function(req, res) { + app.get("/:id/commits", needsPermission("projects.read"), function(req, res) { var projectName = req.params.id; var options = { limit: req.query.limit||20, before: req.query.before }; - runtime.storage.projects.getCommits(projectName,options).then(function(data) { + runtime.storage.projects.getCommits(req.user, projectName,options).then(function(data) { res.json(data); }) .catch(function(err) { @@ -265,11 +267,11 @@ module.exports = { }); // Get an individual commit details - app.get("/:id/commits/:sha", function(req, res) { + app.get("/:id/commits/:sha", needsPermission("projects.read"), function(req, res) { var projectName = req.params.id; var sha = req.params.sha; - runtime.storage.projects.getCommit(projectName,sha).then(function(data) { + runtime.storage.projects.getCommit(req.user, projectName,sha).then(function(data) { res.json({commit:data}); }) .catch(function(err) { @@ -279,11 +281,11 @@ module.exports = { }); // Push local commits to remote - app.post("/:id/push/?*", function(req,res) { + app.post("/:id/push/?*", needsPermission("projects.write"), 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) { + runtime.storage.projects.push(req.user, projectName,remoteBranchName,setRemote).then(function(data) { res.status(204).end(); }) .catch(function(err) { @@ -297,11 +299,11 @@ module.exports = { }); // Pull remote commits - app.post("/:id/pull/?*", function(req,res) { + app.post("/:id/pull/?*", needsPermission("projects.write"), 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) { + runtime.storage.projects.pull(req.user, projectName,remoteBranchName,setRemote).then(function(data) { res.status(204).end(); }) .catch(function(err) { @@ -315,9 +317,9 @@ module.exports = { }); // Abort an ongoing merge - app.delete("/:id/merge", function(req, res) { + app.delete("/:id/merge", needsPermission("projects.write"), function(req, res) { var projectName = req.params.id; - runtime.storage.projects.abortMerge(projectName).then(function(data) { + runtime.storage.projects.abortMerge(req.user, projectName).then(function(data) { res.status(204).end(); }) .catch(function(err) { @@ -331,11 +333,11 @@ module.exports = { }); // Resolve a merge - app.post("/:id/resolve/*", function(req, res) { + app.post("/:id/resolve/*", needsPermission("projects.write"), 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) { + runtime.storage.projects.resolveMerge(req.user, projectName,file,resolution).then(function(data) { res.status(204).end(); }) .catch(function(err) { @@ -349,9 +351,9 @@ module.exports = { }); // Get a list of local branches - app.get("/:id/branches", function(req, res) { + app.get("/:id/branches", needsPermission("projects.read"), function(req, res) { var projectName = req.params.id; - runtime.storage.projects.getBranches(projectName,false).then(function(data) { + runtime.storage.projects.getBranches(req.user, projectName,false).then(function(data) { res.json(data); }) .catch(function(err) { @@ -365,12 +367,13 @@ module.exports = { }); // Get a list of remote branches - app.get("/:id/branches/remote", function(req, res) { + app.get("/:id/branches/remote", needsPermission("projects.read"), function(req, res) { var projectName = req.params.id; - runtime.storage.projects.getBranches(projectName,true).then(function(data) { + runtime.storage.projects.getBranches(req.user, 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 { @@ -380,10 +383,10 @@ module.exports = { }); // Get branch status - commit counts/ahead/behind - app.get("/:id/branches/remote/*/status", function(req, res) { + app.get("/:id/branches/remote/*/status", needsPermission("projects.read"), function(req, res) { var projectName = req.params.id; var branch = req.params[0]; - runtime.storage.projects.getBranchStatus(projectName,branch).then(function(data) { + runtime.storage.projects.getBranchStatus(req.user, projectName,branch).then(function(data) { res.json(data); }) .catch(function(err) { @@ -397,11 +400,11 @@ module.exports = { }); // Set the active local branch - app.post("/:id/branches", function(req, res) { + app.post("/:id/branches", needsPermission("projects.write"), 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) { + runtime.storage.projects.setBranch(req.user, projectName,branchName,isCreate).then(function(data) { res.json(data); }) .catch(function(err) { diff --git a/red/api/index.js b/red/api/index.js index 7336b2331..0cc597acc 100644 --- a/red/api/index.js +++ b/red/api/index.js @@ -46,13 +46,6 @@ function init(_server,_runtime) { adminApp.use(bodyParser.json({limit:maxApiRequestSize})); adminApp.use(bodyParser.urlencoded({limit:maxApiRequestSize,extended:true})); - // Editor - if (!settings.disableEditor) { - editor = require("./editor"); - var editorApp = editor.init(server, runtime); - adminApp.use(editorApp); - } - adminApp.get("/auth/login",auth.login,apiUtil.errorHandler); if (settings.adminAuth) { if (settings.adminAuth.type === "strategy") { @@ -68,6 +61,13 @@ function init(_server,_runtime) { } adminApp.post("/auth/revoke",auth.needsPermission(""),auth.revoke,apiUtil.errorHandler); } + + // Editor + if (!settings.disableEditor) { + editor = require("./editor"); + var editorApp = editor.init(server, runtime); + adminApp.use(editorApp); + } if (settings.httpAdminCors) { var corsHandler = cors(settings.httpAdminCors); diff --git a/red/runtime/storage/localfilesystem/projects/Project.js b/red/runtime/storage/localfilesystem/projects/Project.js index eea9035ca..8893d303a 100644 --- a/red/runtime/storage/localfilesystem/projects/Project.js +++ b/red/runtime/storage/localfilesystem/projects/Project.js @@ -51,9 +51,9 @@ Project.prototype.load = function () { } this.credentialSecret = projectSettings.credentialSecret; + this.git = projectSettings.git || { user:{} }; - this.remote = projectSettings.remote; - +console.log("LOADED",this.git); // this.paths.flowFile = fspath.join(this.path,"flow.json"); // this.paths.credentialsFile = fspath.join(this.path,"flow_cred.json"); @@ -103,12 +103,7 @@ 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; - })); + promises.push(project.loadRemotes()); return when.settle(promises).then(function() { return project; @@ -116,7 +111,51 @@ Project.prototype.load = function () { }); }; -Project.prototype.update = function (data) { +Project.prototype.loadRemotes = function() { + var project = this; + return gitTools.getRemotes(project.path).then(function(remotes) { + project.remotes = remotes; + }).then(function() { + return project.loadBranches(); + }).then(function() { + + var allRemotes = Object.keys(project.remotes); + var match = ""; + allRemotes.forEach(function(remote) { + if (project.branches.remote.indexOf(remote) === 0 && match.length < remote.length) { + match = remote; + } + }); + project.currentRemote = project.parseRemoteBranch(project.branches.remote).remote; + }); +} + +Project.prototype.parseRemoteBranch = function (remoteBranch) { + if (!remoteBranch) { + return {} + } + var project = this; + var allRemotes = Object.keys(project.remotes); + var match = ""; + allRemotes.forEach(function(remote) { + if (remoteBranch.indexOf(remote) === 0 && match.length < remote.length) { + match = remote; + } + }); + return { + remote: match, + branch: remoteBranch.substring(match.length+1) + } + +}; + +Project.prototype.loadBranches = function() { + var project = this; + return gitTools.getBranchInfo(project.path).then(function(branches) { + project.branches = branches; + }); +} +Project.prototype.update = function (user, data) { var promises = []; var project = this; @@ -172,13 +211,63 @@ Project.prototype.update = function (data) { savePackage = true; this.package.description = data.summary; } - if (data.hasOwnProperty('remote')) { - for (var remote in data.remote) { - if (data.remote.hasOwnProperty(remote)) { - authCache.set(project.name,remote,data.remote[remote]); + // if (data.hasOwnProperty('remote')) { + // // TODO: this MUST move under 'git' + // for (var remote in data.remote) { + // if (data.remote.hasOwnProperty(remote)) { + // authCache.set(project.name,remote,data.remote[remote]); + // } + // } + // } + + if (data.hasOwnProperty('git')) { + if (data.git.hasOwnProperty('user')) { + var username; + if (!user) { + username = "_"; + } else { + username = user.username; + } + globalProjectSettings.projects[this.name].git = globalProjectSettings.projects[this.name].git || {}; + globalProjectSettings.projects[this.name].git.user = globalProjectSettings.projects[this.name].git.user || {}; + globalProjectSettings.projects[this.name].git.user[username] = { + name: data.git.user.name, + email: data.git.user.email + } + this.git.user[username] = { + name: data.git.user.name, + email: data.git.user.email + } + saveSettings = true; + } + if (data.git.hasOwnProperty('remotes')) { + var remoteNames = Object.keys(data.git.remotes); + var remotesChanged = false; + var modifyRemotesPromise = when.resolve(); + remoteNames.forEach(function(name) { + if (data.git.remotes[name].removed) { + remotesChanged = true; + modifyRemotesPromise = modifyRemotesPromise.then(function() { gitTools.removeRemote(project.path,name) }); + } else { + if (data.git.remotes[name].url) { + remotesChanged = true; + modifyRemotesPromise = modifyRemotesPromise.then(function() { gitTools.addRemote(project.path,name,data.git.remotes[name])}); + } + if (data.git.remotes[name].username && data.git.remotes[name].password) { + var url = data.git.remotes[name].url || project.remotes[name].fetch; + authCache.set(project.name,url,data.git.remotes[name]); + } + } + }) + if (remotesChanged) { + modifyRemotesPromise = modifyRemotesPromise.then(function() { + return project.loadRemotes(); + }); + promises.push(modifyRemotesPromise); } } } + if (data.hasOwnProperty('files')) { this.package['node-red'] = this.package['node-red'] || { settings: {}}; if (data.files.hasOwnProperty('flow') && this.package['node-red'].settings.flowFile !== data.files.flow) { @@ -223,8 +312,15 @@ Project.prototype.stageFile = function(file) { Project.prototype.unstageFile = function(file) { return gitTools.unstageFile(this.path,file); } -Project.prototype.commit = function(options) { - return gitTools.commit(this.path,options.message); +Project.prototype.commit = function(user, options) { + var username; + if (!user) { + username = "_"; + } else { + username = user.username; + } + var gitUser = this.git.user[username]; + return gitTools.commit(this.path,options.message,gitUser); } Project.prototype.getFileDiff = function(file,type) { return gitTools.getFileDiff(this.path,file,type); @@ -247,8 +343,17 @@ Project.prototype.status = function() { var self = this; var fetchPromise; - if (this.remote) { - fetchPromise = gitTools.fetch(this.path,authCache.get(this.name,'origin')); + if (this.remotes) { + fetchPromise = gitTools.getRemoteBranch(self.path).then(function(remoteBranch) { + var allRemotes = Object.keys(self.remotes); + var match = ""; + allRemotes.forEach(function(remote) { + if (remoteBranch.indexOf(remote) === 0 && match.length < remote.length) { + match = remote; + } + }) + return self.fetch(match); + }); } else { fetchPromise = when.resolve(); } @@ -267,6 +372,7 @@ Project.prototype.status = function() { self.branches.remote = result.branches.remote; if (fetchError) { result.branches.remoteError = { + remote: fetchError.remote, code: fetchError.code } } @@ -283,17 +389,19 @@ Project.prototype.status = function() { }; Project.prototype.push = function (remoteBranchName,setRemote) { - return gitTools.push(this.path, remoteBranchName, setRemote, authCache.get(this.name,'origin')); + var remote = this.parseRemoteBranch(remoteBranchName); + return gitTools.push(this.path, remote.remote || this.currentRemote,remote.branch, setRemote, authCache.get(this.name,this.remotes[this.currentRemote].fetch)); }; Project.prototype.pull = function (remoteBranchName,setRemote) { var self = this; if (setRemote) { return gitTools.setUpstream(this.path, remoteBranchName).then(function() { - return gitTools.pull(self.path, null, authCache.get(self.name,'origin')); + return gitTools.pull(self.path, null, null, authCache.get(self.name,this.remotes[this.currentRemote].fetch)); }) } else { - return gitTools.pull(this.path, remoteBranchName, authCache.get(this.name,'origin')); + var remote = this.parseRemoteBranch(remoteBranchName); + return gitTools.pull(this.path, remote.remote, remote.branch, authCache.get(this.name,this.remotes[this.currentRemote].fetch)); } }; @@ -342,18 +450,41 @@ Project.prototype.abortMerge = function () { return gitTools.abortMerge(this.path); }; -Project.prototype.getBranches = function (remote) { +Project.prototype.getBranches = function (isRemote) { var self = this; var fetchPromise; - if (remote) { - fetchPromise = gitTools.fetch(this.path,authCache.get(this.name,'origin')) + if (isRemote) { + fetchPromise = self.fetch(); } else { fetchPromise = when.resolve(); } return fetchPromise.then(function() { - return gitTools.getBranches(self.path,remote); + return gitTools.getBranches(self.path,isRemote); }); }; + +Project.prototype.fetch = function(remoteName) { + var project = this; + if (remoteName) { + return gitTools.fetch(project.path,remoteName,authCache.get(project.name,project.remotes[remoteName].fetch)).catch(function(err) { + err.remote = remoteName; + throw err; + }) + } else { + var remotes = Object.keys(this.remotes); + var promise = when.resolve(); + remotes.forEach(function(remote) { + promise = promise.then(function() { + return gitTools.fetch(project.path,remote,authCache.get(project.name,project.remotes[remote].fetch)) + }).catch(function(err) { + err.remote = remote; + throw err; + }) + }); + return promise; + } +} + Project.prototype.setBranch = function (branchName, isCreate) { var self = this; return gitTools.checkoutBranch(this.path, branchName, isCreate).then(function() { @@ -402,8 +533,10 @@ Project.prototype.toJSON = function () { flow: this.paths.flowFile, credentials: this.paths.credentialsFile }, - remotes: this.remotes, - branches: this.branches + git: { + remotes: this.remotes, + branches: this.branches + } } }; @@ -438,7 +571,7 @@ function createProjectDirectory(project) { return fs.ensureDir(projectPath); } -function createDefaultProject(project) { +function createDefaultProject(user, project) { var projectPath = fspath.join(projectsDir,project.name); // Create a basic skeleton of a project return gitTools.initRepo(projectPath).then(function() { @@ -502,7 +635,7 @@ function checkProjectFiles(project) { }); } -function createProject(metadata) { +function createProject(user, metadata) { var project = metadata.name; return when.promise(function(resolve,reject) { var projectPath = fspath.join(projectsDir,project); @@ -518,23 +651,20 @@ function createProject(metadata) { if (metadata.hasOwnProperty('credentialSecret')) { projects.projects[project].credentialSecret = metadata.credentialSecret; } - if (metadata.remote) { - projects.projects[project].remote = metadata.remote; - } return settings.set('projects',projects); }).then(function() { - if (metadata.remote) { + if (metadata.git && metadata.git.remotes && metadata.git.remotes.origin) { + var originRemote = metadata.git.remotes.origin; var auth; - if (metadata.remote.hasOwnProperty("username") && metadata.remote.hasOwnProperty("password")) { - authCache.set(project,'origin',{ // TODO: hardcoded remote name - username: metadata.remote.username, - password: metadata.remote.password + if (originRemote.hasOwnProperty("username") && originRemote.hasOwnProperty("password")) { + authCache.set(project,originRemote.url,{ // TODO: hardcoded remote name + username: originRemote.username, + password: originRemote.password } ); - auth = authCache.get(project,'origin'); + auth = authCache.get(project,originRemote.url); } - - return gitTools.clone(metadata.remote,auth,projectPath).then(function(result) { + return gitTools.clone(originRemote,auth,projectPath).then(function(result) { // Check this is a valid project // If it is empty // - if 'populate' flag is set, call populateProject @@ -554,7 +684,7 @@ function createProject(metadata) { }); }) } else { - createDefaultProject(metadata).then(function() { resolve(getProject(project))}).catch(reject); + createDefaultProject(user, metadata).then(function() { resolve(getProject(project))}).catch(reject); } }).catch(reject); }) diff --git a/red/runtime/storage/localfilesystem/projects/git/authCache.js b/red/runtime/storage/localfilesystem/projects/git/authCache.js index 40f0ba9c5..6d925fcbb 100644 --- a/red/runtime/storage/localfilesystem/projects/git/authCache.js +++ b/red/runtime/storage/localfilesystem/projects/git/authCache.js @@ -25,15 +25,13 @@ module.exports = { delete authCache[project]; }, set: function(project,remote,auth) { - if (authCache.hasOwnProperty(project)) { - authCache[project][remote] = auth; - } else { - authCache[project] = { - remote: auth - } - } + console.log("AuthCache.set",remote,auth); + authCache[project] = authCache[project]||{}; + authCache[project][remote] = auth; + // console.log(JSON.stringify(authCache,'',4)); }, get: function(project,remote) { + console.log("AuthCache.get",remote,authCache[project]&&authCache[project][remote]); if (authCache.hasOwnProperty(project)) { return authCache[project][remote]; } diff --git a/red/runtime/storage/localfilesystem/projects/git/authServer.js b/red/runtime/storage/localfilesystem/projects/git/authServer.js index fc94edfaf..a8048721a 100644 --- a/red/runtime/storage/localfilesystem/projects/git/authServer.js +++ b/red/runtime/storage/localfilesystem/projects/git/authServer.js @@ -37,7 +37,6 @@ function getListenPath() { 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) { @@ -46,6 +45,7 @@ var ResponseServer = function(auth) { parts.push(data.substring(0, m)); data = data.substring(m); var line = parts.join(""); + console.log("LINE",line); parts = []; if (line==='Username') { connection.end(auth.username); diff --git a/red/runtime/storage/localfilesystem/projects/git/index.js b/red/runtime/storage/localfilesystem/projects/git/index.js index 58b1a4a17..ff6e76c4f 100644 --- a/red/runtime/storage/localfilesystem/projects/git/index.js +++ b/red/runtime/storage/localfilesystem/projects/git/index.js @@ -311,6 +311,15 @@ function getBranchStatus(cwd,remoteBranch) { }) } +function addRemote(cwd,name,options) { + var args = ["remote","add",name,options.url] + return runGitCommand(args,cwd); +} +function removeRemote(cwd,name) { + var args = ["remote","remove",name]; + return runGitCommand(args,cwd); +} + module.exports = { init: function(_settings,_runtime) { log = _runtime.log @@ -322,12 +331,11 @@ module.exports = { var args = ["branch","--set-upstream-to",remoteBranch]; return runGitCommand(args,cwd); }, - pull: function(cwd,remoteBranch,auth) { + pull: function(cwd,remote,branch,auth) { var args = ["pull"]; - var m = /^(.*?)\/(.*)$/.exec(remoteBranch); - if (m) { - args.push(m[1]); - args.push(m[2]) + if (remote && branch) { + args.push(remote); + args.push(branch); } var promise; if (auth) { @@ -348,17 +356,16 @@ module.exports = { throw err; }); }, - push: function(cwd,remoteBranch,setUpstream, auth) { + push: function(cwd,remote,branch,setUpstream, auth) { var args = ["push"]; - var m = /^(.*?)\/(.*)$/.exec(remoteBranch); - if (m) { + if (branch) { if (setUpstream) { args.push("-u"); } - args.push(m[1]); - args.push("HEAD:"+m[2]); + args.push(remote); + args.push("HEAD:"+branch); } else { - args.push("origin"); + args.push(remote); } args.push("--porcelain"); var promise; @@ -419,9 +426,16 @@ module.exports = { } return runGitCommand(args,cwd); }, - commit: function(cwd, message) { + commit: function(cwd, message, gitUser) { var args = ["commit","-m",message]; - return runGitCommand(args,cwd); + var env; + if (gitUser && gitUser['name'] && gitUser['email']) { + args.unshift('user.name="'+gitUser['name']+'"'); + args.unshift('-c'); + args.unshift('user.email="'+gitUser['email']+'"'); + args.unshift('-c'); + } + return runGitCommand(args,cwd,env); }, getFileDiff(cwd,file,type) { var args = ["diff"]; @@ -433,8 +447,8 @@ module.exports = { args.push(file); return runGitCommand(args,cwd); }, - fetch: function(cwd,auth) { - var args = ["fetch"]; + fetch: function(cwd,remote,auth) { + var args = ["fetch",remote]; if (auth) { return runGitCommandWithAuth(args,cwd,auth); } else { @@ -474,6 +488,9 @@ module.exports = { return runGitCommand(['merge','--abort'],cwd); }, getRemotes: getRemotes, + getRemoteBranch: function(cwd) { + return runGitCommand(['rev-parse','--abbrev-ref','--symbolic-full-name','@{u}'],cwd) + }, getBranches: getBranches, getBranchInfo: getBranchInfo, checkoutBranch: function(cwd, branchName, isCreate) { @@ -484,5 +501,7 @@ module.exports = { args.push(branchName); return runGitCommand(args,cwd); }, - getBranchStatus: getBranchStatus + getBranchStatus: getBranchStatus, + addRemote: addRemote, + removeRemote: removeRemote } diff --git a/red/runtime/storage/localfilesystem/projects/index.js b/red/runtime/storage/localfilesystem/projects/index.js index 5e0eedbb8..dfa9ac339 100644 --- a/red/runtime/storage/localfilesystem/projects/index.js +++ b/red/runtime/storage/localfilesystem/projects/index.js @@ -96,6 +96,11 @@ function init(_settings, _runtime) { } } +function getUserGitSettings(user) { + var userSettings = settings.getUserSettings(user)||{}; + return userSettings.git; +} + function getBackupFilename(filename) { var ffName = fspath.basename(filename); var ffDir = fspath.dirname(filename); @@ -113,10 +118,27 @@ function loadProject(name) { }) } -function getProject(name) { +function listProjects(user) { + return Projects.list(); +} + +function getProject(user, name) { checkActiveProject(name); //return when.resolve(activeProject.info); - return Projects.get(name); + var username; + if (!user) { + username = "_"; + } else { + username = user.username; + } + return Projects.get(name).then(function(project) { + var result = project.toJSON(); + var projectSettings = settings.get("projects").projects; + if (projectSettings[name].git && projectSettings[name].git.user[username]) { + result.git.user = projectSettings[name].git.user[username]; + } + return result; + }); } function checkActiveProject(project) { @@ -125,78 +147,78 @@ function checkActiveProject(project) { throw new Error("Cannot operate on inactive project wanted:"+project+" current:"+(activeProject&&activeProject.name)); } } -function getFiles(project) { +function getFiles(user, project) { checkActiveProject(project); return activeProject.getFiles(); } -function stageFile(project,file) { +function stageFile(user, project,file) { checkActiveProject(project); return activeProject.stageFile(file); } -function unstageFile(project,file) { +function unstageFile(user, project,file) { checkActiveProject(project); return activeProject.unstageFile(file); } -function commit(project,options) { +function commit(user, project,options) { checkActiveProject(project); - return activeProject.commit(options); + return activeProject.commit(user, options); } -function getFileDiff(project,file,type) { +function getFileDiff(user, project,file,type) { checkActiveProject(project); return activeProject.getFileDiff(file,type); } -function getCommits(project,options) { +function getCommits(user, project,options) { checkActiveProject(project); return activeProject.getCommits(options); } -function getCommit(project,sha) { +function getCommit(user, project,sha) { checkActiveProject(project); return activeProject.getCommit(sha); } -function getFile(project,filePath,sha) { +function getFile(user, project,filePath,sha) { checkActiveProject(project); return activeProject.getFile(filePath,sha); } -function push(project,remoteBranchName,setRemote) { +function push(user, project,remoteBranchName,setRemote) { checkActiveProject(project); return activeProject.push(remoteBranchName,setRemote); } -function pull(project,remoteBranchName,setRemote) { +function pull(user, project,remoteBranchName,setRemote) { checkActiveProject(project); return activeProject.pull(remoteBranchName,setRemote).then(function() { return reloadActiveProject("pull"); }); } -function getStatus(project) { +function getStatus(user, project) { checkActiveProject(project); return activeProject.status(); } -function resolveMerge(project,file,resolution) { +function resolveMerge(user, project,file,resolution) { checkActiveProject(project); return activeProject.resolveMerge(file,resolution); } -function abortMerge(project) { +function abortMerge(user, project) { checkActiveProject(project); return activeProject.abortMerge().then(function() { return reloadActiveProject("abort-merge") }); } -function getBranches(project,remote) { +function getBranches(user, project,isRemote) { checkActiveProject(project); - return activeProject.getBranches(remote); + return activeProject.getBranches(isRemote); } -function setBranch(project,branchName,isCreate) { +function setBranch(user, project,branchName,isCreate) { checkActiveProject(project); return activeProject.setBranch(branchName,isCreate).then(function() { return reloadActiveProject("change-branch"); }); } -function getBranchStatus(project,branchName) { +function getBranchStatus(user, project,branchName) { checkActiveProject(project); return activeProject.getBranchStatus(branchName); } -function getActiveProject() { +function getActiveProject(user) { return activeProject; } @@ -212,14 +234,15 @@ function reloadActiveProject(action) { }); }); } -function createProject(metadata) { - return Projects.create(metadata).then(function(p) { - return setActiveProject(p.name); +function createProject(user, metadata) { + // var userSettings = getUserGitSettings(user); + return Projects.create(null,metadata).then(function(p) { + return setActiveProject(user, p.name); }).then(function() { - return getProject(metadata.name); + return getProject(user, metadata.name); }) } -function setActiveProject(projectName) { +function setActiveProject(user, projectName) { return loadProject(projectName).then(function(project) { var globalProjectSettings = settings.get("projects"); globalProjectSettings.activeProject = project.name; @@ -234,7 +257,7 @@ function setActiveProject(projectName) { }) }); } -function updateProject(project,data) { +function updateProject(user, project, data) { if (!activeProject || activeProject.name !== project) { // TODO standardise throw new Error("Cannot update inactive project"); @@ -243,7 +266,7 @@ function updateProject(project,data) { var isReset = data.resetCredentialSecret; var wasInvalid = activeProject.credentialSecretInvalid; - return activeProject.update(data).then(function(result) { + return activeProject.update(user,data).then(function(result) { if (result.flowFilesChanged) { flowsFullPath = activeProject.getFlowFile(); @@ -371,7 +394,7 @@ function saveCredentials(credentials) { module.exports = { init: init, - listProjects: Projects.list, + listProjects: listProjects, getActiveProject: getActiveProject, setActiveProject: setActiveProject, getProject: getProject,