diff options
-rw-r--r-- | lib/git.js | 132 | ||||
-rw-r--r-- | lib/render.js | 1 | ||||
-rw-r--r-- | lib/serve.js | 26 | ||||
-rw-r--r-- | lib/util.js | 1 | ||||
-rw-r--r-- | package.json | 1 |
5 files changed, 154 insertions, 7 deletions
@@ -8,6 +8,8 @@ var Reader = require('pull-reader') var toPull = require('stream-to-pull-stream') var zlib = require('zlib') var looper = require('looper') +var multicb = require('multicb') +var kvdiff = require('pull-kvdiff') var ObjectNotFoundError = u.customError('ObjectNotFoundError') @@ -16,6 +18,7 @@ var types = { commit: true, tree: true, } +var emptyBlobHash = 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391' module.exports = Git @@ -82,6 +85,7 @@ Git.prototype.openObject = function (opts, cb) { } Git.prototype.readObject = function (obj) { + if (obj.offset === obj.next) return pull.empty() return pull( this.app.readBlobSlice(obj.packLink, {start: obj.offset, end: obj.next}), this.decodeObject({ @@ -100,6 +104,19 @@ Git.prototype._findObject = function (opts, cb) { if (!opts.obj) return cb(new TypeError('missing object id')) var self = this var objId = opts.obj + if (objId === emptyBlobHash) { + // special case: the empty blob may be found anywhere + self.app.getMsgDecrypted(opts.headMsgId, function (err, msg) { + if (err) return cb(err) + return cb(null, { + offset: 0, + next: 0, + packLink: null, + idx: null, + msg: msg, + }) + }) + } self.findObjectMsgs(opts, function (err, msgs) { if (err) return cb(err) if (msgs.length === 0) @@ -515,6 +532,7 @@ function readCString(reader, cb) { } Git.prototype.readTree = function (obj) { + var self = this var reader = Reader() reader(this.readObject(obj)) return function (abort, cb) { @@ -529,9 +547,121 @@ Git.prototype.readTree = function (obj) { cb(null, { name: name, mode: mode, - hash: hash.toString('hex') + hash: hash.toString('hex'), + type: mode === 0040000 ? 'tree' : + mode === 0160000 ? 'commit' : 'blob', }) }) }) } } + +Git.prototype.readCommitChanges = function (commit) { + var self = this + return u.readNext(function (cb) { + var done = multicb({pluck: 1}) + commit.parents.forEach(function (rev) { + var cb = done() + self.getObjectMsg({ + obj: rev, + headMsgId: commit.msg.key, + type: 'commit', + }, function (err, msg) { + if (err) return cb(err) + self.openObject({ + obj: rev, + msg: msg.key, + }, function (err, obj) { + if (err) return cb(err) + self.getCommit(obj, cb) + }) + }) + }) + done()(null, commit) + done(function (err, commits) { + if (err) return cb(err) + var done = multicb({pluck: 1}) + commits.forEach(function (commit) { + var cb = done() + if (!commit.tree) return cb(null, pull.empty()) + self.getObjectMsg({ + obj: commit.tree, + headMsgId: commit.msg.key, + type: 'tree', + }, function (err, msg) { + if (err) return cb(err) + self.openObject({ + obj: commit.tree, + msg: commit.msg.key, + }, cb) + }) + }) + done(function (err, trees) { + if (err) return cb(err) + cb(null, self.diffTreesRecursive(trees)) + }) + }) + }) +} + +Git.prototype.diffTrees = function (objs) { + var self = this + return pull( + kvdiff(objs.map(function (obj) { + return self.readTree(obj) + }), 'name'), + pull.map(function (item) { + var diff = item.diff || {} + var head = item.values[item.values.length-1] + var created = true + for (var k = 0; k < item.values.length-1; k++) + if (item.values[k]) created = false + return { + name: item.key, + hash: diff.hash, + mode: diff.mode, + type: item.values.map(function (val) { return val.type }), + deleted: !head, + created: created + } + }) + ) +} + +Git.prototype.diffTreesRecursive = function (objs) { + var self = this + return pull( + self.diffTrees(objs), + paramap(function (item, cb) { + if (!item.type.some(function (t) { return t === 'tree' })) + return cb(null, [item]) + var done = multicb({pluck: 1}) + item.type.forEach(function (type, i) { + var cb = done() + if (type !== 'tree') return cb(null, pull.once(item)) + var hash = item.hash[i] + self.getObjectMsg({ + obj: hash, + headMsgId: objs[i].msg.key, + }, function (err, msg) { + if (err) return cb(err) + self.openObject({ + obj: hash, + msg: msg.key, + }, cb) + }) + }) + done(function (err, objs) { + if (err) return cb(err) + cb(null, pull( + self.diffTreesRecursive(objs), + pull.map(function (f) { + f.name = item.name + '/' + f.name + return f + }) + )) + }) + }, 4), + pull.flatten() + ) +} diff --git a/lib/render.js b/lib/render.js index d28f4b4..880470a 100644 --- a/lib/render.js +++ b/lib/render.js @@ -104,6 +104,7 @@ Render.prototype.emoji = function (emoji) { src: this.opts.emoji_base + emoji + '.png', alt: name, height: 17, + align: 'absmiddle', title: name, }) } diff --git a/lib/serve.js b/lib/serve.js index d2510e1..59a09d5 100644 --- a/lib/serve.js +++ b/lib/serve.js @@ -1440,6 +1440,21 @@ Serve.prototype.gitCommit = function (rev) { )]) : '', h('blockquote', self.app.render.gitCommitBody(commit.body)).outerHTML, + ph('h4', 'files'), + ph('table', pull( + self.app.git.readCommitChanges(commit), + 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.mode ? 'mode changed' + : JSON.stringify(file)) + ]) + }) + )) ] ]), self.wrapPage('git commit ' + rev), @@ -1572,20 +1587,19 @@ Serve.prototype.gitTree = function (rev) { }) }, 8), pull.map(function (item) { - var type = item.mode === 0040000 ? 'tree' : - item.mode === 0160000 ? 'commit' : 'blob' if (!item.msg) return ph('tr', [ ph('td', - u.escapeHTML(item.name) + (type === 'tree' ? '/' : '')), + u.escapeHTML(item.name) + (item.type === 'tree' ? '/' : '')), + ph('td', u.escapeHTML(item.hash)), ph('td', 'missing') ]) - var path = '/git/' + type + '/' + item.hash + var path = '/git/' + item.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)}, - u.escapeHTML(item.name) + (type === 'tree' ? '/' : ''))), + u.escapeHTML(item.name) + (item.type === 'tree' ? '/' : ''))), ph('td', self.phIdLink(item.msg.value.author)), ph('td', @@ -1748,7 +1762,7 @@ Serve.prototype.askWantBlobsForm = function (links) { if (!u.isRef(link.link)) return return ph('tr', [ ph('td', ph('code', link.link)), - ph('td', self.app.render.formatSize(link.size)), + !isNaN(link.size) ? ph('td', self.app.render.formatSize(link.size)) : '', ]) })), ph('input', {type: 'hidden', name: 'action', value: 'want-blobs'}), diff --git a/lib/util.js b/lib/util.js index a5f14f5..e4cf415 100644 --- a/lib/util.js +++ b/lib/util.js @@ -153,6 +153,7 @@ u.customError = function (name) { } u.escapeHTML = function (html) { + if (!html) return '' return html.toString('utf8') .replace(/</g, '<') .replace(/>/g, '>') diff --git a/package.json b/package.json index 42311a4..816c115 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "pull-hash": "^1.0.0", "pull-hyperscript": "^0.2.2", "pull-identify-filetype": "^1.1.0", + "pull-kvdiff": "^0.0.1", "pull-paginate": "^1.0.0", "pull-paramap": "^1.2.1", "pull-reader": "^1.2.9", |