Skip to content
This repository was archived by the owner on Aug 27, 2025. It is now read-only.

Commit 5e4d83e

Browse files
committed
perf: rework read-packed
1 parent 92257e0 commit 5e4d83e

File tree

2 files changed

+85
-130
lines changed

2 files changed

+85
-130
lines changed

eslint.config.mjs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,14 @@ export default tslint.config(
2121
},
2222
tslint.configs.strictTypeChecked,
2323
tslint.configs.stylisticTypeChecked,
24+
{
25+
rules: {
26+
"@typescript-eslint/restrict-template-expressions": [
27+
"error",
28+
{ allowNumber: true },
29+
],
30+
},
31+
},
2432

2533
stylistic.configs.recommended,
2634
prettier,
Lines changed: 77 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -1,202 +1,149 @@
1-
import { readdir, readFile } from "node:fs/promises";
1+
import { readdir } from "node:fs/promises";
22
import { extname, join } from "node:path";
3-
import { promisify } from "node:util";
4-
import { inflate } from "node:zlib";
53
import { Cache } from "../cache/cache";
4+
import { inflate } from "../utils/inflate";
65
import { Pack, Packed } from "./pack";
76

7+
const ENOOBJ = new Error("could not get object info");
8+
89
export async function readPacked(
910
repo: string,
1011
hash: string,
1112

1213
cache: Cache,
1314
): Promise<Buffer> {
1415
const packs = await cache.packs.memo(async () => {
15-
const packs: Pack[] = [];
1616
const packsPath = join(repo, ".git", "objects", "pack");
17+
const files = await readdir(packsPath);
1718

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);
2923
}, repo);
3024

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);
3627

37-
if (packed !== undefined) {
38-
break;
28+
if (packed) {
29+
return await read(pack, packed);
3930
}
4031
}
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;
4733
}
4834

49-
interface Pointer {
50-
next: number;
35+
interface Offset {
36+
_: number;
5137
}
5238

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;
8050
}
8151

82-
const size = (firstByte & 0b1111) | (decodeSize(buffer, pointer) << 4);
52+
const size = (header & 0b1111) | (decLen(buffer, offset) << 4);
8353

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._));
8757

88-
return Buffer.concat([objectHead, objectBody]);
58+
return Buffer.concat([head, body]);
8959
}
9060

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);
9863

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;
10766
}
67+
const base = await read(pack, basePacked);
10868

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];
11471

115-
const instructs = delta.subarray(deltaPointer.next);
72+
const delta = await inflate(buffer.subarray(offset._));
73+
const deltaOffset = { _: 0 } as Offset;
11674

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._);
11978

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);
12281

12382
return Buffer.concat([objectHead, objectBody]);
12483
}
12584

126-
function buildDelta(base: Buffer, instructs: Buffer): Buffer {
85+
function buildDeltas(base: Buffer, instructs: Buffer): Buffer {
12786
const deltas = [];
12887

12988
while (instructs.byteLength !== 0) {
13089
const instruction = instructs.readUint8(0);
13190

13291
if ((instruction & 0x80) === 0) {
13392
const size = instruction & 0x7f;
134-
deltas.push(instructs.subarray(1, size + 1));
13593

94+
deltas.push(instructs.subarray(1, size + 1));
13695
instructs = instructs.subarray(size + 1);
96+
13797
continue;
13898
}
13999

140100
const byteMask = instruction & 0x7f;
141-
const bytePointer: Pointer = { next: 1 };
101+
const byteOffset: Offset = { _: 1 };
142102

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);
145105

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._);
150108
}
151109

152110
return Buffer.concat(deltas);
153111
}
154112

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;
159117

160118
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);
165123

166-
return size;
124+
return x;
167125
}
168126

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;
172130

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);
178134
}
179135

180-
return offset;
136+
return x;
181137
}
182138

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;
195142

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;
199146
}
200147

201-
return value;
148+
return x;
202149
}

0 commit comments

Comments
 (0)