|
1 | | -import { readdir, readFile } from "node:fs/promises"; |
| 1 | +import { readdir } from "node:fs/promises"; |
2 | 2 | import { extname, join } from "node:path"; |
3 | | -import { promisify } from "node:util"; |
4 | | -import { inflate } from "node:zlib"; |
5 | 3 | import { Cache } from "../cache/cache"; |
| 4 | +import { inflate } from "../utils/inflate"; |
6 | 5 | import { Pack, Packed } from "./pack"; |
7 | 6 |
|
| 7 | +const ENOOBJ = new Error("could not get object info"); |
| 8 | + |
8 | 9 | export async function readPacked( |
9 | 10 | repo: string, |
10 | 11 | hash: string, |
11 | 12 |
|
12 | 13 | cache: Cache, |
13 | 14 | ): Promise<Buffer> { |
14 | 15 | const packs = await cache.packs.memo(async () => { |
15 | | -const packs: Pack[] = []; |
16 | 16 | const packsPath = join(repo, ".git", "objects", "pack"); |
| 17 | +const files = await readdir(packsPath); |
17 | 18 |
|
18 | | -const packPaths = await readdir(packsPath); |
19 | | -const idxPaths = packPaths.filter((path) => extname(path) === ".idx"); |
20 | | - |
21 | | -for (const idxPath of idxPaths) { |
22 | | -const idxBuffer = await readFile(join(packsPath, idxPath)); |
23 | | -const packPath = join(packsPath, idxPath.replace(".idx", ".pack")); |
24 | | - |
25 | | -packs.push(new Pack(idxBuffer, packPath)); |
26 | | -} |
27 | | - |
28 | | -return packs; |
| 19 | +const packs = files |
| 20 | +.filter((path) => extname(path) === ".pack") |
| 21 | +.map((pack) => Pack.build(packsPath, pack)); |
| 22 | +return await Promise.all(packs); |
29 | 23 | }, repo); |
30 | 24 |
|
31 | | -let pack: Pack | undefined; |
32 | | -let packed: Packed | undefined; |
33 | | - |
34 | | -for (pack of packs) { |
35 | | -packed = await pack.queryRef(hash); |
| 25 | +for (const pack of packs) { |
| 26 | +const packed = await pack.readRef(hash); |
36 | 27 |
|
37 | | -if (packed !== undefined) { |
38 | | -break; |
| 28 | +if (packed) { |
| 29 | +return await read(pack, packed); |
39 | 30 | } |
40 | 31 | } |
41 | | - |
42 | | -if (pack === undefined || packed === undefined) { |
43 | | -throw new Error("could not get object info"); |
44 | | -} |
45 | | - |
46 | | -return await parse(repo, pack, packed, cache); |
| 32 | +throw ENOOBJ; |
47 | 33 | } |
48 | 34 |
|
49 | | -interface Pointer { |
50 | | -next: number; |
| 35 | +interface Offset { |
| 36 | +_: number; |
51 | 37 | } |
52 | 38 |
|
53 | | -async function parse( |
54 | | -repo: string, |
55 | | -pack: Pack, |
56 | | -packed: Packed, |
57 | | - |
58 | | -cache: Cache, |
59 | | -): Promise<Buffer> { |
60 | | -const { offset, buffer } = packed; |
61 | | -const pointer: Pointer = { next: 0 }; |
62 | | - |
63 | | -const firstByte = buffer.readUint8(0); |
64 | | -pointer.next++; |
65 | | - |
66 | | -const types = [ |
67 | | -"invalid", |
68 | | -"commit", |
69 | | -"tree", |
70 | | -"blob", |
71 | | -"tag", |
72 | | -"reserved", |
73 | | -"ofs-delta", |
74 | | -"ref-delta", |
75 | | -] as const; |
76 | | -const type = types[(firstByte >> 4) & 0b0111]; |
77 | | - |
78 | | -if (type === "invalid" || type === "reserved") { |
79 | | -throw new Error("unsupported object type"); |
| 39 | +async function read(pack: Pack, packed: Packed): Promise<Buffer> { |
| 40 | +const buffer = packed.buffer; |
| 41 | +const header = buffer.readUint8(); |
| 42 | +const offset = { _: 1 } as Offset; |
| 43 | + |
| 44 | +// ref-delta is not supported, since pack is self-contained |
| 45 | +const type = ([null, "commit", "tree", "blob", "tag", null, "ofs-delta", null] as const)[ |
| 46 | +(header >> 4) & 0b0111 |
| 47 | +]; |
| 48 | +if (type === null) { |
| 49 | +throw ENOOBJ; |
80 | 50 | } |
81 | 51 |
|
82 | | -const size = (firstByte & 0b1111) | (decodeSize(buffer, pointer) << 4); |
| 52 | +const size = (header & 0b1111) | (decLen(buffer, offset) << 4); |
83 | 53 |
|
84 | | -if (type !== "ofs-delta" && type !== "ref-delta") { |
85 | | -const objectHead = Buffer.from(`${type} ${size.toFixed()}\0`, "ascii"); |
86 | | -const objectBody = await promisify(inflate)(buffer.subarray(pointer.next)); |
| 54 | +if (type !== "ofs-delta") { |
| 55 | +const head = Buffer.from(`${type} ${size}\0`, "ascii"); |
| 56 | +const body = await inflate(buffer.subarray(offset._)); |
87 | 57 |
|
88 | | -return Buffer.concat([objectHead, objectBody]); |
| 58 | +return Buffer.concat([head, body]); |
89 | 59 | } |
90 | 60 |
|
91 | | -let base: Buffer | undefined; |
92 | | - |
93 | | -if (type === "ofs-delta") { |
94 | | -const baseOffset = offset - decodeOffset(buffer, pointer); |
95 | | - |
96 | | -const packed = await pack.queryOfs(baseOffset); |
97 | | -base = packed?.buffer; |
| 61 | +const baseOffset = packed.offset - decOfs(buffer, offset); |
| 62 | +const basePacked = await pack.readOfs(baseOffset); |
98 | 63 |
|
99 | | -if (base === undefined) { |
100 | | -throw new Error("could not get object info"); |
101 | | -} |
102 | | -} else { |
103 | | -const baseHash = buffer.toString("hex", pointer.next, pointer.next + 20); |
104 | | -pointer.next += 20; |
105 | | - |
106 | | -base = await readPacked(repo, baseHash, cache); |
| 64 | +if (!basePacked) { |
| 65 | +throw ENOOBJ; |
107 | 66 | } |
| 67 | +const base = await read(pack, basePacked); |
108 | 68 |
|
109 | | -const delta = await promisify(inflate)(buffer.subarray(pointer.next)); |
110 | | -const deltaPointer = { next: 0 }; |
111 | | - |
112 | | -decodeSize(delta, deltaPointer); |
113 | | -const objectSize = decodeSize(delta, deltaPointer); |
| 69 | +const terminator = base.indexOf(0); |
| 70 | +const objectType = base.toString("ascii", 0, terminator).split(" ")[0]; |
114 | 71 |
|
115 | | -const instructs = delta.subarray(deltaPointer.next); |
| 72 | +const delta = await inflate(buffer.subarray(offset._)); |
| 73 | +const deltaOffset = { _: 0 } as Offset; |
116 | 74 |
|
117 | | -const headEnd = base.indexOf(0); |
118 | | -const baseType = base.toString("ascii", 0, headEnd).split(" ")[0]; |
| 75 | +decLen(delta, deltaOffset); |
| 76 | +const objectSize = decLen(delta, deltaOffset); |
| 77 | +const instructs = delta.subarray(deltaOffset._); |
119 | 78 |
|
120 | | -const objectHead = Buffer.from(`${baseType} ${objectSize.toFixed()}\0`, "ascii"); |
121 | | -const objectBody = buildDelta(base.subarray(headEnd + 1), instructs); |
| 79 | +const objectHead = Buffer.from(`${objectType} ${objectSize}\0`, "ascii"); |
| 80 | +const objectBody = buildDeltas(base.subarray(terminator + 1), instructs); |
122 | 81 |
|
123 | 82 | return Buffer.concat([objectHead, objectBody]); |
124 | 83 | } |
125 | 84 |
|
126 | | -function buildDelta(base: Buffer, instructs: Buffer): Buffer { |
| 85 | +function buildDeltas(base: Buffer, instructs: Buffer): Buffer { |
127 | 86 | const deltas = []; |
128 | 87 |
|
129 | 88 | while (instructs.byteLength !== 0) { |
130 | 89 | const instruction = instructs.readUint8(0); |
131 | 90 |
|
132 | 91 | if ((instruction & 0x80) === 0) { |
133 | 92 | const size = instruction & 0x7f; |
134 | | -deltas.push(instructs.subarray(1, size + 1)); |
135 | 93 |
|
| 94 | +deltas.push(instructs.subarray(1, size + 1)); |
136 | 95 | instructs = instructs.subarray(size + 1); |
| 96 | + |
137 | 97 | continue; |
138 | 98 | } |
139 | 99 |
|
140 | 100 | const byteMask = instruction & 0x7f; |
141 | | -const bytePointer: Pointer = { next: 1 }; |
| 101 | +const byteOffset: Offset = { _: 1 }; |
142 | 102 |
|
143 | | -const offset = decodeInstruct(instructs, bytePointer, byteMask, "offset"); |
144 | | -const size = decodeInstruct(instructs, bytePointer, byteMask, "size"); |
| 103 | +const ofs = decCpy(instructs, byteOffset, byteMask); |
| 104 | +const len = decCpy(instructs, byteOffset, byteMask >> 4); |
145 | 105 |
|
146 | | -const delta = base.subarray(offset, offset + size); |
147 | | -deltas.push(delta); |
148 | | - |
149 | | -instructs = instructs.subarray(bytePointer.next); |
| 106 | +deltas.push(base.subarray(ofs, ofs + len)); |
| 107 | +instructs = instructs.subarray(byteOffset._); |
150 | 108 | } |
151 | 109 |
|
152 | 110 | return Buffer.concat(deltas); |
153 | 111 | } |
154 | 112 |
|
155 | | -function decodeSize(buffer: Buffer, pointer: Pointer): number { |
156 | | -let byte = 0; |
157 | | -let size = 0; |
158 | | -let shift = 0; |
| 113 | +function decLen(buf: Buffer, ofs: Offset): number { |
| 114 | +let b = 0; |
| 115 | +let s = 0; |
| 116 | +let x = 0; |
159 | 117 |
|
160 | 118 | do { |
161 | | -byte = buffer.readUint8(pointer.next++); |
162 | | -size |= (byte & 0x7f) << shift; |
163 | | -shift += 7; |
164 | | -} while (byte & 0x80); |
| 119 | +b = buf.readUint8(ofs._++); |
| 120 | +x |= (b & 0x7f) << s; |
| 121 | +s += 7; |
| 122 | +} while (b & 0x80); |
165 | 123 |
|
166 | | -return size; |
| 124 | +return x; |
167 | 125 | } |
168 | 126 |
|
169 | | -function decodeOffset(buffer: Buffer, pointer: Pointer): number { |
170 | | -let byte = buffer.readUint8(pointer.next++); |
171 | | -let offset = byte & 0x7f; |
| 127 | +function decOfs(buf: Buffer, ofs: Offset): number { |
| 128 | +let b = buf.readUint8(ofs._++); |
| 129 | +let x = b & 0x7f; |
172 | 130 |
|
173 | | -while (byte & 0x80) { |
174 | | -byte = buffer.readUint8(pointer.next++); |
175 | | -offset++; |
176 | | -offset <<= 7; |
177 | | -offset |= byte & 0x7f; |
| 131 | +while (b & 0x80) { |
| 132 | +b = buf.readUint8(ofs._++); |
| 133 | +x = ((x + 1) << 7) | (b & 0x7f); |
178 | 134 | } |
179 | 135 |
|
180 | | -return offset; |
| 136 | +return x; |
181 | 137 | } |
182 | 138 |
|
183 | | -function decodeInstruct( |
184 | | -buffer: Buffer, |
185 | | -pointer: Pointer, |
186 | | - |
187 | | -mask: number, |
188 | | -type: "offset" | "size", |
189 | | -): number { |
190 | | -let value = 0; |
191 | | -let shift = 0; |
192 | | - |
193 | | -const fm = type === "offset" ? 0 : 4; |
194 | | -const to = type === "offset" ? 4 : 7; |
| 139 | +function decCpy(buf: Buffer, ofs: Offset, mask: number): number { |
| 140 | +let s = 0; |
| 141 | +let x = 0; |
195 | 142 |
|
196 | | -for (let i = fm; i < to; i++) { |
197 | | -value |= (mask >> i) & 1 ? buffer.readUint8(pointer.next++) << shift : 0; |
198 | | -shift += 8; |
| 143 | +for (let i = 0; i < 4; i++) { |
| 144 | +x |= (mask >> i) & 1 && buf.readUint8(ofs._++) << s; |
| 145 | +s += 8; |
199 | 146 | } |
200 | 147 |
|
201 | | -return value; |
| 148 | +return x; |
202 | 149 | } |
0 commit comments