git-fs-s3 0.6.0 → 0.8.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 +7 -3
- package/dist/{chunk-XNSZTA4Y.js → chunk-HBOJR5D3.js} +108 -5
- package/dist/chunk-HBOJR5D3.js.map +1 -0
- package/dist/{chunk-FIFWOMPM.js → chunk-PASVUPKR.js} +64 -2
- package/dist/chunk-PASVUPKR.js.map +1 -0
- package/dist/chunk-ZB6GNJ2J.js +59 -0
- package/dist/chunk-ZB6GNJ2J.js.map +1 -0
- package/dist/http.js +294 -189
- package/dist/http.js.map +1 -1
- package/dist/index.js +79 -76
- package/dist/index.js.map +1 -1
- package/dist/ops.js +166 -30
- package/dist/ops.js.map +1 -1
- package/dist/types/edge-utils.d.ts +2 -0
- package/dist/types/http/reachability.d.ts +12 -0
- package/dist/types/http/receive-pack.d.ts +28 -9
- package/dist/types/http/upload-pack.d.ts +19 -3
- package/dist/types/index.d.ts +1 -1
- package/dist/types/ops/commit.d.ts +9 -1
- package/dist/types/ops/diff.d.ts +10 -3
- package/dist/types/ops/index.d.ts +2 -1
- package/dist/types/ops/packed-writes.d.ts +9 -0
- package/dist/types/ops/tree.d.ts +9 -1
- package/dist/types/pack-write.d.ts +26 -0
- package/dist/types/refs.d.ts +9 -0
- package/package.json +2 -2
- package/dist/chunk-FIFWOMPM.js.map +0 -1
- package/dist/chunk-RMAOPWHJ.js +0 -38
- package/dist/chunk-RMAOPWHJ.js.map +0 -1
- package/dist/chunk-XNSZTA4Y.js.map +0 -1
package/dist/http.js
CHANGED
|
@@ -1,23 +1,26 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
} from "./chunk-RMAOPWHJ.js";
|
|
5
|
-
import {
|
|
2
|
+
encodePack,
|
|
3
|
+
hashGitObject,
|
|
6
4
|
listBranches,
|
|
7
5
|
listLooseRefNames,
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
packBaseName,
|
|
7
|
+
rawFs,
|
|
8
|
+
resolveLooseRefFast,
|
|
9
|
+
runStep,
|
|
10
|
+
storePack
|
|
11
|
+
} from "./chunk-HBOJR5D3.js";
|
|
10
12
|
import {
|
|
11
13
|
concat,
|
|
12
14
|
decodeAscii,
|
|
13
15
|
decodeUtf8,
|
|
14
|
-
deflate,
|
|
15
16
|
encodeUtf8,
|
|
16
17
|
fromHex,
|
|
17
18
|
isSafeFullRefName,
|
|
19
|
+
packFileName,
|
|
20
|
+
parsePackDirectory,
|
|
18
21
|
sha1,
|
|
19
22
|
toHex
|
|
20
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-PASVUPKR.js";
|
|
21
24
|
|
|
22
25
|
// src/http/info-refs.ts
|
|
23
26
|
import git from "isomorphic-git";
|
|
@@ -66,18 +69,6 @@ function sideBandPackfile(packData) {
|
|
|
66
69
|
return concat(...parts);
|
|
67
70
|
}
|
|
68
71
|
|
|
69
|
-
// src/http/types.ts
|
|
70
|
-
var runStep = (hooks, label, fn) => hooks?.step ? hooks.step(label, fn) : fn();
|
|
71
|
-
function rawFs(repo) {
|
|
72
|
-
const fs = repo.fs;
|
|
73
|
-
if (!fs?.promises) {
|
|
74
|
-
throw new TypeError(
|
|
75
|
-
"This operation needs a promise fs (`fs.promises`) \u2014 node:fs and createGitFs both provide one"
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
|
-
return fs.promises;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
72
|
// src/http/info-refs.ts
|
|
82
73
|
async function listAllRefs(repo, defaultBranch) {
|
|
83
74
|
const [branches, tagNames] = await Promise.all([
|
|
@@ -114,7 +105,7 @@ async function handleInfoRefs(repo, options, hooks) {
|
|
|
114
105
|
() => listAllRefs(repo, options.defaultBranch)
|
|
115
106
|
);
|
|
116
107
|
const isUpload = service === "git-upload-pack";
|
|
117
|
-
const caps = isUpload ? `no-progress side-band-64k symref=HEAD:${headSymref} allow-tip-sha1-in-want allow-reachable-sha1-in-want agent=${agent}` : `delete-refs
|
|
108
|
+
const caps = isUpload ? `no-progress side-band-64k symref=HEAD:${headSymref} allow-tip-sha1-in-want allow-reachable-sha1-in-want agent=${agent}` : `report-status delete-refs side-band-64k agent=${agent}`;
|
|
118
109
|
const parts = [pktLine(`# service=${service}
|
|
119
110
|
`), FLUSH];
|
|
120
111
|
if (refs.length === 0) {
|
|
@@ -203,6 +194,45 @@ async function collectReachableOids(repo, startOids, hooks, haveOids, options) {
|
|
|
203
194
|
await Promise.all(startOids.map((oid) => visit(oid)));
|
|
204
195
|
return { oids: Array.from(seen), complete };
|
|
205
196
|
}
|
|
197
|
+
async function collectCommitAncestry(repo, startOids, stopAt) {
|
|
198
|
+
const commits = /* @__PURE__ */ new Set();
|
|
199
|
+
const boundary = /* @__PURE__ */ new Set();
|
|
200
|
+
const queued = /* @__PURE__ */ new Set();
|
|
201
|
+
let frontier = [...new Set(startOids)];
|
|
202
|
+
const BATCH = 50;
|
|
203
|
+
while (frontier.length > 0) {
|
|
204
|
+
const next = [];
|
|
205
|
+
for (let i = 0; i < frontier.length; i += BATCH) {
|
|
206
|
+
await Promise.all(
|
|
207
|
+
frontier.slice(i, i + BATCH).map(async (oid) => {
|
|
208
|
+
if (stopAt?.has(oid)) {
|
|
209
|
+
boundary.add(oid);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
const object = await git2.readObject({ ...repo, oid }).catch(() => null);
|
|
213
|
+
if (object?.type === "tag") {
|
|
214
|
+
const target = object.object.object;
|
|
215
|
+
if (!queued.has(target)) {
|
|
216
|
+
queued.add(target);
|
|
217
|
+
next.push(target);
|
|
218
|
+
}
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
if (object?.type !== "commit") return;
|
|
222
|
+
commits.add(oid);
|
|
223
|
+
for (const parent of object.object.parent) {
|
|
224
|
+
if (!queued.has(parent)) {
|
|
225
|
+
queued.add(parent);
|
|
226
|
+
next.push(parent);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
})
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
frontier = next;
|
|
233
|
+
}
|
|
234
|
+
return { commits, boundary };
|
|
235
|
+
}
|
|
206
236
|
|
|
207
237
|
// src/http/receive-pack.ts
|
|
208
238
|
import git4 from "isomorphic-git";
|
|
@@ -370,30 +400,6 @@ async function buildPackFromExistingPacks(repo, oids) {
|
|
|
370
400
|
}
|
|
371
401
|
|
|
372
402
|
// src/http/repack.ts
|
|
373
|
-
var PACK_OBJECT_TYPE_BITS = {
|
|
374
|
-
commit: 16,
|
|
375
|
-
tree: 32,
|
|
376
|
-
blob: 48,
|
|
377
|
-
tag: 64
|
|
378
|
-
};
|
|
379
|
-
function encodePackObjectHeader(type, length) {
|
|
380
|
-
const bytes = [];
|
|
381
|
-
let more = length > 15;
|
|
382
|
-
bytes.push(
|
|
383
|
-
(more ? 128 : 0) | PACK_OBJECT_TYPE_BITS[type] | length & 15
|
|
384
|
-
);
|
|
385
|
-
length >>>= 4;
|
|
386
|
-
while (more) {
|
|
387
|
-
more = length > 127;
|
|
388
|
-
bytes.push((more ? 128 : 0) | length & 127);
|
|
389
|
-
length >>>= 7;
|
|
390
|
-
}
|
|
391
|
-
return new Uint8Array(bytes);
|
|
392
|
-
}
|
|
393
|
-
async function hashGitObject(type, content) {
|
|
394
|
-
const prefix = encodeUtf8(`${type} ${content.length}\0`);
|
|
395
|
-
return sha1(concat(prefix, content));
|
|
396
|
-
}
|
|
397
403
|
async function readAndVerifyObjects(repo, oids) {
|
|
398
404
|
const objects = /* @__PURE__ */ new Map();
|
|
399
405
|
const BATCH_SIZE = 100;
|
|
@@ -422,36 +428,6 @@ async function readAndVerifyObjects(repo, oids) {
|
|
|
422
428
|
}
|
|
423
429
|
return objects;
|
|
424
430
|
}
|
|
425
|
-
async function buildVerifiedPack(oids, objects) {
|
|
426
|
-
const header = new Uint8Array(12);
|
|
427
|
-
const view = new DataView(header.buffer);
|
|
428
|
-
header.set(encodeUtf8("PACK"), 0);
|
|
429
|
-
view.setUint32(4, 2, false);
|
|
430
|
-
view.setUint32(8, oids.length, false);
|
|
431
|
-
const chunks = [header];
|
|
432
|
-
const BATCH_SIZE = 100;
|
|
433
|
-
for (let i = 0; i < oids.length; i += BATCH_SIZE) {
|
|
434
|
-
const batch = oids.slice(i, i + BATCH_SIZE);
|
|
435
|
-
const encoded = await Promise.all(
|
|
436
|
-
batch.map(async (oid) => {
|
|
437
|
-
const entry = objects.get(oid);
|
|
438
|
-
if (!entry) {
|
|
439
|
-
throw new Error(`repack: missing verified object for ${oid}`);
|
|
440
|
-
}
|
|
441
|
-
const objHeader = encodePackObjectHeader(
|
|
442
|
-
entry.type,
|
|
443
|
-
entry.content.length
|
|
444
|
-
);
|
|
445
|
-
const compressed = await deflate(entry.content);
|
|
446
|
-
return concat(objHeader, compressed);
|
|
447
|
-
})
|
|
448
|
-
);
|
|
449
|
-
chunks.push(...encoded);
|
|
450
|
-
}
|
|
451
|
-
const packBody = concat(...chunks);
|
|
452
|
-
const trailer = fromHex(await sha1(packBody));
|
|
453
|
-
return concat(packBody, trailer);
|
|
454
|
-
}
|
|
455
431
|
var REPACK_PACK_COUNT_THRESHOLD = 4;
|
|
456
432
|
async function readPackDirectory(repo) {
|
|
457
433
|
const entries = await rawFs(repo).readdir(`${repo.gitdir}/objects/pack`).catch(() => []);
|
|
@@ -485,17 +461,30 @@ async function repackRepository(repo, options, hooks) {
|
|
|
485
461
|
const { oids: reachable, complete } = await collectReachableOids(
|
|
486
462
|
repo,
|
|
487
463
|
tipOids,
|
|
488
|
-
hooks
|
|
464
|
+
hooks,
|
|
465
|
+
void 0,
|
|
466
|
+
{ verifyBlobs: false }
|
|
489
467
|
);
|
|
490
468
|
if (!complete || reachable.length === 0) return [];
|
|
491
469
|
const packDir = `${repo.gitdir}/objects/pack`;
|
|
492
|
-
const packBuffer = await buildPackFromExistingPacks(repo, reachable) ?? await
|
|
493
|
-
reachable
|
|
494
|
-
|
|
495
|
-
|
|
470
|
+
const packBuffer = await buildPackFromExistingPacks(repo, reachable) ?? await (async () => {
|
|
471
|
+
const objects = await readAndVerifyObjects(repo, reachable);
|
|
472
|
+
return encodePack(
|
|
473
|
+
reachable.map((oid) => {
|
|
474
|
+
const object = objects.get(oid);
|
|
475
|
+
if (!object) {
|
|
476
|
+
throw new Error(`repack: missing verified object for ${oid}`);
|
|
477
|
+
}
|
|
478
|
+
return object;
|
|
479
|
+
})
|
|
480
|
+
);
|
|
481
|
+
})();
|
|
496
482
|
await fsp.mkdir(packDir, { recursive: true });
|
|
497
|
-
const newBase =
|
|
483
|
+
const newBase = packBaseName(packBuffer);
|
|
498
484
|
const newPackFile = packFileName(newBase, "pack");
|
|
485
|
+
const preexisting = (await readPackDirectory(repo)).some(
|
|
486
|
+
(file) => file.base === newBase
|
|
487
|
+
);
|
|
499
488
|
await fsp.writeFile(`${packDir}/${newPackFile}`, packBuffer);
|
|
500
489
|
const { oids: indexedOids } = await git3.indexPack({
|
|
501
490
|
...repo,
|
|
@@ -504,12 +493,13 @@ async function repackRepository(repo, options, hooks) {
|
|
|
504
493
|
});
|
|
505
494
|
const indexed = new Set(indexedOids);
|
|
506
495
|
if (reachable.some((oid) => !indexed.has(oid))) {
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
496
|
+
if (!preexisting)
|
|
497
|
+
await Promise.all(
|
|
498
|
+
["pack", "idx"].map(
|
|
499
|
+
(kind) => fsp.unlink(`${packDir}/${packFileName(newBase, kind)}`).catch(() => {
|
|
500
|
+
})
|
|
501
|
+
)
|
|
502
|
+
);
|
|
513
503
|
throw new Error(
|
|
514
504
|
"repack: indexed pack is missing reachable objects \u2014 aborting"
|
|
515
505
|
);
|
|
@@ -517,12 +507,14 @@ async function repackRepository(repo, options, hooks) {
|
|
|
517
507
|
const stale = (await readPackDirectory(repo)).filter(
|
|
518
508
|
(file) => file.base !== newBase
|
|
519
509
|
);
|
|
520
|
-
|
|
521
|
-
|
|
510
|
+
const unlinkAll = (files) => Promise.all(
|
|
511
|
+
files.map(
|
|
522
512
|
(file) => fsp.unlink(`${packDir}/${file.name}`).catch(() => {
|
|
523
513
|
})
|
|
524
514
|
)
|
|
525
515
|
);
|
|
516
|
+
await unlinkAll(stale.filter((file) => file.kind === "idx"));
|
|
517
|
+
await unlinkAll(stale.filter((file) => file.kind !== "idx"));
|
|
526
518
|
return stale.map((file) => `objects/pack/${file.name}`);
|
|
527
519
|
} catch (err) {
|
|
528
520
|
hooks?.onWarn?.("repack failed (non-fatal)", err);
|
|
@@ -532,8 +524,10 @@ async function repackRepository(repo, options, hooks) {
|
|
|
532
524
|
|
|
533
525
|
// src/http/receive-pack.ts
|
|
534
526
|
var ZERO_OID = "0".repeat(40);
|
|
527
|
+
var OID_RE = /^[0-9a-f]{40}$/;
|
|
535
528
|
function parseReceivePackBody(body) {
|
|
536
529
|
const refUpdates = [];
|
|
530
|
+
let capabilities;
|
|
537
531
|
let pos = 0;
|
|
538
532
|
while (pos + 4 <= body.length) {
|
|
539
533
|
const len = Number.parseInt(decodeAscii(body.subarray(pos, pos + 4)), 16);
|
|
@@ -542,15 +536,21 @@ function parseReceivePackBody(body) {
|
|
|
542
536
|
break;
|
|
543
537
|
}
|
|
544
538
|
if (len < 4) break;
|
|
545
|
-
const line = decodeUtf8(body.subarray(pos + 4, pos + len)).replace(/\n$/, "").split("\0")
|
|
539
|
+
const [line, caps] = decodeUtf8(body.subarray(pos + 4, pos + len)).replace(/\n$/, "").split("\0");
|
|
540
|
+
if (capabilities === void 0) {
|
|
541
|
+
capabilities = (caps ?? "").split(" ").filter(Boolean);
|
|
542
|
+
}
|
|
546
543
|
pos += len;
|
|
547
|
-
const
|
|
548
|
-
|
|
549
|
-
if (oldOid && newOid && refName) {
|
|
544
|
+
const [oldOid, newOid, refName] = (line ?? "").split(" ");
|
|
545
|
+
if (oldOid && newOid && refName && OID_RE.test(oldOid) && OID_RE.test(newOid)) {
|
|
550
546
|
refUpdates.push({ oldOid, newOid, refName });
|
|
551
547
|
}
|
|
552
548
|
}
|
|
553
|
-
return {
|
|
549
|
+
return {
|
|
550
|
+
refUpdates,
|
|
551
|
+
packData: body.subarray(pos),
|
|
552
|
+
capabilities: capabilities ?? []
|
|
553
|
+
};
|
|
554
554
|
}
|
|
555
555
|
async function ensureRepoInitialized(repo, defaultBranch) {
|
|
556
556
|
const fsp = rawFs(repo);
|
|
@@ -568,17 +568,8 @@ async function indexIncomingPack(repo, packData, hooks) {
|
|
|
568
568
|
hooks,
|
|
569
569
|
"write + indexPack incoming pack",
|
|
570
570
|
async () => {
|
|
571
|
-
const
|
|
572
|
-
|
|
573
|
-
await fsp.mkdir(packDir, { recursive: true });
|
|
574
|
-
const packName2 = `recv-${Date.now()}`;
|
|
575
|
-
await fsp.writeFile(`${packDir}/${packName2}.pack`, packData);
|
|
576
|
-
await git4.indexPack({
|
|
577
|
-
...repo,
|
|
578
|
-
dir: packDir,
|
|
579
|
-
filepath: `${packName2}.pack`
|
|
580
|
-
});
|
|
581
|
-
return packName2;
|
|
571
|
+
const { base } = await storePack(repo, packData);
|
|
572
|
+
return base;
|
|
582
573
|
}
|
|
583
574
|
);
|
|
584
575
|
return [
|
|
@@ -611,11 +602,27 @@ async function collectExistingRefTips(repo) {
|
|
|
611
602
|
);
|
|
612
603
|
return new Set(oids.filter((oid) => oid !== null));
|
|
613
604
|
}
|
|
605
|
+
async function currentBranchRef(repo) {
|
|
606
|
+
return git4.currentBranch({ ...repo, fullname: true }).then((ref) => ref ?? null).catch(() => null);
|
|
607
|
+
}
|
|
614
608
|
async function validateRefUpdates(repo, refUpdates) {
|
|
609
|
+
const headRef = await currentBranchRef(repo);
|
|
610
|
+
const seen = /* @__PURE__ */ new Set();
|
|
615
611
|
return Promise.all(
|
|
616
|
-
refUpdates.map(
|
|
617
|
-
|
|
618
|
-
|
|
612
|
+
refUpdates.map(async ({ oldOid, newOid, refName }) => {
|
|
613
|
+
if (seen.has(refName)) {
|
|
614
|
+
return { refName, ok: false, reason: "duplicate ref update" };
|
|
615
|
+
}
|
|
616
|
+
seen.add(refName);
|
|
617
|
+
if (newOid === ZERO_OID && refName === headRef) {
|
|
618
|
+
return {
|
|
619
|
+
refName,
|
|
620
|
+
ok: false,
|
|
621
|
+
reason: "deletion of the current branch prohibited"
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
return await checkRefCas(repo, refName, oldOid) ?? { refName, ok: true };
|
|
625
|
+
})
|
|
619
626
|
);
|
|
620
627
|
}
|
|
621
628
|
async function applyRefUpdates(repo, refUpdates, hooks) {
|
|
@@ -630,6 +637,16 @@ async function applyRefUpdates(repo, refUpdates, hooks) {
|
|
|
630
637
|
await git4.deleteRef({ ...repo, ref: refName }).catch(() => {
|
|
631
638
|
});
|
|
632
639
|
} else {
|
|
640
|
+
if (refName.startsWith("refs/heads/")) {
|
|
641
|
+
const { type } = await git4.readObject({ ...repo, oid: newOid, format: "content" }).catch(() => ({ type: "missing" }));
|
|
642
|
+
if (type !== "commit") {
|
|
643
|
+
return {
|
|
644
|
+
refName,
|
|
645
|
+
ok: false,
|
|
646
|
+
reason: `branch must point at a commit, not a ${type}`
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
}
|
|
633
650
|
await git4.writeRef({
|
|
634
651
|
...repo,
|
|
635
652
|
ref: refName,
|
|
@@ -642,16 +659,21 @@ async function applyRefUpdates(repo, refUpdates, hooks) {
|
|
|
642
659
|
)
|
|
643
660
|
);
|
|
644
661
|
}
|
|
645
|
-
function receivePackResponse(results) {
|
|
646
|
-
const
|
|
647
|
-
|
|
662
|
+
function receivePackResponse(results, options) {
|
|
663
|
+
const capabilities = options?.capabilities ?? ["report-status"];
|
|
664
|
+
const report = capabilities.includes("report-status") ? concat(
|
|
665
|
+
pktLine(
|
|
666
|
+
options?.unpackError ? `unpack ${options.unpackError}
|
|
667
|
+
` : "unpack ok\n"
|
|
668
|
+
),
|
|
648
669
|
...results.map(
|
|
649
670
|
({ refName, ok, reason }) => pktLine(ok ? `ok ${refName}
|
|
650
671
|
` : `ng ${refName} ${reason}
|
|
651
672
|
`)
|
|
652
673
|
),
|
|
653
674
|
FLUSH
|
|
654
|
-
);
|
|
675
|
+
) : new Uint8Array();
|
|
676
|
+
const responseBody = capabilities.includes("side-band-64k") && report.length > 0 ? sideBandPackfile(report) : report;
|
|
655
677
|
return {
|
|
656
678
|
status: 200,
|
|
657
679
|
headers: {
|
|
@@ -667,11 +689,29 @@ async function applyReceivePack(repo, parsed, options, hooks) {
|
|
|
667
689
|
const acceptedUpdates = parsed.refUpdates.filter(
|
|
668
690
|
(_, index) => preflight[index]?.ok
|
|
669
691
|
);
|
|
670
|
-
const
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
692
|
+
const fsp = rawFs(repo);
|
|
693
|
+
const packDir = `${repo.gitdir}/objects/pack`;
|
|
694
|
+
const existingPacks = new Set(
|
|
695
|
+
await fsp.readdir(packDir).catch(() => [])
|
|
674
696
|
);
|
|
697
|
+
let incomingPackPaths;
|
|
698
|
+
try {
|
|
699
|
+
incomingPackPaths = await indexIncomingPack(
|
|
700
|
+
repo,
|
|
701
|
+
acceptedUpdates.some((update) => update.newOid !== ZERO_OID) ? parsed.packData : new Uint8Array(),
|
|
702
|
+
hooks
|
|
703
|
+
);
|
|
704
|
+
} catch (err) {
|
|
705
|
+
hooks?.onWarn?.("receive-pack: incoming pack rejected", err);
|
|
706
|
+
return {
|
|
707
|
+
results: preflight.map(
|
|
708
|
+
(result2) => result2.ok ? { ...result2, ok: false, reason: "unpacker error" } : result2
|
|
709
|
+
),
|
|
710
|
+
stalePackPaths: [],
|
|
711
|
+
unpackError: "index-pack failed"
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
const removableIncoming = incomingPackPaths.filter((path) => !existingPacks.has(path.slice(packDir.length + 1))).sort((a, b) => Number(b.endsWith(".idx")) - Number(a.endsWith(".idx")));
|
|
675
715
|
const newTips = acceptedUpdates.filter((update) => update.newOid !== ZERO_OID).map((update) => update.newOid);
|
|
676
716
|
if (newTips.length > 0) {
|
|
677
717
|
const haveOids = await collectExistingRefTips(repo);
|
|
@@ -685,15 +725,13 @@ async function applyReceivePack(repo, parsed, options, hooks) {
|
|
|
685
725
|
haveOids
|
|
686
726
|
);
|
|
687
727
|
if (!complete) {
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
)
|
|
693
|
-
);
|
|
728
|
+
for (const filepath of removableIncoming) {
|
|
729
|
+
await fsp.unlink(filepath).catch(() => {
|
|
730
|
+
});
|
|
731
|
+
}
|
|
694
732
|
return {
|
|
695
733
|
results: preflight.map(
|
|
696
|
-
(
|
|
734
|
+
(result2) => result2.ok ? { ...result2, ok: false, reason: "incomplete object graph" } : result2
|
|
697
735
|
),
|
|
698
736
|
stalePackPaths: []
|
|
699
737
|
};
|
|
@@ -701,12 +739,12 @@ async function applyReceivePack(repo, parsed, options, hooks) {
|
|
|
701
739
|
}
|
|
702
740
|
const applied = await applyRefUpdates(repo, acceptedUpdates, hooks);
|
|
703
741
|
const results = preflight.map(
|
|
704
|
-
(
|
|
742
|
+
(result2, index) => result2.ok ? applied.find(
|
|
705
743
|
(entry) => entry.refName === parsed.refUpdates[index]?.refName
|
|
706
|
-
) ??
|
|
744
|
+
) ?? result2 : result2
|
|
707
745
|
);
|
|
708
746
|
const repackOptions = options.repack;
|
|
709
|
-
const stalePackPaths = repackOptions === false || !results.some((
|
|
747
|
+
const stalePackPaths = repackOptions === false || !results.some((result2) => result2.ok) ? [] : await runStep(
|
|
710
748
|
hooks,
|
|
711
749
|
"repack",
|
|
712
750
|
() => repackRepository(repo, repackOptions, hooks)
|
|
@@ -717,87 +755,161 @@ async function applyReceivePack(repo, parsed, options, hooks) {
|
|
|
717
755
|
// src/http/upload-pack.ts
|
|
718
756
|
import git5 from "isomorphic-git";
|
|
719
757
|
var ZERO_OID2 = "0".repeat(40);
|
|
720
|
-
|
|
721
|
-
|
|
758
|
+
var OID_RE2 = /^[0-9a-f]{40}$/;
|
|
759
|
+
var LOOSE_FANOUT_RE = /^[0-9a-f]{2}$/;
|
|
760
|
+
function result(body) {
|
|
761
|
+
return {
|
|
762
|
+
status: 200,
|
|
763
|
+
headers: {
|
|
764
|
+
"Content-Type": "application/x-git-upload-pack-result",
|
|
765
|
+
"Cache-Control": "no-cache"
|
|
766
|
+
},
|
|
767
|
+
body
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
function errorResult(message) {
|
|
771
|
+
return result(pktLine(`ERR ${message}
|
|
772
|
+
`));
|
|
773
|
+
}
|
|
774
|
+
function parseUploadPackRequest(body) {
|
|
722
775
|
const wants = [];
|
|
723
776
|
const haves = [];
|
|
724
777
|
let done = false;
|
|
725
|
-
|
|
778
|
+
let invalid = false;
|
|
779
|
+
let capabilities;
|
|
780
|
+
for (const line of parsePktLines(body)) {
|
|
726
781
|
if (!line) continue;
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
782
|
+
const text = line.replace(/\n$/, "");
|
|
783
|
+
if (text.startsWith("want ")) {
|
|
784
|
+
const [oid, ...caps] = text.slice(5).split(" ");
|
|
785
|
+
if (capabilities === void 0) capabilities = caps.filter(Boolean);
|
|
786
|
+
if (oid !== void 0 && OID_RE2.test(oid)) wants.push(oid);
|
|
787
|
+
else invalid = true;
|
|
788
|
+
} else if (text.startsWith("have ")) {
|
|
789
|
+
const oid = text.slice(5).trim();
|
|
790
|
+
if (OID_RE2.test(oid)) {
|
|
791
|
+
if (oid !== ZERO_OID2) haves.push(oid);
|
|
792
|
+
} else {
|
|
793
|
+
invalid = true;
|
|
734
794
|
}
|
|
735
|
-
}
|
|
736
|
-
if (line.startsWith("done")) {
|
|
795
|
+
} else if (text === "done") {
|
|
737
796
|
done = true;
|
|
738
797
|
}
|
|
739
798
|
}
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
799
|
+
return {
|
|
800
|
+
wants: [...new Set(wants)],
|
|
801
|
+
haves: [...new Set(haves)],
|
|
802
|
+
done,
|
|
803
|
+
capabilities: capabilities ?? [],
|
|
804
|
+
invalid
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
async function servableSinglePack(repo, wants, hooks) {
|
|
808
|
+
const fsp = rawFs(repo);
|
|
809
|
+
const objectsDir = `${repo.gitdir}/objects`;
|
|
810
|
+
const [objectEntries, packEntries] = await runStep(
|
|
811
|
+
hooks,
|
|
812
|
+
"readdir objects + objects/pack",
|
|
813
|
+
() => Promise.all([
|
|
814
|
+
fsp.readdir(objectsDir).catch(() => []),
|
|
815
|
+
fsp.readdir(`${objectsDir}/pack`).catch(() => [])
|
|
816
|
+
])
|
|
817
|
+
);
|
|
818
|
+
if (objectEntries.some((name) => LOOSE_FANOUT_RE.test(name))) return null;
|
|
819
|
+
const packs = parsePackDirectory(packEntries).filter(
|
|
820
|
+
(file) => file.kind === "pack"
|
|
821
|
+
);
|
|
822
|
+
const pack = packs[0];
|
|
823
|
+
if (packs.length !== 1 || pack === void 0) return null;
|
|
824
|
+
const [packData, idxData] = await runStep(
|
|
825
|
+
hooks,
|
|
826
|
+
"read consolidated pack (fast path)",
|
|
827
|
+
() => Promise.all([
|
|
828
|
+
fsp.readFile(`${objectsDir}/pack/${pack.name}`),
|
|
829
|
+
fsp.readFile(`${objectsDir}/pack/${packFileName(pack.base, "idx")}`)
|
|
830
|
+
]).catch(() => [null, null])
|
|
831
|
+
);
|
|
832
|
+
if (!(packData instanceof Uint8Array) || !(idxData instanceof Uint8Array)) {
|
|
833
|
+
return null;
|
|
746
834
|
}
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
835
|
+
const indexed = readPackIndex(idxData);
|
|
836
|
+
if (!indexed) return null;
|
|
837
|
+
const oids = new Set(indexed.map((entry) => entry.oid));
|
|
838
|
+
return wants.every((oid) => oids.has(oid)) ? packData : null;
|
|
839
|
+
}
|
|
840
|
+
async function handleUploadPack(repo, body, options, hooks) {
|
|
841
|
+
const request = parseUploadPackRequest(body);
|
|
842
|
+
if (request.invalid) {
|
|
843
|
+
return errorResult("upload-pack: protocol error, malformed object id");
|
|
753
844
|
}
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
845
|
+
const { wants, haves, done, capabilities } = request;
|
|
846
|
+
const frame = (pack) => capabilities.includes("side-band-64k") || capabilities.includes("side-band") ? sideBandPackfile(pack) : concat(pack);
|
|
847
|
+
if (wants.length === 0 || haves.length > 0 && !done) {
|
|
848
|
+
return result(concat(pktLine("NAK\n")));
|
|
849
|
+
}
|
|
850
|
+
const { refs } = await runStep(
|
|
851
|
+
hooks,
|
|
852
|
+
"listAllRefs",
|
|
853
|
+
() => listAllRefs(repo, "HEAD")
|
|
854
|
+
);
|
|
855
|
+
const tips = new Set(refs.map((ref) => ref.oid));
|
|
856
|
+
const untipped = wants.filter((oid) => !tips.has(oid));
|
|
857
|
+
if (untipped.length > 0) {
|
|
858
|
+
const { commits } = await runStep(
|
|
757
859
|
hooks,
|
|
758
|
-
"
|
|
759
|
-
() =>
|
|
860
|
+
"verify wants reachable",
|
|
861
|
+
() => collectCommitAncestry(repo, [...tips])
|
|
760
862
|
);
|
|
761
|
-
const
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
const packData = await runStep(
|
|
765
|
-
hooks,
|
|
766
|
-
"read consolidated pack (fast path)",
|
|
767
|
-
() => rawFs(repo).readFile(`${packDirPath}/${packName}`)
|
|
768
|
-
);
|
|
769
|
-
const bytes = packData instanceof Uint8Array ? packData : new TextEncoder().encode(packData);
|
|
770
|
-
return {
|
|
771
|
-
status: 200,
|
|
772
|
-
headers: {
|
|
773
|
-
"Content-Type": "application/x-git-upload-pack-result",
|
|
774
|
-
"Cache-Control": "no-cache"
|
|
775
|
-
},
|
|
776
|
-
body: concat(pktLine("NAK\n"), sideBandPackfile(bytes))
|
|
777
|
-
};
|
|
863
|
+
const missing = untipped.find((oid) => !commits.has(oid));
|
|
864
|
+
if (missing !== void 0) {
|
|
865
|
+
return errorResult(`upload-pack: not our ref ${missing}`);
|
|
778
866
|
}
|
|
779
867
|
}
|
|
868
|
+
if (haves.length === 0) {
|
|
869
|
+
const pack = await servableSinglePack(repo, wants, hooks);
|
|
870
|
+
if (pack) return result(concat(pktLine("NAK\n"), frame(pack)));
|
|
871
|
+
}
|
|
780
872
|
if (options?.beforeWalk) {
|
|
781
873
|
await runStep(hooks, "beforeWalk", options.beforeWalk);
|
|
782
874
|
}
|
|
783
875
|
let haveSet;
|
|
784
876
|
if (haves.length > 0) {
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
|
|
877
|
+
haveSet = await runStep(hooks, "have boundary", async () => {
|
|
878
|
+
const { commits: clientCommits } = await collectCommitAncestry(
|
|
879
|
+
repo,
|
|
880
|
+
haves
|
|
881
|
+
);
|
|
882
|
+
const { boundary } = await collectCommitAncestry(
|
|
883
|
+
repo,
|
|
884
|
+
wants,
|
|
885
|
+
clientCommits
|
|
886
|
+
);
|
|
887
|
+
const boundaryTrees = await Promise.all(
|
|
888
|
+
[...boundary].map(async (oid) => {
|
|
889
|
+
const { commit } = await git5.readCommit({ ...repo, oid });
|
|
890
|
+
return commit.tree;
|
|
891
|
+
})
|
|
892
|
+
);
|
|
893
|
+
const { oids: boundaryObjects } = await collectReachableOids(
|
|
894
|
+
repo,
|
|
895
|
+
boundaryTrees,
|
|
896
|
+
hooks,
|
|
897
|
+
void 0,
|
|
898
|
+
{ verifyBlobs: false }
|
|
899
|
+
);
|
|
900
|
+
return /* @__PURE__ */ new Set([...clientCommits, ...boundaryObjects]);
|
|
901
|
+
});
|
|
793
902
|
}
|
|
794
|
-
const { oids: wantOids } = await runStep(
|
|
903
|
+
const { oids: wantOids, complete } = await runStep(
|
|
795
904
|
hooks,
|
|
796
905
|
"collectReachableOids(wants)",
|
|
797
906
|
() => collectReachableOids(repo, wants, hooks, haveSet, {
|
|
798
907
|
verifyBlobs: false
|
|
799
908
|
})
|
|
800
909
|
);
|
|
910
|
+
if (!complete) {
|
|
911
|
+
return errorResult("upload-pack: repository is missing objects");
|
|
912
|
+
}
|
|
801
913
|
const oids = haveSet ? wantOids.filter((oid) => !haveSet.has(oid)) : wantOids;
|
|
802
914
|
const reused = await runStep(
|
|
803
915
|
hooks,
|
|
@@ -808,14 +920,7 @@ async function handleUploadPack(repo, body, options, hooks) {
|
|
|
808
920
|
const { packfile } = await git5.packObjects({ ...repo, oids });
|
|
809
921
|
return packfile instanceof Uint8Array ? packfile : new Uint8Array(packfile ?? []);
|
|
810
922
|
});
|
|
811
|
-
return
|
|
812
|
-
status: 200,
|
|
813
|
-
headers: {
|
|
814
|
-
"Content-Type": "application/x-git-upload-pack-result",
|
|
815
|
-
"Cache-Control": "no-cache"
|
|
816
|
-
},
|
|
817
|
-
body: concat(pktLine("NAK\n"), sideBandPackfile(packBytes))
|
|
818
|
-
};
|
|
923
|
+
return result(concat(pktLine("NAK\n"), frame(packBytes)));
|
|
819
924
|
}
|
|
820
925
|
export {
|
|
821
926
|
FLUSH,
|