git-fs-s3 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,256 +0,0 @@
1
- import {
2
- GitConflictError,
3
- GitInvalidRequestError,
4
- concat,
5
- deflate,
6
- encodeUtf8,
7
- fromHex,
8
- isSafeBranchName,
9
- packFileName,
10
- rawFs,
11
- sha1,
12
- toHex
13
- } from "./chunk-HO6OPXUS.js";
14
-
15
- // src/ops/branch.ts
16
- import git from "isomorphic-git";
17
- function assertSafeBranchName(name) {
18
- if (!isSafeBranchName(name)) {
19
- throw new GitInvalidRequestError(`Invalid branch name: ${name}`);
20
- }
21
- }
22
- var FULL_SHA_RE = /^[0-9a-f]{40}$/i;
23
- function hasRecursiveFileLister(fs) {
24
- return typeof fs === "object" && fs !== null && "listFilesRecursively" in fs && typeof fs.listFilesRecursively === "function";
25
- }
26
- function getPromisesFs(fs) {
27
- const promises = fs.promises;
28
- return promises ?? null;
29
- }
30
- function decodeFileContent(content) {
31
- return typeof content === "string" ? content : new TextDecoder().decode(content);
32
- }
33
- async function listLooseBranches(repo) {
34
- return listLooseRefNames(repo, "refs/heads");
35
- }
36
- async function listLooseRefNames(repo, dir) {
37
- if (!hasRecursiveFileLister(repo.fs)) return null;
38
- const names = await repo.fs.listFilesRecursively(`${repo.gitdir}/${dir}`);
39
- return names.filter(isSafeBranchName);
40
- }
41
- async function readPackedBranches(repo) {
42
- if (!hasRecursiveFileLister(repo.fs)) return /* @__PURE__ */ new Map();
43
- const promisesFs = getPromisesFs(repo.fs);
44
- if (!promisesFs) return /* @__PURE__ */ new Map();
45
- try {
46
- const content = await promisesFs.readFile(
47
- `${repo.gitdir}/packed-refs`,
48
- "utf8"
49
- );
50
- const refs = /* @__PURE__ */ new Map();
51
- for (const line of decodeFileContent(content).split("\n")) {
52
- const match = /^([0-9a-f]{40}) refs\/heads\/(.+)$/i.exec(line);
53
- if (match?.[1] && match[2] && isSafeBranchName(match[2])) {
54
- refs.set(match[2], match[1]);
55
- }
56
- }
57
- return refs;
58
- } catch {
59
- return /* @__PURE__ */ new Map();
60
- }
61
- }
62
- async function getCurrentLooseBranch(repo) {
63
- if (!hasRecursiveFileLister(repo.fs)) return null;
64
- const promisesFs = getPromisesFs(repo.fs);
65
- if (!promisesFs) return null;
66
- try {
67
- const content = await promisesFs.readFile(`${repo.gitdir}/HEAD`, "utf8");
68
- const head = decodeFileContent(content).trim();
69
- const match = /^ref: refs\/heads\/(.+)$/.exec(head);
70
- return match?.[1] && isSafeBranchName(match[1]) ? match[1] : null;
71
- } catch {
72
- return null;
73
- }
74
- }
75
- async function resolveLooseRefFast(repo, ref) {
76
- const promisesFs = getPromisesFs(repo.fs);
77
- if (promisesFs) {
78
- try {
79
- const content = await promisesFs.readFile(
80
- `${repo.gitdir}/${ref}`,
81
- "utf8"
82
- );
83
- const oid = decodeFileContent(content).trim();
84
- if (FULL_SHA_RE.test(oid)) return oid;
85
- } catch {
86
- }
87
- }
88
- return git.resolveRef({ ...repo, ref });
89
- }
90
- async function listBranches(repo) {
91
- try {
92
- const [looseBranches, looseCurrentBranch, packedBranches] = await Promise.all([
93
- listLooseBranches(repo),
94
- getCurrentLooseBranch(repo),
95
- readPackedBranches(repo)
96
- ]);
97
- const branches = looseBranches === null ? await git.listBranches(repo) : [.../* @__PURE__ */ new Set([...looseBranches, ...packedBranches.keys()])].sort();
98
- const currentBranch = looseBranches === null ? await git.currentBranch({ ...repo, fullname: false }).catch(() => null) : looseCurrentBranch;
99
- return Promise.all(
100
- branches.map(async (branch) => {
101
- const packedCommit = packedBranches.get(branch);
102
- return {
103
- name: branch,
104
- commit: packedCommit && !looseBranches?.includes(branch) ? packedCommit : await resolveLooseRefFast(repo, `refs/heads/${branch}`),
105
- isDefault: branch === currentBranch
106
- };
107
- })
108
- );
109
- } catch (err) {
110
- if (err.code === "NotFoundError") return [];
111
- throw err;
112
- }
113
- }
114
- async function createBranchFrom(repo, name, startPoint) {
115
- assertSafeBranchName(name);
116
- assertSafeBranchName(startPoint);
117
- const object = await resolveLooseRefFast(repo, `refs/heads/${startPoint}`);
118
- try {
119
- await git.branch({ ...repo, ref: name, checkout: false, object });
120
- } catch (err) {
121
- if (err.code === "AlreadyExistsError") {
122
- throw new GitConflictError(`Branch ${name} already exists`);
123
- }
124
- throw err;
125
- }
126
- }
127
- async function deleteBranchByName(repo, name) {
128
- assertSafeBranchName(name);
129
- const current = await getCurrentLooseBranch(repo) ?? await git.currentBranch({ ...repo, fullname: false }).catch(() => null);
130
- if (current === name) {
131
- throw new GitInvalidRequestError(
132
- `Cannot delete ${name}: it is the default branch`
133
- );
134
- }
135
- await git.deleteBranch({ ...repo, ref: name });
136
- }
137
- async function assertBranchExists(repo, name) {
138
- assertSafeBranchName(name);
139
- await resolveLooseRefFast(repo, `refs/heads/${name}`);
140
- }
141
-
142
- // src/pack-write.ts
143
- import git2 from "isomorphic-git";
144
- var PACK_OBJECT_TYPE_BITS = {
145
- commit: 16,
146
- tree: 32,
147
- blob: 48,
148
- tag: 64
149
- };
150
- function encodePackObjectHeader(type, length) {
151
- const bytes = [];
152
- let more = length > 15;
153
- bytes.push(
154
- (more ? 128 : 0) | PACK_OBJECT_TYPE_BITS[type] | length & 15
155
- );
156
- let rest = Math.floor(length / 16);
157
- while (more) {
158
- more = rest > 127;
159
- bytes.push((more ? 128 : 0) | rest & 127);
160
- rest = Math.floor(rest / 128);
161
- }
162
- return new Uint8Array(bytes);
163
- }
164
- function hashGitObject(type, content) {
165
- return sha1(concat(encodeUtf8(`${type} ${content.length}\0`), content));
166
- }
167
- async function encodePack(objects) {
168
- const header = new Uint8Array(12);
169
- const view = new DataView(header.buffer);
170
- header.set(encodeUtf8("PACK"), 0);
171
- view.setUint32(4, 2, false);
172
- view.setUint32(8, objects.length, false);
173
- const chunks = [header];
174
- const BATCH_SIZE = 100;
175
- for (let i = 0; i < objects.length; i += BATCH_SIZE) {
176
- const encoded = await Promise.all(
177
- objects.slice(i, i + BATCH_SIZE).map(
178
- async (object) => concat(
179
- encodePackObjectHeader(object.type, object.content.length),
180
- await deflate(object.content)
181
- )
182
- )
183
- );
184
- chunks.push(...encoded);
185
- }
186
- const body = concat(...chunks);
187
- return concat(body, fromHex(await sha1(body)));
188
- }
189
- function packBaseName(pack) {
190
- return `pack-${toHex(pack.subarray(pack.length - 20))}`;
191
- }
192
- async function storePack(repo, pack) {
193
- const fsp = rawFs(repo);
194
- const packDir = `${repo.gitdir}/objects/pack`;
195
- const base = packBaseName(pack);
196
- await fsp.mkdir(packDir, { recursive: true });
197
- await fsp.writeFile(`${packDir}/${packFileName(base, "pack")}`, pack);
198
- const { oids } = await git2.indexPack({
199
- ...repo,
200
- dir: packDir,
201
- filepath: packFileName(base, "pack")
202
- });
203
- return { base, oids };
204
- }
205
-
206
- // src/semaphore.ts
207
- function createSemaphore(limit) {
208
- if (!Number.isInteger(limit) || limit < 1) {
209
- throw new Error(
210
- `createSemaphore: limit must be an integer >= 1, got ${limit}`
211
- );
212
- }
213
- let active = 0;
214
- const waiting = [];
215
- function release() {
216
- const next = waiting.shift();
217
- if (next) next();
218
- else active--;
219
- }
220
- function acquire() {
221
- if (active < limit) {
222
- active++;
223
- return Promise.resolve();
224
- }
225
- return new Promise((resolve) => {
226
- waiting.push(resolve);
227
- });
228
- }
229
- return {
230
- run(fn) {
231
- return acquire().then(async () => {
232
- try {
233
- return await fn();
234
- } finally {
235
- release();
236
- }
237
- });
238
- }
239
- };
240
- }
241
-
242
- export {
243
- assertSafeBranchName,
244
- listLooseRefNames,
245
- resolveLooseRefFast,
246
- listBranches,
247
- createBranchFrom,
248
- deleteBranchByName,
249
- assertBranchExists,
250
- hashGitObject,
251
- encodePack,
252
- packBaseName,
253
- storePack,
254
- createSemaphore
255
- };
256
- //# sourceMappingURL=chunk-QH3HS7VI.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/ops/branch.ts","../src/pack-write.ts","../src/semaphore.ts"],"sourcesContent":["import git from \"isomorphic-git\";\nimport { GitConflictError, GitInvalidRequestError } from \"../git-errors.js\";\nimport { isSafeBranchName } from \"../refs.js\";\nimport type { Repo } from \"./types.js\";\n\nexport interface Branch {\n\tname: string;\n\tcommit: string;\n\tisDefault: boolean;\n}\n\n/**\n * Defense in depth for every branch-name argument below: `git.deleteBranch`\n * and raw resolveRef/writeRef reads don't validate ref names internally the\n * way `git.branch` does (see refs.ts) — guard at the point the primitives are\n * actually called, not just at an API boundary far above.\n */\nexport function assertSafeBranchName(name: string): void {\n\tif (!isSafeBranchName(name)) {\n\t\tthrow new GitInvalidRequestError(`Invalid branch name: ${name}`);\n\t}\n}\n\nconst FULL_SHA_RE = /^[0-9a-f]{40}$/i;\n\ntype RecursiveFileLister = {\n\tlistFilesRecursively(dirpath: string): Promise<string[]>;\n};\n\nfunction hasRecursiveFileLister(\n\tfs: Repo[\"fs\"],\n): fs is Repo[\"fs\"] & RecursiveFileLister {\n\treturn (\n\t\ttypeof fs === \"object\" &&\n\t\tfs !== null &&\n\t\t\"listFilesRecursively\" in fs &&\n\t\ttypeof (fs as Partial<RecursiveFileLister>).listFilesRecursively ===\n\t\t\t\"function\"\n\t);\n}\n\ntype PromisesFs = {\n\treadFile(path: string, encoding: \"utf8\"): Promise<string | Uint8Array>;\n};\n\n// Repo.fs is typed as isomorphic-git's own fs union (promise- or\n// callback-based) since that's what git.readTree's signature allows, but\n// every fs this package actually constructs (git-fs.ts) is promise-based —\n// this just keeps the callback-style branch honest instead of crashing on\n// it. Shared by every loose-ref fast path below instead of each repeating\n// the same cast + presence check.\nfunction getPromisesFs(fs: Repo[\"fs\"]): PromisesFs | null {\n\tconst promises = (fs as { promises?: PromisesFs }).promises;\n\treturn promises ?? null;\n}\n\nfunction decodeFileContent(content: string | Uint8Array): string {\n\treturn typeof content === \"string\"\n\t\t? content\n\t\t: new TextDecoder().decode(content);\n}\n\n/**\n * Lists loose branch names without isomorphic-git's recursive readdir/stat\n * traversal. Object stores already return only leaf keys in a recursive LIST,\n * so this is one request regardless of branch-name nesting. Ordinary\n * filesystems retain isomorphic-git's implementation and packed refs remain\n * supported there.\n */\nasync function listLooseBranches(repo: Repo): Promise<string[] | null> {\n\treturn listLooseRefNames(repo, \"refs/heads\");\n}\n\n/**\n * Loose ref names under `dir` (e.g. `refs/tags`) in one recursive listing,\n * or `null` when the fs can't list recursively (use isomorphic-git's walk).\n */\nexport async function listLooseRefNames(\n\trepo: Repo,\n\tdir: \"refs/heads\" | \"refs/tags\",\n): Promise<string[] | null> {\n\tif (!hasRecursiveFileLister(repo.fs)) return null;\n\tconst names = await repo.fs.listFilesRecursively(`${repo.gitdir}/${dir}`);\n\treturn names.filter(isSafeBranchName);\n}\n\n/** Read packed branch refs so the object-store fast path preserves Git semantics. */\nasync function readPackedBranches(repo: Repo): Promise<Map<string, string>> {\n\tif (!hasRecursiveFileLister(repo.fs)) return new Map();\n\tconst promisesFs = getPromisesFs(repo.fs);\n\tif (!promisesFs) return new Map();\n\ttry {\n\t\tconst content = await promisesFs.readFile(\n\t\t\t`${repo.gitdir}/packed-refs`,\n\t\t\t\"utf8\",\n\t\t);\n\t\tconst refs = new Map<string, string>();\n\t\tfor (const line of decodeFileContent(content).split(\"\\n\")) {\n\t\t\tconst match = /^([0-9a-f]{40}) refs\\/heads\\/(.+)$/i.exec(line);\n\t\t\tif (match?.[1] && match[2] && isSafeBranchName(match[2])) {\n\t\t\t\trefs.set(match[2], match[1]);\n\t\t\t}\n\t\t}\n\t\treturn refs;\n\t} catch {\n\t\treturn new Map();\n\t}\n}\n\n/** Read the symbolic HEAD directly when the object-store fs is available. */\nasync function getCurrentLooseBranch(repo: Repo): Promise<string | null> {\n\tif (!hasRecursiveFileLister(repo.fs)) return null;\n\tconst promisesFs = getPromisesFs(repo.fs);\n\tif (!promisesFs) return null;\n\ttry {\n\t\tconst content = await promisesFs.readFile(`${repo.gitdir}/HEAD`, \"utf8\");\n\t\tconst head = decodeFileContent(content).trim();\n\t\tconst match = /^ref: refs\\/heads\\/(.+)$/.exec(head);\n\t\treturn match?.[1] && isSafeBranchName(match[1]) ? match[1] : null;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Resolve a fully-qualified ref's oid with one read instead of isomorphic-\n * git's own `resolveRef`, which does a stat *then* a read for a loose ref\n * (two round trips against object storage) — wasteful specifically here,\n * where the caller already knows the ref exists (it came from a directory\n * listing moments earlier). Reads the loose file directly and falls back to\n * `git.resolveRef` only when that doesn't pan out (packed-refs, or anything\n * else the plain-loose-file assumption doesn't cover), so correctness for\n * those cases is unchanged — this only removes the redundant round trip on\n * the common path.\n */\nexport async function resolveLooseRefFast(\n\trepo: Repo,\n\tref: string,\n): Promise<string> {\n\tconst promisesFs = getPromisesFs(repo.fs);\n\tif (promisesFs) {\n\t\ttry {\n\t\t\tconst content = await promisesFs.readFile(\n\t\t\t\t`${repo.gitdir}/${ref}`,\n\t\t\t\t\"utf8\",\n\t\t\t);\n\t\t\tconst oid = decodeFileContent(content).trim();\n\t\t\tif (FULL_SHA_RE.test(oid)) return oid;\n\t\t} catch {\n\t\t\t// Not a loose file (packed-refs, or genuinely absent) — fall through.\n\t\t}\n\t}\n\treturn git.resolveRef({ ...repo, ref });\n}\n\n/** All branches with their tip commits; [] for an empty repository. */\nexport async function listBranches(repo: Repo): Promise<Branch[]> {\n\ttry {\n\t\tconst [looseBranches, looseCurrentBranch, packedBranches] =\n\t\t\tawait Promise.all([\n\t\t\t\tlistLooseBranches(repo),\n\t\t\t\tgetCurrentLooseBranch(repo),\n\t\t\t\treadPackedBranches(repo),\n\t\t\t]);\n\t\tconst branches =\n\t\t\tlooseBranches === null\n\t\t\t\t? await git.listBranches(repo)\n\t\t\t\t: [...new Set([...looseBranches, ...packedBranches.keys()])].sort();\n\t\tconst currentBranch =\n\t\t\tlooseBranches === null\n\t\t\t\t? await git\n\t\t\t\t\t\t.currentBranch({ ...repo, fullname: false })\n\t\t\t\t\t\t.catch(() => null)\n\t\t\t\t: looseCurrentBranch;\n\n\t\treturn Promise.all(\n\t\t\tbranches.map(async (branch) => {\n\t\t\t\tconst packedCommit = packedBranches.get(branch);\n\t\t\t\treturn {\n\t\t\t\t\tname: branch,\n\t\t\t\t\tcommit:\n\t\t\t\t\t\tpackedCommit && !looseBranches?.includes(branch)\n\t\t\t\t\t\t\t? packedCommit\n\t\t\t\t\t\t\t: await resolveLooseRefFast(repo, `refs/heads/${branch}`),\n\t\t\t\t\tisDefault: branch === currentBranch,\n\t\t\t\t};\n\t\t\t}),\n\t\t);\n\t} catch (err: unknown) {\n\t\tif ((err as { code?: string }).code === \"NotFoundError\") return [];\n\t\tthrow err;\n\t}\n}\n\n/** Create `name` pointing at the tip of `startPoint` (no checkout). */\nexport async function createBranchFrom(\n\trepo: Repo,\n\tname: string,\n\tstartPoint: string,\n): Promise<void> {\n\tassertSafeBranchName(name);\n\tassertSafeBranchName(startPoint);\n\tconst object = await resolveLooseRefFast(repo, `refs/heads/${startPoint}`);\n\ttry {\n\t\tawait git.branch({ ...repo, ref: name, checkout: false, object });\n\t} catch (err) {\n\t\tif ((err as { code?: string }).code === \"AlreadyExistsError\") {\n\t\t\tthrow new GitConflictError(`Branch ${name} already exists`);\n\t\t}\n\t\tthrow err;\n\t}\n}\n\n/** Delete a branch ref (validated — deleteBranch has no internal ref check). */\nexport async function deleteBranchByName(\n\trepo: Repo,\n\tname: string,\n): Promise<void> {\n\tassertSafeBranchName(name);\n\t// Same rule as receive-pack: deleting the branch HEAD names leaves every\n\t// clone without a default branch.\n\tconst current =\n\t\t(await getCurrentLooseBranch(repo)) ??\n\t\t(await git.currentBranch({ ...repo, fullname: false }).catch(() => null));\n\tif (current === name) {\n\t\tthrow new GitInvalidRequestError(\n\t\t\t`Cannot delete ${name}: it is the default branch`,\n\t\t);\n\t}\n\tawait git.deleteBranch({ ...repo, ref: name });\n}\n\n/** Throws (NotFoundError) unless the branch resolves. */\nexport async function assertBranchExists(\n\trepo: Repo,\n\tname: string,\n): Promise<void> {\n\tassertSafeBranchName(name);\n\tawait resolveLooseRefFast(repo, `refs/heads/${name}`);\n}\n","/**\n * Writing packfiles: serialize whole (never deltified) objects into a pack,\n * and store a pack plus its index in a repository. Shared by repack (the\n * loose-object fallback) and packed writes (ops/packed-writes.ts).\n *\n * Edge-compatible: Web Crypto SHA-1, CompressionStream, Uint8Array only.\n */\nimport git from \"isomorphic-git\";\nimport {\n\tconcat,\n\tdeflate,\n\tencodeUtf8,\n\tfromHex,\n\tsha1,\n\ttoHex,\n} from \"./edge-utils.js\";\nimport { rawFs } from \"./http/types.js\";\nimport type { Repo } from \"./ops/types.js\";\nimport { packFileName } from \"./pack-files.js\";\n\nexport type PackObjectType = \"commit\" | \"tree\" | \"blob\" | \"tag\";\n\n// Git pack object type bits (bits 6-4 of the header's first byte).\nconst PACK_OBJECT_TYPE_BITS: Record<PackObjectType, number> = {\n\tcommit: 0b0010000,\n\ttree: 0b0100000,\n\tblob: 0b0110000,\n\ttag: 0b1000000,\n};\n\n// First byte: continuation bit | 3-bit type | low 4 bits of length; the rest\n// of the length follows 7 bits at a time, little-endian.\nfunction encodePackObjectHeader(\n\ttype: PackObjectType,\n\tlength: number,\n): Uint8Array {\n\tconst bytes: number[] = [];\n\tlet more = length > 0b1111;\n\tbytes.push(\n\t\t(more ? 0b10000000 : 0) | PACK_OBJECT_TYPE_BITS[type] | (length & 0b1111),\n\t);\n\tlet rest = Math.floor(length / 16);\n\twhile (more) {\n\t\tmore = rest > 0b01111111;\n\t\tbytes.push((more ? 0b10000000 : 0) | (rest & 0b01111111));\n\t\trest = Math.floor(rest / 128);\n\t}\n\treturn new Uint8Array(bytes);\n}\n\n/** Git's object id: sha1(\"<type> <byte length>\\0<content>\"). */\nexport function hashGitObject(\n\ttype: string,\n\tcontent: Uint8Array,\n): Promise<string> {\n\treturn sha1(concat(encodeUtf8(`${type} ${content.length}\\0`), content));\n}\n\nexport interface PackObject {\n\ttype: PackObjectType;\n\tcontent: Uint8Array<ArrayBuffer>;\n}\n\n/** A version-2 pack holding `objects` as full entries, in order. */\nexport async function encodePack(\n\tobjects: readonly PackObject[],\n): Promise<Uint8Array<ArrayBuffer>> {\n\tconst header = new Uint8Array(12);\n\tconst view = new DataView(header.buffer);\n\theader.set(encodeUtf8(\"PACK\"), 0);\n\tview.setUint32(4, 2, false);\n\tview.setUint32(8, objects.length, false);\n\n\tconst chunks: Uint8Array[] = [header];\n\tconst BATCH_SIZE = 100;\n\tfor (let i = 0; i < objects.length; i += BATCH_SIZE) {\n\t\tconst encoded = await Promise.all(\n\t\t\tobjects\n\t\t\t\t.slice(i, i + BATCH_SIZE)\n\t\t\t\t.map(async (object) =>\n\t\t\t\t\tconcat(\n\t\t\t\t\t\tencodePackObjectHeader(object.type, object.content.length),\n\t\t\t\t\t\tawait deflate(object.content),\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t);\n\t\tchunks.push(...encoded);\n\t}\n\t// The trailer is the SHA-1 of every preceding byte.\n\tconst body = concat(...chunks);\n\treturn concat(body, fromHex(await sha1(body)));\n}\n\n/**\n * A pack's conventional base name, `pack-<checksum>`: the trailer SHA-1 is a\n * function of the content, so two writers can never pick the same name for\n * different packs, and rewriting the same pack is idempotent.\n */\nexport function packBaseName(pack: Uint8Array): string {\n\treturn `pack-${toHex(pack.subarray(pack.length - 20))}`;\n}\n\n/**\n * Store `pack` in `repo` and index it (isomorphic-git's `indexPack`, which\n * re-hashes every object). The `.idx` is written last: a pack only becomes\n * visible to readers once its index exists. Returns the base name and the\n * oids the index lists.\n */\nexport async function storePack(\n\trepo: Repo,\n\tpack: Uint8Array,\n): Promise<{ base: string; oids: string[] }> {\n\tconst fsp = rawFs(repo);\n\tconst packDir = `${repo.gitdir}/objects/pack`;\n\tconst base = packBaseName(pack);\n\tawait fsp.mkdir(packDir, { recursive: true });\n\tawait fsp.writeFile(`${packDir}/${packFileName(base, \"pack\")}`, pack);\n\tconst { oids } = await git.indexPack({\n\t\t...repo,\n\t\tdir: packDir,\n\t\tfilepath: packFileName(base, \"pack\"),\n\t});\n\treturn { base, oids };\n}\n","/**\n * A FIFO semaphore for bounding in-flight I/O.\n *\n * An unbounded `Promise.all` over a list whose size the caller (or an\n * attacker) controls — a git tree with tens of thousands of blob entries, say,\n * each costing one object-store read — issues that many concurrent requests\n * from a single push or clone. Sockets and memory then scale with the input,\n * and S3-compatible providers rate-limit per account, so one hostile push\n * degrades every other tenant. Bounding the *in-flight* count caps resource\n * use at `limit` no matter how wide the input is, while still letting reads\n * overlap up to that limit instead of running strictly sequentially.\n */\nexport interface Semaphore {\n\trun<T>(fn: () => Promise<T>): Promise<T>;\n}\n\n/**\n * Create a FIFO semaphore admitting at most `limit` concurrent `run`\n * executions. Waiters are queued as promises and released in call order — no\n * polling or timers — so a caller blocked on a full semaphore is woken the\n * instant a permit frees, not on the next tick of a poll loop.\n */\nexport function createSemaphore(limit: number): Semaphore {\n\tif (!Number.isInteger(limit) || limit < 1) {\n\t\tthrow new Error(\n\t\t\t`createSemaphore: limit must be an integer >= 1, got ${limit}`,\n\t\t);\n\t}\n\n\tlet active = 0;\n\tconst waiting: Array<() => void> = [];\n\n\t/**\n\t * Hand the permit to the next waiter instead of dropping it, so `active`\n\t * only decreases when nothing is queued and never exceeds `limit`.\n\t */\n\tfunction release(): void {\n\t\tconst next = waiting.shift();\n\t\tif (next) next();\n\t\telse active--;\n\t}\n\n\tfunction acquire(): Promise<void> {\n\t\tif (active < limit) {\n\t\t\tactive++;\n\t\t\treturn Promise.resolve();\n\t\t}\n\t\treturn new Promise<void>((resolve) => {\n\t\t\twaiting.push(resolve);\n\t\t});\n\t}\n\n\treturn {\n\t\trun<T>(fn: () => Promise<T>): Promise<T> {\n\t\t\treturn acquire().then(async () => {\n\t\t\t\ttry {\n\t\t\t\t\treturn await fn();\n\t\t\t\t} finally {\n\t\t\t\t\trelease();\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\t};\n}\n"],"mappings":"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","names":["git","git"]}