UNPKG

alinea

Version:
197 lines (195 loc) 5.74 kB
import "../../chunks/chunk-NZLE2WMY.js"; // src/core/source/GitDelta.ts import { assert } from "../util/Assert.js"; var MIN_MATCH = 4; var MAX_LITERAL = 127; var MAX_COPY_SIZE = 16777215; function createGitDelta(base, target) { const out = []; writeVarInt(base.length, out); writeVarInt(target.length, out); const index = buildBaseIndex(base); const literals = []; let i = 0; while (i < target.length) { const best = findBestMatch(base, target, i, index); if (!best || best.length < MIN_MATCH) { literals.push(target[i]); i++; if (literals.length === MAX_LITERAL) flushLiterals(literals, out); continue; } flushLiterals(literals, out); let remaining = best.length; let copyOffset = best.offset; while (remaining > 0) { const chunkSize = Math.min(remaining, MAX_COPY_SIZE); writeCopyInstruction(copyOffset, chunkSize, out); copyOffset += chunkSize; remaining -= chunkSize; } i += best.length; } flushLiterals(literals, out); return Uint8Array.from(out); } function applyGitDelta(base, delta) { let pos = 0; const [baseSize, baseSizePos] = readVarInt(delta, pos); pos = baseSizePos; const [resultSize, resultSizePos] = readVarInt(delta, pos); pos = resultSizePos; assert(baseSize === base.length, "Git delta base size mismatch"); const result = new Uint8Array(resultSize); let outPos = 0; while (pos < delta.length) { const opcode = delta[pos++]; if (opcode & 128) { let offset = 0; let size = 0; if (opcode & 1) offset |= delta[pos++]; if (opcode & 2) offset |= delta[pos++] << 8; if (opcode & 4) offset |= delta[pos++] << 16; if (opcode & 8) offset |= delta[pos++] << 24; if (opcode & 16) size |= delta[pos++]; if (opcode & 32) size |= delta[pos++] << 8; if (opcode & 64) size |= delta[pos++] << 16; if (size === 0) size = 65536; assert(offset >= 0 && offset + size <= base.length, "Invalid delta copy"); assert(outPos + size <= result.length, "Delta expands beyond target size"); result.set(base.subarray(offset, offset + size), outPos); outPos += size; continue; } assert(opcode !== 0, "Invalid delta opcode 0"); assert(pos + opcode <= delta.length, "Invalid delta insert"); assert(outPos + opcode <= result.length, "Delta expands beyond target size"); result.set(delta.subarray(pos, pos + opcode), outPos); pos += opcode; outPos += opcode; } assert(outPos === result.length, "Delta result size mismatch"); return result; } function writeVarInt(value, out) { let n = value >>> 0; while (true) { let byte = n & 127; n >>>= 7; if (n !== 0) byte |= 128; out.push(byte); if (n === 0) break; } } function readVarInt(data, start) { let shift = 0; let value = 0; let pos = start; while (true) { assert(pos < data.length, "Invalid varint"); const byte = data[pos++]; value |= (byte & 127) << shift; if ((byte & 128) === 0) break; shift += 7; assert(shift <= 28, "Varint too large"); } return [value >>> 0, pos]; } function flushLiterals(literals, out) { if (literals.length === 0) return; let pos = 0; while (pos < literals.length) { const chunk = Math.min(MAX_LITERAL, literals.length - pos); out.push(chunk); for (let i = 0; i < chunk; i++) out.push(literals[pos + i]); pos += chunk; } literals.length = 0; } function writeCopyInstruction(offset, size, out) { assert(size > 0, "Invalid copy size"); let opcode = 128; const params = []; const offset0 = offset & 255; const offset1 = offset >>> 8 & 255; const offset2 = offset >>> 16 & 255; const offset3 = offset >>> 24 & 255; if (offset0 !== 0) { opcode |= 1; params.push(offset0); } if (offset1 !== 0) { opcode |= 2; params.push(offset1); } if (offset2 !== 0) { opcode |= 4; params.push(offset2); } if (offset3 !== 0) { opcode |= 8; params.push(offset3); } const size0 = size & 255; const size1 = size >>> 8 & 255; const size2 = size >>> 16 & 255; if (size !== 65536) { if (size0 !== 0) { opcode |= 16; params.push(size0); } if (size1 !== 0) { opcode |= 32; params.push(size1); } if (size2 !== 0) { opcode |= 64; params.push(size2); } } out.push(opcode); for (const byte of params) out.push(byte); } function buildBaseIndex(base) { const index = /* @__PURE__ */ new Map(); for (let i = 0; i + MIN_MATCH <= base.length; i++) { const key = key4(base, i); const list = index.get(key); if (list) { if (list.length >= 64) list.shift(); list.push(i); } else { index.set(key, [i]); } } return index; } function findBestMatch(base, target, targetPos, index) { if (targetPos + MIN_MATCH > target.length) return void 0; const key = key4(target, targetPos); const candidates = index.get(key); if (!candidates || candidates.length === 0) return void 0; let bestOffset = 0; let bestLength = 0; for (let i = candidates.length - 1; i >= 0; i--) { const basePos = candidates[i]; let length = 0; while (basePos + length < base.length && targetPos + length < target.length && base[basePos + length] === target[targetPos + length]) { length++; } if (length > bestLength) { bestLength = length; bestOffset = basePos; if (bestLength >= MAX_COPY_SIZE) break; } } if (bestLength < MIN_MATCH) return void 0; return { offset: bestOffset, length: bestLength }; } function key4(data, pos) { return data[pos] << 24 | data[pos + 1] << 16 | data[pos + 2] << 8 | data[pos + 3]; } export { applyGitDelta, createGitDelta };