aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcel <cel@f/6sQ6d2CMxRUhLpspgGIulDxDCwYD7DzFzPNr7u5AU=.ed25519>2017-05-28 16:12:14 -1000
committercel <cel@f/6sQ6d2CMxRUhLpspgGIulDxDCwYD7DzFzPNr7u5AU=.ed25519>2017-05-28 18:30:28 -1000
commit00d61405ca36e8d160cc992acc3660d1cf73e2f3 (patch)
tree5a798851a8396aa28992d83f329ec68a3f7820bd
parent0de03de70ddf0612355e332e7a8ed860555d6b36 (diff)
downloadpatchfoo-00d61405ca36e8d160cc992acc3660d1cf73e2f3.tar.gz
patchfoo-00d61405ca36e8d160cc992acc3660d1cf73e2f3.zip
Render git trees, tags, and blobs
-rw-r--r--lib/git.js74
-rw-r--r--lib/serve.js262
2 files changed, 335 insertions, 1 deletions
diff --git a/lib/git.js b/lib/git.js
index 048e2b3..07061e2 100644
--- a/lib/git.js
+++ b/lib/git.js
@@ -10,6 +10,12 @@ var zlib = require('zlib')
var ObjectNotFoundError = u.customError('ObjectNotFoundError')
+var types = {
+ blob: true,
+ commit: true,
+ tree: true,
+}
+
module.exports = Git
function Git(app) {
@@ -456,3 +462,71 @@ Git.prototype.getCommit = function (obj, cb) {
cb(null, commit)
}))
}
+
+Git.prototype.getTag = function (obj, cb) {
+ pull(this.readObject(obj), u.pullConcat(function (err, buf) {
+ if (err) return cb(err)
+ var tag = {
+ msg: obj.msg,
+ }
+ var authorLine, tagterLine
+ var lines = buf.toString('utf8').split('\n')
+ for (var line; (line = lines.shift()); ) {
+ var parts = line.split(' ')
+ var prop = parts.shift()
+ var value = parts.join(' ')
+ switch (prop) {
+ case 'object':
+ tag.object = value
+ break
+ case 'type':
+ if (!types[value])
+ return cb(new TypeError('unknown git object type ' + type))
+ tag.type = value
+ break
+ case 'tag':
+ tag.tag = value
+ break
+ case 'tagger':
+ tag.tagger = parseName(value)
+ break
+ default:
+ return cb(new TypeError('unknown git object property ' + prop))
+ }
+ }
+ tag.body = lines.join('\n')
+ cb(null, tag)
+ }))
+}
+
+function readCString(reader, cb) {
+ var chars = []
+ reader.read(1, function next(err, ch) {
+ if (err) return cb(err)
+ if (ch[0] === 0) return cb(null, Buffer.concat(chars).toString('utf8'))
+ chars.push(ch)
+ reader.read(1, next)
+ })
+}
+
+Git.prototype.readTree = function (obj) {
+ var reader = Reader()
+ reader(this.readObject(obj))
+ return function (abort, cb) {
+ if (abort) return reader.abort(abort, cb)
+ readCString(reader, function (err, str) {
+ if (err) return cb(err)
+ var parts = str.split(' ')
+ var mode = parseInt(parts[0], 8)
+ var name = parts.slice(1).join(' ')
+ reader.read(20, function (err, hash) {
+ if (err) return cb(err)
+ cb(null, {
+ name: name,
+ mode: mode,
+ hash: hash.toString('hex')
+ })
+ })
+ })
+ }
+}
diff --git a/lib/serve.js b/lib/serve.js
index f1ec351..64aa82b 100644
--- a/lib/serve.js
+++ b/lib/serve.js
@@ -1282,7 +1282,9 @@ Serve.prototype.git = function (url) {
var m = /^\/?([^\/]*)\/?(.*)?$/.exec(url)
switch (m[1]) {
case 'commit': return this.gitCommit(m[2])
- case 'tag': return this.gitCommit(m[2])
+ case 'tag': return this.gitTag(m[2])
+ case 'tree': return this.gitTree(m[2])
+ case 'blob': return this.gitBlob(m[2])
case 'raw': return this.gitRaw(m[2])
default: return this.respond(404, 'Not found')
}
@@ -1442,6 +1444,264 @@ Serve.prototype.gitCommit = function (rev) {
})
}
+Serve.prototype.gitTag = function (rev) {
+ var self = this
+ if (!/[0-9a-f]{24}/.test(rev)) {
+ return pull(
+ ph('div.error', 'rev is not a git object id'),
+ self.wrapPage('git'),
+ self.respondSink(400)
+ )
+ }
+ if (!u.isRef(self.query.msg)) return pull(
+ ph('div.error', 'missing message id'),
+ self.wrapPage('git tag ' + rev),
+ self.respondSink(400)
+ )
+
+ self.app.git.openObject({
+ obj: rev,
+ msg: self.query.msg,
+ }, function (err, obj) {
+ if (err && err.name === 'BlobNotFoundError')
+ return self.askWantBlobs(err.links)
+ if (err) return pull(
+ pull.once(u.renderError(err).outerHTML),
+ self.wrapPage('git tag ' + rev),
+ self.respondSink(400)
+ )
+ var msgDate = new Date(obj.msg.value.timestamp)
+ self.app.git.getTag(obj, function (err, tag) {
+ var missingBlobs
+ if (err && err.name === 'BlobNotFoundError')
+ missingBlobs = err.links, err = null
+ if (err) return pull(
+ pull.once(u.renderError(err).outerHTML),
+ self.wrapPage('git tag ' + rev),
+ self.respondSink(400)
+ )
+ pull(
+ ph('section', [
+ ph('h3', ph('a', {href: ''}, rev)),
+ ph('div', [
+ self.phIdLink(obj.msg.value.author), ' pushed ',
+ ph('a', {
+ href: self.app.render.toUrl(obj.msg.key),
+ title: msgDate.toLocaleString(),
+ }, htime(msgDate))
+ ]),
+ missingBlobs ? self.askWantBlobsForm(missingBlobs) : [
+ ph('div', [
+ self.gitAuthorLink(tag.tagger),
+ ' tagged ',
+ ph('span', {title: tag.tagger.date.toLocaleString()},
+ htime(tag.tagger.date)),
+ ' in ', tag.tagger.tz
+ ]),
+ tag.type, ' ',
+ pull(
+ pull.once(tag.object),
+ pull.asyncMap(function (id, cb) {
+ self.app.git.getObjectMsg({
+ obj: id,
+ type: tag.type,
+ headMsgId: obj.msg.key,
+ }, function (err, msg) {
+ var path = '/git/' + tag.type + '/' + id
+ + '?msg=' + encodeURIComponent(msg.key)
+ cb(null, [ph('code', ph('a', {
+ href: self.app.render.toUrl(path)
+ }, id.substr(0, 8))), ' '])
+ })
+ })
+ ), ' ',
+ ph('code', u.escapeHTML(tag.tag)),
+ h('pre', self.app.render.linkify(tag.body)).outerHTML,
+ ]
+ ]),
+ self.wrapPage('git tag ' + rev),
+ self.respondSink(missingBlobs ? 409 : 200)
+ )
+ })
+ })
+}
+
+Serve.prototype.gitTree = function (rev) {
+ var self = this
+ if (!/[0-9a-f]{24}/.test(rev)) {
+ return pull(
+ ph('div.error', 'rev is not a git object id'),
+ self.wrapPage('git'),
+ self.respondSink(400)
+ )
+ }
+ if (!u.isRef(self.query.msg)) return pull(
+ ph('div.error', 'missing message id'),
+ self.wrapPage('git tree ' + rev),
+ self.respondSink(400)
+ )
+
+ self.app.git.openObject({
+ obj: rev,
+ msg: self.query.msg,
+ }, function (err, obj) {
+ var missingBlobs
+ if (err && err.name === 'BlobNotFoundError')
+ missingBlobs = err.links, err = null
+ if (err) return pull(
+ pull.once(u.renderError(err).outerHTML),
+ self.wrapPage('git tree ' + rev),
+ self.respondSink(400)
+ )
+ var msgDate = new Date(obj.msg.value.timestamp)
+ pull(
+ ph('section', [
+ ph('h3', ph('a', {href: ''}, rev)),
+ ph('div', [
+ self.phIdLink(obj.msg.value.author), ' ',
+ ph('a', {
+ href: self.app.render.toUrl(obj.msg.key),
+ title: msgDate.toLocaleString(),
+ }, htime(msgDate))
+ ]),
+ missingBlobs ? self.askWantBlobsForm(missingBlobs) : ph('table', [
+ pull(
+ self.app.git.readTree(obj),
+ paramap(function (file, cb) {
+ self.app.git.getObjectMsg({
+ obj: file.hash,
+ headMsgId: obj.msg.key,
+ }, function (err, msg) {
+ if (err && err.name === 'ObjectNotFoundError') return cb(null, file)
+ if (err) return cb(err)
+ file.msg = msg
+ cb(null, file)
+ })
+ }, 8),
+ pull.map(function (item) {
+ var type = item.mode === 0040000 ? 'tree' :
+ item.mode === 0160000 ? 'commit' : 'blob'
+ var path = '/git/' + type + '/' + item.hash
+ + '?msg=' + encodeURIComponent(item.msg.key)
+ var fileDate = new Date(item.msg.value.timestamp)
+ return ph('tr', [
+ ph('td',
+ ph('a', {href: self.app.render.toUrl(path)},
+ item.name + (type === 'tree' ? '/' : ''))),
+ item.msg ? ph('td',
+ self.phIdLink(item.msg.value.author)) : '',
+ item.msg ? ph('td',
+ ph('a', {
+ href: self.app.render.toUrl(item.msg.key),
+ title: fileDate.toLocaleString(),
+ }, htime(fileDate))
+ ) : '',
+ ])
+ })
+ )
+ ]),
+ ]),
+ self.wrapPage('git tree ' + rev),
+ self.respondSink(missingBlobs ? 409 : 200)
+ )
+ })
+}
+
+Serve.prototype.gitBlob = function (rev) {
+ var self = this
+ if (!/[0-9a-f]{24}/.test(rev)) {
+ return pull(
+ ph('div.error', 'rev is not a git object id'),
+ self.wrapPage('git'),
+ self.respondSink(400)
+ )
+ }
+ if (!u.isRef(self.query.msg)) return pull(
+ ph('div.error', 'missing message id'),
+ self.wrapPage('git object ' + rev),
+ self.respondSink(400)
+ )
+
+ self.app.getMsgDecrypted(self.query.msg, function (err, msg) {
+ if (err) return pull(
+ pull.once(u.renderError(err).outerHTML),
+ self.wrapPage('git object ' + rev),
+ self.respondSink(400)
+ )
+ var msgDate = new Date(msg.value.timestamp)
+ self.app.git.openObject({
+ obj: rev,
+ msg: msg.key,
+ }, function (err, obj) {
+ var missingBlobs
+ if (err && err.name === 'BlobNotFoundError')
+ missingBlobs = err.links, err = null
+ if (err) return pull(
+ pull.once(u.renderError(err).outerHTML),
+ self.wrapPage('git object ' + rev),
+ self.respondSink(400)
+ )
+ pull(
+ ph('section', [
+ ph('h3', ph('a', {href: ''}, rev)),
+ ph('div', [
+ self.phIdLink(msg.value.author), ' ',
+ ph('a', {
+ href: self.app.render.toUrl(msg.key),
+ title: msgDate.toLocaleString(),
+ }, htime(msgDate))
+ ]),
+ missingBlobs ? self.askWantBlobsForm(missingBlobs) : pull(
+ self.app.git.readObject(obj),
+ self.wrapBinary({
+ rawUrl: self.app.render.toUrl('/git/raw/' + rev
+ + '?msg=' + encodeURIComponent(msg.key))
+ })
+ ),
+ ]),
+ self.wrapPage('git blob ' + rev),
+ self.respondSink(200)
+ )
+ })
+ })
+}
+
+// wrap a binary source and render it or turn into an embed
+Serve.prototype.wrapBinary = function (opts) {
+ var self = this
+ return function (read) {
+ var readRendered, type
+ read = ident(function (ext) {
+ type = ext && mime.lookup(ext) || 'text/plain'
+ })(read)
+ return function (abort, cb) {
+ if (readRendered) return readRendered(abort, cb)
+ if (abort) return read(abort, cb)
+ if (!type) read(null, function (end, buf) {
+ if (end) return cb(end)
+ if (!type) return cb(new Error('unable to get type'))
+ readRendered = pickSource(type, cat([pull.once(buf), read]))
+ readRendered(null, cb)
+ })
+ }
+ }
+ function pickSource(type, read) {
+ if (/^image\//.test(type)) {
+ read(true, function (err) {
+ if (err && err !== true) console.trace(err)
+ })
+ return ph('img', {
+ src: opts.rawUrl
+ })
+ }
+ return ph('pre', pull.map(function (buf) {
+ return h('div',
+ self.app.render.linkify(buf.toString('utf8'))
+ ).innerHTML
+ })(read))
+ }
+}
+
Serve.prototype.wrapPublic = function (opts) {
var self = this
return u.hyperwrap(function (thread, cb) {