git-fs-s3 0.3.12 → 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 +367 -80
- 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 +273 -80
- 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
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 [];
|
|
@@ -556,6 +819,20 @@ async function checkRefCas(repo, refName, oldOid) {
|
|
|
556
819
|
reason: "non-fast-forward, ref updated by another push"
|
|
557
820
|
};
|
|
558
821
|
}
|
|
822
|
+
async function collectExistingRefTips(repo) {
|
|
823
|
+
const [branches, tags] = await Promise.all([
|
|
824
|
+
import_isomorphic_git5.default.listBranches(repo),
|
|
825
|
+
import_isomorphic_git5.default.listTags(repo)
|
|
826
|
+
]);
|
|
827
|
+
const refs = [
|
|
828
|
+
...branches.map((b) => `refs/heads/${b}`),
|
|
829
|
+
...tags.map((t) => `refs/tags/${t}`)
|
|
830
|
+
];
|
|
831
|
+
const oids = await Promise.all(
|
|
832
|
+
refs.map((ref) => import_isomorphic_git5.default.resolveRef({ ...repo, ref }).catch(() => null))
|
|
833
|
+
);
|
|
834
|
+
return new Set(oids.filter((oid) => oid !== null));
|
|
835
|
+
}
|
|
559
836
|
async function validateRefUpdates(repo, refUpdates) {
|
|
560
837
|
return Promise.all(
|
|
561
838
|
refUpdates.map(
|
|
@@ -619,7 +896,10 @@ async function applyReceivePack(repo, parsed, options, hooks) {
|
|
|
619
896
|
);
|
|
620
897
|
const newTips = acceptedUpdates.filter((update) => update.newOid !== ZERO_OID).map((update) => update.newOid);
|
|
621
898
|
if (newTips.length > 0) {
|
|
622
|
-
const haveOids =
|
|
899
|
+
const haveOids = await collectExistingRefTips(repo);
|
|
900
|
+
for (const update of acceptedUpdates) {
|
|
901
|
+
if (update.oldOid !== ZERO_OID) haveOids.add(update.oldOid);
|
|
902
|
+
}
|
|
623
903
|
const { complete } = await collectReachableOids(
|
|
624
904
|
repo,
|
|
625
905
|
newTips,
|
|
@@ -700,7 +980,7 @@ async function handleUploadPack(repo, body, options, hooks) {
|
|
|
700
980
|
"readdir objects/pack",
|
|
701
981
|
() => rawFs(repo).readdir(packDirPath).catch(() => [])
|
|
702
982
|
);
|
|
703
|
-
const packNames = entries.filter((
|
|
983
|
+
const packNames = parsePackDirectory(entries).filter((file) => file.kind === "pack").map((file) => file.name);
|
|
704
984
|
const packName = packNames[0];
|
|
705
985
|
if (packNames.length === 1 && packName !== void 0) {
|
|
706
986
|
const packData = await runStep(
|
|
@@ -722,27 +1002,34 @@ async function handleUploadPack(repo, body, options, hooks) {
|
|
|
722
1002
|
if (options?.beforeWalk) {
|
|
723
1003
|
await runStep(hooks, "beforeWalk", options.beforeWalk);
|
|
724
1004
|
}
|
|
725
|
-
|
|
726
|
-
hooks,
|
|
727
|
-
"collectReachableOids(wants)",
|
|
728
|
-
() => collectReachableOids(repo, wants, hooks)
|
|
729
|
-
);
|
|
730
|
-
let oids = wantOids;
|
|
1005
|
+
let haveSet;
|
|
731
1006
|
if (haves.length > 0) {
|
|
732
1007
|
const { oids: haveOids } = await runStep(
|
|
733
1008
|
hooks,
|
|
734
1009
|
"collectReachableOids(haves)",
|
|
735
|
-
() => collectReachableOids(repo, haves, hooks
|
|
1010
|
+
() => collectReachableOids(repo, haves, hooks, void 0, {
|
|
1011
|
+
verifyBlobs: false
|
|
1012
|
+
})
|
|
736
1013
|
);
|
|
737
|
-
|
|
738
|
-
oids = oids.filter((oid) => !haveSet.has(oid));
|
|
1014
|
+
haveSet = new Set(haveOids);
|
|
739
1015
|
}
|
|
740
|
-
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(
|
|
741
1025
|
hooks,
|
|
742
|
-
`
|
|
743
|
-
() =>
|
|
1026
|
+
`reuse pack entries (${oids.length} oids)`,
|
|
1027
|
+
() => buildPackFromExistingPacks(repo, oids)
|
|
744
1028
|
);
|
|
745
|
-
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
|
+
});
|
|
746
1033
|
return {
|
|
747
1034
|
status: 200,
|
|
748
1035
|
headers: {
|