git-fs-s3 0.3.13 → 0.4.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/dist/chunk-RMAOPWHJ.js +38 -0
- package/dist/chunk-RMAOPWHJ.js.map +1 -0
- package/dist/{chunk-3BDDEGAP.js → chunk-Z4656PGE.js} +6 -2
- package/dist/chunk-Z4656PGE.js.map +1 -0
- package/dist/http.cjs +349 -79
- package/dist/http.cjs.map +1 -1
- package/dist/http.d.cts +11 -2
- package/dist/http.d.ts +11 -2
- package/dist/http.js +255 -79
- package/dist/http.js.map +1 -1
- package/dist/index.cjs +145 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +101 -3
- package/dist/index.d.ts +101 -3
- package/dist/index.js +114 -16
- package/dist/index.js.map +1 -1
- package/dist/ops.cjs +4 -1
- package/dist/ops.cjs.map +1 -1
- package/dist/ops.js +1 -1
- package/dist/s3.d.cts +1 -1
- package/dist/s3.d.ts +1 -1
- package/dist/{types-QgIkUR_q.d.cts → types-dbHzZ4ed.d.cts} +13 -3
- package/dist/{types-QgIkUR_q.d.ts → types-dbHzZ4ed.d.ts} +13 -3
- package/package.json +9 -9
- package/dist/chunk-3BDDEGAP.js.map +0 -1
|
@@ -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":[]}
|
|
@@ -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-Z4656PGE.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":[]}
|
package/dist/http.cjs
CHANGED
|
@@ -111,15 +111,24 @@ var BAD_REF_COMPONENT = (
|
|
|
111
111
|
// biome-ignore lint/suspicious/noControlCharactersInRegex: control chars are exactly what git's own ref-name rules reject — this needs to match the same range.
|
|
112
112
|
/(^|[/.])([/.]|$)|^@$|@\{|[\x00-\x20\x7f~^:?*[\\]|\.lock(\/|$)/
|
|
113
113
|
);
|
|
114
|
+
var FULL_SHA_RE = /^[0-9a-f]{40}$/i;
|
|
114
115
|
function isSafeFullRefName(ref) {
|
|
115
116
|
if (!ref.startsWith("refs/heads/") && !ref.startsWith("refs/tags/")) {
|
|
116
117
|
return false;
|
|
117
118
|
}
|
|
118
119
|
return !BAD_REF_COMPONENT.test(ref);
|
|
119
120
|
}
|
|
121
|
+
function isSafeBranchName(name) {
|
|
122
|
+
if (!name || name.startsWith("refs/") || name === "HEAD") return false;
|
|
123
|
+
if (FULL_SHA_RE.test(name)) return false;
|
|
124
|
+
return !BAD_REF_COMPONENT.test(name);
|
|
125
|
+
}
|
|
120
126
|
|
|
121
127
|
// src/ops/branch.ts
|
|
122
|
-
var
|
|
128
|
+
var FULL_SHA_RE2 = /^[0-9a-f]{40}$/i;
|
|
129
|
+
function hasRecursiveFileLister(fs) {
|
|
130
|
+
return typeof fs === "object" && fs !== null && "listFilesRecursively" in fs && typeof fs.listFilesRecursively === "function";
|
|
131
|
+
}
|
|
123
132
|
function getPromisesFs(fs) {
|
|
124
133
|
const promises = fs.promises;
|
|
125
134
|
return promises ?? null;
|
|
@@ -127,6 +136,48 @@ function getPromisesFs(fs) {
|
|
|
127
136
|
function decodeFileContent(content) {
|
|
128
137
|
return typeof content === "string" ? content : new TextDecoder().decode(content);
|
|
129
138
|
}
|
|
139
|
+
async function listLooseBranches(repo) {
|
|
140
|
+
return listLooseRefNames(repo, "refs/heads");
|
|
141
|
+
}
|
|
142
|
+
async function listLooseRefNames(repo, dir) {
|
|
143
|
+
if (!hasRecursiveFileLister(repo.fs)) return null;
|
|
144
|
+
const names = await repo.fs.listFilesRecursively(`${repo.gitdir}/${dir}`);
|
|
145
|
+
return names.filter(isSafeBranchName);
|
|
146
|
+
}
|
|
147
|
+
async function readPackedBranches(repo) {
|
|
148
|
+
if (!hasRecursiveFileLister(repo.fs)) return /* @__PURE__ */ new Map();
|
|
149
|
+
const promisesFs = getPromisesFs(repo.fs);
|
|
150
|
+
if (!promisesFs) return /* @__PURE__ */ new Map();
|
|
151
|
+
try {
|
|
152
|
+
const content = await promisesFs.readFile(
|
|
153
|
+
`${repo.gitdir}/packed-refs`,
|
|
154
|
+
"utf8"
|
|
155
|
+
);
|
|
156
|
+
const refs = /* @__PURE__ */ new Map();
|
|
157
|
+
for (const line of decodeFileContent(content).split("\n")) {
|
|
158
|
+
const match = /^([0-9a-f]{40}) refs\/heads\/(.+)$/i.exec(line);
|
|
159
|
+
if (match?.[1] && match[2] && isSafeBranchName(match[2])) {
|
|
160
|
+
refs.set(match[2], match[1]);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return refs;
|
|
164
|
+
} catch {
|
|
165
|
+
return /* @__PURE__ */ new Map();
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
async function getCurrentLooseBranch(repo) {
|
|
169
|
+
if (!hasRecursiveFileLister(repo.fs)) return null;
|
|
170
|
+
const promisesFs = getPromisesFs(repo.fs);
|
|
171
|
+
if (!promisesFs) return null;
|
|
172
|
+
try {
|
|
173
|
+
const content = await promisesFs.readFile(`${repo.gitdir}/HEAD`, "utf8");
|
|
174
|
+
const head = decodeFileContent(content).trim();
|
|
175
|
+
const match = /^ref: refs\/heads\/(.+)$/.exec(head);
|
|
176
|
+
return match?.[1] && isSafeBranchName(match[1]) ? match[1] : null;
|
|
177
|
+
} catch {
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
130
181
|
async function resolveLooseRefFast(repo, ref) {
|
|
131
182
|
const promisesFs = getPromisesFs(repo.fs);
|
|
132
183
|
if (promisesFs) {
|
|
@@ -136,12 +187,36 @@ async function resolveLooseRefFast(repo, ref) {
|
|
|
136
187
|
"utf8"
|
|
137
188
|
);
|
|
138
189
|
const oid = decodeFileContent(content).trim();
|
|
139
|
-
if (
|
|
190
|
+
if (FULL_SHA_RE2.test(oid)) return oid;
|
|
140
191
|
} catch {
|
|
141
192
|
}
|
|
142
193
|
}
|
|
143
194
|
return import_isomorphic_git.default.resolveRef({ ...repo, ref });
|
|
144
195
|
}
|
|
196
|
+
async function listBranches(repo) {
|
|
197
|
+
try {
|
|
198
|
+
const [looseBranches, looseCurrentBranch, packedBranches] = await Promise.all([
|
|
199
|
+
listLooseBranches(repo),
|
|
200
|
+
getCurrentLooseBranch(repo),
|
|
201
|
+
readPackedBranches(repo)
|
|
202
|
+
]);
|
|
203
|
+
const branches = looseBranches === null ? await import_isomorphic_git.default.listBranches(repo) : [.../* @__PURE__ */ new Set([...looseBranches, ...packedBranches.keys()])].sort();
|
|
204
|
+
const currentBranch = looseBranches === null ? await import_isomorphic_git.default.currentBranch({ ...repo, fullname: false }).catch(() => null) : looseCurrentBranch;
|
|
205
|
+
return Promise.all(
|
|
206
|
+
branches.map(async (branch) => {
|
|
207
|
+
const packedCommit = packedBranches.get(branch);
|
|
208
|
+
return {
|
|
209
|
+
name: branch,
|
|
210
|
+
commit: packedCommit && !looseBranches?.includes(branch) ? packedCommit : await resolveLooseRefFast(repo, `refs/heads/${branch}`),
|
|
211
|
+
isDefault: branch === currentBranch
|
|
212
|
+
};
|
|
213
|
+
})
|
|
214
|
+
);
|
|
215
|
+
} catch (err) {
|
|
216
|
+
if (err.code === "NotFoundError") return [];
|
|
217
|
+
throw err;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
145
220
|
|
|
146
221
|
// src/http/pkt-line.ts
|
|
147
222
|
var FLUSH = new Uint8Array([
|
|
@@ -201,34 +276,24 @@ function rawFs(repo) {
|
|
|
201
276
|
|
|
202
277
|
// src/http/info-refs.ts
|
|
203
278
|
async function listAllRefs(repo, defaultBranch = "main") {
|
|
204
|
-
const [branches,
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
Promise.resolve(import_isomorphic_git2.default.currentBranch({ ...repo, fullname: true })).then((cb) => cb ?? `refs/heads/${defaultBranch}`).catch(() => `refs/heads/${defaultBranch}`)
|
|
209
|
-
]);
|
|
210
|
-
const [branchRefs, tagRefs] = await Promise.all([
|
|
211
|
-
Promise.all(
|
|
212
|
-
branches.map(async (branch) => {
|
|
213
|
-
try {
|
|
214
|
-
const oid = await resolveLooseRefFast(repo, `refs/heads/${branch}`);
|
|
215
|
-
return { name: `refs/heads/${branch}`, oid };
|
|
216
|
-
} catch {
|
|
217
|
-
return null;
|
|
218
|
-
}
|
|
219
|
-
})
|
|
220
|
-
),
|
|
221
|
-
Promise.all(
|
|
222
|
-
tags.map(async (tag) => {
|
|
223
|
-
try {
|
|
224
|
-
const oid = await resolveLooseRefFast(repo, `refs/tags/${tag}`);
|
|
225
|
-
return { name: `refs/tags/${tag}`, oid };
|
|
226
|
-
} catch {
|
|
227
|
-
return null;
|
|
228
|
-
}
|
|
229
|
-
})
|
|
279
|
+
const [branches, tagNames] = await Promise.all([
|
|
280
|
+
listBranches(repo),
|
|
281
|
+
listLooseRefNames(repo, "refs/tags").then(
|
|
282
|
+
(names) => names ?? import_isomorphic_git2.default.listTags(repo)
|
|
230
283
|
)
|
|
231
284
|
]);
|
|
285
|
+
const tagRefs = await Promise.all(
|
|
286
|
+
tagNames.map(async (tag) => {
|
|
287
|
+
const name = `refs/tags/${tag}`;
|
|
288
|
+
return resolveLooseRefFast(repo, name).then((oid) => ({ name, oid })).catch(() => null);
|
|
289
|
+
})
|
|
290
|
+
);
|
|
291
|
+
const branchRefs = branches.map((b) => ({
|
|
292
|
+
name: `refs/heads/${b.name}`,
|
|
293
|
+
oid: b.commit
|
|
294
|
+
}));
|
|
295
|
+
const defaultRef = branches.find((b) => b.isDefault);
|
|
296
|
+
const headSymref = defaultRef ? `refs/heads/${defaultRef.name}` : await Promise.resolve(import_isomorphic_git2.default.currentBranch({ ...repo, fullname: true })).then((cb) => cb ?? `refs/heads/${defaultBranch}`).catch(() => `refs/heads/${defaultBranch}`);
|
|
232
297
|
const headOid = branchRefs.find((r) => r?.name === headSymref)?.oid ?? await import_isomorphic_git2.default.resolveRef({ ...repo, ref: "HEAD" }).catch(() => null);
|
|
233
298
|
const refs = [];
|
|
234
299
|
if (headOid) refs.push({ name: "HEAD", oid: headOid });
|
|
@@ -281,7 +346,8 @@ async function handleInfoRefs(repo, options, hooks) {
|
|
|
281
346
|
var import_isomorphic_git3 = __toESM(require("isomorphic-git"), 1);
|
|
282
347
|
var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
283
348
|
var MISSING_OBJECT_RETRY_DELAY_MS = 200;
|
|
284
|
-
async function collectReachableOids(repo, startOids, hooks, haveOids) {
|
|
349
|
+
async function collectReachableOids(repo, startOids, hooks, haveOids, options) {
|
|
350
|
+
const verifyBlobs = options?.verifyBlobs ?? true;
|
|
285
351
|
const have = haveOids instanceof Set ? haveOids : new Set(haveOids ?? []);
|
|
286
352
|
const seen = /* @__PURE__ */ new Set();
|
|
287
353
|
let complete = true;
|
|
@@ -289,23 +355,26 @@ async function collectReachableOids(repo, startOids, hooks, haveOids) {
|
|
|
289
355
|
async function readAndVisitChildren(oid) {
|
|
290
356
|
const obj = await import_isomorphic_git3.default.readObject({ ...repo, oid });
|
|
291
357
|
seen.add(oid);
|
|
292
|
-
|
|
358
|
+
const children = [];
|
|
293
359
|
if (obj.type === "commit") {
|
|
294
|
-
const
|
|
295
|
-
children
|
|
360
|
+
const commit = obj.object;
|
|
361
|
+
children.push([commit.tree, "tree"]);
|
|
362
|
+
for (const parent of commit.parent) children.push([parent, "commit"]);
|
|
296
363
|
} else if (obj.type === "tree") {
|
|
297
|
-
const
|
|
298
|
-
|
|
364
|
+
const tree = obj.object;
|
|
365
|
+
for (const entry of tree) {
|
|
366
|
+
if (entry.type === "commit") continue;
|
|
367
|
+
children.push([entry.oid, entry.type]);
|
|
368
|
+
}
|
|
299
369
|
} else if (obj.type === "tag") {
|
|
300
|
-
|
|
301
|
-
children = [tag.object];
|
|
370
|
+
children.push([obj.object.object, void 0]);
|
|
302
371
|
}
|
|
303
|
-
await Promise.all(children.map(visit));
|
|
372
|
+
await Promise.all(children.map(([child, type]) => visit(child, type)));
|
|
304
373
|
}
|
|
305
|
-
function visit(oid) {
|
|
374
|
+
function visit(oid, type) {
|
|
306
375
|
const existing = promises.get(oid);
|
|
307
376
|
if (existing) return existing;
|
|
308
|
-
if (have.has(oid)) {
|
|
377
|
+
if (have.has(oid) || type === "blob" && !verifyBlobs) {
|
|
309
378
|
seen.add(oid);
|
|
310
379
|
const p2 = Promise.resolve();
|
|
311
380
|
promises.set(oid, p2);
|
|
@@ -327,7 +396,7 @@ async function collectReachableOids(repo, startOids, hooks, haveOids) {
|
|
|
327
396
|
promises.set(oid, p);
|
|
328
397
|
return p;
|
|
329
398
|
}
|
|
330
|
-
await Promise.all(startOids.map(visit));
|
|
399
|
+
await Promise.all(startOids.map((oid) => visit(oid)));
|
|
331
400
|
return { oids: Array.from(seen), complete };
|
|
332
401
|
}
|
|
333
402
|
|
|
@@ -336,6 +405,193 @@ var import_isomorphic_git5 = __toESM(require("isomorphic-git"), 1);
|
|
|
336
405
|
|
|
337
406
|
// src/http/repack.ts
|
|
338
407
|
var import_isomorphic_git4 = __toESM(require("isomorphic-git"), 1);
|
|
408
|
+
|
|
409
|
+
// src/pack-files.ts
|
|
410
|
+
var PACK_FILE_KINDS = [
|
|
411
|
+
"pack",
|
|
412
|
+
"idx",
|
|
413
|
+
"keep",
|
|
414
|
+
"bitmap",
|
|
415
|
+
"rev"
|
|
416
|
+
];
|
|
417
|
+
var KNOWN_KINDS = new Set(PACK_FILE_KINDS);
|
|
418
|
+
function isPackFileKind(value) {
|
|
419
|
+
return KNOWN_KINDS.has(value);
|
|
420
|
+
}
|
|
421
|
+
function parsePackFileName(name) {
|
|
422
|
+
const dot = name.lastIndexOf(".");
|
|
423
|
+
if (dot <= 0) return null;
|
|
424
|
+
const kind = name.slice(dot + 1);
|
|
425
|
+
if (!isPackFileKind(kind)) return null;
|
|
426
|
+
return { base: name.slice(0, dot), kind, name };
|
|
427
|
+
}
|
|
428
|
+
function packFileName(base, kind) {
|
|
429
|
+
return `${base}.${kind}`;
|
|
430
|
+
}
|
|
431
|
+
function parsePackDirectory(entries) {
|
|
432
|
+
return entries.map(parsePackFileName).filter((file) => file !== null);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// src/http/pack-reuse.ts
|
|
436
|
+
var OBJ_OFS_DELTA = 6;
|
|
437
|
+
var OBJ_REF_DELTA = 7;
|
|
438
|
+
var OID_BYTES = 20;
|
|
439
|
+
var PACK_TRAILER_BYTES = 20;
|
|
440
|
+
var IDX_V2 = {
|
|
441
|
+
magic: 4285812579,
|
|
442
|
+
version: 2,
|
|
443
|
+
headerBytes: 8,
|
|
444
|
+
fanoutEntries: 256,
|
|
445
|
+
/** Offsets with this bit set index the 64-bit large-offset table. */
|
|
446
|
+
largeOffsetFlag: 2147483648
|
|
447
|
+
};
|
|
448
|
+
function readPackIndex(idx) {
|
|
449
|
+
const view = new DataView(idx.buffer, idx.byteOffset, idx.byteLength);
|
|
450
|
+
const namesStart = IDX_V2.headerBytes + IDX_V2.fanoutEntries * 4;
|
|
451
|
+
if (idx.byteLength < namesStart || view.getUint32(0) !== IDX_V2.magic || view.getUint32(4) !== IDX_V2.version) {
|
|
452
|
+
return null;
|
|
453
|
+
}
|
|
454
|
+
const count = view.getUint32(namesStart - 4);
|
|
455
|
+
const crcStart = namesStart + count * OID_BYTES;
|
|
456
|
+
const offsetsStart = crcStart + count * 4;
|
|
457
|
+
if (idx.byteLength < offsetsStart + count * 4) return null;
|
|
458
|
+
const entries = [];
|
|
459
|
+
for (let i = 0; i < count; i++) {
|
|
460
|
+
const offset = view.getUint32(offsetsStart + i * 4);
|
|
461
|
+
if (offset & IDX_V2.largeOffsetFlag) return null;
|
|
462
|
+
const at = namesStart + i * OID_BYTES;
|
|
463
|
+
entries.push({ oid: toHex(idx.subarray(at, at + OID_BYTES)), offset });
|
|
464
|
+
}
|
|
465
|
+
return entries;
|
|
466
|
+
}
|
|
467
|
+
function indexPack(idx, pack) {
|
|
468
|
+
const located = readPackIndex(idx);
|
|
469
|
+
if (!located) return null;
|
|
470
|
+
located.sort((a, b) => a.offset - b.offset);
|
|
471
|
+
const entries = /* @__PURE__ */ new Map();
|
|
472
|
+
const oidAtOffset = /* @__PURE__ */ new Map();
|
|
473
|
+
const packEnd = pack.byteLength - PACK_TRAILER_BYTES;
|
|
474
|
+
for (let i = 0; i < located.length; i++) {
|
|
475
|
+
const { oid, offset } = located[i];
|
|
476
|
+
const end = located[i + 1]?.offset ?? packEnd;
|
|
477
|
+
entries.set(oid, { pack, start: offset, end });
|
|
478
|
+
oidAtOffset.set(offset, oid);
|
|
479
|
+
}
|
|
480
|
+
return { entries, oidAtOffset };
|
|
481
|
+
}
|
|
482
|
+
function readEntryHeader(bytes, start) {
|
|
483
|
+
let byte = bytes[start];
|
|
484
|
+
const type = byte >> 4 & 7;
|
|
485
|
+
let size = byte & 15;
|
|
486
|
+
let shift = 4;
|
|
487
|
+
let at = start + 1;
|
|
488
|
+
while (byte & 128) {
|
|
489
|
+
byte = bytes[at++];
|
|
490
|
+
size += (byte & 127) * 2 ** shift;
|
|
491
|
+
shift += 7;
|
|
492
|
+
}
|
|
493
|
+
return { type, size, length: at - start };
|
|
494
|
+
}
|
|
495
|
+
function encodeEntryHeader(type, size) {
|
|
496
|
+
const bytes = [];
|
|
497
|
+
let rest = Math.floor(size / 16);
|
|
498
|
+
bytes.push((rest > 0 ? 128 : 0) | type << 4 | size % 16);
|
|
499
|
+
while (rest > 0) {
|
|
500
|
+
const low = rest % 128;
|
|
501
|
+
rest = Math.floor(rest / 128);
|
|
502
|
+
bytes.push((rest > 0 ? 128 : 0) | low);
|
|
503
|
+
}
|
|
504
|
+
return new Uint8Array(bytes);
|
|
505
|
+
}
|
|
506
|
+
function readOfsDeltaOffset(bytes, start) {
|
|
507
|
+
let at = start;
|
|
508
|
+
let byte = bytes[at++];
|
|
509
|
+
let distance = byte & 127;
|
|
510
|
+
while (byte & 128) {
|
|
511
|
+
byte = bytes[at++];
|
|
512
|
+
distance = (distance + 1) * 128 + (byte & 127);
|
|
513
|
+
}
|
|
514
|
+
return { distance, length: at - start };
|
|
515
|
+
}
|
|
516
|
+
function reusableEntry(entry, indexed) {
|
|
517
|
+
const { pack, start, end } = entry;
|
|
518
|
+
const header = readEntryHeader(pack, start);
|
|
519
|
+
const bodyStart = start + header.length;
|
|
520
|
+
if (header.type === OBJ_REF_DELTA) {
|
|
521
|
+
return {
|
|
522
|
+
bytes: pack.subarray(start, end),
|
|
523
|
+
baseOid: toHex(pack.subarray(bodyStart, bodyStart + OID_BYTES))
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
if (header.type !== OBJ_OFS_DELTA) {
|
|
527
|
+
return { bytes: pack.subarray(start, end), baseOid: null };
|
|
528
|
+
}
|
|
529
|
+
const { distance, length } = readOfsDeltaOffset(pack, bodyStart);
|
|
530
|
+
const baseOid = indexed.oidAtOffset.get(start - distance);
|
|
531
|
+
if (baseOid === void 0) return null;
|
|
532
|
+
return {
|
|
533
|
+
bytes: concat(
|
|
534
|
+
encodeEntryHeader(OBJ_REF_DELTA, header.size),
|
|
535
|
+
fromHex(baseOid),
|
|
536
|
+
pack.subarray(bodyStart + length, end)
|
|
537
|
+
),
|
|
538
|
+
baseOid
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
async function buildPackFromExistingPacks(repo, oids) {
|
|
542
|
+
const fsp = rawFs(repo);
|
|
543
|
+
const packDir = `${repo.gitdir}/objects/pack`;
|
|
544
|
+
const packs = parsePackDirectory(
|
|
545
|
+
await fsp.readdir(packDir).catch(() => [])
|
|
546
|
+
).filter((file) => file.kind === "pack");
|
|
547
|
+
const indexes = [];
|
|
548
|
+
for (const { base } of packs) {
|
|
549
|
+
const [pack, idx] = await Promise.all([
|
|
550
|
+
fsp.readFile(`${packDir}/${packFileName(base, "pack")}`),
|
|
551
|
+
fsp.readFile(`${packDir}/${packFileName(base, "idx")}`)
|
|
552
|
+
]).catch(() => [null, null]);
|
|
553
|
+
if (!(pack instanceof Uint8Array) || !(idx instanceof Uint8Array)) {
|
|
554
|
+
return null;
|
|
555
|
+
}
|
|
556
|
+
const indexed = indexPack(idx, pack);
|
|
557
|
+
if (!indexed) return null;
|
|
558
|
+
indexes.push(indexed);
|
|
559
|
+
}
|
|
560
|
+
const locate = (oid) => {
|
|
561
|
+
for (const indexed of indexes) {
|
|
562
|
+
const entry = indexed.entries.get(oid);
|
|
563
|
+
if (entry) return reusableEntry(entry, indexed);
|
|
564
|
+
}
|
|
565
|
+
return null;
|
|
566
|
+
};
|
|
567
|
+
const ordered = [];
|
|
568
|
+
const done = /* @__PURE__ */ new Set();
|
|
569
|
+
const pending = /* @__PURE__ */ new Set();
|
|
570
|
+
const visit = (oid) => {
|
|
571
|
+
if (done.has(oid)) return true;
|
|
572
|
+
if (pending.has(oid)) return false;
|
|
573
|
+
const entry = locate(oid);
|
|
574
|
+
if (!entry) return false;
|
|
575
|
+
pending.add(oid);
|
|
576
|
+
if (entry.baseOid !== null && !visit(entry.baseOid)) return false;
|
|
577
|
+
pending.delete(oid);
|
|
578
|
+
done.add(oid);
|
|
579
|
+
ordered.push(entry.bytes);
|
|
580
|
+
return true;
|
|
581
|
+
};
|
|
582
|
+
for (const oid of oids) {
|
|
583
|
+
if (!visit(oid)) return null;
|
|
584
|
+
}
|
|
585
|
+
const header = new Uint8Array(12);
|
|
586
|
+
header.set(encodeUtf8("PACK"), 0);
|
|
587
|
+
const view = new DataView(header.buffer);
|
|
588
|
+
view.setUint32(4, 2);
|
|
589
|
+
view.setUint32(8, ordered.length);
|
|
590
|
+
const body = concat(header, ...ordered);
|
|
591
|
+
return concat(body, fromHex(await sha1(body)));
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// src/http/repack.ts
|
|
339
595
|
var PACK_OBJECT_TYPE_BITS = {
|
|
340
596
|
commit: 16,
|
|
341
597
|
tree: 32,
|
|
@@ -419,13 +675,13 @@ async function buildVerifiedPack(oids, objects) {
|
|
|
419
675
|
return concat(packBody, trailer);
|
|
420
676
|
}
|
|
421
677
|
var REPACK_PACK_COUNT_THRESHOLD = 4;
|
|
678
|
+
async function readPackDirectory(repo) {
|
|
679
|
+
const entries = await rawFs(repo).readdir(`${repo.gitdir}/objects/pack`).catch(() => []);
|
|
680
|
+
return parsePackDirectory(entries);
|
|
681
|
+
}
|
|
422
682
|
async function countPacks(repo) {
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
return entries.filter((f) => f.endsWith(".pack")).length;
|
|
426
|
-
} catch {
|
|
427
|
-
return 0;
|
|
428
|
-
}
|
|
683
|
+
const files = await readPackDirectory(repo);
|
|
684
|
+
return files.filter((file) => file.kind === "pack").length;
|
|
429
685
|
}
|
|
430
686
|
async function repackRepository(repo, options, hooks) {
|
|
431
687
|
const threshold = options?.threshold ?? REPACK_PACK_COUNT_THRESHOLD;
|
|
@@ -448,41 +704,48 @@ async function repackRepository(repo, options, hooks) {
|
|
|
448
704
|
)
|
|
449
705
|
)).filter((oid) => oid !== null);
|
|
450
706
|
if (tipOids.length === 0) return [];
|
|
451
|
-
const { oids, complete } = await collectReachableOids(
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
707
|
+
const { oids: reachable, complete } = await collectReachableOids(
|
|
708
|
+
repo,
|
|
709
|
+
tipOids,
|
|
710
|
+
hooks
|
|
711
|
+
);
|
|
712
|
+
if (!complete || reachable.length === 0) return [];
|
|
455
713
|
const packDir = `${repo.gitdir}/objects/pack`;
|
|
714
|
+
const packBuffer = await buildPackFromExistingPacks(repo, reachable) ?? await buildVerifiedPack(
|
|
715
|
+
reachable,
|
|
716
|
+
await readAndVerifyObjects(repo, reachable)
|
|
717
|
+
);
|
|
456
718
|
await fsp.mkdir(packDir, { recursive: true });
|
|
457
719
|
const newBase = `pack-${Date.now()}`;
|
|
458
|
-
const newPackFile =
|
|
459
|
-
const newIdxFile = `${newBase}.idx`;
|
|
720
|
+
const newPackFile = packFileName(newBase, "pack");
|
|
460
721
|
await fsp.writeFile(`${packDir}/${newPackFile}`, packBuffer);
|
|
461
722
|
const { oids: indexedOids } = await import_isomorphic_git4.default.indexPack({
|
|
462
723
|
...repo,
|
|
463
724
|
dir: packDir,
|
|
464
725
|
filepath: newPackFile
|
|
465
726
|
});
|
|
466
|
-
const expected = new Set(oids);
|
|
467
727
|
const indexed = new Set(indexedOids);
|
|
468
|
-
if (
|
|
469
|
-
await
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
728
|
+
if (reachable.some((oid) => !indexed.has(oid))) {
|
|
729
|
+
await Promise.all(
|
|
730
|
+
["pack", "idx"].map(
|
|
731
|
+
(kind) => fsp.unlink(`${packDir}/${packFileName(newBase, kind)}`).catch(() => {
|
|
732
|
+
})
|
|
733
|
+
)
|
|
734
|
+
);
|
|
473
735
|
throw new Error(
|
|
474
|
-
"repack: indexed pack
|
|
736
|
+
"repack: indexed pack is missing reachable objects \u2014 aborting"
|
|
475
737
|
);
|
|
476
738
|
}
|
|
477
|
-
const
|
|
478
|
-
|
|
479
|
-
(f) => f !== newPackFile && f !== newIdxFile && (f.endsWith(".pack") || f.endsWith(".idx") || f.endsWith(".keep"))
|
|
739
|
+
const stale = (await readPackDirectory(repo)).filter(
|
|
740
|
+
(file) => file.base !== newBase
|
|
480
741
|
);
|
|
481
742
|
await Promise.all(
|
|
482
|
-
|
|
483
|
-
|
|
743
|
+
stale.map(
|
|
744
|
+
(file) => fsp.unlink(`${packDir}/${file.name}`).catch(() => {
|
|
745
|
+
})
|
|
746
|
+
)
|
|
484
747
|
);
|
|
485
|
-
return
|
|
748
|
+
return stale.map((file) => `objects/pack/${file.name}`);
|
|
486
749
|
} catch (err) {
|
|
487
750
|
hooks?.onWarn?.("repack failed (non-fatal)", err);
|
|
488
751
|
return [];
|
|
@@ -717,7 +980,7 @@ async function handleUploadPack(repo, body, options, hooks) {
|
|
|
717
980
|
"readdir objects/pack",
|
|
718
981
|
() => rawFs(repo).readdir(packDirPath).catch(() => [])
|
|
719
982
|
);
|
|
720
|
-
const packNames = entries.filter((
|
|
983
|
+
const packNames = parsePackDirectory(entries).filter((file) => file.kind === "pack").map((file) => file.name);
|
|
721
984
|
const packName = packNames[0];
|
|
722
985
|
if (packNames.length === 1 && packName !== void 0) {
|
|
723
986
|
const packData = await runStep(
|
|
@@ -739,27 +1002,34 @@ async function handleUploadPack(repo, body, options, hooks) {
|
|
|
739
1002
|
if (options?.beforeWalk) {
|
|
740
1003
|
await runStep(hooks, "beforeWalk", options.beforeWalk);
|
|
741
1004
|
}
|
|
742
|
-
|
|
743
|
-
hooks,
|
|
744
|
-
"collectReachableOids(wants)",
|
|
745
|
-
() => collectReachableOids(repo, wants, hooks)
|
|
746
|
-
);
|
|
747
|
-
let oids = wantOids;
|
|
1005
|
+
let haveSet;
|
|
748
1006
|
if (haves.length > 0) {
|
|
749
1007
|
const { oids: haveOids } = await runStep(
|
|
750
1008
|
hooks,
|
|
751
1009
|
"collectReachableOids(haves)",
|
|
752
|
-
() => collectReachableOids(repo, haves, hooks
|
|
1010
|
+
() => collectReachableOids(repo, haves, hooks, void 0, {
|
|
1011
|
+
verifyBlobs: false
|
|
1012
|
+
})
|
|
753
1013
|
);
|
|
754
|
-
|
|
755
|
-
oids = oids.filter((oid) => !haveSet.has(oid));
|
|
1014
|
+
haveSet = new Set(haveOids);
|
|
756
1015
|
}
|
|
757
|
-
const {
|
|
1016
|
+
const { oids: wantOids } = await runStep(
|
|
1017
|
+
hooks,
|
|
1018
|
+
"collectReachableOids(wants)",
|
|
1019
|
+
() => collectReachableOids(repo, wants, hooks, haveSet, {
|
|
1020
|
+
verifyBlobs: false
|
|
1021
|
+
})
|
|
1022
|
+
);
|
|
1023
|
+
const oids = haveSet ? wantOids.filter((oid) => !haveSet.has(oid)) : wantOids;
|
|
1024
|
+
const reused = await runStep(
|
|
758
1025
|
hooks,
|
|
759
|
-
`
|
|
760
|
-
() =>
|
|
1026
|
+
`reuse pack entries (${oids.length} oids)`,
|
|
1027
|
+
() => buildPackFromExistingPacks(repo, oids)
|
|
761
1028
|
);
|
|
762
|
-
const packBytes =
|
|
1029
|
+
const packBytes = reused ?? await runStep(hooks, `packObjects (${oids.length} oids)`, async () => {
|
|
1030
|
+
const { packfile } = await import_isomorphic_git6.default.packObjects({ ...repo, oids });
|
|
1031
|
+
return packfile instanceof Uint8Array ? packfile : new Uint8Array(packfile ?? []);
|
|
1032
|
+
});
|
|
763
1033
|
return {
|
|
764
1034
|
status: 200,
|
|
765
1035
|
headers: {
|