aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcel <cel@f/6sQ6d2CMxRUhLpspgGIulDxDCwYD7DzFzPNr7u5AU=.ed25519>2018-01-08 17:17:18 -1000
committercel <cel@f/6sQ6d2CMxRUhLpspgGIulDxDCwYD7DzFzPNr7u5AU=.ed25519>2018-01-08 17:17:18 -1000
commitf752c762185f7493a5420f461bc8e766db84fc60 (patch)
tree654b7bf9c811e015d8e58613d042d82a59dfeb99
parent27f1966a6512bd07a9fe91717a46d1a95281da32 (diff)
downloadpatchfoo-f752c762185f7493a5420f461bc8e766db84fc60.tar.gz
patchfoo-f752c762185f7493a5420f461bc8e766db84fc60.zip
Render git diffs
-rw-r--r--lib/serve.js181
-rw-r--r--package-lock.json4
-rw-r--r--package.json1
-rw-r--r--static/styles.css3
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 <rev1>..<rev2>'),
+ 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 '<td class="code-linenum">' +
+ (num ? '<a href="#' + idEnc + '">' +
+ num + '</a>' +
+ (updateId && i === lineNums.length-1 && s !== '-' ?
+ ' <a href="?comment=' + idEnc + '#' + idEnc + '">…</a>'
+ : '')
+ : '') + '</td>'
+ }
+ */
+ }),
+ ph('td', ph('pre', u.escapeHTML(html)))
+ ])
+
+ // TODO: line-comments
+ /*
+ (lineCommentThreads[newLineNum] ?
+ '<tr><td colspan=4>' +
+ lineCommentThreads[newLineNum] +
+ '</td></tr>'
+ : commit && query.comment === id ?
+ '<tr><td colspan=4>' +
+ forms.lineComment(req, repo, updateId, commit, filename, newLineNum) +
+ '</td></tr>'
+ : '')
+ */
+ })
+ )
+ ]
+ })
+ )
+ ])
+ )
+}
+
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; }