git-list-pack
Version:
create a readable stream of a packfile's contents, sans an index file
329 lines (276 loc) • 6.51 kB
JavaScript
module.exports = unpack
var inflate = require('inflate/min.js')
, binary = require('bops')
var OFS_DELTA = 6
, REF_DELTA = 7
function unpack(read, oncksum) {
var need_input = false
, states = []
, state = null
, ended = false
, buffer = []
, got = 0
var buffer_offset = 0
var inflate_stream = null
, inflated_fragments = []
, uninflated_fragments = []
, inflate_finished = false
, _inflate_wants
var offset = 12
, header_size = 0
var current_object_header = []
, current_ofs_header = []
var expanded_size
, object_count
, prev_object
, reference = null
, version
, cksum
, type
, last
var byte_need = 0
, byte_accum = []
var queued = []
, emit
oncksum = oncksum || function() {}
want_bytes(4); become(bytes, got_header)
stream.is = 'min-stream-pull-filter'
return stream
function stream(close, callback) {
if(queued.length) {
var out = queued.shift()
if(out === null) {
return callback()
}
return callback(null, out)
}
emit = callback
read(null, onread)
}
function onread(err, buf) {
if(buf === undefined) {
return read(err)
}
return write(buf)
}
function queue(obj) {
queued.push(obj)
if(emit) while(queued.length) {
emit(null, queued.shift())
}
}
function want_bytes(num) {
byte_need = num
byte_accum.length = 0
}
function write(buf) {
buffer.push(buf)
got += buf.length
if(!ended) {
execute()
}
read(null, onread)
}
function got_header() {
for(var i = 0, len = 4; i < len; ++i) {
if(last[i] !== 'PACK'.charCodeAt(i)) {
emit(new Error(
'invalid header'
))
return
}
}
want_bytes(4); become(bytes, got_header_version)
}
function got_header_version() {
// no-op for now
want_bytes(4); become(bytes, got_object_count)
}
function got_object_count() {
object_count = last[3] | (last[2] << 8) | (last[1] << 16) | (last[0] << 24)
object_count >>>= 0
want_bytes(1); become(bytes, start_object_header)
}
function start_object_header() {
current_object_header.length = 0
header_size = 0
iter_object_header()
}
function iter_object_header() {
var byt = last[0]
current_object_header.push(byt)
if(!(byt & 0x80)) {
finish_object_header()
} else {
want_bytes(1); become(bytes, iter_object_header)
}
}
function finish_object_header() {
var size = current_object_header[0] & 0x0F
, shift = 4
, idx = 1
, byt
header_size = current_object_header.length
type = current_object_header[0] >> 4 & 7
while(idx < current_object_header.length) {
size += (current_object_header[idx++] & 0x7F) << shift
shift += 7
}
expanded_size = size
if(type < 5) {
start_inflate()
} else if(type === OFS_DELTA) {
start_ofs_delta()
} else if(type === REF_DELTA) {
start_ref_delta()
}
}
function start_inflate() {
states[0] = write_inflate
inflate_stream = inflate_stream ? inflate_stream.recycle() : inflate(inflate_read, finish_inflate)
inflated_fragments.length = 0
inflate_finished = false
iter()
function iter() {
inflate_stream(null, function(err, data) {
if(err) {
return emit(err)
}
inflated_fragments.push(data)
iter()
})
}
}
function inflate_read(close, ready) {
if(close === true) {
return
}
if(close) {
return emit(close)
}
_inflate_wants = ready
}
function write_inflate() {
var next
while(buffer.length && !inflate_finished) {
next = buffer.shift()
if(buffer_offset) {
if(buffer_offset === next.length) {
buffer_offset = 0
continue
}
next = binary.subarray(next, buffer_offset)
buffer_offset = 0
}
got -= next.length
_inflate_wants(null, next)
}
if(!buffer.length && !inflate_finished) {
need_input = true
}
}
function finish_inflate(unused, read) {
inflate_finished = true
queue(prev_object = {
reference: reference
, data: binary.join(inflated_fragments)
, type: type
, offset: offset
, num: object_count - 1
})
offset += read + header_size + (reference ? reference.length : 0)
header_size = 0
--object_count
reference = null
if(unused.length) {
buffer = unused.concat(buffer)
for(var i = 0, len = unused.length; i < len; ++i) {
got += unused[i].length
}
buffer_offset = 0
}
if(!object_count) {
want_bytes(20); become(bytes, got_checksum)
} else {
want_bytes(1); become(bytes, start_object_header)
}
}
function start_ofs_delta() {
current_ofs_header.length = 0
want_bytes(1); become(bytes, iter_ofs_delta)
}
function iter_ofs_delta() {
var byt = last[0]
current_ofs_header.push(byt)
if(!(byt & 0x80)) {
reference = binary.from(current_ofs_header)
start_inflate()
} else {
want_bytes(1); become(bytes, iter_ofs_delta)
}
}
function start_ref_delta() {
want_bytes(20); become(bytes, got_ref_delta_reference)
}
function got_ref_delta_reference() {
reference = binary.from(last)
start_inflate()
}
function got_checksum() {
oncksum(binary.from(last))
queue(null)
ended = true
}
function execute() {
while(1) {
states[0]()
if(need_input || ended) {
break
}
}
need_input = false
}
function bytes() {
var value
while(byte_need--) {
value = take()
if(need_input) {
byte_need += 1
break
}
byte_accum[byte_accum.length] = value
}
if(!need_input) {
unbecome(byte_accum)
}
}
function take() {
var val
if(!buffer.length) {
need_input = true
} else if(buffer_offset === buffer[0].length) {
buffer.shift()
buffer_offset = 0
val = take()
} else {
val = buffer[0][buffer_offset++]
}
return val
}
function become(fn, then) {
if(typeof then !== 'function') {
throw new Error
}
last = null
if(states.length < 1) {
states.unshift(then)
} else {
states[0] = then
}
states.unshift(fn)
}
function unbecome(result) {
states.shift()
last = result
}
}