Skip to content
File

Blob: src/worker/git/pack/packMeta.ts

typescript144 lines
1import { bytesToHex } from "@/worker/common";
2 
3export type PackReadOptions = {
4 limiter?: { run<T>(label: string, fn: () => Promise<T>): Promise<T> };
5 countSubrequest?: (n?: number) => boolean | void;
6 signal?: AbortSignal;
7};
8 
9export type PackHeaderEx = {
10 type: number;
11 sizeVarBytes: Uint8Array;
12 headerLen: number;
13 baseOid?: string;
14 baseRel?: number;
15};
16 
17/**
18 * Read a byte range from an R2 `.pack` object.
19 */
20export async function readPackRange(
21 env: Env,
22 key: string,
23 offset: number,
24 length: number,
25 options?: PackReadOptions
26): Promise<Uint8Array | undefined> {
27 if (options?.signal?.aborted) return undefined;
28 
29 const run = async () => {
30 const obj = await env.REPO_BUCKET.get(key, { range: { offset, length } });
31 if (!obj) return undefined;
32 return new Uint8Array(await obj.arrayBuffer());
33 };
34 
35 if (options?.limiter) {
36 options.countSubrequest?.();
37 return await options.limiter.run("r2:get-range", run);
38 }
39 return await run();
40}
41 
42/**
43 * Read and parse a pack entry header at the given offset.
44 */
45export async function readPackHeaderEx(
46 env: Env,
47 key: string,
48 offset: number,
49 options?: PackReadOptions
50): Promise<PackHeaderEx | undefined> {
51 const head = await readPackRange(env, key, offset, 128, options);
52 if (!head) return undefined;
53 return readPackHeaderExFromBuf(head, 0);
54}
55 
56/**
57 * Parse a pack entry header from an in-memory buffer.
58 */
59export function readPackHeaderExFromBuf(buf: Uint8Array, offset: number): PackHeaderEx | undefined {
60 let p = offset;
61 if (p >= buf.length) return undefined;
62 
63 const start = p;
64 let c = buf[p++];
65 const type = (c >> 4) & 0x07;
66 while (c & 0x80) {
67 if (p >= buf.length) return undefined;
68 c = buf[p++];
69 }
70 
71 const sizeVarBytes = buf.subarray(start, p);
72 if (type === 7) {
73 if (p + 20 > buf.length) return undefined;
74 return {
75 type,
76 sizeVarBytes,
77 headerLen: sizeVarBytes.length + 20,
78 baseOid: bytesToHex(buf.subarray(p, p + 20)),
79 };
80 }
81 
82 if (type === 6) {
83 const ofsStart = p;
84 if (p >= buf.length) return undefined;
85 
86 let distance = 0;
87 let byte = buf[p++];
88 distance = byte & 0x7f;
89 while (byte & 0x80) {
90 if (p >= buf.length) return undefined;
91 byte = buf[p++];
92 distance = ((distance + 1) << 7) | (byte & 0x7f);
93 }
94 
95 return {
96 type,
97 sizeVarBytes,
98 headerLen: sizeVarBytes.length + (p - ofsStart),
99 baseRel: distance,
100 };
101 }
102 
103 return { type, sizeVarBytes, headerLen: sizeVarBytes.length };
104}
105 
106/**
107 * Returns the encoded byte length of an OFS_DELTA distance without allocating.
108 * Use this in convergence loops where only the length matters.
109 */
110export function ofsDeltaDistanceLength(rel: number): number {
111 if (rel <= 0) return 1;
112 let current = rel >>> 0;
113 let count = 0;
114 while (true) {
115 count++;
116 const group = current & 0x7f;
117 current = ((current - group) >>> 7) - 1;
118 if (current < 0) break;
119 }
120 return count;
121}
122 
123/**
124 * Encodes OFS_DELTA distance using Git's varint-with-add-one scheme.
125 */
126export function encodeOfsDeltaDistance(rel: number): Uint8Array {
127 if (rel <= 0) return new Uint8Array([0]);
128 
129 let current = rel >>> 0;
130 const groups: number[] = [];
131 while (true) {
132 const group = current & 0x7f;
133 groups.push(group);
134 current = ((current - group) >>> 7) - 1;
135 if (current < 0) break;
136 }
137 
138 groups.reverse();
139 for (let index = 0; index < groups.length - 1; index++) {
140 groups[index] |= 0x80;
141 }
142 return new Uint8Array(groups);
143}