git-fs-s3 0.7.0 → 0.9.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 +10 -6
- 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 +299 -192
- package/dist/http.js.map +1 -1
- package/dist/index.js +79 -76
- package/dist/index.js.map +1 -1
- package/dist/ops.js +282 -253
- package/dist/ops.js.map +1 -1
- package/dist/types/edge-utils.d.ts +2 -0
- package/dist/types/errors.d.ts +0 -1
- package/dist/types/http/index.d.ts +2 -2
- package/dist/types/http/pack-reuse.d.ts +2 -1
- 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 +7 -3
- package/dist/types/ops/file-history.d.ts +4 -18
- package/dist/types/ops/history.d.ts +30 -43
- package/dist/types/ops/index.d.ts +4 -3
- package/dist/types/ops/last-commit.d.ts +2 -8
- package/dist/types/ops/packed-writes.d.ts +9 -0
- package/dist/types/ops/tree.d.ts +9 -1
- package/dist/types/ops/types.d.ts +10 -16
- 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([
|
|
@@ -97,11 +88,12 @@ async function listAllRefs(repo, defaultBranch) {
|
|
|
97
88
|
oid: b.commit
|
|
98
89
|
}));
|
|
99
90
|
const defaultRef = branches.find((b) => b.isDefault);
|
|
100
|
-
const
|
|
101
|
-
const
|
|
91
|
+
const current = defaultRef ? null : await git.currentBranch({ ...repo, fullname: true }).catch(() => void 0);
|
|
92
|
+
const headSymref = defaultRef ? `refs/heads/${defaultRef.name}` : current ?? `refs/heads/${defaultBranch}`;
|
|
93
|
+
const headOid = branchRefs.find((r) => r.name === headSymref)?.oid ?? (defaultRef || current ? null : await git.resolveRef({ ...repo, ref: "HEAD" }).catch(() => null));
|
|
102
94
|
const refs = [];
|
|
103
95
|
if (headOid) refs.push({ name: "HEAD", oid: headOid });
|
|
104
|
-
|
|
96
|
+
refs.push(...branchRefs);
|
|
105
97
|
for (const r of tagRefs) if (r) refs.push(r);
|
|
106
98
|
return { refs, headSymref };
|
|
107
99
|
}
|
|
@@ -114,7 +106,7 @@ async function handleInfoRefs(repo, options, hooks) {
|
|
|
114
106
|
() => listAllRefs(repo, options.defaultBranch)
|
|
115
107
|
);
|
|
116
108
|
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
|
|
109
|
+
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
110
|
const parts = [pktLine(`# service=${service}
|
|
119
111
|
`), FLUSH];
|
|
120
112
|
if (refs.length === 0) {
|
|
@@ -203,6 +195,45 @@ async function collectReachableOids(repo, startOids, hooks, haveOids, options) {
|
|
|
203
195
|
await Promise.all(startOids.map((oid) => visit(oid)));
|
|
204
196
|
return { oids: Array.from(seen), complete };
|
|
205
197
|
}
|
|
198
|
+
async function collectCommitAncestry(repo, startOids, stopAt) {
|
|
199
|
+
const commits = /* @__PURE__ */ new Set();
|
|
200
|
+
const boundary = /* @__PURE__ */ new Set();
|
|
201
|
+
const queued = /* @__PURE__ */ new Set();
|
|
202
|
+
let frontier = [...new Set(startOids)];
|
|
203
|
+
const BATCH = 50;
|
|
204
|
+
while (frontier.length > 0) {
|
|
205
|
+
const next = [];
|
|
206
|
+
for (let i = 0; i < frontier.length; i += BATCH) {
|
|
207
|
+
await Promise.all(
|
|
208
|
+
frontier.slice(i, i + BATCH).map(async (oid) => {
|
|
209
|
+
if (stopAt?.has(oid)) {
|
|
210
|
+
boundary.add(oid);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const object = await git2.readObject({ ...repo, oid }).catch(() => null);
|
|
214
|
+
if (object?.type === "tag") {
|
|
215
|
+
const target = object.object.object;
|
|
216
|
+
if (!queued.has(target)) {
|
|
217
|
+
queued.add(target);
|
|
218
|
+
next.push(target);
|
|
219
|
+
}
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
if (object?.type !== "commit") return;
|
|
223
|
+
commits.add(oid);
|
|
224
|
+
for (const parent of object.object.parent) {
|
|
225
|
+
if (!queued.has(parent)) {
|
|
226
|
+
queued.add(parent);
|
|
227
|
+
next.push(parent);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
})
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
frontier = next;
|
|
234
|
+
}
|
|
235
|
+
return { commits, boundary };
|
|
236
|
+
}
|
|
206
237
|
|
|
207
238
|
// src/http/receive-pack.ts
|
|
208
239
|
import git4 from "isomorphic-git";
|
|
@@ -370,30 +401,6 @@ async function buildPackFromExistingPacks(repo, oids) {
|
|
|
370
401
|
}
|
|
371
402
|
|
|
372
403
|
// 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
404
|
async function readAndVerifyObjects(repo, oids) {
|
|
398
405
|
const objects = /* @__PURE__ */ new Map();
|
|
399
406
|
const BATCH_SIZE = 100;
|
|
@@ -422,36 +429,6 @@ async function readAndVerifyObjects(repo, oids) {
|
|
|
422
429
|
}
|
|
423
430
|
return objects;
|
|
424
431
|
}
|
|
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
432
|
var REPACK_PACK_COUNT_THRESHOLD = 4;
|
|
456
433
|
async function readPackDirectory(repo) {
|
|
457
434
|
const entries = await rawFs(repo).readdir(`${repo.gitdir}/objects/pack`).catch(() => []);
|
|
@@ -485,17 +462,30 @@ async function repackRepository(repo, options, hooks) {
|
|
|
485
462
|
const { oids: reachable, complete } = await collectReachableOids(
|
|
486
463
|
repo,
|
|
487
464
|
tipOids,
|
|
488
|
-
hooks
|
|
465
|
+
hooks,
|
|
466
|
+
void 0,
|
|
467
|
+
{ verifyBlobs: false }
|
|
489
468
|
);
|
|
490
469
|
if (!complete || reachable.length === 0) return [];
|
|
491
470
|
const packDir = `${repo.gitdir}/objects/pack`;
|
|
492
|
-
const packBuffer = await buildPackFromExistingPacks(repo, reachable) ?? await
|
|
493
|
-
reachable
|
|
494
|
-
|
|
495
|
-
|
|
471
|
+
const packBuffer = await buildPackFromExistingPacks(repo, reachable) ?? await (async () => {
|
|
472
|
+
const objects = await readAndVerifyObjects(repo, reachable);
|
|
473
|
+
return encodePack(
|
|
474
|
+
reachable.map((oid) => {
|
|
475
|
+
const object = objects.get(oid);
|
|
476
|
+
if (!object) {
|
|
477
|
+
throw new Error(`repack: missing verified object for ${oid}`);
|
|
478
|
+
}
|
|
479
|
+
return object;
|
|
480
|
+
})
|
|
481
|
+
);
|
|
482
|
+
})();
|
|
496
483
|
await fsp.mkdir(packDir, { recursive: true });
|
|
497
|
-
const newBase =
|
|
484
|
+
const newBase = packBaseName(packBuffer);
|
|
498
485
|
const newPackFile = packFileName(newBase, "pack");
|
|
486
|
+
const preexisting = (await readPackDirectory(repo)).some(
|
|
487
|
+
(file) => file.base === newBase
|
|
488
|
+
);
|
|
499
489
|
await fsp.writeFile(`${packDir}/${newPackFile}`, packBuffer);
|
|
500
490
|
const { oids: indexedOids } = await git3.indexPack({
|
|
501
491
|
...repo,
|
|
@@ -504,12 +494,13 @@ async function repackRepository(repo, options, hooks) {
|
|
|
504
494
|
});
|
|
505
495
|
const indexed = new Set(indexedOids);
|
|
506
496
|
if (reachable.some((oid) => !indexed.has(oid))) {
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
497
|
+
if (!preexisting)
|
|
498
|
+
await Promise.all(
|
|
499
|
+
["pack", "idx"].map(
|
|
500
|
+
(kind) => fsp.unlink(`${packDir}/${packFileName(newBase, kind)}`).catch(() => {
|
|
501
|
+
})
|
|
502
|
+
)
|
|
503
|
+
);
|
|
513
504
|
throw new Error(
|
|
514
505
|
"repack: indexed pack is missing reachable objects \u2014 aborting"
|
|
515
506
|
);
|
|
@@ -517,12 +508,14 @@ async function repackRepository(repo, options, hooks) {
|
|
|
517
508
|
const stale = (await readPackDirectory(repo)).filter(
|
|
518
509
|
(file) => file.base !== newBase
|
|
519
510
|
);
|
|
520
|
-
|
|
521
|
-
|
|
511
|
+
const unlinkAll = (files) => Promise.all(
|
|
512
|
+
files.map(
|
|
522
513
|
(file) => fsp.unlink(`${packDir}/${file.name}`).catch(() => {
|
|
523
514
|
})
|
|
524
515
|
)
|
|
525
516
|
);
|
|
517
|
+
await unlinkAll(stale.filter((file) => file.kind === "idx"));
|
|
518
|
+
await unlinkAll(stale.filter((file) => file.kind !== "idx"));
|
|
526
519
|
return stale.map((file) => `objects/pack/${file.name}`);
|
|
527
520
|
} catch (err) {
|
|
528
521
|
hooks?.onWarn?.("repack failed (non-fatal)", err);
|
|
@@ -532,8 +525,10 @@ async function repackRepository(repo, options, hooks) {
|
|
|
532
525
|
|
|
533
526
|
// src/http/receive-pack.ts
|
|
534
527
|
var ZERO_OID = "0".repeat(40);
|
|
528
|
+
var OID_RE = /^[0-9a-f]{40}$/;
|
|
535
529
|
function parseReceivePackBody(body) {
|
|
536
530
|
const refUpdates = [];
|
|
531
|
+
let capabilities;
|
|
537
532
|
let pos = 0;
|
|
538
533
|
while (pos + 4 <= body.length) {
|
|
539
534
|
const len = Number.parseInt(decodeAscii(body.subarray(pos, pos + 4)), 16);
|
|
@@ -542,15 +537,21 @@ function parseReceivePackBody(body) {
|
|
|
542
537
|
break;
|
|
543
538
|
}
|
|
544
539
|
if (len < 4) break;
|
|
545
|
-
const line = decodeUtf8(body.subarray(pos + 4, pos + len)).replace(/\n$/, "").split("\0")
|
|
540
|
+
const [line, caps] = decodeUtf8(body.subarray(pos + 4, pos + len)).replace(/\n$/, "").split("\0");
|
|
541
|
+
if (capabilities === void 0) {
|
|
542
|
+
capabilities = (caps ?? "").split(" ").filter(Boolean);
|
|
543
|
+
}
|
|
546
544
|
pos += len;
|
|
547
|
-
const
|
|
548
|
-
|
|
549
|
-
if (oldOid && newOid && refName) {
|
|
545
|
+
const [oldOid, newOid, refName] = (line ?? "").split(" ");
|
|
546
|
+
if (oldOid && newOid && refName && OID_RE.test(oldOid) && OID_RE.test(newOid)) {
|
|
550
547
|
refUpdates.push({ oldOid, newOid, refName });
|
|
551
548
|
}
|
|
552
549
|
}
|
|
553
|
-
return {
|
|
550
|
+
return {
|
|
551
|
+
refUpdates,
|
|
552
|
+
packData: body.subarray(pos),
|
|
553
|
+
capabilities: capabilities ?? []
|
|
554
|
+
};
|
|
554
555
|
}
|
|
555
556
|
async function ensureRepoInitialized(repo, defaultBranch) {
|
|
556
557
|
const fsp = rawFs(repo);
|
|
@@ -568,17 +569,8 @@ async function indexIncomingPack(repo, packData, hooks) {
|
|
|
568
569
|
hooks,
|
|
569
570
|
"write + indexPack incoming pack",
|
|
570
571
|
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;
|
|
572
|
+
const { base } = await storePack(repo, packData);
|
|
573
|
+
return base;
|
|
582
574
|
}
|
|
583
575
|
);
|
|
584
576
|
return [
|
|
@@ -611,11 +603,27 @@ async function collectExistingRefTips(repo) {
|
|
|
611
603
|
);
|
|
612
604
|
return new Set(oids.filter((oid) => oid !== null));
|
|
613
605
|
}
|
|
606
|
+
async function currentBranchRef(repo) {
|
|
607
|
+
return git4.currentBranch({ ...repo, fullname: true }).then((ref) => ref ?? null).catch(() => null);
|
|
608
|
+
}
|
|
614
609
|
async function validateRefUpdates(repo, refUpdates) {
|
|
610
|
+
const headRef = await currentBranchRef(repo);
|
|
611
|
+
const seen = /* @__PURE__ */ new Set();
|
|
615
612
|
return Promise.all(
|
|
616
|
-
refUpdates.map(
|
|
617
|
-
|
|
618
|
-
|
|
613
|
+
refUpdates.map(async ({ oldOid, newOid, refName }) => {
|
|
614
|
+
if (seen.has(refName)) {
|
|
615
|
+
return { refName, ok: false, reason: "duplicate ref update" };
|
|
616
|
+
}
|
|
617
|
+
seen.add(refName);
|
|
618
|
+
if (newOid === ZERO_OID && refName === headRef) {
|
|
619
|
+
return {
|
|
620
|
+
refName,
|
|
621
|
+
ok: false,
|
|
622
|
+
reason: "deletion of the current branch prohibited"
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
return await checkRefCas(repo, refName, oldOid) ?? { refName, ok: true };
|
|
626
|
+
})
|
|
619
627
|
);
|
|
620
628
|
}
|
|
621
629
|
async function applyRefUpdates(repo, refUpdates, hooks) {
|
|
@@ -630,6 +638,16 @@ async function applyRefUpdates(repo, refUpdates, hooks) {
|
|
|
630
638
|
await git4.deleteRef({ ...repo, ref: refName }).catch(() => {
|
|
631
639
|
});
|
|
632
640
|
} else {
|
|
641
|
+
if (refName.startsWith("refs/heads/")) {
|
|
642
|
+
const { type } = await git4.readObject({ ...repo, oid: newOid, format: "content" }).catch(() => ({ type: "missing" }));
|
|
643
|
+
if (type !== "commit") {
|
|
644
|
+
return {
|
|
645
|
+
refName,
|
|
646
|
+
ok: false,
|
|
647
|
+
reason: `branch must point at a commit, not a ${type}`
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
}
|
|
633
651
|
await git4.writeRef({
|
|
634
652
|
...repo,
|
|
635
653
|
ref: refName,
|
|
@@ -642,16 +660,21 @@ async function applyRefUpdates(repo, refUpdates, hooks) {
|
|
|
642
660
|
)
|
|
643
661
|
);
|
|
644
662
|
}
|
|
645
|
-
function receivePackResponse(results) {
|
|
646
|
-
const
|
|
647
|
-
|
|
663
|
+
function receivePackResponse(results, options) {
|
|
664
|
+
const capabilities = options?.capabilities ?? ["report-status"];
|
|
665
|
+
const report = capabilities.includes("report-status") ? concat(
|
|
666
|
+
pktLine(
|
|
667
|
+
options?.unpackError ? `unpack ${options.unpackError}
|
|
668
|
+
` : "unpack ok\n"
|
|
669
|
+
),
|
|
648
670
|
...results.map(
|
|
649
671
|
({ refName, ok, reason }) => pktLine(ok ? `ok ${refName}
|
|
650
672
|
` : `ng ${refName} ${reason}
|
|
651
673
|
`)
|
|
652
674
|
),
|
|
653
675
|
FLUSH
|
|
654
|
-
);
|
|
676
|
+
) : new Uint8Array();
|
|
677
|
+
const responseBody = capabilities.includes("side-band-64k") && report.length > 0 ? sideBandPackfile(report) : report;
|
|
655
678
|
return {
|
|
656
679
|
status: 200,
|
|
657
680
|
headers: {
|
|
@@ -667,11 +690,29 @@ async function applyReceivePack(repo, parsed, options, hooks) {
|
|
|
667
690
|
const acceptedUpdates = parsed.refUpdates.filter(
|
|
668
691
|
(_, index) => preflight[index]?.ok
|
|
669
692
|
);
|
|
670
|
-
const
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
693
|
+
const fsp = rawFs(repo);
|
|
694
|
+
const packDir = `${repo.gitdir}/objects/pack`;
|
|
695
|
+
const existingPacks = new Set(
|
|
696
|
+
await fsp.readdir(packDir).catch(() => [])
|
|
674
697
|
);
|
|
698
|
+
let incomingPackPaths;
|
|
699
|
+
try {
|
|
700
|
+
incomingPackPaths = await indexIncomingPack(
|
|
701
|
+
repo,
|
|
702
|
+
acceptedUpdates.some((update) => update.newOid !== ZERO_OID) ? parsed.packData : new Uint8Array(),
|
|
703
|
+
hooks
|
|
704
|
+
);
|
|
705
|
+
} catch (err) {
|
|
706
|
+
hooks?.onWarn?.("receive-pack: incoming pack rejected", err);
|
|
707
|
+
return {
|
|
708
|
+
results: preflight.map(
|
|
709
|
+
(result2) => result2.ok ? { ...result2, ok: false, reason: "unpacker error" } : result2
|
|
710
|
+
),
|
|
711
|
+
stalePackPaths: [],
|
|
712
|
+
unpackError: "index-pack failed"
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
const removableIncoming = incomingPackPaths.filter((path) => !existingPacks.has(path.slice(packDir.length + 1))).sort((a, b) => Number(b.endsWith(".idx")) - Number(a.endsWith(".idx")));
|
|
675
716
|
const newTips = acceptedUpdates.filter((update) => update.newOid !== ZERO_OID).map((update) => update.newOid);
|
|
676
717
|
if (newTips.length > 0) {
|
|
677
718
|
const haveOids = await collectExistingRefTips(repo);
|
|
@@ -685,15 +726,13 @@ async function applyReceivePack(repo, parsed, options, hooks) {
|
|
|
685
726
|
haveOids
|
|
686
727
|
);
|
|
687
728
|
if (!complete) {
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
)
|
|
693
|
-
);
|
|
729
|
+
for (const filepath of removableIncoming) {
|
|
730
|
+
await fsp.unlink(filepath).catch(() => {
|
|
731
|
+
});
|
|
732
|
+
}
|
|
694
733
|
return {
|
|
695
734
|
results: preflight.map(
|
|
696
|
-
(
|
|
735
|
+
(result2) => result2.ok ? { ...result2, ok: false, reason: "incomplete object graph" } : result2
|
|
697
736
|
),
|
|
698
737
|
stalePackPaths: []
|
|
699
738
|
};
|
|
@@ -701,12 +740,12 @@ async function applyReceivePack(repo, parsed, options, hooks) {
|
|
|
701
740
|
}
|
|
702
741
|
const applied = await applyRefUpdates(repo, acceptedUpdates, hooks);
|
|
703
742
|
const results = preflight.map(
|
|
704
|
-
(
|
|
743
|
+
(result2, index) => result2.ok ? applied.find(
|
|
705
744
|
(entry) => entry.refName === parsed.refUpdates[index]?.refName
|
|
706
|
-
) ??
|
|
745
|
+
) ?? result2 : result2
|
|
707
746
|
);
|
|
708
747
|
const repackOptions = options.repack;
|
|
709
|
-
const stalePackPaths = repackOptions === false || !results.some((
|
|
748
|
+
const stalePackPaths = repackOptions === false || !results.some((result2) => result2.ok) ? [] : await runStep(
|
|
710
749
|
hooks,
|
|
711
750
|
"repack",
|
|
712
751
|
() => repackRepository(repo, repackOptions, hooks)
|
|
@@ -717,87 +756,161 @@ async function applyReceivePack(repo, parsed, options, hooks) {
|
|
|
717
756
|
// src/http/upload-pack.ts
|
|
718
757
|
import git5 from "isomorphic-git";
|
|
719
758
|
var ZERO_OID2 = "0".repeat(40);
|
|
720
|
-
|
|
721
|
-
|
|
759
|
+
var OID_RE2 = /^[0-9a-f]{40}$/;
|
|
760
|
+
var LOOSE_FANOUT_RE = /^[0-9a-f]{2}$/;
|
|
761
|
+
function result(body) {
|
|
762
|
+
return {
|
|
763
|
+
status: 200,
|
|
764
|
+
headers: {
|
|
765
|
+
"Content-Type": "application/x-git-upload-pack-result",
|
|
766
|
+
"Cache-Control": "no-cache"
|
|
767
|
+
},
|
|
768
|
+
body
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
function errorResult(message) {
|
|
772
|
+
return result(pktLine(`ERR ${message}
|
|
773
|
+
`));
|
|
774
|
+
}
|
|
775
|
+
function parseUploadPackRequest(body) {
|
|
722
776
|
const wants = [];
|
|
723
777
|
const haves = [];
|
|
724
778
|
let done = false;
|
|
725
|
-
|
|
779
|
+
let invalid = false;
|
|
780
|
+
let capabilities;
|
|
781
|
+
for (const line of parsePktLines(body)) {
|
|
726
782
|
if (!line) continue;
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
783
|
+
const text = line.replace(/\n$/, "");
|
|
784
|
+
if (text.startsWith("want ")) {
|
|
785
|
+
const [oid, ...caps] = text.slice(5).split(" ");
|
|
786
|
+
if (capabilities === void 0) capabilities = caps.filter(Boolean);
|
|
787
|
+
if (oid !== void 0 && OID_RE2.test(oid)) wants.push(oid);
|
|
788
|
+
else invalid = true;
|
|
789
|
+
} else if (text.startsWith("have ")) {
|
|
790
|
+
const oid = text.slice(5).trim();
|
|
791
|
+
if (OID_RE2.test(oid)) {
|
|
792
|
+
if (oid !== ZERO_OID2) haves.push(oid);
|
|
793
|
+
} else {
|
|
794
|
+
invalid = true;
|
|
734
795
|
}
|
|
735
|
-
}
|
|
736
|
-
if (line.startsWith("done")) {
|
|
796
|
+
} else if (text === "done") {
|
|
737
797
|
done = true;
|
|
738
798
|
}
|
|
739
799
|
}
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
800
|
+
return {
|
|
801
|
+
wants: [...new Set(wants)],
|
|
802
|
+
haves: [...new Set(haves)],
|
|
803
|
+
done,
|
|
804
|
+
capabilities: capabilities ?? [],
|
|
805
|
+
invalid
|
|
806
|
+
};
|
|
807
|
+
}
|
|
808
|
+
async function servableSinglePack(repo, wants, hooks) {
|
|
809
|
+
const fsp = rawFs(repo);
|
|
810
|
+
const objectsDir = `${repo.gitdir}/objects`;
|
|
811
|
+
const [objectEntries, packEntries] = await runStep(
|
|
812
|
+
hooks,
|
|
813
|
+
"readdir objects + objects/pack",
|
|
814
|
+
() => Promise.all([
|
|
815
|
+
fsp.readdir(objectsDir).catch(() => []),
|
|
816
|
+
fsp.readdir(`${objectsDir}/pack`).catch(() => [])
|
|
817
|
+
])
|
|
818
|
+
);
|
|
819
|
+
if (objectEntries.some((name) => LOOSE_FANOUT_RE.test(name))) return null;
|
|
820
|
+
const packs = parsePackDirectory(packEntries).filter(
|
|
821
|
+
(file) => file.kind === "pack"
|
|
822
|
+
);
|
|
823
|
+
const pack = packs[0];
|
|
824
|
+
if (packs.length !== 1 || pack === void 0) return null;
|
|
825
|
+
const [packData, idxData] = await runStep(
|
|
826
|
+
hooks,
|
|
827
|
+
"read consolidated pack (fast path)",
|
|
828
|
+
() => Promise.all([
|
|
829
|
+
fsp.readFile(`${objectsDir}/pack/${pack.name}`),
|
|
830
|
+
fsp.readFile(`${objectsDir}/pack/${packFileName(pack.base, "idx")}`)
|
|
831
|
+
]).catch(() => [null, null])
|
|
832
|
+
);
|
|
833
|
+
if (!(packData instanceof Uint8Array) || !(idxData instanceof Uint8Array)) {
|
|
834
|
+
return null;
|
|
746
835
|
}
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
836
|
+
const indexed = readPackIndex(idxData);
|
|
837
|
+
if (!indexed) return null;
|
|
838
|
+
const oids = new Set(indexed.map((entry) => entry.oid));
|
|
839
|
+
return wants.every((oid) => oids.has(oid)) ? packData : null;
|
|
840
|
+
}
|
|
841
|
+
async function handleUploadPack(repo, body, options, hooks) {
|
|
842
|
+
const request = parseUploadPackRequest(body);
|
|
843
|
+
if (request.invalid) {
|
|
844
|
+
return errorResult("upload-pack: protocol error, malformed object id");
|
|
753
845
|
}
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
846
|
+
const { wants, haves, done, capabilities } = request;
|
|
847
|
+
const frame = (pack) => capabilities.includes("side-band-64k") || capabilities.includes("side-band") ? sideBandPackfile(pack) : concat(pack);
|
|
848
|
+
if (wants.length === 0 || haves.length > 0 && !done) {
|
|
849
|
+
return result(concat(pktLine("NAK\n")));
|
|
850
|
+
}
|
|
851
|
+
const { refs } = await runStep(
|
|
852
|
+
hooks,
|
|
853
|
+
"listAllRefs",
|
|
854
|
+
() => listAllRefs(repo, "HEAD")
|
|
855
|
+
);
|
|
856
|
+
const tips = new Set(refs.map((ref) => ref.oid));
|
|
857
|
+
const untipped = wants.filter((oid) => !tips.has(oid));
|
|
858
|
+
if (untipped.length > 0) {
|
|
859
|
+
const { commits } = await runStep(
|
|
757
860
|
hooks,
|
|
758
|
-
"
|
|
759
|
-
() =>
|
|
861
|
+
"verify wants reachable",
|
|
862
|
+
() => collectCommitAncestry(repo, [...tips])
|
|
760
863
|
);
|
|
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
|
-
};
|
|
864
|
+
const missing = untipped.find((oid) => !commits.has(oid));
|
|
865
|
+
if (missing !== void 0) {
|
|
866
|
+
return errorResult(`upload-pack: not our ref ${missing}`);
|
|
778
867
|
}
|
|
779
868
|
}
|
|
869
|
+
if (haves.length === 0) {
|
|
870
|
+
const pack = await servableSinglePack(repo, wants, hooks);
|
|
871
|
+
if (pack) return result(concat(pktLine("NAK\n"), frame(pack)));
|
|
872
|
+
}
|
|
780
873
|
if (options?.beforeWalk) {
|
|
781
874
|
await runStep(hooks, "beforeWalk", options.beforeWalk);
|
|
782
875
|
}
|
|
783
876
|
let haveSet;
|
|
784
877
|
if (haves.length > 0) {
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
|
|
878
|
+
haveSet = await runStep(hooks, "have boundary", async () => {
|
|
879
|
+
const { commits: clientCommits } = await collectCommitAncestry(
|
|
880
|
+
repo,
|
|
881
|
+
haves
|
|
882
|
+
);
|
|
883
|
+
const { boundary } = await collectCommitAncestry(
|
|
884
|
+
repo,
|
|
885
|
+
wants,
|
|
886
|
+
clientCommits
|
|
887
|
+
);
|
|
888
|
+
const boundaryTrees = await Promise.all(
|
|
889
|
+
[...boundary].map(async (oid) => {
|
|
890
|
+
const { commit } = await git5.readCommit({ ...repo, oid });
|
|
891
|
+
return commit.tree;
|
|
892
|
+
})
|
|
893
|
+
);
|
|
894
|
+
const { oids: boundaryObjects } = await collectReachableOids(
|
|
895
|
+
repo,
|
|
896
|
+
boundaryTrees,
|
|
897
|
+
hooks,
|
|
898
|
+
void 0,
|
|
899
|
+
{ verifyBlobs: false }
|
|
900
|
+
);
|
|
901
|
+
return /* @__PURE__ */ new Set([...clientCommits, ...boundaryObjects]);
|
|
902
|
+
});
|
|
793
903
|
}
|
|
794
|
-
const { oids: wantOids } = await runStep(
|
|
904
|
+
const { oids: wantOids, complete } = await runStep(
|
|
795
905
|
hooks,
|
|
796
906
|
"collectReachableOids(wants)",
|
|
797
907
|
() => collectReachableOids(repo, wants, hooks, haveSet, {
|
|
798
908
|
verifyBlobs: false
|
|
799
909
|
})
|
|
800
910
|
);
|
|
911
|
+
if (!complete) {
|
|
912
|
+
return errorResult("upload-pack: repository is missing objects");
|
|
913
|
+
}
|
|
801
914
|
const oids = haveSet ? wantOids.filter((oid) => !haveSet.has(oid)) : wantOids;
|
|
802
915
|
const reused = await runStep(
|
|
803
916
|
hooks,
|
|
@@ -808,14 +921,7 @@ async function handleUploadPack(repo, body, options, hooks) {
|
|
|
808
921
|
const { packfile } = await git5.packObjects({ ...repo, oids });
|
|
809
922
|
return packfile instanceof Uint8Array ? packfile : new Uint8Array(packfile ?? []);
|
|
810
923
|
});
|
|
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
|
-
};
|
|
924
|
+
return result(concat(pktLine("NAK\n"), frame(packBytes)));
|
|
819
925
|
}
|
|
820
926
|
export {
|
|
821
927
|
FLUSH,
|
|
@@ -830,6 +936,7 @@ export {
|
|
|
830
936
|
listAllRefs,
|
|
831
937
|
parsePktLines,
|
|
832
938
|
parseReceivePackBody,
|
|
939
|
+
parseUploadPackRequest,
|
|
833
940
|
pktLine,
|
|
834
941
|
pktLineBuffer,
|
|
835
942
|
rawFs,
|