git-fs-s3 0.3.6 → 0.3.8

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.
@@ -0,0 +1,118 @@
1
+ import {
2
+ GitInvalidRequestError,
3
+ isSafeBranchName
4
+ } from "./chunk-YQFNY6PG.js";
5
+
6
+ // src/ops/branch.ts
7
+ import git from "isomorphic-git";
8
+ function assertSafeBranchName(name) {
9
+ if (!isSafeBranchName(name)) {
10
+ throw new GitInvalidRequestError(`Invalid branch name: ${name}`);
11
+ }
12
+ }
13
+ var FULL_SHA_RE = /^[0-9a-f]{40}$/i;
14
+ function hasRecursiveFileLister(fs) {
15
+ return typeof fs === "object" && fs !== null && "listFilesRecursively" in fs && typeof fs.listFilesRecursively === "function";
16
+ }
17
+ async function listLooseBranches(repo) {
18
+ if (!hasRecursiveFileLister(repo.fs)) return null;
19
+ const names = await repo.fs.listFilesRecursively(`${repo.gitdir}/refs/heads`);
20
+ return names.filter(isSafeBranchName);
21
+ }
22
+ async function readPackedBranches(repo) {
23
+ if (!hasRecursiveFileLister(repo.fs)) return /* @__PURE__ */ new Map();
24
+ const promisesFs = repo.fs.promises;
25
+ if (!promisesFs) return /* @__PURE__ */ new Map();
26
+ try {
27
+ const content = await promisesFs.readFile(
28
+ `${repo.gitdir}/packed-refs`,
29
+ "utf8"
30
+ );
31
+ const refs = /* @__PURE__ */ new Map();
32
+ for (const line of (typeof content === "string" ? content : new TextDecoder().decode(content)).split("\n")) {
33
+ const match = /^([0-9a-f]{40}) refs\/heads\/(.+)$/i.exec(line);
34
+ if (match?.[1] && match[2] && isSafeBranchName(match[2])) {
35
+ refs.set(match[2], match[1]);
36
+ }
37
+ }
38
+ return refs;
39
+ } catch {
40
+ return /* @__PURE__ */ new Map();
41
+ }
42
+ }
43
+ async function getCurrentLooseBranch(repo) {
44
+ if (!hasRecursiveFileLister(repo.fs)) return null;
45
+ const promisesFs = repo.fs.promises;
46
+ if (!promisesFs) return null;
47
+ try {
48
+ const content = await promisesFs.readFile(`${repo.gitdir}/HEAD`, "utf8");
49
+ const head = (typeof content === "string" ? content : new TextDecoder().decode(content)).trim();
50
+ const match = /^ref: refs\/heads\/(.+)$/.exec(head);
51
+ return match?.[1] && isSafeBranchName(match[1]) ? match[1] : null;
52
+ } catch {
53
+ return null;
54
+ }
55
+ }
56
+ async function resolveLooseRefFast(repo, ref) {
57
+ const promisesFs = repo.fs.promises;
58
+ if (promisesFs) {
59
+ try {
60
+ const content = await promisesFs.readFile(
61
+ `${repo.gitdir}/${ref}`,
62
+ "utf8"
63
+ );
64
+ const oid = (typeof content === "string" ? content : new TextDecoder().decode(content)).trim();
65
+ if (FULL_SHA_RE.test(oid)) return oid;
66
+ } catch {
67
+ }
68
+ }
69
+ return git.resolveRef({ ...repo, ref });
70
+ }
71
+ async function listBranches(repo) {
72
+ try {
73
+ const [looseBranches, looseCurrentBranch, packedBranches] = await Promise.all([
74
+ listLooseBranches(repo),
75
+ getCurrentLooseBranch(repo),
76
+ readPackedBranches(repo)
77
+ ]);
78
+ const branches = looseBranches === null ? await git.listBranches(repo) : [.../* @__PURE__ */ new Set([...looseBranches, ...packedBranches.keys()])].sort();
79
+ const currentBranch = looseBranches === null ? await git.currentBranch({ ...repo, fullname: false }).catch(() => null) : looseCurrentBranch;
80
+ return Promise.all(
81
+ branches.map(async (branch) => {
82
+ const packedCommit = packedBranches.get(branch);
83
+ return {
84
+ name: branch,
85
+ commit: packedCommit && !looseBranches?.includes(branch) ? packedCommit : await resolveLooseRefFast(repo, `refs/heads/${branch}`),
86
+ isDefault: branch === currentBranch
87
+ };
88
+ })
89
+ );
90
+ } catch (err) {
91
+ if (err.code === "NotFoundError") return [];
92
+ throw err;
93
+ }
94
+ }
95
+ async function createBranchFrom(repo, name, startPoint = "main") {
96
+ assertSafeBranchName(name);
97
+ assertSafeBranchName(startPoint);
98
+ const object = await resolveLooseRefFast(repo, `refs/heads/${startPoint}`);
99
+ await git.branch({ ...repo, ref: name, checkout: false, object });
100
+ }
101
+ async function deleteBranchByName(repo, name) {
102
+ assertSafeBranchName(name);
103
+ await git.deleteBranch({ ...repo, ref: name });
104
+ }
105
+ async function assertBranchExists(repo, name) {
106
+ assertSafeBranchName(name);
107
+ await resolveLooseRefFast(repo, `refs/heads/${name}`);
108
+ }
109
+
110
+ export {
111
+ assertSafeBranchName,
112
+ resolveLooseRefFast,
113
+ listBranches,
114
+ createBranchFrom,
115
+ deleteBranchByName,
116
+ assertBranchExists
117
+ };
118
+ //# sourceMappingURL=chunk-H7IGQRIA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/ops/branch.ts"],"sourcesContent":["import git from \"isomorphic-git\";\nimport { 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\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\tif (!hasRecursiveFileLister(repo.fs)) return null;\n\tconst names = await repo.fs.listFilesRecursively(`${repo.gitdir}/refs/heads`);\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 = (\n\t\trepo.fs as {\n\t\t\tpromises?: {\n\t\t\t\treadFile(path: string, encoding: \"utf8\"): Promise<string | Uint8Array>;\n\t\t\t};\n\t\t}\n\t).promises;\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 (typeof content === \"string\"\n\t\t\t? content\n\t\t\t: new TextDecoder().decode(content)\n\t\t).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 = (\n\t\trepo.fs as {\n\t\t\tpromises?: {\n\t\t\t\treadFile(path: string, encoding: \"utf8\"): Promise<string | Uint8Array>;\n\t\t\t};\n\t\t}\n\t).promises;\n\tif (!promisesFs) return null;\n\ttry {\n\t\tconst content = await promisesFs.readFile(`${repo.gitdir}/HEAD`, \"utf8\");\n\t\tconst head = (\n\t\t\ttypeof content === \"string\" ? content : new TextDecoder().decode(content)\n\t\t).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\t// Repo.fs is typed as isomorphic-git's own fs union (promise- or\n\t// callback-based) since that's what git.readTree's signature allows, but\n\t// every fs this package actually constructs (git-fs.ts) is promise-based\n\t// — the runtime check just keeps the callback-style branch honest instead\n\t// of crashing on it.\n\tconst promisesFs = (\n\t\trepo.fs as {\n\t\t\tpromises?: {\n\t\t\t\treadFile(path: string, encoding: \"utf8\"): Promise<string | Uint8Array>;\n\t\t\t};\n\t\t}\n\t).promises;\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 = (\n\t\t\t\ttypeof content === \"string\"\n\t\t\t\t\t? content\n\t\t\t\t\t: new TextDecoder().decode(content)\n\t\t\t).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 = \"main\",\n): Promise<void> {\n\tassertSafeBranchName(name);\n\tassertSafeBranchName(startPoint);\n\tconst object = await resolveLooseRefFast(repo, `refs/heads/${startPoint}`);\n\tawait git.branch({ ...repo, ref: name, checkout: false, object });\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\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"],"mappings":";;;;;;AAAA,OAAO,SAAS;AAiBT,SAAS,qBAAqB,MAAoB;AACxD,MAAI,CAAC,iBAAiB,IAAI,GAAG;AAC5B,UAAM,IAAI,uBAAuB,wBAAwB,IAAI,EAAE;AAAA,EAChE;AACD;AAEA,IAAM,cAAc;AAMpB,SAAS,uBACR,IACyC;AACzC,SACC,OAAO,OAAO,YACd,OAAO,QACP,0BAA0B,MAC1B,OAAQ,GAAoC,yBAC3C;AAEH;AASA,eAAe,kBAAkB,MAAsC;AACtE,MAAI,CAAC,uBAAuB,KAAK,EAAE,EAAG,QAAO;AAC7C,QAAM,QAAQ,MAAM,KAAK,GAAG,qBAAqB,GAAG,KAAK,MAAM,aAAa;AAC5E,SAAO,MAAM,OAAO,gBAAgB;AACrC;AAGA,eAAe,mBAAmB,MAA0C;AAC3E,MAAI,CAAC,uBAAuB,KAAK,EAAE,EAAG,QAAO,oBAAI,IAAI;AACrD,QAAM,aACL,KAAK,GAKJ;AACF,MAAI,CAAC,WAAY,QAAO,oBAAI,IAAI;AAChC,MAAI;AACH,UAAM,UAAU,MAAM,WAAW;AAAA,MAChC,GAAG,KAAK,MAAM;AAAA,MACd;AAAA,IACD;AACA,UAAM,OAAO,oBAAI,IAAoB;AACrC,eAAW,SAAS,OAAO,YAAY,WACpC,UACA,IAAI,YAAY,EAAE,OAAO,OAAO,GACjC,MAAM,IAAI,GAAG;AACd,YAAM,QAAQ,sCAAsC,KAAK,IAAI;AAC7D,UAAI,QAAQ,CAAC,KAAK,MAAM,CAAC,KAAK,iBAAiB,MAAM,CAAC,CAAC,GAAG;AACzD,aAAK,IAAI,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC;AAAA,MAC5B;AAAA,IACD;AACA,WAAO;AAAA,EACR,QAAQ;AACP,WAAO,oBAAI,IAAI;AAAA,EAChB;AACD;AAGA,eAAe,sBAAsB,MAAoC;AACxE,MAAI,CAAC,uBAAuB,KAAK,EAAE,EAAG,QAAO;AAC7C,QAAM,aACL,KAAK,GAKJ;AACF,MAAI,CAAC,WAAY,QAAO;AACxB,MAAI;AACH,UAAM,UAAU,MAAM,WAAW,SAAS,GAAG,KAAK,MAAM,SAAS,MAAM;AACvE,UAAM,QACL,OAAO,YAAY,WAAW,UAAU,IAAI,YAAY,EAAE,OAAO,OAAO,GACvE,KAAK;AACP,UAAM,QAAQ,2BAA2B,KAAK,IAAI;AAClD,WAAO,QAAQ,CAAC,KAAK,iBAAiB,MAAM,CAAC,CAAC,IAAI,MAAM,CAAC,IAAI;AAAA,EAC9D,QAAQ;AACP,WAAO;AAAA,EACR;AACD;AAaA,eAAsB,oBACrB,MACA,KACkB;AAMlB,QAAM,aACL,KAAK,GAKJ;AACF,MAAI,YAAY;AACf,QAAI;AACH,YAAM,UAAU,MAAM,WAAW;AAAA,QAChC,GAAG,KAAK,MAAM,IAAI,GAAG;AAAA,QACrB;AAAA,MACD;AACA,YAAM,OACL,OAAO,YAAY,WAChB,UACA,IAAI,YAAY,EAAE,OAAO,OAAO,GAClC,KAAK;AACP,UAAI,YAAY,KAAK,GAAG,EAAG,QAAO;AAAA,IACnC,QAAQ;AAAA,IAER;AAAA,EACD;AACA,SAAO,IAAI,WAAW,EAAE,GAAG,MAAM,IAAI,CAAC;AACvC;AAGA,eAAsB,aAAa,MAA+B;AACjE,MAAI;AACH,UAAM,CAAC,eAAe,oBAAoB,cAAc,IACvD,MAAM,QAAQ,IAAI;AAAA,MACjB,kBAAkB,IAAI;AAAA,MACtB,sBAAsB,IAAI;AAAA,MAC1B,mBAAmB,IAAI;AAAA,IACxB,CAAC;AACF,UAAM,WACL,kBAAkB,OACf,MAAM,IAAI,aAAa,IAAI,IAC3B,CAAC,GAAG,oBAAI,IAAI,CAAC,GAAG,eAAe,GAAG,eAAe,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK;AACpE,UAAM,gBACL,kBAAkB,OACf,MAAM,IACL,cAAc,EAAE,GAAG,MAAM,UAAU,MAAM,CAAC,EAC1C,MAAM,MAAM,IAAI,IACjB;AAEJ,WAAO,QAAQ;AAAA,MACd,SAAS,IAAI,OAAO,WAAW;AAC9B,cAAM,eAAe,eAAe,IAAI,MAAM;AAC9C,eAAO;AAAA,UACN,MAAM;AAAA,UACN,QACC,gBAAgB,CAAC,eAAe,SAAS,MAAM,IAC5C,eACA,MAAM,oBAAoB,MAAM,cAAc,MAAM,EAAE;AAAA,UAC1D,WAAW,WAAW;AAAA,QACvB;AAAA,MACD,CAAC;AAAA,IACF;AAAA,EACD,SAAS,KAAc;AACtB,QAAK,IAA0B,SAAS,gBAAiB,QAAO,CAAC;AACjE,UAAM;AAAA,EACP;AACD;AAGA,eAAsB,iBACrB,MACA,MACA,aAAa,QACG;AAChB,uBAAqB,IAAI;AACzB,uBAAqB,UAAU;AAC/B,QAAM,SAAS,MAAM,oBAAoB,MAAM,cAAc,UAAU,EAAE;AACzE,QAAM,IAAI,OAAO,EAAE,GAAG,MAAM,KAAK,MAAM,UAAU,OAAO,OAAO,CAAC;AACjE;AAGA,eAAsB,mBACrB,MACA,MACgB;AAChB,uBAAqB,IAAI;AACzB,QAAM,IAAI,aAAa,EAAE,GAAG,MAAM,KAAK,KAAK,CAAC;AAC9C;AAGA,eAAsB,mBACrB,MACA,MACgB;AAChB,uBAAqB,IAAI;AACzB,QAAM,oBAAoB,MAAM,cAAc,IAAI,EAAE;AACrD;","names":[]}
@@ -64,6 +64,109 @@ function readBlobContent(blob) {
64
64
  };
65
65
  }
66
66
 
67
+ // src/git-errors.ts
68
+ var GitError = class extends Error {
69
+ statusCode;
70
+ retryable;
71
+ constructor(message, statusCode = 500, retryable = false) {
72
+ super(message);
73
+ this.name = this.constructor.name;
74
+ this.statusCode = statusCode;
75
+ this.retryable = retryable;
76
+ Error.captureStackTrace?.(this, this.constructor);
77
+ }
78
+ toJSON() {
79
+ return {
80
+ error: this.name,
81
+ message: this.message,
82
+ statusCode: this.statusCode,
83
+ retryable: this.retryable
84
+ };
85
+ }
86
+ };
87
+ var GitPathNotFoundError = class extends GitError {
88
+ constructor(message) {
89
+ super(message, 404, false);
90
+ }
91
+ };
92
+ var GitObjectNotFoundError = class extends GitError {
93
+ constructor(message) {
94
+ super(message, 404, false);
95
+ }
96
+ };
97
+ var GitRefNotFoundError = class extends GitError {
98
+ constructor(message) {
99
+ super(message, 404, false);
100
+ }
101
+ };
102
+ var GitRepositoryNotFoundError = class extends GitError {
103
+ constructor(message) {
104
+ super(message, 404, false);
105
+ }
106
+ };
107
+ var GitConflictError = class extends GitError {
108
+ conflicts;
109
+ constructor(message, conflicts = []) {
110
+ super(message, 409, false);
111
+ this.conflicts = conflicts;
112
+ }
113
+ toJSON() {
114
+ return { ...super.toJSON(), conflicts: this.conflicts };
115
+ }
116
+ };
117
+ var GitAuthenticationError = class extends GitError {
118
+ constructor(message) {
119
+ super(message, 401, false);
120
+ }
121
+ };
122
+ var GitAuthorizationError = class extends GitError {
123
+ constructor(message) {
124
+ super(message, 403, false);
125
+ }
126
+ };
127
+ var GitRateLimitError = class extends GitError {
128
+ constructor(message) {
129
+ super(message, 429, false);
130
+ }
131
+ };
132
+ var GitInvalidRequestError = class extends GitError {
133
+ constructor(message) {
134
+ super(message, 400, false);
135
+ }
136
+ };
137
+ var GitProtocolError = class extends GitError {
138
+ constructor(message) {
139
+ super(message, 400, false);
140
+ }
141
+ };
142
+ function formatErrorResponse(error) {
143
+ if (error instanceof GitError) {
144
+ return {
145
+ status: error.statusCode,
146
+ body: error.toJSON(),
147
+ headers: error.statusCode === 401 ? { "WWW-Authenticate": 'Basic realm="Git Repository"' } : void 0
148
+ };
149
+ }
150
+ if (error instanceof Error) {
151
+ return {
152
+ status: 500,
153
+ body: {
154
+ error: "InternalServerError",
155
+ message: "An internal error occurred",
156
+ retryable: true
157
+ }
158
+ };
159
+ }
160
+ return {
161
+ status: 500,
162
+ body: {
163
+ error: "UnknownError",
164
+ message: "An unknown error occurred",
165
+ retryable: true
166
+ }
167
+ };
168
+ }
169
+
67
170
  // src/refs.ts
68
171
  var BAD_REF_COMPONENT = (
69
172
  // biome-ignore lint/suspicious/noControlCharactersInRegex: control chars are exactly what git's own ref-name rules reject — this needs to match the same range.
@@ -113,6 +216,18 @@ export {
113
216
  deflate,
114
217
  hasNullByte,
115
218
  readBlobContent,
219
+ GitError,
220
+ GitPathNotFoundError,
221
+ GitObjectNotFoundError,
222
+ GitRefNotFoundError,
223
+ GitRepositoryNotFoundError,
224
+ GitConflictError,
225
+ GitAuthenticationError,
226
+ GitAuthorizationError,
227
+ GitRateLimitError,
228
+ GitInvalidRequestError,
229
+ GitProtocolError,
230
+ formatErrorResponse,
116
231
  isSafeFullRefName,
117
232
  isSafeBranchName,
118
233
  isFullSha,
@@ -120,4 +235,4 @@ export {
120
235
  isSafeRepoPath,
121
236
  qualifyBranchRef
122
237
  };
123
- //# sourceMappingURL=chunk-RUE2NR43.js.map
238
+ //# sourceMappingURL=chunk-YQFNY6PG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/edge-utils.ts","../src/git-errors.ts","../src/refs.ts"],"sourcesContent":["/**\n * Edge-compatible utilities replacing node:crypto, node:zlib, and Buffer.\n *\n * Every function here uses only Web APIs (SubtleCrypto, CompressionStream,\n * TextEncoder/TextDecoder) — no Node built-ins. They work on Cloudflare\n * Workers, Vercel Edge, Deno Deploy, and Node >= 18.\n */\n\nconst textEncoder = new TextEncoder();\nconst textDecoder = new TextDecoder();\n\n// ---------------------------------------------------------------------------\n// Text\n// ---------------------------------------------------------------------------\n\n/**\n * Encode a UTF-8 string to bytes.\n *\n * Return type pinned to `Uint8Array<ArrayBuffer>` (not the bare `Uint8Array`,\n * whose default type argument differs across TypeScript versions) so it's\n * always assignable to Fetch API `BodyInit` regardless of a consumer's own\n * TypeScript/lib version.\n */\nexport function encodeUtf8(data: string): Uint8Array<ArrayBuffer> {\n\treturn textEncoder.encode(data);\n}\n\n/** Decode bytes as UTF-8. */\nexport function decodeUtf8(data: Uint8Array): string {\n\treturn textDecoder.decode(data);\n}\n\n/** Decode bytes as ASCII. */\nexport function decodeAscii(data: Uint8Array): string {\n\tlet s = \"\";\n\tfor (let i = 0; i < data.length; i++)\n\t\ts += String.fromCharCode(data[i] as number);\n\treturn s;\n}\n\n// ---------------------------------------------------------------------------\n// Array manipulation\n// ---------------------------------------------------------------------------\n\n/** Concatenate any number of Uint8Arrays into one. */\nexport function concat(...parts: Uint8Array[]): Uint8Array<ArrayBuffer> {\n\tlet total = 0;\n\tfor (const p of parts) total += p.length;\n\tconst out = new Uint8Array(total);\n\tlet offset = 0;\n\tfor (const p of parts) {\n\t\tout.set(p, offset);\n\t\toffset += p.length;\n\t}\n\treturn out;\n}\n\n// ---------------------------------------------------------------------------\n// Encoding\n// ---------------------------------------------------------------------------\n\n/** Uint8Array → lowercase hex string. */\nexport function toHex(data: Uint8Array): string {\n\tlet hex = \"\";\n\tfor (let i = 0; i < data.length; i++)\n\t\thex += (data[i] as number).toString(16).padStart(2, \"0\");\n\treturn hex;\n}\n\n/** Uint8Array → base64 string. */\nexport function toBase64(data: Uint8Array): string {\n\tlet binary = \"\";\n\tfor (let i = 0; i < data.length; i++)\n\t\tbinary += String.fromCharCode(data[i] as number);\n\treturn btoa(binary);\n}\n\n/** Hex string → Uint8Array. */\nexport function fromHex(hex: string): Uint8Array<ArrayBuffer> {\n\tconst bytes = new Uint8Array(hex.length / 2);\n\tfor (let i = 0; i < bytes.length; i++) {\n\t\tbytes[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);\n\t}\n\treturn bytes;\n}\n\n// ---------------------------------------------------------------------------\n// Crypto\n// ---------------------------------------------------------------------------\n\n/** SHA-1 hash via Web Crypto API. Returns a hex string. */\nexport async function sha1(data: Uint8Array | string): Promise<string> {\n\tconst bytes = typeof data === \"string\" ? encodeUtf8(data) : data;\n\tconst hash = await globalThis.crypto.subtle.digest(\"SHA-1\", bytes);\n\treturn toHex(new Uint8Array(hash));\n}\n\n// ---------------------------------------------------------------------------\n// Compression\n// ---------------------------------------------------------------------------\n\n/**\n * Deflate compress via the CompressionStream Web API.\n * Falls back to throwing if CompressionStream is unavailable (very old runtimes).\n */\nexport async function deflate(\n\tdata: Uint8Array,\n): Promise<Uint8Array<ArrayBuffer>> {\n\tconst stream = new Blob([data])\n\t\t.stream()\n\t\t.pipeThrough(new CompressionStream(\"deflate\"));\n\treturn new Uint8Array(await new Response(stream).arrayBuffer());\n}\n\n// ---------------------------------------------------------------------------\n// Binary detection (replaces Buffer.includes(0) pattern)\n// ---------------------------------------------------------------------------\n\n/** Check if a Uint8Array contains a null byte. */\nexport function hasNullByte(data: Uint8Array): boolean {\n\treturn data.includes(0);\n}\n\n/**\n * Read a blob as text or binary metadata — the edge-compatible replacement\n * for the `Buffer.from(blob)` pattern used throughout diff.ts and history.ts.\n */\nexport function readBlobContent(blob: Uint8Array): {\n\tisBinary: boolean;\n\ttext: string;\n\tbytes: Uint8Array;\n} {\n\tconst isBinary = hasNullByte(blob);\n\treturn {\n\t\tisBinary,\n\t\ttext: isBinary ? \"\" : decodeUtf8(blob),\n\t\tbytes: blob,\n\t};\n}\n","/**\n * Git-server error types carrying an HTTP status and a retryability flag, so\n * transport layers can map internal failures to responses without inspecting\n * messages. Extend {@link GitError} for app-specific cases (storage backends,\n * quota, …) and {@link formatErrorResponse} keeps working for them.\n */\nexport class GitError extends Error {\n\tstatusCode: number;\n\tretryable: boolean;\n\n\tconstructor(message: string, statusCode = 500, retryable = false) {\n\t\tsuper(message);\n\t\tthis.name = this.constructor.name;\n\t\tthis.statusCode = statusCode;\n\t\tthis.retryable = retryable;\n\t\tError.captureStackTrace?.(this, this.constructor);\n\t}\n\n\ttoJSON(): Record<string, unknown> {\n\t\treturn {\n\t\t\terror: this.name,\n\t\t\tmessage: this.message,\n\t\t\tstatusCode: this.statusCode,\n\t\t\tretryable: this.retryable,\n\t\t};\n\t}\n}\n\n/** A file/directory path not found within a tree (404). */\nexport class GitPathNotFoundError extends GitError {\n\tconstructor(message: string) {\n\t\tsuper(message, 404, false);\n\t}\n}\n\n/** A git object not found (404). */\nexport class GitObjectNotFoundError extends GitError {\n\tconstructor(message: string) {\n\t\tsuper(message, 404, false);\n\t}\n}\n\n/** A ref (branch/tag) not found (404). */\nexport class GitRefNotFoundError extends GitError {\n\tconstructor(message: string) {\n\t\tsuper(message, 404, false);\n\t}\n}\n\n/** The repository itself not found (404). */\nexport class GitRepositoryNotFoundError extends GitError {\n\tconstructor(message: string) {\n\t\tsuper(message, 404, false);\n\t}\n}\n\nexport interface MergeConflictDetail {\n\tfile: string;\n\tbaseLines?: string[];\n\tsourceLines?: string[];\n\ttargetLines?: string[];\n}\n\n/** A merge conflict (409), carrying per-file conflict detail. */\nexport class GitConflictError extends GitError {\n\tconflicts: MergeConflictDetail[];\n\n\tconstructor(message: string, conflicts: MergeConflictDetail[] = []) {\n\t\tsuper(message, 409, false);\n\t\tthis.conflicts = conflicts;\n\t}\n\n\toverride toJSON(): Record<string, unknown> {\n\t\treturn { ...super.toJSON(), conflicts: this.conflicts };\n\t}\n}\n\n/** Authentication failed (401). */\nexport class GitAuthenticationError extends GitError {\n\tconstructor(message: string) {\n\t\tsuper(message, 401, false);\n\t}\n}\n\n/** Authorization failed (403). */\nexport class GitAuthorizationError extends GitError {\n\tconstructor(message: string) {\n\t\tsuper(message, 403, false);\n\t}\n}\n\n/** Too many failed attempts (429). */\nexport class GitRateLimitError extends GitError {\n\tconstructor(message: string) {\n\t\tsuper(message, 429, false);\n\t}\n}\n\n/** Malformed request (400). */\nexport class GitInvalidRequestError extends GitError {\n\tconstructor(message: string) {\n\t\tsuper(message, 400, false);\n\t}\n}\n\n/** Git wire-protocol violation (400). */\nexport class GitProtocolError extends GitError {\n\tconstructor(message: string) {\n\t\tsuper(message, 400, false);\n\t}\n}\n\n/**\n * Map any error to an HTTP response shape. 401s carry the WWW-Authenticate\n * header git clients need before they will prompt for credentials. Non-GitError\n * failures are masked as opaque 500s — internal messages don't leak.\n */\nexport function formatErrorResponse(error: unknown): {\n\tstatus: number;\n\tbody: Record<string, unknown>;\n\theaders?: Record<string, string>;\n} {\n\tif (error instanceof GitError) {\n\t\treturn {\n\t\t\tstatus: error.statusCode,\n\t\t\tbody: error.toJSON(),\n\t\t\theaders:\n\t\t\t\terror.statusCode === 401\n\t\t\t\t\t? { \"WWW-Authenticate\": 'Basic realm=\"Git Repository\"' }\n\t\t\t\t\t: undefined,\n\t\t};\n\t}\n\n\tif (error instanceof Error) {\n\t\treturn {\n\t\t\tstatus: 500,\n\t\t\tbody: {\n\t\t\t\terror: \"InternalServerError\",\n\t\t\t\tmessage: \"An internal error occurred\",\n\t\t\t\tretryable: true,\n\t\t\t},\n\t\t};\n\t}\n\n\treturn {\n\t\tstatus: 500,\n\t\tbody: {\n\t\t\terror: \"UnknownError\",\n\t\t\tmessage: \"An unknown error occurred\",\n\t\t\tretryable: true,\n\t\t},\n\t};\n}\n","/**\n * Git ref-name validation, mirroring isomorphic-git's own internal `isValidRef`\n * character-class rules (the check `git.branch` and top-level `git.writeRef`\n * run before touching disk).\n *\n * Several of isomorphic-git's OTHER ref-touching primitives — `git.commit`,\n * `git.merge`, `git.deleteBranch`, and top-level `git.resolveRef`/\n * `git.deleteRef` — do NOT run this check internally: they resolve straight\n * through `fs.write`/`fs.rm(join(gitdir, ref))` with no jail to the gitdir.\n * On a shared-storage server (many repos under one prefix or base directory),\n * every branch/ref name that originates from request input must be validated\n * against these predicates before it reaches any of those primitives —\n * otherwise a `\"../\"`-laden name lets a caller with write access to any single\n * repo read, corrupt, or delete another repo's ref/object files.\n */\n\nconst BAD_REF_COMPONENT =\n\t// biome-ignore lint/suspicious/noControlCharactersInRegex: control chars are exactly what git's own ref-name rules reject — this needs to match the same range.\n\t/(^|[/.])([/.]|$)|^@$|@\\{|[\\x00-\\x20\\x7f~^:?*[\\\\]|\\.lock(\\/|$)/;\n\nconst FULL_SHA_RE = /^[0-9a-f]{40}$/i;\n\n/** Validates a fully-qualified ref (must start with refs/heads/ or refs/tags/). */\nexport function isSafeFullRefName(ref: string): boolean {\n\tif (!ref.startsWith(\"refs/heads/\") && !ref.startsWith(\"refs/tags/\")) {\n\t\treturn false;\n\t}\n\treturn !BAD_REF_COMPONENT.test(ref);\n}\n\n/**\n * Validates a bare branch name (no refs/ prefix). Rejects anything that looks\n * like a full ref path — a name of `\"refs/heads/x\"` would otherwise sail\n * through unprefixed at call sites that build `refs/heads/${name}` themselves\n * (doubling the prefix into something that still resolves), or be used as-is\n * at call sites that pass a name already containing `\"refs/\"` straight\n * through. Also rejects 40-hex SHA-shaped values so a stored branch name can\n * never be ambiguous with a commit SHA at write time; use\n * {@link isSafeRefName} on read paths that accept both shapes.\n */\nexport function isSafeBranchName(name: string): boolean {\n\tif (!name || name.startsWith(\"refs/\") || name === \"HEAD\") return false;\n\tif (FULL_SHA_RE.test(name)) return false;\n\treturn !BAD_REF_COMPONENT.test(name);\n}\n\n/** True for a full 40-hex-char commit SHA — the shape {@link isSafeBranchName} deliberately rejects. */\nexport function isFullSha(value: string): boolean {\n\treturn FULL_SHA_RE.test(value);\n}\n\n/**\n * Validates a \"ref\" field that may name either a branch or a commit SHA it's\n * pinned to — the shape read-path route params take (permalinks, raw links).\n * Both shapes still go through the traversal check.\n */\nexport function isSafeRefName(value: string): boolean {\n\treturn isSafeBranchName(value) || isFullSha(value);\n}\n\n/**\n * Validates a repo-relative file path from request input: relative, no `..`\n * segments, no `.git/` prefix, no null bytes. Use this anywhere a path\n * segment comes straight off a URL or form field rather than re-deriving the\n * checks ad hoc.\n */\nexport function isSafeRepoPath(p: string): boolean {\n\tif (p.startsWith(\"/\")) return false;\n\tif (p.split(\"/\").some((segment) => segment === \"..\")) return false;\n\tif (/^\\.git(\\/|$)/i.test(p)) return false;\n\tif (p.includes(\"\\0\")) return false;\n\treturn true;\n}\n\n/**\n * Qualify a bare branch name to `refs/heads/<name>` before handing it to\n * isomorphic-git. `resolveRef`/`expand` try several candidate paths in\n * sequence for a bare name — `ref`, `refs/ref`, `refs/tags/ref`,\n * `refs/heads/ref`, … — missing (and, against object storage, paying a real\n * round trip for) the first three every time. For a branch-only ref model,\n * skip straight to the winner. Left untouched: already-qualified refs,\n * `\"HEAD\"` (its own first candidate, already optimal), and 40-hex oids\n * (resolved locally by isomorphic-git with no I/O at all).\n */\nexport function qualifyBranchRef(ref: string): string {\n\tif (ref.startsWith(\"refs/\") || ref === \"HEAD\" || FULL_SHA_RE.test(ref)) {\n\t\treturn ref;\n\t}\n\treturn `refs/heads/${ref}`;\n}\n"],"mappings":";AAQA,IAAM,cAAc,IAAI,YAAY;AACpC,IAAM,cAAc,IAAI,YAAY;AAc7B,SAAS,WAAW,MAAuC;AACjE,SAAO,YAAY,OAAO,IAAI;AAC/B;AAGO,SAAS,WAAW,MAA0B;AACpD,SAAO,YAAY,OAAO,IAAI;AAC/B;AAGO,SAAS,YAAY,MAA0B;AACrD,MAAI,IAAI;AACR,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ;AAChC,SAAK,OAAO,aAAa,KAAK,CAAC,CAAW;AAC3C,SAAO;AACR;AAOO,SAAS,UAAU,OAA8C;AACvE,MAAI,QAAQ;AACZ,aAAW,KAAK,MAAO,UAAS,EAAE;AAClC,QAAM,MAAM,IAAI,WAAW,KAAK;AAChC,MAAI,SAAS;AACb,aAAW,KAAK,OAAO;AACtB,QAAI,IAAI,GAAG,MAAM;AACjB,cAAU,EAAE;AAAA,EACb;AACA,SAAO;AACR;AAOO,SAAS,MAAM,MAA0B;AAC/C,MAAI,MAAM;AACV,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ;AAChC,WAAQ,KAAK,CAAC,EAAa,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG;AACxD,SAAO;AACR;AAGO,SAAS,SAAS,MAA0B;AAClD,MAAI,SAAS;AACb,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ;AAChC,cAAU,OAAO,aAAa,KAAK,CAAC,CAAW;AAChD,SAAO,KAAK,MAAM;AACnB;AAGO,SAAS,QAAQ,KAAsC;AAC7D,QAAM,QAAQ,IAAI,WAAW,IAAI,SAAS,CAAC;AAC3C,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACtC,UAAM,CAAC,IAAI,OAAO,SAAS,IAAI,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,GAAG,EAAE;AAAA,EAC3D;AACA,SAAO;AACR;AAOA,eAAsB,KAAK,MAA4C;AACtE,QAAM,QAAQ,OAAO,SAAS,WAAW,WAAW,IAAI,IAAI;AAC5D,QAAM,OAAO,MAAM,WAAW,OAAO,OAAO,OAAO,SAAS,KAAK;AACjE,SAAO,MAAM,IAAI,WAAW,IAAI,CAAC;AAClC;AAUA,eAAsB,QACrB,MACmC;AACnC,QAAM,SAAS,IAAI,KAAK,CAAC,IAAI,CAAC,EAC5B,OAAO,EACP,YAAY,IAAI,kBAAkB,SAAS,CAAC;AAC9C,SAAO,IAAI,WAAW,MAAM,IAAI,SAAS,MAAM,EAAE,YAAY,CAAC;AAC/D;AAOO,SAAS,YAAY,MAA2B;AACtD,SAAO,KAAK,SAAS,CAAC;AACvB;AAMO,SAAS,gBAAgB,MAI9B;AACD,QAAM,WAAW,YAAY,IAAI;AACjC,SAAO;AAAA,IACN;AAAA,IACA,MAAM,WAAW,KAAK,WAAW,IAAI;AAAA,IACrC,OAAO;AAAA,EACR;AACD;;;ACpIO,IAAM,WAAN,cAAuB,MAAM;AAAA,EACnC;AAAA,EACA;AAAA,EAEA,YAAY,SAAiB,aAAa,KAAK,YAAY,OAAO;AACjE,UAAM,OAAO;AACb,SAAK,OAAO,KAAK,YAAY;AAC7B,SAAK,aAAa;AAClB,SAAK,YAAY;AACjB,UAAM,oBAAoB,MAAM,KAAK,WAAW;AAAA,EACjD;AAAA,EAEA,SAAkC;AACjC,WAAO;AAAA,MACN,OAAO,KAAK;AAAA,MACZ,SAAS,KAAK;AAAA,MACd,YAAY,KAAK;AAAA,MACjB,WAAW,KAAK;AAAA,IACjB;AAAA,EACD;AACD;AAGO,IAAM,uBAAN,cAAmC,SAAS;AAAA,EAClD,YAAY,SAAiB;AAC5B,UAAM,SAAS,KAAK,KAAK;AAAA,EAC1B;AACD;AAGO,IAAM,yBAAN,cAAqC,SAAS;AAAA,EACpD,YAAY,SAAiB;AAC5B,UAAM,SAAS,KAAK,KAAK;AAAA,EAC1B;AACD;AAGO,IAAM,sBAAN,cAAkC,SAAS;AAAA,EACjD,YAAY,SAAiB;AAC5B,UAAM,SAAS,KAAK,KAAK;AAAA,EAC1B;AACD;AAGO,IAAM,6BAAN,cAAyC,SAAS;AAAA,EACxD,YAAY,SAAiB;AAC5B,UAAM,SAAS,KAAK,KAAK;AAAA,EAC1B;AACD;AAUO,IAAM,mBAAN,cAA+B,SAAS;AAAA,EAC9C;AAAA,EAEA,YAAY,SAAiB,YAAmC,CAAC,GAAG;AACnE,UAAM,SAAS,KAAK,KAAK;AACzB,SAAK,YAAY;AAAA,EAClB;AAAA,EAES,SAAkC;AAC1C,WAAO,EAAE,GAAG,MAAM,OAAO,GAAG,WAAW,KAAK,UAAU;AAAA,EACvD;AACD;AAGO,IAAM,yBAAN,cAAqC,SAAS;AAAA,EACpD,YAAY,SAAiB;AAC5B,UAAM,SAAS,KAAK,KAAK;AAAA,EAC1B;AACD;AAGO,IAAM,wBAAN,cAAoC,SAAS;AAAA,EACnD,YAAY,SAAiB;AAC5B,UAAM,SAAS,KAAK,KAAK;AAAA,EAC1B;AACD;AAGO,IAAM,oBAAN,cAAgC,SAAS;AAAA,EAC/C,YAAY,SAAiB;AAC5B,UAAM,SAAS,KAAK,KAAK;AAAA,EAC1B;AACD;AAGO,IAAM,yBAAN,cAAqC,SAAS;AAAA,EACpD,YAAY,SAAiB;AAC5B,UAAM,SAAS,KAAK,KAAK;AAAA,EAC1B;AACD;AAGO,IAAM,mBAAN,cAA+B,SAAS;AAAA,EAC9C,YAAY,SAAiB;AAC5B,UAAM,SAAS,KAAK,KAAK;AAAA,EAC1B;AACD;AAOO,SAAS,oBAAoB,OAIlC;AACD,MAAI,iBAAiB,UAAU;AAC9B,WAAO;AAAA,MACN,QAAQ,MAAM;AAAA,MACd,MAAM,MAAM,OAAO;AAAA,MACnB,SACC,MAAM,eAAe,MAClB,EAAE,oBAAoB,+BAA+B,IACrD;AAAA,IACL;AAAA,EACD;AAEA,MAAI,iBAAiB,OAAO;AAC3B,WAAO;AAAA,MACN,QAAQ;AAAA,MACR,MAAM;AAAA,QACL,OAAO;AAAA,QACP,SAAS;AAAA,QACT,WAAW;AAAA,MACZ;AAAA,IACD;AAAA,EACD;AAEA,SAAO;AAAA,IACN,QAAQ;AAAA,IACR,MAAM;AAAA,MACL,OAAO;AAAA,MACP,SAAS;AAAA,MACT,WAAW;AAAA,IACZ;AAAA,EACD;AACD;;;ACxIA,IAAM;AAAA;AAAA,EAEL;AAAA;AAED,IAAM,cAAc;AAGb,SAAS,kBAAkB,KAAsB;AACvD,MAAI,CAAC,IAAI,WAAW,aAAa,KAAK,CAAC,IAAI,WAAW,YAAY,GAAG;AACpE,WAAO;AAAA,EACR;AACA,SAAO,CAAC,kBAAkB,KAAK,GAAG;AACnC;AAYO,SAAS,iBAAiB,MAAuB;AACvD,MAAI,CAAC,QAAQ,KAAK,WAAW,OAAO,KAAK,SAAS,OAAQ,QAAO;AACjE,MAAI,YAAY,KAAK,IAAI,EAAG,QAAO;AACnC,SAAO,CAAC,kBAAkB,KAAK,IAAI;AACpC;AAGO,SAAS,UAAU,OAAwB;AACjD,SAAO,YAAY,KAAK,KAAK;AAC9B;AAOO,SAAS,cAAc,OAAwB;AACrD,SAAO,iBAAiB,KAAK,KAAK,UAAU,KAAK;AAClD;AAQO,SAAS,eAAe,GAAoB;AAClD,MAAI,EAAE,WAAW,GAAG,EAAG,QAAO;AAC9B,MAAI,EAAE,MAAM,GAAG,EAAE,KAAK,CAAC,YAAY,YAAY,IAAI,EAAG,QAAO;AAC7D,MAAI,gBAAgB,KAAK,CAAC,EAAG,QAAO;AACpC,MAAI,EAAE,SAAS,IAAI,EAAG,QAAO;AAC7B,SAAO;AACR;AAYO,SAAS,iBAAiB,KAAqB;AACrD,MAAI,IAAI,WAAW,OAAO,KAAK,QAAQ,UAAU,YAAY,KAAK,GAAG,GAAG;AACvE,WAAO;AAAA,EACR;AACA,SAAO,cAAc,GAAG;AACzB;","names":[]}
package/dist/http.cjs CHANGED
@@ -52,7 +52,7 @@ __export(http_exports, {
52
52
  module.exports = __toCommonJS(http_exports);
53
53
 
54
54
  // src/http/info-refs.ts
55
- var import_isomorphic_git = __toESM(require("isomorphic-git"), 1);
55
+ var import_isomorphic_git2 = __toESM(require("isomorphic-git"), 1);
56
56
 
57
57
  // src/edge-utils.ts
58
58
  var textEncoder = new TextEncoder();
@@ -96,6 +96,39 @@ async function deflate(data) {
96
96
  return new Uint8Array(await new Response(stream).arrayBuffer());
97
97
  }
98
98
 
99
+ // src/ops/branch.ts
100
+ var import_isomorphic_git = __toESM(require("isomorphic-git"), 1);
101
+
102
+ // src/refs.ts
103
+ var BAD_REF_COMPONENT = (
104
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: control chars are exactly what git's own ref-name rules reject — this needs to match the same range.
105
+ /(^|[/.])([/.]|$)|^@$|@\{|[\x00-\x20\x7f~^:?*[\\]|\.lock(\/|$)/
106
+ );
107
+ function isSafeFullRefName(ref) {
108
+ if (!ref.startsWith("refs/heads/") && !ref.startsWith("refs/tags/")) {
109
+ return false;
110
+ }
111
+ return !BAD_REF_COMPONENT.test(ref);
112
+ }
113
+
114
+ // src/ops/branch.ts
115
+ var FULL_SHA_RE = /^[0-9a-f]{40}$/i;
116
+ async function resolveLooseRefFast(repo, ref) {
117
+ const promisesFs = repo.fs.promises;
118
+ if (promisesFs) {
119
+ try {
120
+ const content = await promisesFs.readFile(
121
+ `${repo.gitdir}/${ref}`,
122
+ "utf8"
123
+ );
124
+ const oid = (typeof content === "string" ? content : new TextDecoder().decode(content)).trim();
125
+ if (FULL_SHA_RE.test(oid)) return oid;
126
+ } catch {
127
+ }
128
+ }
129
+ return import_isomorphic_git.default.resolveRef({ ...repo, ref });
130
+ }
131
+
99
132
  // src/http/pkt-line.ts
100
133
  var FLUSH = new Uint8Array([
101
134
  48,
@@ -155,19 +188,16 @@ function rawFs(repo) {
155
188
  // src/http/info-refs.ts
156
189
  async function listAllRefs(repo, defaultBranch = "main") {
157
190
  const [branches, tags, headSymref] = await Promise.all([
158
- import_isomorphic_git.default.listBranches(repo),
159
- import_isomorphic_git.default.listTags(repo),
191
+ import_isomorphic_git2.default.listBranches(repo),
192
+ import_isomorphic_git2.default.listTags(repo),
160
193
  // Wrap with Promise.resolve so a mock/stub returning undefined doesn't crash .then()
161
- Promise.resolve(import_isomorphic_git.default.currentBranch({ ...repo, fullname: true })).then((cb) => cb ?? `refs/heads/${defaultBranch}`).catch(() => `refs/heads/${defaultBranch}`)
194
+ Promise.resolve(import_isomorphic_git2.default.currentBranch({ ...repo, fullname: true })).then((cb) => cb ?? `refs/heads/${defaultBranch}`).catch(() => `refs/heads/${defaultBranch}`)
162
195
  ]);
163
196
  const [branchRefs, tagRefs] = await Promise.all([
164
197
  Promise.all(
165
198
  branches.map(async (branch) => {
166
199
  try {
167
- const oid = await import_isomorphic_git.default.resolveRef({
168
- ...repo,
169
- ref: `refs/heads/${branch}`
170
- });
200
+ const oid = await resolveLooseRefFast(repo, `refs/heads/${branch}`);
171
201
  return { name: `refs/heads/${branch}`, oid };
172
202
  } catch {
173
203
  return null;
@@ -177,10 +207,7 @@ async function listAllRefs(repo, defaultBranch = "main") {
177
207
  Promise.all(
178
208
  tags.map(async (tag) => {
179
209
  try {
180
- const oid = await import_isomorphic_git.default.resolveRef({
181
- ...repo,
182
- ref: `refs/tags/${tag}`
183
- });
210
+ const oid = await resolveLooseRefFast(repo, `refs/tags/${tag}`);
184
211
  return { name: `refs/tags/${tag}`, oid };
185
212
  } catch {
186
213
  return null;
@@ -188,7 +215,7 @@ async function listAllRefs(repo, defaultBranch = "main") {
188
215
  })
189
216
  )
190
217
  ]);
191
- const headOid = branchRefs.find((r) => r?.name === headSymref)?.oid ?? await import_isomorphic_git.default.resolveRef({ ...repo, ref: "HEAD" }).catch(() => null);
218
+ const headOid = branchRefs.find((r) => r?.name === headSymref)?.oid ?? await import_isomorphic_git2.default.resolveRef({ ...repo, ref: "HEAD" }).catch(() => null);
192
219
  const refs = [];
193
220
  if (headOid) refs.push({ name: "HEAD", oid: headOid });
194
221
  for (const r of branchRefs) if (r) refs.push(r);
@@ -237,7 +264,7 @@ async function handleInfoRefs(repo, options, hooks) {
237
264
  }
238
265
 
239
266
  // src/http/reachability.ts
240
- var import_isomorphic_git2 = __toESM(require("isomorphic-git"), 1);
267
+ var import_isomorphic_git3 = __toESM(require("isomorphic-git"), 1);
241
268
  var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
242
269
  var MISSING_OBJECT_RETRY_DELAY_MS = 200;
243
270
  async function collectReachableOids(repo, startOids, hooks) {
@@ -245,17 +272,17 @@ async function collectReachableOids(repo, startOids, hooks) {
245
272
  let complete = true;
246
273
  const promises = /* @__PURE__ */ new Map();
247
274
  async function readAndVisitChildren(oid) {
248
- const obj = await import_isomorphic_git2.default.readObject({ ...repo, oid });
275
+ const obj = await import_isomorphic_git3.default.readObject({ ...repo, oid });
249
276
  seen.add(oid);
250
277
  let children = [];
251
278
  if (obj.type === "commit") {
252
- const { commit } = await import_isomorphic_git2.default.readCommit({ ...repo, oid });
279
+ const { commit } = await import_isomorphic_git3.default.readCommit({ ...repo, oid });
253
280
  children = [commit.tree, ...commit.parent];
254
281
  } else if (obj.type === "tree") {
255
- const { tree } = await import_isomorphic_git2.default.readTree({ ...repo, oid });
282
+ const { tree } = await import_isomorphic_git3.default.readTree({ ...repo, oid });
256
283
  children = tree.map((e) => e.oid);
257
284
  } else if (obj.type === "tag") {
258
- const { tag } = await import_isomorphic_git2.default.readTag({ ...repo, oid });
285
+ const { tag } = await import_isomorphic_git3.default.readTag({ ...repo, oid });
259
286
  children = [tag.object];
260
287
  }
261
288
  await Promise.all(children.map(visit));
@@ -284,22 +311,10 @@ async function collectReachableOids(repo, startOids, hooks) {
284
311
  }
285
312
 
286
313
  // src/http/receive-pack.ts
287
- var import_isomorphic_git4 = __toESM(require("isomorphic-git"), 1);
288
-
289
- // src/refs.ts
290
- var BAD_REF_COMPONENT = (
291
- // biome-ignore lint/suspicious/noControlCharactersInRegex: control chars are exactly what git's own ref-name rules reject — this needs to match the same range.
292
- /(^|[/.])([/.]|$)|^@$|@\{|[\x00-\x20\x7f~^:?*[\\]|\.lock(\/|$)/
293
- );
294
- function isSafeFullRefName(ref) {
295
- if (!ref.startsWith("refs/heads/") && !ref.startsWith("refs/tags/")) {
296
- return false;
297
- }
298
- return !BAD_REF_COMPONENT.test(ref);
299
- }
314
+ var import_isomorphic_git5 = __toESM(require("isomorphic-git"), 1);
300
315
 
301
316
  // src/http/repack.ts
302
- var import_isomorphic_git3 = __toESM(require("isomorphic-git"), 1);
317
+ var import_isomorphic_git4 = __toESM(require("isomorphic-git"), 1);
303
318
  var PACK_OBJECT_TYPE_BITS = {
304
319
  commit: 16,
305
320
  tree: 32,
@@ -331,7 +346,7 @@ async function readAndVerifyObjects(repo, oids) {
331
346
  const batch = oids.slice(i, i + BATCH_SIZE);
332
347
  const entries = await Promise.all(
333
348
  batch.map(async (oid) => {
334
- const { type, object } = await import_isomorphic_git3.default.readObject({
349
+ const { type, object } = await import_isomorphic_git4.default.readObject({
335
350
  ...repo,
336
351
  oid,
337
352
  format: "content"
@@ -406,8 +421,8 @@ async function repackRepository(repo, options, hooks) {
406
421
  return [];
407
422
  }
408
423
  const [branches, tags] = await Promise.all([
409
- import_isomorphic_git3.default.listBranches(repo),
410
- import_isomorphic_git3.default.listTags(repo)
424
+ import_isomorphic_git4.default.listBranches(repo),
425
+ import_isomorphic_git4.default.listTags(repo)
411
426
  ]);
412
427
  const refNames = [
413
428
  ...branches.map((b) => `refs/heads/${b}`),
@@ -415,7 +430,7 @@ async function repackRepository(repo, options, hooks) {
415
430
  ];
416
431
  const tipOids = (await Promise.all(
417
432
  refNames.map(
418
- (ref) => import_isomorphic_git3.default.resolveRef({ ...repo, ref }).catch(() => null)
433
+ (ref) => import_isomorphic_git4.default.resolveRef({ ...repo, ref }).catch(() => null)
419
434
  )
420
435
  )).filter((oid) => oid !== null);
421
436
  if (tipOids.length === 0) return [];
@@ -429,7 +444,7 @@ async function repackRepository(repo, options, hooks) {
429
444
  const newPackFile = `${newBase}.pack`;
430
445
  const newIdxFile = `${newBase}.idx`;
431
446
  await fsp.writeFile(`${packDir}/${newPackFile}`, packBuffer);
432
- const { oids: indexedOids } = await import_isomorphic_git3.default.indexPack({
447
+ const { oids: indexedOids } = await import_isomorphic_git4.default.indexPack({
433
448
  ...repo,
434
449
  dir: packDir,
435
450
  filepath: newPackFile
@@ -489,7 +504,7 @@ async function ensureRepoInitialized(repo, defaultBranch = "main") {
489
504
  } catch {
490
505
  await fsp.mkdir(repo.gitdir, { recursive: true }).catch(() => {
491
506
  });
492
- await import_isomorphic_git4.default.init({ ...repo, dir: repo.gitdir, defaultBranch, bare: true });
507
+ await import_isomorphic_git5.default.init({ ...repo, dir: repo.gitdir, defaultBranch, bare: true });
493
508
  }
494
509
  }
495
510
  async function indexIncomingPack(repo, packData, hooks) {
@@ -500,7 +515,7 @@ async function indexIncomingPack(repo, packData, hooks) {
500
515
  await fsp.mkdir(packDir, { recursive: true });
501
516
  const packName = `recv-${Date.now()}`;
502
517
  await fsp.writeFile(`${packDir}/${packName}.pack`, packData);
503
- await import_isomorphic_git4.default.indexPack({
518
+ await import_isomorphic_git5.default.indexPack({
504
519
  ...repo,
505
520
  dir: packDir,
506
521
  filepath: `${packName}.pack`
@@ -516,7 +531,7 @@ async function applyRefUpdates(repo, refUpdates, hooks) {
516
531
  if (!isSafeFullRefName(refName)) {
517
532
  return { refName, ok: false, reason: "invalid ref name" };
518
533
  }
519
- const currentOid = await import_isomorphic_git4.default.resolveRef({ ...repo, ref: refName }).catch(() => ZERO_OID);
534
+ const currentOid = await import_isomorphic_git5.default.resolveRef({ ...repo, ref: refName }).catch(() => ZERO_OID);
520
535
  if (currentOid !== oldOid) {
521
536
  return {
522
537
  refName,
@@ -525,10 +540,10 @@ async function applyRefUpdates(repo, refUpdates, hooks) {
525
540
  };
526
541
  }
527
542
  if (newOid === ZERO_OID) {
528
- await import_isomorphic_git4.default.deleteRef({ ...repo, ref: refName }).catch(() => {
543
+ await import_isomorphic_git5.default.deleteRef({ ...repo, ref: refName }).catch(() => {
529
544
  });
530
545
  } else {
531
- await import_isomorphic_git4.default.writeRef({
546
+ await import_isomorphic_git5.default.writeRef({
532
547
  ...repo,
533
548
  ref: refName,
534
549
  value: newOid,
@@ -573,7 +588,7 @@ async function applyReceivePack(repo, parsed, options, hooks) {
573
588
  }
574
589
 
575
590
  // src/http/upload-pack.ts
576
- var import_isomorphic_git5 = __toESM(require("isomorphic-git"), 1);
591
+ var import_isomorphic_git6 = __toESM(require("isomorphic-git"), 1);
577
592
  var ZERO_OID2 = "0".repeat(40);
578
593
  async function handleUploadPack(repo, body, options, hooks) {
579
594
  const lines = parsePktLines(body);
@@ -656,7 +671,7 @@ async function handleUploadPack(repo, body, options, hooks) {
656
671
  const { packfile } = await runStep(
657
672
  hooks,
658
673
  `packObjects (${oids.length} oids)`,
659
- () => import_isomorphic_git5.default.packObjects({ ...repo, oids })
674
+ () => import_isomorphic_git6.default.packObjects({ ...repo, oids })
660
675
  );
661
676
  const packBytes = packfile instanceof Uint8Array ? packfile : new Uint8Array(packfile ?? []);
662
677
  return {