Add commit history view in sidebar

This commit is contained in:
Nick O'Leary 2017-10-09 23:37:19 +01:00
parent eae390acf5
commit 19c84eb694
No known key found for this signature in database
GPG Key ID: 4F2157149161A6C9
7 changed files with 346 additions and 69 deletions

View File

@ -1509,10 +1509,10 @@ RED.diff = (function() {
label.text("@@ -"+(diffLines[end-1].a.i+1)+" +"+(diffLines[end-1].b.i+1));
}
diffRow.click(function(evt) {
console.log(start,end,diffLines.length);
// console.log(start,end,diffLines.length);
if (end - start > 20) {
var startPos = $(this).offset();
console.log(startPos);
// console.log(startPos);
if (start > 0) {
for (var i=start;i<start+10;i++) {
createDiffLine(diffLines[i]).addClass("unchanged").insertBefore($(this));
@ -1646,8 +1646,63 @@ RED.diff = (function() {
return string1 === string2 ? 0 : 1;
}
function showUnifiedDiff(diff,title) {
var hunks = parseUnifiedDiff(diff);
function createUnifiedDiffTable(files) {
var diffPanel = $('<div></div>');
files.forEach(function(file) {
var hunks = file.hunks;
var codeTable = $("<table>").appendTo(diffPanel);
$('<colgroup><col width="50"><col width="50"><col width="100%"></colgroup>').appendTo(codeTable);
var codeBody = $('<tbody>').appendTo(codeTable);
var diffRow = $('<tr class="node-text-diff-file-header">').appendTo(codeBody);
var content = $('<td colspan="3"></td>').appendTo(diffRow);
var label = $('<span></span>').text(file.file).appendTo(content);
for (var i=0;i<hunks.length;i++) {
var diffRow = $('<tr class="node-text-diff-header">').appendTo(codeBody);
var content = $('<td colspan="3"></td>').appendTo(diffRow);
var label = $('<span></span>').text(hunks[i].header).appendTo(content);
var localLine = hunks[i].localStartLine;
var remoteLine = hunks[i].remoteStartLine;
for (var j=0;j<hunks[i].lines.length;j++) {
var lineText = hunks[i].lines[j];
if (lineText[0] === '\\' || lineText === "") {
// Comment line - bail out of this hunk
break;
}
diffRow = $('<tr>').appendTo(codeBody);
var localLineNo = $('<td class="lineno">').appendTo(diffRow);
var remoteLineNo = $('<td class="lineno">').appendTo(diffRow);
var line = $('<td class="linetext">').appendTo(diffRow);
$('<span class="prefix">').text(lineText[0]).appendTo(line);
$('<span>').text(lineText.substring(1)).appendTo(line);
if (lineText[0] === '+') {
line.addClass("added");
remoteLineNo.text(remoteLine++);
} else if (lineText[0] === '-') {
line.addClass("removed");
localLineNo.text(localLine++);
} else {
line.addClass("unchanged");
if (localLine > 0) {
localLineNo.text(localLine++);
}
if (remoteLine > 0) {
remoteLineNo.text(remoteLine++);
}
}
}
}
});
return diffPanel;
}
function showCommitDiff(diff,title) {
var commit = parseCommitDiff(diff);
var trayOptions = {
title: title||"Compare Changes", //TODO: nls
width: Infinity,
@ -1671,83 +1726,125 @@ RED.diff = (function() {
$('<colgroup><col width="50"><col width="50"><col width="100%"></colgroup>').appendTo(codeTable);
var codeBody = $('<tbody>').appendTo(codeTable);
for (var i=0;i<hunks.length;i++) {
var diffRow = $('<tr class="node-text-diff-header">').appendTo(codeBody);
var content = $('<td colspan="3"></td>').appendTo(diffRow);
var label = $('<span></span>').text(hunks[i].header).appendTo(content);
var localLine = hunks[i].localStartLine;
var remoteLine = hunks[i].remoteStartLine;
var diffRow = $('<tr class="node-text-diff-file-header">').appendTo(codeBody);
var content = $('<td colspan="3"></td>').appendTo(diffRow);
var label = $('<pre></pre>').text(commit.preamble).appendTo(content);
createUnifiedDiffTable(commit.files).appendTo(diffPanel);
for (var j=0;j<hunks[i].lines.length;j++) {
var lineText = hunks[i].lines[j];
diffRow = $('<tr>').appendTo(codeBody);
var localLineNo = $('<td class="lineno">').appendTo(diffRow);
var remoteLineNo = $('<td class="lineno">').appendTo(diffRow);
var line = $('<td class="linetext">').appendTo(diffRow);
$('<span class="prefix">').text(lineText[0]).appendTo(line);
$('<span>').text(lineText.substring(1)).appendTo(line);
if (lineText[0] === '+') {
line.addClass("added");
remoteLineNo.text(remoteLine++);
} else if (lineText[0] === '-') {
line.addClass("removed");
localLineNo.text(localLine++);
} else {
line.addClass("unchanged");
localLineNo.text(localLine++);
remoteLineNo.text(remoteLine++);
}
}
}
},
close: function() {
diffVisible = false;
},
show: function() {
}
}
RED.tray.show(trayOptions);
}
function showUnifiedDiff(diff,title) {
var files = parseUnifiedDiff(diff);
var trayOptions = {
title: title||"Compare Changes", //TODO: nls
width: Infinity,
overlay: true,
buttons: [
{
text: RED._("common.label.done"),
click: function() {
RED.tray.close();
}
}
],
resize: function(dimensions) {
// trayWidth = dimensions.width;
},
open: function(tray) {
var trayBody = tray.find('.editor-tray-body');
var diffPanel = $('<div class="node-text-diff"></div>').appendTo(trayBody);
createUnifiedDiffTable(files).appendTo(diffPanel);
},
close: function() {
diffVisible = false;
},
show: function() {
}
}
RED.tray.show(trayOptions);
}
function parseCommitDiff(diff) {
var result = {
};
var lines = diff.split("\n");
for (var i=0;i<lines.length;i++) {
if (/^diff /.test(lines[i])) {
result.files = parseUnifiedDiff(lines.slice(i));
break;
}
}
result.preamble = lines.slice(0,i).join("\n");
return result;
}
function parseUnifiedDiff(diff) {
var lines = diff.split("\n");
var hunks = [];
var inHunk = false;
var currentHunk;
var lines;
if (Array.isArray(diff)) {
lines = diff;
} else {
lines = diff.split("\n");
}
var fileHeader = /^\+\+\+ b\/(.*)\t?/;
var hunkHeader = /^@@ -((\d+)(,(\d+))?) \+((\d+)(,(\d+))?) @@ ?(.*)$/;
var comment = /^\\/;
var localChange = /^-/;
var remoteChange = /^\+/;
var files = [];
var currentFile;
var hunks = [];
var currentHunk;
for (var i=0;i<lines.length;i++) {
var hunkLine = hunkHeader.exec(lines[i]);
if (hunkLine) {
if (inHunk) {
hunks.push(currentHunk);
var line = lines[i];
if (/^diff/.test(line)) {
if (currentHunk) {
currentFile.hunks.push(currentHunk);
files.push(currentFile);
}
currentHunk = {
header: lines[i],
localStartLine: hunkLine[2],
localLength: hunkLine[4]||1,
remoteStartLine: hunkLine[6],
remoteLength: hunkLine[8]||1,
lines: []
currentHunk = null;
currentFile = {
file: null,
hunks: []
}
} else {
var fileLine = fileHeader.exec(line);
if (fileLine) {
currentFile.file = fileLine[1];
} else {
var hunkLine = hunkHeader.exec(line);
if (hunkLine) {
if (currentHunk) {
currentFile.hunks.push(currentHunk);
}
currentHunk = {
header: line,
localStartLine: hunkLine[2],
localLength: hunkLine[4]||1,
remoteStartLine: hunkLine[6],
remoteLength: hunkLine[8]||1,
lines: []
}
} else if (currentHunk) {
currentHunk.lines.push(line);
}
}
inHunk = true;
} else if (inHunk) {
currentHunk.lines.push(lines[i]);
}
}
if (currentHunk) {
hunks.push(currentHunk);
currentFile.hunks.push(currentHunk);
files.push(currentFile);
}
return hunks;
return files;
}
return {
@ -1755,6 +1852,7 @@ RED.diff = (function() {
getRemoteDiff: getRemoteDiff,
showRemoteDiff: showRemoteDiff,
showUnifiedDiff: showUnifiedDiff,
showCommitDiff: showCommitDiff,
mergeDiff: mergeDiff
}
})();

View File

@ -29,6 +29,9 @@ RED.sidebar.versionControl = (function() {
var bulkChangeSpinner;
var commitButton;
var localCommitList;
// TODO: DRY projectSummary.js
function addSpinnerOverlay(container) {
var spinner = $('<div class="projects-dialog-spinner"><img src="red/images/spin.svg"/></div>').appendTo(container);
@ -315,6 +318,37 @@ RED.sidebar.versionControl = (function() {
collapsible: true
});
var bg = $('<div style="float: right"></div>').appendTo(localHistory.header);
$('<button class="editor-button editor-button-small"><i class="fa fa-refresh"></i></button>')
.appendTo(bg)
.click(function(evt) {
evt.preventDefault();
refreshLocalCommits();
})
localCommitList = $("<ol>",{style:"position: absolute; top: 0px; bottom: 0; right:0; left:0;"}).appendTo(localHistory.content);
localCommitList.editableList({
addButton: false,
scrollOnAdd: false,
addItem: function(row,index,entry) {
row.addClass('sidebar-version-control-commit-entry');
row.click(function(e) {
var activeProject = RED.projects.getActiveProject();
if (activeProject) {
$.getJSON("/projects/"+activeProject.name+"/commits/"+entry.sha,function(result) {
RED.diff.showCommitDiff(result.commit);
});
}
});
var container = $('<div>').appendTo(row);
$('<div class="sidebar-version-control-commit-sha">').text(entry.sha.substring(0,7)).appendTo(container);
$('<div class="sidebar-version-control-commit-subject">').text(entry.subject).appendTo(container);
$('<div class="sidebar-version-control-commit-user">').text(entry.author).appendTo(container);
$('<div class="sidebar-version-control-commit-date">').text(humanizeSinceDate(parseInt(entry.date))).appendTo(container);
}
});
var remoteHistory = sections.add({
title: "Remote History",
collapsible: true
@ -335,6 +369,26 @@ RED.sidebar.versionControl = (function() {
}
function humanizeSinceDate(date) {
var delta = (Date.now()/1000) - date;
var daysDelta = Math.floor(delta / (60*60*24));
if (daysDelta > 30) {
return (new Date(date*1000)).toLocaleDateString();
} else if (daysDelta > 0) {
return daysDelta+" day"+(daysDelta>1?"s":"")+" ago";
}
var hoursDelta = Math.floor(delta / (60*60));
if (hoursDelta > 0) {
return hoursDelta+" hour"+(hoursDelta>1?"s":"")+" ago";
}
var minutesDelta = Math.floor(delta / 60);
if (minutesDelta > 0) {
return minutesDelta+" minute"+(minutesDelta>1?"s":"")+" ago";
}
return "Seconds ago";
}
function updateBulk(files,unstaged) {
var activeProject = RED.projects.getActiveProject();
if (unstaged) {
@ -369,6 +423,21 @@ RED.sidebar.versionControl = (function() {
var emptyStagedItem = { label:"None" };
function refreshLocalCommits() {
localCommitList.editableList('empty');
var spinner = addSpinnerOverlay(localCommitList);
var activeProject = RED.projects.getActiveProject();
if (activeProject) {
$.getJSON("/projects/"+activeProject.name+"/commits",function(result) {
result.commits.forEach(function(c) {
localCommitList.editableList('addItem',c);
})
spinner.remove();
});
}
}
function refreshFiles(result) {
if (bulkChangeSpinner) {
bulkChangeSpinner.remove();
@ -464,6 +533,7 @@ RED.sidebar.versionControl = (function() {
unstagedChangesList.editableList('empty');
stagedChangesList.editableList('empty');
}
refreshInProgress = true;
var activeProject = RED.projects.getActiveProject();

View File

@ -601,12 +601,21 @@
tr.end-block {
border-bottom: 1px solid #f0f0f0;
}
tr.node-text-diff-file-header td {
background: #f3f3f3;
padding: 5px 10px;
color: #333;
}
tr.node-text-diff-header td {
padding: 5px 10px;
text-align: left;
color: #999;
color: #666;
background: #ffd;
height: 30px;
vertical-align: middle;
border-top: 1px solid #f0f0f0;
border-bottom: 1px solid #f0f0f0;
}
tr.node-text-diff-expand td {
cursor: pointer;

View File

@ -247,16 +247,7 @@
overflow-y: auto;
padding: 8px 20px 20px;
}
.sidebar-version-control-change-container {
position: relative;
height: 50%;
box-sizing: border-box;
border-top: 1px solid $secondary-border-color;
transition: height 0.2s ease-in-out;
&:first-child {
// border-bottom: 1px solid $primary-border-color;
}
.sidebar-version-control {
.red-ui-editableList-container {
background: #f9f9f9;
padding: 0;
@ -270,6 +261,17 @@
border-radius: 0;
}
}
.sidebar-version-control-change-container {
position: relative;
height: 50%;
box-sizing: border-box;
border-top: 1px solid $secondary-border-color;
transition: height 0.2s ease-in-out;
&:first-child {
// border-bottom: 1px solid $primary-border-color;
}
}
.sidebar-version-control-change-commit-box {
position:absolute;
bottom: 0;
@ -323,6 +325,38 @@
background: #fefefe;
}
}
.sidebar-version-control-commit-entry {
min-height: 20px;
padding: 5px 10px;
position: relative;
white-space: nowrap;
cursor: pointer;
&:hover {
background: #eee;
}
}
.sidebar-version-control-commit-sha {
float: right;
font-family: monospace;
color: #c38888;
display: inline-block;
font-size: 0.85em;
margin-left: 5px;
}
.sidebar-version-control-commit-subject {
color: #666;
}
.sidebar-version-control-commit-date {
color: #999;
font-size: 0.85em;
}
.sidebar-version-control-commit-user {
float: right;
color: #999;
font-size: 0.85em;
}
.sidebar-version-control-change-header {
color: #666;
background: #f6f6f6;

View File

@ -203,6 +203,31 @@ module.exports = {
})
});
app.get("/:id/commits", function(req, res) {
var projectName = req.params.id;
var options = {};
runtime.storage.projects.getCommits(projectName,options).then(function(data) {
res.json(data);
})
.catch(function(err) {
console.log(err.stack);
res.status(400).json({error:"unexpected_error", message:err.toString()});
})
});
app.get("/:id/commits/:sha", function(req, res) {
var projectName = req.params.id;
var sha = req.params.sha;
runtime.storage.projects.getCommit(projectName,sha).then(function(data) {
res.json({commit:data});
})
.catch(function(err) {
console.log(err.stack);
res.status(400).json({error:"unexpected_error", message:err.toString()});
})
});
app.get(new RegExp("/([^\/]+)\/files\/(.*)"), function(req,res) {
// Get project file
});

View File

@ -173,6 +173,28 @@ function getFiles(localRepo) {
})
}
function parseLog(log) {
var lines = log.split("\n");
var currentCommit = null;
var commits = [];
lines.forEach(function(l) {
if (/^sha: /.test(l)) {
if (currentCommit) {
commits.push(currentCommit);
}
currentCommit = {}
}
var m = /^(.*): (.*)$/.exec(l);
if (m) {
currentCommit[m[1]] = m[2];
}
});
if (currentCommit) {
commits.push(currentCommit);
}
return {commits: commits};
}
var gitCommand = "git";
module.exports = {
initRepo: function(cwd) {
@ -219,5 +241,13 @@ module.exports = {
}
args.push(file);
return runCommand(gitCommand,args,cwd);
},
getCommits: function(cwd,options) {
var args = ["log", "--format=sha: %H%nauthor: %an%ndate: %ct%nsubject: %s","-n 10"];
return runCommand(gitCommand,args,cwd).then(parseLog);
},
getCommit: function(cwd,sha) {
var args = ["show",sha];
return runCommand(gitCommand,args,cwd);
}
}

View File

@ -355,6 +355,15 @@ function getFileDiff(project,file,type) {
var projectPath = fspath.join(projectsDir,project);
return gitTools.getFileDiff(projectPath,file,type);
}
function getCommits(project,options) {
var projectPath = fspath.join(projectsDir,project);
return gitTools.getCommits(projectPath,options);
}
function getCommit(project,sha) {
var projectPath = fspath.join(projectsDir,project);
return gitTools.getCommit(projectPath,sha);
}
function getFile(project,path) {
}
@ -512,6 +521,8 @@ module.exports = {
unstageFile: unstageFile,
commit: commit,
getFileDiff: getFileDiff,
getCommits: getCommits,
getCommit: getCommit,
getFlows: getFlows,
saveFlows: saveFlows,