aboutsummaryrefslogtreecommitdiff
path: root/lib/git.js
diff options
context:
space:
mode:
Diffstat (limited to 'lib/git.js')
-rw-r--r--lib/git.js262
1 files changed, 170 insertions, 92 deletions
diff --git a/lib/git.js b/lib/git.js
index 36e4587..048e2b3 100644
--- a/lib/git.js
+++ b/lib/git.js
@@ -8,121 +8,166 @@ var Reader = require('pull-reader')
var toPull = require('stream-to-pull-stream')
var zlib = require('zlib')
+var ObjectNotFoundError = u.customError('ObjectNotFoundError')
+
module.exports = Git
function Git(app) {
this.app = app
+
this.findObject = memo({
- cache: false,
+ cache: lru(5),
asString: function (opts) {
- return opts.id + opts.headMsgId
+ return opts.obj + opts.headMsgId
}
}, this._findObject.bind(this))
+
+ this.findObjectInMsg = memo({
+ cache: lru(5),
+ asString: function (opts) {
+ return opts.obj + opts.msg
+ }
+ }, this._findObjectInMsg.bind(this))
+
+ this.getPackIndex = memo({
+ cache: lru(4),
+ asString: JSON.stringify
+ }, this._getPackIndex.bind(this))
}
+// open, read, buffer and callback an object
Git.prototype.getObject = function (opts, cb) {
- pull(
- this.readObject(opts),
- u.pullConcat(cb)
- )
+ var self = this
+ self.openObject(opts, function (err, obj) {
+ if (err) return cb(err)
+ pull(
+ self.readObject(obj),
+ u.pullConcat(cb)
+ )
+ })
}
// get a message that pushed an object
-Git.prototype.getObjectMsg = function (opts) {
+Git.prototype.getObjectMsg = function (opts, cb) {
this.findObject(opts, function (err, loc) {
if (err) return cb(err)
cb(null, loc.msg)
})
}
-Git.prototype.readObject = function (opts) {
+Git.prototype.openObject = function (opts, cb) {
var self = this
- return u.readNext(function (cb) {
- self.findObject(opts, function (err, loc) {
+ self.findObjectInMsg(opts, function (err, loc) {
+ if (err) return cb(err)
+ self.app.ensureHasBlobs([loc.packLink], function (err) {
if (err) return cb(err)
- self.app.ensureHasBlobs([loc.packLink], function (err) {
- if (err) return cb(err)
- cb(null, pull(
- self.app.readBlob(loc.packLink, {start: loc.offset, end: loc.next}),
- self.decodeObject({
- type: opts.type,
- length: opts.length,
- packLink: loc.packLink,
- idx: loc.idx,
- })
- ))
+ cb(null, {
+ type: opts.type,
+ length: opts.length,
+ offset: loc.offset,
+ next: loc.next,
+ packLink: loc.packLink,
+ idx: loc.idx,
+ msg: loc.msg,
})
})
})
}
+Git.prototype.readObject = function (obj) {
+ return pull(
+ this.app.readBlob(obj.packLink, {start: obj.offset, end: obj.next}),
+ this.decodeObject({
+ type: obj.type,
+ length: obj.length,
+ packLink: obj.packLink,
+ idx: obj.idx,
+ })
+ )
+}
+
// find which packfile contains a git object, and where in the packfile it is
// located
Git.prototype._findObject = function (opts, cb) {
+ if (!opts.headMsgId) return cb(new TypeError('missing head message id'))
+ if (!opts.obj) return cb(new TypeError('missing object id'))
var self = this
- var objId = opts.id
- var objIdBuf = new Buffer(objId, 'hex')
+ var objId = opts.obj
self.findObjectMsgs(opts, function (err, msgs) {
if (err) return cb(err)
if (msgs.length === 0)
- return cb(new Error('unable to find git object ' + objId))
- // if blobs may need to be fetched, try to ask the user about as many of them
- // at one time as possible
- var packidxs = [].concat.apply([], msgs.map(function (msg) {
- var c = msg.value.content
- var idxs = u.toArray(c.indexes).map(u.toLink)
- return u.toArray(c.packs).map(u.toLink).map(function (pack, i) {
- var idx = idxs[i]
- if (pack && idx) return {
- msg: msg,
- packLink: pack,
- idxLink: idx,
- }
- })
- })).filter(Boolean)
- var blobLinks = packidxs.length === 1
- ? [packidxs[0].idxLink, packidxs[0].packLink]
- : packidxs.map(function (packidx) {
- return packidx.idxLink
- })
- self.app.ensureHasBlobs(blobLinks, function (err) {
- if (err) return cb(err)
- pull(
- pull.values(packidxs),
- paramap(function (pack, cb) {
- console.error('get idx', pack.idxLink)
- self.getPackIndex(pack.idxLink, function (err, idx) {
- if (err) return cb(err)
- var offset = idx.find(objIdBuf)
- // console.error('got idx', err, pack.idxId, offset)
- if (!offset) return cb()
- cb(null, {
- offset: offset.offset,
- next: offset.next,
- packLink: pack.packLink,
- idx: idx,
- msg: pack.msg,
- })
- })
- }, 4),
- pull.filter(),
- pull.take(1),
- pull.collect(function (err, offsets) {
+ return cb(new ObjectNotFoundError('unable to find git object ' + objId))
+ self.findObjectInMsgs(objId, msgs, cb)
+ })
+}
+
+Git.prototype._findObjectInMsg = function (opts, cb) {
+ if (!opts.msg) return cb(new TypeError('missing message id'))
+ if (!opts.obj) return cb(new TypeError('missing object id'))
+ var self = this
+ self.app.getMsgDecrypted(opts.msg, function (err, msg) {
+ if (err) return cb(err)
+ self.findObjectInMsgs(opts.obj, [msg], cb)
+ })
+}
+
+Git.prototype.findObjectInMsgs = function (objId, msgs, cb) {
+ var self = this
+ var objIdBuf = new Buffer(objId, 'hex')
+ // if blobs may need to be fetched, try to ask the user about as many of them
+ // at one time as possible
+ var packidxs = [].concat.apply([], msgs.map(function (msg) {
+ var c = msg.value.content
+ var idxs = u.toArray(c.indexes).map(u.toLink)
+ return u.toArray(c.packs).map(u.toLink).map(function (pack, i) {
+ var idx = idxs[i]
+ if (pack && idx) return {
+ msg: msg,
+ packLink: pack,
+ idxLink: idx,
+ }
+ })
+ })).filter(Boolean)
+ var blobLinks = packidxs.length === 1
+ ? [packidxs[0].idxLink, packidxs[0].packLink]
+ : packidxs.map(function (packidx) {
+ return packidx.idxLink
+ })
+ self.app.ensureHasBlobs(blobLinks, function (err) {
+ if (err) return cb(err)
+ pull(
+ pull.values(packidxs),
+ paramap(function (pack, cb) {
+ self.getPackIndex(pack.idxLink, function (err, idx) {
if (err) return cb(err)
- if (offsets.length === 0)
- return cb(new Error('unable to find git object ' + objId +
- ' in ' + msgs.length + ' messages'))
- cb(null, offsets[0])
+ var offset = idx.find(objIdBuf)
+ if (!offset) return cb()
+ cb(null, {
+ offset: offset.offset,
+ next: offset.next,
+ packLink: pack.packLink,
+ idx: idx,
+ msg: pack.msg,
+ })
})
- )
- })
+ }, 4),
+ pull.filter(),
+ pull.take(1),
+ pull.collect(function (err, offsets) {
+ if (err) return cb(err)
+ if (offsets.length === 0)
+ return cb(new ObjectNotFoundError('unable to find git object '
+ + objId + ' in ' + msgs.length + ' messages'))
+ cb(null, offsets[0])
+ })
+ )
})
}
// given an object id and ssb msg id, get a set of messages of which at least one pushed the object.
Git.prototype.findObjectMsgs = function (opts, cb) {
var self = this
- var id = opts.id
+ var id = opts.obj
var headMsgId = opts.headMsgId
var ended = false
var waiting = 0
@@ -144,7 +189,6 @@ Git.prototype.findObjectMsgs = function (opts, cb) {
;(function getMsg(id) {
waiting++
- console.error('get msg', id)
self.app.getMsgDecrypted(id, function (err, msg) {
waiting--
if (ended) return
@@ -159,7 +203,6 @@ Git.prototype.findObjectMsgs = function (opts, cb) {
|| (u.toArray(c.commits).some(objectMatches))) {
// found the object
return cbOnce(null, [msg])
- // console.error('found', msg.key)
} else if (!c.object_ids) {
// the object might be here
maybeMsgs.push(msg)
@@ -167,14 +210,13 @@ Git.prototype.findObjectMsgs = function (opts, cb) {
// traverse the DAG to keep looking for the object
u.toArray(c.repoBranch).filter(u.isRef).forEach(getMsg)
if (waiting === 0) {
- // console.error('trying messages', maybeMsgs.map(function (msg) { return msg.key}))
cbOnce(null, maybeMsgs)
}
})
})(headMsgId)
}
-Git.prototype.getPackIndex = function (idxBlobLink, cb) {
+Git.prototype._getPackIndex = function (idxBlobLink, cb) {
pull(this.app.readBlob(idxBlobLink), packidx(cb))
}
@@ -250,7 +292,6 @@ Git.prototype.decodeObject = function (opts) {
})
function getObjectFromRefDelta(length, cb) {
- console.error('read from ref delta')
reader.read(20, function (end, sourceHash) {
if (end) return cb(end)
var inflatedReader = Reader()
@@ -259,11 +300,9 @@ Git.prototype.decodeObject = function (opts) {
if (err) return cb(err)
readVarInt(inflatedReader, function (err, expectedTargetLength) {
if (err) return cb(err)
- // console.error('getting object', sourceHash)
var offset = opts.idx.find(sourceHash)
if (!offset) return cb(null, 'missing source object ' +
sourcehash.toString('hex'))
- console.error('get pack', opts.packLink, offset.offset, offset.next)
var readSource = pull(
self.app.readBlob(opts.packLink, {
start: offset.offset,
@@ -276,7 +315,6 @@ Git.prototype.decodeObject = function (opts) {
idx: opts.idx
})
)
- // console.error('patching', length, expectedTargetLength)
cb(null, patchObject(inflatedReader, length, readSource, expectedTargetLength))
})
})
@@ -328,7 +366,6 @@ function readOffsetSize(cmd, reader, readCb) {
function patchObject(deltaReader, deltaLength, readSource, targetLength) {
var srcBuf
var ended
- // console.error('patching', deltaLength, targetLength)
return u.readNext(function (cb) {
pull(readSource, u.pullConcat(function (err, buf) {
@@ -339,21 +376,15 @@ function patchObject(deltaReader, deltaLength, readSource, targetLength) {
})
function read(abort, cb) {
- // console.error('pa', abort, ended)
if (ended) return cb(ended)
deltaReader.read(1, function (end, dBuf) {
- // console.error("read", end, dBuf)
- // if (ended = end) return console.error('patched', deltaLength, targetLength, end), cb(end)
if (ended = end) return cb(end)
var cmd = dBuf[0]
- // console.error('cmd', cmd & 0x80, cmd)
if (cmd & 0x80)
// skip a variable amount and then pass through a variable amount
readOffsetSize(cmd, deltaReader, function (err, offset, size) {
- // console.error('offset', err, offset, size)
if (err) return earlyEnd(err)
var buf = srcBuf.slice(offset, offset + size)
- // console.error('buf', buf)
cb(end, buf)
})
else if (cmd)
@@ -369,12 +400,59 @@ function patchObject(deltaReader, deltaLength, readSource, targetLength) {
}
}
-Git.prototype.getCommit = function (opts, cb) {
- this.getObject(opts, function (err, buf) {
+var gitNameRegex = /^(.*) <(([^>@]*)(@[^>]*)?)> (.*) (.*)$/
+function parseName(line) {
+ var m = gitNameRegex.exec(line)
+ if (!m) return null
+ return {
+ name: m[1],
+ email: m[2],
+ localpart: m[3],
+ feed: u.isRef(m[4]) && m[4] || undefined,
+ date: new Date(m[5] * 1000),
+ tz: m[6],
+ }
+}
+
+Git.prototype.getCommit = function (obj, cb) {
+ pull(this.readObject(obj), u.pullConcat(function (err, buf) {
if (err) return cb(err)
var commit = {
- body: buf.toString('ascii')
+ msg: obj.msg,
+ parents: [],
}
+ var authorLine, committerLine
+ 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 'tree':
+ commit.tree = value
+ break
+ case 'parent':
+ commit.parents.push(value)
+ break
+ case 'author':
+ authorLine = value
+ break
+ case 'committer':
+ committerLine = value
+ break
+ case 'gpgsig':
+ var sigLines = [value]
+ while (lines[0] && lines[0][0] == ' ')
+ sigLines.push(lines.shift().slice(1))
+ commit.gpgsig = sigLines.join('\n')
+ break
+ default:
+ return cb(new TypeError('unknown git object property ' + prop))
+ }
+ }
+ commit.committer = parseName(committerLine)
+ if (authorLine !== committerLine) commit.author = parseName(authorLine)
+ commit.body = lines.join('\n')
cb(null, commit)
- })
+ }))
}