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)); label.text("@@ -"+(diffLines[end-1].a.i+1)+" +"+(diffLines[end-1].b.i+1));
} }
diffRow.click(function(evt) { diffRow.click(function(evt) {
console.log(start,end,diffLines.length); // console.log(start,end,diffLines.length);
if (end - start > 20) { if (end - start > 20) {
var startPos = $(this).offset(); var startPos = $(this).offset();
console.log(startPos); // console.log(startPos);
if (start > 0) { if (start > 0) {
for (var i=start;i<start+10;i++) { for (var i=start;i<start+10;i++) {
createDiffLine(diffLines[i]).addClass("unchanged").insertBefore($(this)); createDiffLine(diffLines[i]).addClass("unchanged").insertBefore($(this));
@ -1646,8 +1646,63 @@ RED.diff = (function() {
return string1 === string2 ? 0 : 1; return string1 === string2 ? 0 : 1;
} }
function showUnifiedDiff(diff,title) { function createUnifiedDiffTable(files) {
var hunks = parseUnifiedDiff(diff); 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 = { var trayOptions = {
title: title||"Compare Changes", //TODO: nls title: title||"Compare Changes", //TODO: nls
width: Infinity, width: Infinity,
@ -1671,83 +1726,125 @@ RED.diff = (function() {
$('<colgroup><col width="50"><col width="50"><col width="100%"></colgroup>').appendTo(codeTable); $('<colgroup><col width="50"><col width="50"><col width="100%"></colgroup>').appendTo(codeTable);
var codeBody = $('<tbody>').appendTo(codeTable); var codeBody = $('<tbody>').appendTo(codeTable);
for (var i=0;i<hunks.length;i++) { var diffRow = $('<tr class="node-text-diff-file-header">').appendTo(codeBody);
var content = $('<td colspan="3"></td>').appendTo(diffRow);
var diffRow = $('<tr class="node-text-diff-header">').appendTo(codeBody); var label = $('<pre></pre>').text(commit.preamble).appendTo(content);
var content = $('<td colspan="3"></td>').appendTo(diffRow);
var label = $('<span></span>').text(hunks[i].header).appendTo(content);
createUnifiedDiffTable(commit.files).appendTo(diffPanel);
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];
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() { close: function() {
diffVisible = false; diffVisible = false;
}, },
show: function() { show: function() {
} }
} }
RED.tray.show(trayOptions); 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) { function parseUnifiedDiff(diff) {
var lines = diff.split("\n"); var lines;
var hunks = []; if (Array.isArray(diff)) {
var inHunk = false; lines = diff;
var currentHunk; } else {
lines = diff.split("\n");
}
var fileHeader = /^\+\+\+ b\/(.*)\t?/;
var hunkHeader = /^@@ -((\d+)(,(\d+))?) \+((\d+)(,(\d+))?) @@ ?(.*)$/; var hunkHeader = /^@@ -((\d+)(,(\d+))?) \+((\d+)(,(\d+))?) @@ ?(.*)$/;
var comment = /^\\/; var files = [];
var localChange = /^-/; var currentFile;
var remoteChange = /^\+/; var hunks = [];
var currentHunk;
for (var i=0;i<lines.length;i++) { for (var i=0;i<lines.length;i++) {
var hunkLine = hunkHeader.exec(lines[i]); var line = lines[i];
if (hunkLine) { if (/^diff/.test(line)) {
if (inHunk) { if (currentHunk) {
hunks.push(currentHunk); currentFile.hunks.push(currentHunk);
files.push(currentFile);
} }
currentHunk = { currentHunk = null;
header: lines[i], currentFile = {
localStartLine: hunkLine[2], file: null,
localLength: hunkLine[4]||1, hunks: []
remoteStartLine: hunkLine[6], }
remoteLength: hunkLine[8]||1, } else {
lines: [] 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) { if (currentHunk) {
hunks.push(currentHunk); currentFile.hunks.push(currentHunk);
files.push(currentFile);
} }
return hunks; return files;
} }
return { return {
@ -1755,6 +1852,7 @@ RED.diff = (function() {
getRemoteDiff: getRemoteDiff, getRemoteDiff: getRemoteDiff,
showRemoteDiff: showRemoteDiff, showRemoteDiff: showRemoteDiff,
showUnifiedDiff: showUnifiedDiff, showUnifiedDiff: showUnifiedDiff,
showCommitDiff: showCommitDiff,
mergeDiff: mergeDiff mergeDiff: mergeDiff
} }
})(); })();

View File

@ -29,6 +29,9 @@ RED.sidebar.versionControl = (function() {
var bulkChangeSpinner; var bulkChangeSpinner;
var commitButton; var commitButton;
var localCommitList;
// TODO: DRY projectSummary.js // TODO: DRY projectSummary.js
function addSpinnerOverlay(container) { function addSpinnerOverlay(container) {
var spinner = $('<div class="projects-dialog-spinner"><img src="red/images/spin.svg"/></div>').appendTo(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 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({ var remoteHistory = sections.add({
title: "Remote History", title: "Remote History",
collapsible: true 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) { function updateBulk(files,unstaged) {
var activeProject = RED.projects.getActiveProject(); var activeProject = RED.projects.getActiveProject();
if (unstaged) { if (unstaged) {
@ -369,6 +423,21 @@ RED.sidebar.versionControl = (function() {
var emptyStagedItem = { label:"None" }; 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) { function refreshFiles(result) {
if (bulkChangeSpinner) { if (bulkChangeSpinner) {
bulkChangeSpinner.remove(); bulkChangeSpinner.remove();
@ -464,6 +533,7 @@ RED.sidebar.versionControl = (function() {
unstagedChangesList.editableList('empty'); unstagedChangesList.editableList('empty');
stagedChangesList.editableList('empty'); stagedChangesList.editableList('empty');
} }
refreshInProgress = true; refreshInProgress = true;
var activeProject = RED.projects.getActiveProject(); var activeProject = RED.projects.getActiveProject();

View File

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

View File

@ -247,16 +247,7 @@
overflow-y: auto; overflow-y: auto;
padding: 8px 20px 20px; padding: 8px 20px 20px;
} }
.sidebar-version-control {
.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;
}
.red-ui-editableList-container { .red-ui-editableList-container {
background: #f9f9f9; background: #f9f9f9;
padding: 0; padding: 0;
@ -270,6 +261,17 @@
border-radius: 0; border-radius: 0;
} }
} }
.sidebar-version-control-change-container {
position: relative;
height: 50%;
box-sizing: border-box;
border-top: 1px solid $secondary-border-color;
transition: height 0.2s ease-in-out;
&:first-child {
// border-bottom: 1px solid $primary-border-color;
}
}
.sidebar-version-control-change-commit-box { .sidebar-version-control-change-commit-box {
position:absolute; position:absolute;
bottom: 0; bottom: 0;
@ -323,6 +325,38 @@
background: #fefefe; 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 { .sidebar-version-control-change-header {
color: #666; color: #666;
background: #f6f6f6; 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) { app.get(new RegExp("/([^\/]+)\/files\/(.*)"), function(req,res) {
// Get project file // 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"; var gitCommand = "git";
module.exports = { module.exports = {
initRepo: function(cwd) { initRepo: function(cwd) {
@ -219,5 +241,13 @@ module.exports = {
} }
args.push(file); args.push(file);
return runCommand(gitCommand,args,cwd); 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); var projectPath = fspath.join(projectsDir,project);
return gitTools.getFileDiff(projectPath,file,type); 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) { function getFile(project,path) {
} }
@ -512,6 +521,8 @@ module.exports = {
unstageFile: unstageFile, unstageFile: unstageFile,
commit: commit, commit: commit,
getFileDiff: getFileDiff, getFileDiff: getFileDiff,
getCommits: getCommits,
getCommit: getCommit,
getFlows: getFlows, getFlows: getFlows,
saveFlows: saveFlows, saveFlows: saveFlows,