From f752c762185f7493a5420f461bc8e766db84fc60 Mon Sep 17 00:00:00 2001 From: cel Date: Mon, 8 Jan 2018 17:17:18 -1000 Subject: Render git diffs --- lib/serve.js | 181 +++++++++++++++++++++++++++++++++++++++++++++++++++++- package-lock.json | 4 ++ package.json | 1 + static/styles.css | 3 + 4 files changed, 187 insertions(+), 2 deletions(-) diff --git a/lib/serve.js b/lib/serve.js index f146c49..de2757e 100644 --- a/lib/serve.js +++ b/lib/serve.js @@ -23,6 +23,7 @@ var emojis = require('emoji-named-characters') var jpeg = require('jpeg-autorotate') var unzip = require('unzip') var Catch = require('pull-catch') +var Diff = require('diff') module.exports = Serve @@ -1648,6 +1649,7 @@ Serve.prototype.git = function (url) { case 'tree': return this.gitTree(m[2]) case 'blob': return this.gitBlob(m[2]) case 'raw': return this.gitRaw(m[2]) + case 'diff': return this.gitDiff(m[2]) default: return this.respond(404, 'Not found') } } @@ -1782,10 +1784,14 @@ Serve.prototype.gitCommit = function (rev) { pull.map(function (file) { return ph('tr', [ ph('td', ph('code', u.escapeHTML(file.name))), - // ph('td', ph('code', u.escapeHTML(JSON.stringify(file.msg)))), ph('td', file.deleted ? 'deleted' : file.created ? 'created' - : file.hash ? 'changed' + : file.hash ? + ph('a', {href: + self.app.render.toUrl('/git/diff/' + + file.hash[0] + '..' + file.hash[1] + + '?msg=' + encodeURIComponent(obj.msg.key)) + }, 'changed') : file.mode ? 'mode changed' : JSON.stringify(file)) ]) @@ -2016,6 +2022,177 @@ Serve.prototype.gitBlob = function (rev) { }) } +Serve.prototype.gitDiff = function (revs) { + var self = this + var parts = revs.split('..') + if (parts.length !== 2) return pull( + ph('div.error', 'revs should be ..'), + self.wrapPage('git diff'), + self.respondSink(400) + ) + var rev1 = parts[0] + var rev2 = parts[1] + if (!/[0-9a-f]{24}/.test(rev1)) return pull( + ph('div.error', 'rev 1 is not a git object id'), + self.wrapPage('git diff'), + self.respondSink(400) + ) + if (!/[0-9a-f]{24}/.test(rev2)) return pull( + ph('div.error', 'rev 2 is not a git object id'), + self.wrapPage('git diff'), + self.respondSink(400) + ) + + if (!u.isRef(self.query.msg)) return pull( + ph('div.error', 'missing message id'), + self.wrapPage('git diff'), + self.respondSink(400) + ) + + var done = multicb({pluck: 1, spread: true}) + // the msg qs param should point to the message for rev2 object. the msg for + // rev1 object we will have to look up. + self.app.git.getObjectMsg({ + obj: rev1, + headMsgId: self.query.msg, + type: 'blob', + }, done()) + self.getMsgDecryptedMaybeOoo(self.query.msg, done()) + done(function (err, msg1, msg2) { + if (err) return pull( + pull.once(u.renderError(err).outerHTML), + self.wrapPage('git diff ' + revs), + self.respondSink(400) + ) + var msg1Date = new Date(msg1.value.timestamp) + var msg2Date = new Date(msg2.value.timestamp) + var revsShort = rev1.substr(0, 8) + '..' + rev2.substr(0, 8) + pull( + ph('section', [ + ph('h3', ph('a', {href: ''}, revsShort)), + ph('div', [ + ph('a', { + href: self.app.render.toUrl('/git/blob/' + rev1 + '?msg=' + encodeURIComponent(msg1.key)) + }, rev1), ' ', + self.phIdLink(msg1.value.author), ' ', + ph('a', { + href: self.app.render.toUrl(msg1.key), + title: msg1Date.toLocaleString(), + }, htime(msg1Date)) + ]), + ph('div', [ + ph('a', { + href: self.app.render.toUrl('/git/blob/' + rev2 + '?msg=' + encodeURIComponent(msg2.key)) + }, rev2), ' ', + self.phIdLink(msg2.value.author), ' ', + ph('a', { + href: self.app.render.toUrl(msg2.key), + title: msg2Date.toLocaleString(), + }, htime(msg2Date)) + ]), + u.readNext(function (cb) { + var done = multicb({pluck: 1, spread: true}) + self.app.git.openObject({ + obj: rev1, + msg: msg1.key, + }, done()) + self.app.git.openObject({ + obj: rev2, + msg: msg2.key, + }, done()) + done(function (err, obj1, obj2) { + if (err && err.name === 'BlobNotFoundError') + return cb(null, self.askWantBlobsForm(err.links)) + if (err) return cb(err) + + var done = multicb({pluck: 1, spread: true}) + pull.collect(done())(self.app.git.readObject(obj1)) + pull.collect(done())(self.app.git.readObject(obj2)) + done(function (err, bufs1, bufs2) { + if (err) return cb(err) + var str1 = Buffer.concat(bufs1, obj1.length).toString('utf8') + var str2 = Buffer.concat(bufs2, obj2.length).toString('utf8') + var diff = Diff.structuredPatch('', '', str1, str2) + cb(null, self.gitDiffTable(diff)) + }) + }) + }) + ]), + self.wrapPage('git diff'), + self.respondSink(200) + ) + }) +} + +Serve.prototype.gitDiffTable = function (diff) { + var self = this + return pull( + ph('table', [ + ph('tr', [ + ]), + pull( + pull.values(diff.hunks), + pull.map(function (hunk) { + var oldLine = hunk.oldStart + var newLine = hunk.newStart + return [ + ph('tr', [ + ph('td', {colspan: 2}), + ph('td', ph('pre', + '@@ -' + oldLine + ',' + hunk.oldLines + ' ' + + '+' + newLine + ',' + hunk.newLines + ' @@')) + ]), + pull( + pull.values(hunk.lines), + pull.map(function (line) { + var s = line[0] + if (s == '\\') return + var html = self.app.render.highlight(line) + var lineNums = [s == '+' ? '' : oldLine++, s == '-' ? '' : newLine++] + // var id = [filename].concat(lineNums).join('-') + var newLineNum = lineNums[lineNums.length-1] + return ph('tr', { + class: s == '+' ? 'diff-new' : s == '-' ? 'diff-old' : '' + }, [ + lineNums.map(function (num, i) { + return ph('td', String(num)) + // TODO: allow linking to comments + /* + var idEnc = encodeURIComponent(id) + return '' + + (num ? '' + + num + '' + + (updateId && i === lineNums.length-1 && s !== '-' ? + ' ' + : '') + : '') + '' + } + */ + }), + ph('td', ph('pre', u.escapeHTML(html))) + ]) + + // TODO: line-comments + /* + (lineCommentThreads[newLineNum] ? + '' + + lineCommentThreads[newLineNum] + + '' + : commit && query.comment === id ? + '' + + forms.lineComment(req, repo, updateId, commit, filename, newLineNum) + + '' + : '') + */ + }) + ) + ] + }) + ) + ]) + ) +} + Serve.prototype.gitObjectLinks = function (headMsgId, type) { var self = this return paramap(function (id, cb) { diff --git a/package-lock.json b/package-lock.json index 8d381d4..3a9bf62 100644 --- a/package-lock.json +++ b/package-lock.json @@ -180,6 +180,10 @@ "streamsearch": "0.1.2" } }, + "diff": { + "version": "3.3.1", + "resolved": "http://localhost:8989/blobs/get/&+6G9mvp4Q/5bujMKlbFdbrub2IXMPleXIqTqyyvY5Ww=.sha256" + }, "ed2curve": { "version": "0.1.4", "resolved": "http://localhost:8989/blobs/get/&X6VtEiGqSVFiIHAZAXFZXB5q9iUd3gULIU9c0xyynNo=.sha256", diff --git a/package.json b/package.json index 62ec8d9..65ae820 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "asyncmemo": "^1.1.0", "base64-url": "^2.0.0", "busboy": "^0.2.14", + "diff": "^3.3.1", "emoji-named-characters": "^1.0.2", "emoji-server": "^1.0.0", "hashlru": "^2.1.0", diff --git a/static/styles.css b/static/styles.css index c26cf59..b22074f 100644 --- a/static/styles.css +++ b/static/styles.css @@ -193,3 +193,6 @@ table.ssb-object td { .chess-square-dark { background-color: #ccc; } + +.diff-old { background-color: #ffe2dd; } +.diff-new { background-color: #d1ffd6; } -- cgit v1.2.3