123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326 |
- var inflateStream = require('./inflate-stream.js');
- var inflate = require('./inflate.js');
- var deflate = require('./deflate.js');
- var sha1 = require('git-sha1');
- var bodec = require('bodec');
- var typeToNum = {
- commit: 1,
- tree: 2,
- blob: 3,
- tag: 4,
- "ofs-delta": 6,
- "ref-delta": 7
- };
- var numToType = {};
- for (var type in typeToNum) {
- var num = typeToNum[type];
- numToType[num] = type;
- }
- exports.parseEntry = parseEntry;
- function parseEntry(chunk) {
- var offset = 0;
- var byte = chunk[offset++];
- var type = numToType[(byte >> 4) & 0x7];
- var size = byte & 0xf;
- var left = 4;
- while (byte & 0x80) {
- byte = chunk[offset++];
- size |= (byte & 0x7f) << left;
- left += 7;
- }
- size = size >>> 0;
- var ref;
- if (type === "ref-delta") {
- ref = bodec.toHex(bodec.slice(chunk, offset, offset += 20));
- }
- else if (type === "ofs-delta") {
- byte = chunk[offset++];
- ref = byte & 0x7f;
- while (byte & 0x80) {
- byte = chunk[offset++];
- ref = ((ref + 1) << 7) | (byte & 0x7f);
- }
- }
- var body = inflate(bodec.slice(chunk, offset));
- if (body.length !== size) {
- throw new Error("Size mismatch");
- }
- var result = {
- type: type,
- body: body
- };
- if (typeof ref !== "undefined") {
- result.ref = ref;
- }
- return result;
- }
- exports.decodePack = decodePack;
- function decodePack(emit) {
- var state = $pack;
- var sha1sum = sha1();
- var inf = inflateStream();
- var offset = 0;
- var position = 0;
- var version = 0x4b434150; // PACK reversed
- var num = 0;
- var type = 0;
- var length = 0;
- var ref = null;
- var checksum = "";
- var start = 0;
- var parts = [];
- return function (chunk) {
- if (chunk === undefined) {
- if (num || checksum.length < 40) throw new Error("Unexpected end of input stream");
- return emit();
- }
- for (var i = 0, l = chunk.length; i < l; i++) {
- // console.log([state, i, chunk[i].toString(16)]);
- if (!state) throw new Error("Unexpected extra bytes: " + bodec.slice(chunk, i));
- state = state(chunk[i], i, chunk);
- position++;
- }
- if (!state) return;
- if (state !== $checksum) sha1sum.update(chunk);
- var buff = inf.flush();
- if (buff.length) {
- parts.push(buff);
- }
- };
- // The first four bytes in a packfile are the bytes 'PACK'
- function $pack(byte) {
- if ((version & 0xff) === byte) {
- version >>>= 8;
- return version ? $pack : $version;
- }
- throw new Error("Invalid packfile header");
- }
- // The version is stored as an unsigned 32 integer in network byte order.
- // It must be version 2 or 3.
- function $version(byte) {
- version = (version << 8) | byte;
- if (++offset < 4) return $version;
- if (version >= 2 && version <= 3) {
- offset = 0;
- return $num;
- }
- throw new Error("Invalid version number " + num);
- }
- // The number of objects in this packfile is also stored as an unsigned 32 bit int.
- function $num(byte) {
- num = (num << 8) | byte;
- if (++offset < 4) return $num;
- offset = 0;
- emit({version: version, num: num});
- return $header;
- }
- // n-byte type and length (3-bit type, (n-1)*7+4-bit length)
- // CTTTSSSS
- // C is continue bit, TTT is type, S+ is length
- function $header(byte) {
- if (start === 0) start = position;
- type = byte >> 4 & 0x07;
- length = byte & 0x0f;
- if (byte & 0x80) {
- offset = 4;
- return $header2;
- }
- return afterHeader();
- }
- // Second state in the same header parsing.
- // CSSSSSSS*
- function $header2(byte) {
- length |= (byte & 0x7f) << offset;
- if (byte & 0x80) {
- offset += 7;
- return $header2;
- }
- return afterHeader();
- }
- // Common helper for finishing tiny and normal headers.
- function afterHeader() {
- offset = 0;
- if (type === 6) {
- ref = 0;
- return $ofsDelta;
- }
- if (type === 7) {
- ref = "";
- return $refDelta;
- }
- // console.log({type: type,length: length})
- return $body;
- }
- // Big-endian modified base 128 number encoded ref offset
- function $ofsDelta(byte) {
- ref = byte & 0x7f;
- if (byte & 0x80) return $ofsDelta2;
- return $body;
- }
- function $ofsDelta2(byte) {
- ref = ((ref + 1) << 7) | (byte & 0x7f);
- if (byte & 0x80) return $ofsDelta2;
- return $body;
- }
- // 20 byte raw sha1 hash for ref
- function $refDelta(byte) {
- ref += toHex(byte);
- if (++offset < 20) return $refDelta;
- return $body;
- }
- // Common helper for generating 2-character hex numbers
- function toHex(num) {
- return num < 0x10 ? "0" + num.toString(16) : num.toString(16);
- }
- // Common helper for emitting all three object shapes
- function emitObject() {
- var body = bodec.join(parts);
- if (body.length !== length) {
- throw new Error("Body length mismatch");
- }
- var item = {
- type: numToType[type],
- size: length,
- body: body,
- offset: start
- };
- if (ref) item.ref = ref;
- parts.length = 0;
- start = 0;
- offset = 0;
- type = 0;
- length = 0;
- ref = null;
- emit(item);
- }
- // Feed the deflated code to the inflate engine
- function $body(byte, i, chunk) {
- if (inf.write(byte)) return $body;
- var buf = inf.flush();
- if (buf.length !== length) throw new Error("Length mismatch, expected " + length + " got " + buf.length);
- inf.recycle();
- if (buf.length) {
- parts.push(buf);
- }
- emitObject();
- // If this was all the objects, start calculating the sha1sum
- if (--num) return $header;
- sha1sum.update(bodec.slice(chunk, 0, i + 1));
- return $checksum;
- }
- // 20 byte checksum
- function $checksum(byte) {
- checksum += toHex(byte);
- if (++offset < 20) return $checksum;
- var actual = sha1sum.digest();
- if (checksum !== actual) throw new Error("Checksum mismatch: " + actual + " != " + checksum);
- }
- }
- exports.encodePack = encodePack;
- function encodePack(emit) {
- var sha1sum = sha1();
- var left;
- return function (item) {
- if (item === undefined) {
- if (left !== 0) throw new Error("Some items were missing");
- return emit();
- }
- if (typeof item.num === "number") {
- if (left !== undefined) throw new Error("Header already sent");
- left = item.num;
- write(packHeader(item.num));
- }
- else if (typeof item.type === "string" && bodec.isBinary(item.body)) {
- // The header must be sent before items.
- if (typeof left !== "number") throw new Error("Headers not sent yet");
- // Make sure we haven't sent all the items already
- if (!left) throw new Error("All items already sent");
- // Send the item in packstream format
- write(packFrame(item));
- // Send the checksum after the last item
- if (!--left) {
- emit(bodec.fromHex(sha1sum.digest()));
- }
- }
- else {
- throw new Error("Invalid item");
- }
- };
- function write(chunk) {
- sha1sum.update(chunk);
- emit(chunk);
- }
- }
- function packHeader(length) {
- return bodec.fromArray([
- 0x50, 0x41, 0x43, 0x4b, // PACK
- 0, 0, 0, 2, // version 2
- length >> 24, // Num of objects
- (length >> 16) & 0xff,
- (length >> 8) & 0xff,
- length & 0xff
- ]);
- }
- function packFrame(item) {
- var length = item.body.length;
- // write TYPE_AND_BASE128_SIZE
- var head = [(typeToNum[item.type] << 4) | (length & 0xf)];
- var i = 0;
- length >>= 4;
- while (length) {
- head[i++] |= 0x80;
- head[i] = length & 0x7f;
- length >>= 7;
- }
- if (typeof item.ref === "number") {
- // write BIG_ENDIAN_MODIFIED_BASE_128_NUMBER
- var offset = item.ref;
- // Calculate how many digits we need in base 128 and move the pointer
- i += Math.floor(Math.log(offset) / Math.log(0x80)) + 1;
- // Write the last digit
- head[i] = offset & 0x7f;
- // Then write the rest
- while (offset >>= 7) {
- head[--i] = 0x80 | (--offset & 0x7f);
- }
- }
- var parts = [bodec.fromArray(head)];
- if (typeof item.ref === "string") {
- parts.push(bodec.fromHex(item.ref));
- }
- parts.push(deflate(item.body));
- return bodec.join(parts);
- }
|