UNPKG

git-cat-file

Version:

A pure-JavaScript implementation of `git cat-file -p` for Node.js.

160 lines (133 loc) 5.18 kB
/** * https://github.com/kawanet/git-cat-file * * @see https://github.com/git/git/blob/master/Documentation/technical/pack-format.txt */ import {promises as fs} from "fs" import {inflateSync} from "zlib" import type {GCF} from "../types/git-cat-file.d.ts" import type {ObjStore} from "./obj-store.ts" const TypeBit = { OBJ_COMMIT: 1, OBJ_TREE: 2, OBJ_BLOB: 3, OBJ_TAG: 4, OBJ_OFS_DELTA: 6, OBJ_REF_DELTA: 7, } as const const typeNames: GCF.ObjType[] = [null, "commit", "tree", "blob", "tag"] const deltaTypes = {6: "OBJ_OFS_DELTA", 7: "OBJ_REF_DELTA"} // const toHex = (buf: Buffer) => buf.toString("hex").replace(/(\w.)(?=\w)/g, "$1 "); export async function readPackedObject(fh: fs.FileHandle, start: number, store: ObjStore): Promise<Partial<GCF.IObject>> { const buffer = Buffer.alloc(28) await fh.read({buffer, position: start}) // console.warn(`read: ${start} (${toHex(buffer)})`); let offset = 0 let c = buffer[offset++] const typeBit = (c & 0x70) >> 4 const type = typeNames[typeBit] const deltaType = deltaTypes[typeBit as keyof typeof deltaTypes] // console.warn(`type: ${typeBit} (${deltaType || type})`); if (!deltaType && !type) throw new TypeError(`Invalid type: ${typeBit}`) let size = c & 0x0F { let shift = 4 while (c & 0x80) { c = buffer[offset++] size += ((c & 0x7F) << shift) shift += 7 } } // console.warn(`size: ${size} bytes`); if (typeBit === TypeBit.OBJ_OFS_DELTA) { let c = buffer[offset++] let baseOffset = c & 0x7F while (c & 0x80) { baseOffset += 1 // see unpack-objects.c c = buffer[offset++] baseOffset = (baseOffset << 7) + (c & 0x7F) } if (start < baseOffset) { throw new TypeError(`offset value out of bound: ${start} < ${baseOffset}`) } // console.warn(`delta: ${start} + ${offset}`); const delta = await readData(fh, start + offset, size) // console.warn(`base: ${start} - ${baseOffset}`); const base = await readPackedObject(fh, start - baseOffset, store) const data = applyDelta(base.data, delta) return {type: base.type, data} } if (typeBit === TypeBit.OBJ_REF_DELTA) { const end = offset + 20 const oid = buffer.slice(offset, end).toString("hex") offset = end // console.warn(`delta: ${start} + ${offset}`); const delta = await readData(fh, start + offset, size) // console.warn(`base: ${oid}`); const base = await store.getObject(oid) const data = applyDelta(base.data, delta) return {type: base.type, data} } if (!size) { const data = Buffer.alloc(0) return {type, data} } // console.warn(`position: ${start} + ${offset}`); const data = await readData(fh, start + offset, size) return {type, data} } async function readData(fh: fs.FileHandle, position: number, size: number): Promise<Buffer> { const bufSize = Math.ceil(size * 17 / 16 / 512) * 512 const buffer = Buffer.alloc(bufSize) await fh.read({buffer, position}) return inflateSync(buffer, {maxOutputLength: size}) } function applyDelta(baseData: Buffer, deltaData: Buffer): Buffer { let deltaPos = 0 let dstPos = 0 // console.warn(`delta: ${toHex(deltaData.slice(0, 32))}`); const srcSize = readSize() if (!srcSize) throw new TypeError(`Invalid source size: ${srcSize}`) const dstSize = readSize() if (!dstSize) throw new TypeError(`Invalid dest size: ${dstSize}`) const dstData = Buffer.alloc(dstSize) const deltaEnd = deltaData.length while (deltaPos < deltaEnd) { const inst = deltaData[deltaPos++] if (inst & 0x80) { let offset = 0 let size = 0 if (inst & 0x01) offset += deltaData[deltaPos++] if (inst & 0x02) offset += (deltaData[deltaPos++] << 8) if (inst & 0x04) offset += (deltaData[deltaPos++] << 16) if (inst & 0x08) offset += (deltaData[deltaPos++] << 24) if (inst & 0x10) size += deltaData[deltaPos++] if (inst & 0x20) size += (deltaData[deltaPos++] << 8) if (inst & 0x40) size += (deltaData[deltaPos++] << 16) if (!size) size = 0x10000 // console.warn(`copy: ${inst.toString(2)} offset=${offset} size=${size}`); baseData.copy(dstData, dstPos, offset, offset + size) dstPos += size } else if (inst) { const end = deltaPos + inst // console.warn(`add: ${inst}`); deltaData.copy(dstData, dstPos, deltaPos, end) deltaPos = end dstPos += inst } else { throw TypeError(`unexpected delta opcode: ${inst}`) } } return dstData function readSize() { let c = deltaData[deltaPos++] let shift = 7 let size = c & 0x7F while (c & 0x80) { c = deltaData[deltaPos++] size += ((c & 0x7F) << shift) shift += 7 } return size } }