git-fs-s3 0.3.13 → 0.5.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.
- package/README.md +23 -17
- package/dist/{chunk-3BDDEGAP.js → chunk-2JP4MS6H.js} +7 -3
- package/dist/chunk-2JP4MS6H.js.map +1 -0
- package/dist/{chunk-YQFNY6PG.js → chunk-FIFWOMPM.js} +1 -1
- package/dist/chunk-FIFWOMPM.js.map +1 -0
- package/dist/chunk-RMAOPWHJ.js +38 -0
- package/dist/chunk-RMAOPWHJ.js.map +1 -0
- package/dist/http.js +257 -81
- package/dist/http.js.map +1 -1
- package/dist/index.js +115 -18
- package/dist/index.js.map +1 -1
- package/dist/ops.js +2 -2
- package/dist/types/cache.d.ts +81 -0
- package/dist/types/edge-utils.d.ts +43 -0
- package/dist/types/errors.d.ts +16 -0
- package/dist/types/git-errors.d.ts +70 -0
- package/dist/types/git-fs.d.ts +60 -0
- package/dist/types/git-layout.d.ts +52 -0
- package/dist/types/http/index.d.ts +7 -0
- package/dist/types/http/info-refs.d.ts +27 -0
- package/dist/types/http/pack-reuse.d.ts +26 -0
- package/dist/types/http/pkt-line.d.ts +14 -0
- package/dist/types/http/reachability.d.ts +36 -0
- package/dist/types/http/receive-pack.d.ts +54 -0
- package/dist/types/http/repack.d.ts +24 -0
- package/dist/types/http/types.d.ts +40 -0
- package/dist/types/http/upload-pack.d.ts +21 -0
- package/dist/types/index.d.ts +11 -0
- package/dist/types/ops/branch.d.ts +38 -0
- package/dist/types/ops/commit.d.ts +45 -0
- package/dist/types/ops/diff.d.ts +24 -0
- package/dist/types/ops/file-history.d.ts +41 -0
- package/dist/types/ops/history.d.ts +85 -0
- package/dist/types/ops/index.d.ts +9 -0
- package/dist/types/ops/last-commit.d.ts +23 -0
- package/dist/types/ops/merge.d.ts +28 -0
- package/dist/types/ops/tree.d.ts +19 -0
- package/dist/{types-BHoHOaQt.d.ts → types/ops/types.d.ts} +7 -9
- package/dist/types/pack-files.d.ts +28 -0
- package/dist/types/path.d.ts +9 -0
- package/dist/types/refs.d.ts +54 -0
- package/dist/types/retry.d.ts +52 -0
- package/dist/types/stores/memory.d.ts +16 -0
- package/dist/{s3.d.ts → types/stores/s3.d.ts} +4 -7
- package/dist/{types-QgIkUR_q.d.cts → types/types.d.ts} +22 -23
- package/package.json +19 -23
- package/dist/chunk-3BDDEGAP.js.map +0 -1
- package/dist/chunk-YQFNY6PG.js.map +0 -1
- package/dist/http.cjs +0 -793
- package/dist/http.cjs.map +0 -1
- package/dist/http.d.cts +0 -206
- package/dist/http.d.ts +0 -206
- package/dist/index.cjs +0 -815
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -381
- package/dist/index.d.ts +0 -381
- package/dist/ops.cjs +0 -1057
- package/dist/ops.cjs.map +0 -1
- package/dist/ops.d.cts +0 -302
- package/dist/ops.d.ts +0 -302
- package/dist/s3.cjs +0 -123
- package/dist/s3.cjs.map +0 -1
- package/dist/s3.d.cts +0 -36
- package/dist/types-BHoHOaQt.d.cts +0 -53
- package/dist/types-QgIkUR_q.d.ts +0 -121
package/README.md
CHANGED
|
@@ -31,7 +31,7 @@ Use it to build git-backed CMSes, notes apps with real version history, lightwei
|
|
|
31
31
|
## Install
|
|
32
32
|
|
|
33
33
|
```bash
|
|
34
|
-
npm install git-fs-s3 isomorphic-git
|
|
34
|
+
npm install git-fs-s3 isomorphic-git # ESM only, Node.js 22+
|
|
35
35
|
# for the S3 store (any S3-compatible provider):
|
|
36
36
|
npm install @aws-sdk/client-s3
|
|
37
37
|
```
|
|
@@ -88,9 +88,11 @@ The stores compose as decorators. For a serving path that hits object storage hu
|
|
|
88
88
|
|
|
89
89
|
```typescript
|
|
90
90
|
import {
|
|
91
|
+
classifyGitdirPath,
|
|
91
92
|
createCachedStore,
|
|
92
93
|
createGitFs,
|
|
93
94
|
createRetryStore,
|
|
95
|
+
isMutableGitdirEntry,
|
|
94
96
|
} from "git-fs-s3";
|
|
95
97
|
import { S3ObjectStore } from "git-fs-s3/s3";
|
|
96
98
|
|
|
@@ -99,9 +101,6 @@ const store = createCachedStore(
|
|
|
99
101
|
new S3ObjectStore({
|
|
100
102
|
client,
|
|
101
103
|
bucket: "my-git-repos",
|
|
102
|
-
// cosmetic: keeps refs/config human-readable in bucket UIs
|
|
103
|
-
contentType: (key) =>
|
|
104
|
-
/\/(HEAD|config)$|\/refs\//.test(key) ? "text/plain" : undefined,
|
|
105
104
|
}),
|
|
106
105
|
),
|
|
107
106
|
{
|
|
@@ -114,20 +113,23 @@ const store = createCachedStore(
|
|
|
114
113
|
// process can keep serving a pre-push ref (or fail to notice a freshly
|
|
115
114
|
// pushed pack exists at all) for the rest of ttlMs. Cheap: both are one
|
|
116
115
|
// small object or a bounded listing.
|
|
116
|
+
// Keys here are `repos/<owner>/<repo>/git/<path inside the gitdir>`.
|
|
117
117
|
ttlForKey: (key) =>
|
|
118
|
-
|
|
118
|
+
isMutableGitdirEntry(classifyGitdirPath(key.split("/").slice(4).join("/")))
|
|
119
|
+
? 5_000
|
|
120
|
+
: undefined,
|
|
121
|
+
copy: false, // isomorphic-git never mutates what it reads
|
|
119
122
|
},
|
|
120
123
|
);
|
|
121
124
|
|
|
122
|
-
const fs = createGitFs(store, {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
});
|
|
125
|
+
const fs = createGitFs(store, { looseObjectHints: true });
|
|
126
|
+
|
|
127
|
+
// Once the repository is known to exist: its gitdir stat and the
|
|
128
|
+
// packed-refs/shallow probes git makes constantly cost zero store calls.
|
|
129
|
+
fs.registerGitdir(gitdir, { absentFiles: ["packed-refs", "shallow"] });
|
|
127
130
|
|
|
128
131
|
// Before a full-history walk (commit log, reachability traversal):
|
|
129
|
-
await fs.
|
|
130
|
-
await fs.prefetchPacks(gitdir); // parallel pack warm-up
|
|
132
|
+
await fs.prefetchPacks(gitdir); // parallel .pack/.idx warm-up
|
|
131
133
|
|
|
132
134
|
// After anything writes to the bucket *around* this fs (a hydrate/sync
|
|
133
135
|
// pipeline, a bulk upload, another process): drop the affected state, or
|
|
@@ -142,15 +144,19 @@ fs.invalidate("repos/alice/blog");
|
|
|
142
144
|
Returns a promise-based fs client for isomorphic-git's `fs` option, plus git-aware maintenance methods.
|
|
143
145
|
|
|
144
146
|
- `options.prefix` — key prefix all paths resolve under (e.g. `"repos/alice/blog"`). Paths that would traverse above it throw `EINVAL`.
|
|
145
|
-
- `options.looseObjectHints` — remember, per gitdir, whether any loose objects exist so fully packed repos skip every guaranteed-miss loose read.
|
|
146
|
-
- `options.isStructurallyAbsent(path)` — paths that are known never to exist answer `ENOENT` with zero store calls (e.g. `packed-refs`/`shallow` under your gitdir layout). Match precisely — `refs/heads/packed-refs` is a legal branch.
|
|
147
|
+
- `options.looseObjectHints` — remember, per gitdir, whether any loose objects exist so fully packed repos skip every guaranteed-miss loose read. The first loose-object read under a gitdir detects it (one coalesced LIST); a loose write flips it back instantly, so it can't go stale mid-push. Default off.
|
|
147
148
|
- `options.hintTtlMs` — loose-hint TTL, default 1 h. `options.onNote` — diagnostic sink.
|
|
148
149
|
|
|
149
150
|
The returned fs also exposes:
|
|
150
151
|
|
|
151
|
-
- `
|
|
152
|
-
- `
|
|
153
|
-
- `
|
|
152
|
+
- `registerGitdir(gitdir, { absentFiles? })` — declare an existing gitdir: `stat` answers "directory" and each top-level `absentFiles` entry answers `ENOENT`, with zero store calls. isomorphic-git stats the gitdir on every command.
|
|
153
|
+
- `detectLooseObjects(gitdir)` — detect the loose-object hint up front (otherwise done on the first loose read).
|
|
154
|
+
- `prefetchPacks(gitdir, { maxPacks? })` — warm every `.pack`/`.idx` in parallel before a sequential walk (skipped past `maxPacks * 2` entries).
|
|
155
|
+
- `invalidate(pathPrefix)` — drop hints and registered gitdirs under the prefix and forward to the store's `invalidate` if it has one.
|
|
156
|
+
|
|
157
|
+
### Git layout
|
|
158
|
+
|
|
159
|
+
`classifyGitdirPath(path)` turns a gitdir-relative path into a typed `GitdirEntry` (`head`, `config`, `refs`, `objects-dir`, `pack-dir`, `pack-file`, `loose-object`, `other`); `isMutableGitdirEntry(entry)` is true for the ones that change between pushes. `parsePackFileName`/`parsePackDirectory`/`packFileName` give typed access to `objects/pack` entries.
|
|
154
160
|
|
|
155
161
|
### `S3ObjectStore({ client, bucket, prefix?, contentType? })`
|
|
156
162
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
GitInvalidRequestError,
|
|
3
3
|
isSafeBranchName
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-FIFWOMPM.js";
|
|
5
5
|
|
|
6
6
|
// src/ops/branch.ts
|
|
7
7
|
import git from "isomorphic-git";
|
|
@@ -22,8 +22,11 @@ function decodeFileContent(content) {
|
|
|
22
22
|
return typeof content === "string" ? content : new TextDecoder().decode(content);
|
|
23
23
|
}
|
|
24
24
|
async function listLooseBranches(repo) {
|
|
25
|
+
return listLooseRefNames(repo, "refs/heads");
|
|
26
|
+
}
|
|
27
|
+
async function listLooseRefNames(repo, dir) {
|
|
25
28
|
if (!hasRecursiveFileLister(repo.fs)) return null;
|
|
26
|
-
const names = await repo.fs.listFilesRecursively(`${repo.gitdir}
|
|
29
|
+
const names = await repo.fs.listFilesRecursively(`${repo.gitdir}/${dir}`);
|
|
27
30
|
return names.filter(isSafeBranchName);
|
|
28
31
|
}
|
|
29
32
|
async function readPackedBranches(repo) {
|
|
@@ -116,10 +119,11 @@ async function assertBranchExists(repo, name) {
|
|
|
116
119
|
|
|
117
120
|
export {
|
|
118
121
|
assertSafeBranchName,
|
|
122
|
+
listLooseRefNames,
|
|
119
123
|
resolveLooseRefFast,
|
|
120
124
|
listBranches,
|
|
121
125
|
createBranchFrom,
|
|
122
126
|
deleteBranchByName,
|
|
123
127
|
assertBranchExists
|
|
124
128
|
};
|
|
125
|
-
//# sourceMappingURL=chunk-
|
|
129
|
+
//# sourceMappingURL=chunk-2JP4MS6H.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\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 = \"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":"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","names":[]}
|
|
@@ -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(\n\tdata: Uint8Array<ArrayBuffer> | string,\n): 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/** Deflate (zlib) via the CompressionStream Web API. */\nexport async function deflate(\n\tdata: Uint8Array<ArrayBuffer>,\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":"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","names":[]}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// src/pack-files.ts
|
|
2
|
+
var PACK_FILE_KINDS = [
|
|
3
|
+
"pack",
|
|
4
|
+
"idx",
|
|
5
|
+
"keep",
|
|
6
|
+
"bitmap",
|
|
7
|
+
"rev"
|
|
8
|
+
];
|
|
9
|
+
var READABLE_PACK_FILE_KINDS = /* @__PURE__ */ new Set([
|
|
10
|
+
"pack",
|
|
11
|
+
"idx"
|
|
12
|
+
]);
|
|
13
|
+
var KNOWN_KINDS = new Set(PACK_FILE_KINDS);
|
|
14
|
+
function isPackFileKind(value) {
|
|
15
|
+
return KNOWN_KINDS.has(value);
|
|
16
|
+
}
|
|
17
|
+
function parsePackFileName(name) {
|
|
18
|
+
const dot = name.lastIndexOf(".");
|
|
19
|
+
if (dot <= 0) return null;
|
|
20
|
+
const kind = name.slice(dot + 1);
|
|
21
|
+
if (!isPackFileKind(kind)) return null;
|
|
22
|
+
return { base: name.slice(0, dot), kind, name };
|
|
23
|
+
}
|
|
24
|
+
function packFileName(base, kind) {
|
|
25
|
+
return `${base}.${kind}`;
|
|
26
|
+
}
|
|
27
|
+
function parsePackDirectory(entries) {
|
|
28
|
+
return entries.map(parsePackFileName).filter((file) => file !== null);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export {
|
|
32
|
+
PACK_FILE_KINDS,
|
|
33
|
+
READABLE_PACK_FILE_KINDS,
|
|
34
|
+
parsePackFileName,
|
|
35
|
+
packFileName,
|
|
36
|
+
parsePackDirectory
|
|
37
|
+
};
|
|
38
|
+
//# sourceMappingURL=chunk-RMAOPWHJ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/pack-files.ts"],"sourcesContent":["/**\n * Typed view of a git `objects/pack` directory listing.\n *\n * Every pack is a set of sibling files sharing one base name\n * (`pack-<hash>.pack`, `.idx`, and optionally `.keep`/`.bitmap`/`.rev`).\n * Parsing names once into {@link PackFileName} lets callers group, filter and\n * rename by kind instead of re-matching filename strings at each call site.\n *\n * Edge-compatible: no node: imports.\n */\n\n/** Every file kind git writes into `objects/pack`. */\nexport const PACK_FILE_KINDS = [\n\t\"pack\",\n\t\"idx\",\n\t\"keep\",\n\t\"bitmap\",\n\t\"rev\",\n] as const;\nexport type PackFileKind = (typeof PACK_FILE_KINDS)[number];\n\n/** Kinds isomorphic-git actually reads; the rest are git-CLI accelerators. */\nexport const READABLE_PACK_FILE_KINDS: ReadonlySet<PackFileKind> = new Set([\n\t\"pack\",\n\t\"idx\",\n]);\n\nexport interface PackFileName {\n\t/** Shared base of a pack's sibling files, e.g. `pack-1a2b…`. */\n\treadonly base: string;\n\treadonly kind: PackFileKind;\n\t/** The file name as listed, e.g. `pack-1a2b….idx`. */\n\treadonly name: string;\n}\n\nconst KNOWN_KINDS: ReadonlySet<string> = new Set(PACK_FILE_KINDS);\n\nfunction isPackFileKind(value: string): value is PackFileKind {\n\treturn KNOWN_KINDS.has(value);\n}\n\n/** Parse a pack-directory entry; `null` for anything git didn't put there. */\nexport function parsePackFileName(name: string): PackFileName | null {\n\tconst dot = name.lastIndexOf(\".\");\n\tif (dot <= 0) return null;\n\tconst kind = name.slice(dot + 1);\n\tif (!isPackFileKind(kind)) return null;\n\treturn { base: name.slice(0, dot), kind, name };\n}\n\n/** File name of the `kind` sibling of the pack with this base name. */\nexport function packFileName(base: string, kind: PackFileKind): string {\n\treturn `${base}.${kind}`;\n}\n\n/** Parse a directory listing, dropping entries that aren't pack files. */\nexport function parsePackDirectory(entries: readonly string[]): PackFileName[] {\n\treturn entries\n\t\t.map(parsePackFileName)\n\t\t.filter((file): file is PackFileName => file !== null);\n}\n"],"mappings":";AAYO,IAAM,kBAAkB;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACD;AAIO,IAAM,2BAAsD,oBAAI,IAAI;AAAA,EAC1E;AAAA,EACA;AACD,CAAC;AAUD,IAAM,cAAmC,IAAI,IAAI,eAAe;AAEhE,SAAS,eAAe,OAAsC;AAC7D,SAAO,YAAY,IAAI,KAAK;AAC7B;AAGO,SAAS,kBAAkB,MAAmC;AACpE,QAAM,MAAM,KAAK,YAAY,GAAG;AAChC,MAAI,OAAO,EAAG,QAAO;AACrB,QAAM,OAAO,KAAK,MAAM,MAAM,CAAC;AAC/B,MAAI,CAAC,eAAe,IAAI,EAAG,QAAO;AAClC,SAAO,EAAE,MAAM,KAAK,MAAM,GAAG,GAAG,GAAG,MAAM,KAAK;AAC/C;AAGO,SAAS,aAAa,MAAc,MAA4B;AACtE,SAAO,GAAG,IAAI,IAAI,IAAI;AACvB;AAGO,SAAS,mBAAmB,SAA4C;AAC9E,SAAO,QACL,IAAI,iBAAiB,EACrB,OAAO,CAAC,SAA+B,SAAS,IAAI;AACvD;","names":[]}
|