git-fs-s3 0.3.5
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/LICENSE +21 -0
- package/README.md +272 -0
- package/dist/chunk-4QPWSRYC.js +123 -0
- package/dist/chunk-4QPWSRYC.js.map +1 -0
- package/dist/chunk-T5NHPY7U.js +118 -0
- package/dist/chunk-T5NHPY7U.js.map +1 -0
- package/dist/http.cjs +692 -0
- package/dist/http.cjs.map +1 -0
- package/dist/http.d.cts +197 -0
- package/dist/http.d.ts +197 -0
- package/dist/http.js +594 -0
- package/dist/http.js.map +1 -0
- package/dist/index.cjs +801 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +373 -0
- package/dist/index.d.ts +373 -0
- package/dist/index.js +568 -0
- package/dist/index.js.map +1 -0
- package/dist/ops.cjs +1021 -0
- package/dist/ops.cjs.map +1 -0
- package/dist/ops.d.cts +290 -0
- package/dist/ops.d.ts +290 -0
- package/dist/ops.js +889 -0
- package/dist/ops.js.map +1 -0
- package/dist/s3.cjs +123 -0
- package/dist/s3.cjs.map +1 -0
- package/dist/s3.d.cts +36 -0
- package/dist/s3.d.ts +36 -0
- package/dist/s3.js +104 -0
- package/dist/s3.js.map +1 -0
- package/dist/types-BHoHOaQt.d.cts +53 -0
- package/dist/types-BHoHOaQt.d.ts +53 -0
- package/dist/types-QgIkUR_q.d.cts +121 -0
- package/dist/types-QgIkUR_q.d.ts +121 -0
- package/package.json +104 -0
package/dist/http.cjs
ADDED
|
@@ -0,0 +1,692 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/http/index.ts
|
|
31
|
+
var http_exports = {};
|
|
32
|
+
__export(http_exports, {
|
|
33
|
+
FLUSH: () => FLUSH,
|
|
34
|
+
REPACK_PACK_COUNT_THRESHOLD: () => REPACK_PACK_COUNT_THRESHOLD,
|
|
35
|
+
applyReceivePack: () => applyReceivePack,
|
|
36
|
+
applyRefUpdates: () => applyRefUpdates,
|
|
37
|
+
collectReachableOids: () => collectReachableOids,
|
|
38
|
+
ensureRepoInitialized: () => ensureRepoInitialized,
|
|
39
|
+
handleInfoRefs: () => handleInfoRefs,
|
|
40
|
+
handleUploadPack: () => handleUploadPack,
|
|
41
|
+
indexIncomingPack: () => indexIncomingPack,
|
|
42
|
+
listAllRefs: () => listAllRefs,
|
|
43
|
+
parsePktLines: () => parsePktLines,
|
|
44
|
+
parseReceivePackBody: () => parseReceivePackBody,
|
|
45
|
+
pktLine: () => pktLine,
|
|
46
|
+
pktLineBuffer: () => pktLineBuffer,
|
|
47
|
+
rawFs: () => rawFs,
|
|
48
|
+
receivePackResponse: () => receivePackResponse,
|
|
49
|
+
repackRepository: () => repackRepository,
|
|
50
|
+
sideBandPackfile: () => sideBandPackfile
|
|
51
|
+
});
|
|
52
|
+
module.exports = __toCommonJS(http_exports);
|
|
53
|
+
|
|
54
|
+
// src/http/info-refs.ts
|
|
55
|
+
var import_isomorphic_git = __toESM(require("isomorphic-git"), 1);
|
|
56
|
+
|
|
57
|
+
// src/edge-utils.ts
|
|
58
|
+
var textEncoder = new TextEncoder();
|
|
59
|
+
var textDecoder = new TextDecoder();
|
|
60
|
+
function encodeUtf8(data) {
|
|
61
|
+
return textEncoder.encode(data);
|
|
62
|
+
}
|
|
63
|
+
function decodeUtf8(data) {
|
|
64
|
+
return textDecoder.decode(data);
|
|
65
|
+
}
|
|
66
|
+
function decodeAscii(data) {
|
|
67
|
+
let s = "";
|
|
68
|
+
for (let i = 0; i < data.length; i++)
|
|
69
|
+
s += String.fromCharCode(data[i]);
|
|
70
|
+
return s;
|
|
71
|
+
}
|
|
72
|
+
function concat(...parts) {
|
|
73
|
+
let total = 0;
|
|
74
|
+
for (const p of parts) total += p.length;
|
|
75
|
+
const out = new Uint8Array(total);
|
|
76
|
+
let offset = 0;
|
|
77
|
+
for (const p of parts) {
|
|
78
|
+
out.set(p, offset);
|
|
79
|
+
offset += p.length;
|
|
80
|
+
}
|
|
81
|
+
return out;
|
|
82
|
+
}
|
|
83
|
+
function toHex(data) {
|
|
84
|
+
let hex = "";
|
|
85
|
+
for (let i = 0; i < data.length; i++)
|
|
86
|
+
hex += data[i].toString(16).padStart(2, "0");
|
|
87
|
+
return hex;
|
|
88
|
+
}
|
|
89
|
+
async function sha1(data) {
|
|
90
|
+
const bytes = typeof data === "string" ? encodeUtf8(data) : data;
|
|
91
|
+
const hash = await globalThis.crypto.subtle.digest("SHA-1", bytes);
|
|
92
|
+
return toHex(new Uint8Array(hash));
|
|
93
|
+
}
|
|
94
|
+
async function deflate(data) {
|
|
95
|
+
const stream = new Blob([data]).stream().pipeThrough(new CompressionStream("deflate"));
|
|
96
|
+
return new Uint8Array(await new Response(stream).arrayBuffer());
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// src/http/pkt-line.ts
|
|
100
|
+
var FLUSH = new Uint8Array([
|
|
101
|
+
48,
|
|
102
|
+
48,
|
|
103
|
+
48,
|
|
104
|
+
48
|
|
105
|
+
]);
|
|
106
|
+
function pktLine(data) {
|
|
107
|
+
const body = encodeUtf8(data);
|
|
108
|
+
const len = (body.length + 4).toString(16).padStart(4, "0");
|
|
109
|
+
return concat(encodeUtf8(len), body);
|
|
110
|
+
}
|
|
111
|
+
function pktLineBuffer(body) {
|
|
112
|
+
const len = (body.length + 4).toString(16).padStart(4, "0");
|
|
113
|
+
return concat(encodeUtf8(len), body);
|
|
114
|
+
}
|
|
115
|
+
function parsePktLines(buf) {
|
|
116
|
+
const lines = [];
|
|
117
|
+
let pos = 0;
|
|
118
|
+
while (pos + 4 <= buf.length) {
|
|
119
|
+
const len = Number.parseInt(decodeAscii(buf.subarray(pos, pos + 4)), 16);
|
|
120
|
+
if (len === 0) {
|
|
121
|
+
lines.push(null);
|
|
122
|
+
pos += 4;
|
|
123
|
+
} else if (len >= 4) {
|
|
124
|
+
lines.push(decodeUtf8(buf.subarray(pos + 4, pos + len)));
|
|
125
|
+
pos += len;
|
|
126
|
+
} else {
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return lines;
|
|
131
|
+
}
|
|
132
|
+
var SIDE_BAND_MAX_CHUNK = 65515;
|
|
133
|
+
function sideBandPackfile(packData) {
|
|
134
|
+
const parts = [];
|
|
135
|
+
for (let offset = 0; offset < packData.length; offset += SIDE_BAND_MAX_CHUNK) {
|
|
136
|
+
const chunk = packData.subarray(offset, offset + SIDE_BAND_MAX_CHUNK);
|
|
137
|
+
parts.push(pktLineBuffer(concat(new Uint8Array([1]), chunk)));
|
|
138
|
+
}
|
|
139
|
+
parts.push(FLUSH);
|
|
140
|
+
return concat(...parts);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// src/http/types.ts
|
|
144
|
+
var runStep = (hooks, label, fn) => hooks?.step ? hooks.step(label, fn) : fn();
|
|
145
|
+
function rawFs(repo) {
|
|
146
|
+
const fs = repo.fs;
|
|
147
|
+
if (!fs?.promises) {
|
|
148
|
+
throw new TypeError(
|
|
149
|
+
"This operation needs a promise fs (`fs.promises`) \u2014 node:fs and createGitFs both provide one"
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
return fs.promises;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// src/http/info-refs.ts
|
|
156
|
+
async function listAllRefs(repo, defaultBranch = "main") {
|
|
157
|
+
const [branches, tags, headOid, headSymref] = await Promise.all([
|
|
158
|
+
import_isomorphic_git.default.listBranches(repo),
|
|
159
|
+
import_isomorphic_git.default.listTags(repo),
|
|
160
|
+
import_isomorphic_git.default.resolveRef({ ...repo, ref: "HEAD" }).catch(() => null),
|
|
161
|
+
// Wrap with Promise.resolve so a mock/stub returning undefined doesn't crash .then()
|
|
162
|
+
Promise.resolve(import_isomorphic_git.default.currentBranch({ ...repo, fullname: true })).then((cb) => cb ?? `refs/heads/${defaultBranch}`).catch(() => `refs/heads/${defaultBranch}`)
|
|
163
|
+
]);
|
|
164
|
+
const [branchRefs, tagRefs] = await Promise.all([
|
|
165
|
+
Promise.all(
|
|
166
|
+
branches.map(async (branch) => {
|
|
167
|
+
try {
|
|
168
|
+
const oid = await import_isomorphic_git.default.resolveRef({
|
|
169
|
+
...repo,
|
|
170
|
+
ref: `refs/heads/${branch}`
|
|
171
|
+
});
|
|
172
|
+
return { name: `refs/heads/${branch}`, oid };
|
|
173
|
+
} catch {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
})
|
|
177
|
+
),
|
|
178
|
+
Promise.all(
|
|
179
|
+
tags.map(async (tag) => {
|
|
180
|
+
try {
|
|
181
|
+
const oid = await import_isomorphic_git.default.resolveRef({
|
|
182
|
+
...repo,
|
|
183
|
+
ref: `refs/tags/${tag}`
|
|
184
|
+
});
|
|
185
|
+
return { name: `refs/tags/${tag}`, oid };
|
|
186
|
+
} catch {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
})
|
|
190
|
+
)
|
|
191
|
+
]);
|
|
192
|
+
const refs = [];
|
|
193
|
+
if (headOid) refs.push({ name: "HEAD", oid: headOid });
|
|
194
|
+
for (const r of branchRefs) if (r) refs.push(r);
|
|
195
|
+
for (const r of tagRefs) if (r) refs.push(r);
|
|
196
|
+
return { refs, headSymref };
|
|
197
|
+
}
|
|
198
|
+
async function handleInfoRefs(repo, options, hooks) {
|
|
199
|
+
const { service } = options;
|
|
200
|
+
const agent = options.agent ?? "git-fs-s3";
|
|
201
|
+
const { refs, headSymref } = await runStep(
|
|
202
|
+
hooks,
|
|
203
|
+
"listAllRefs",
|
|
204
|
+
() => listAllRefs(repo, options.defaultBranch ?? "main")
|
|
205
|
+
);
|
|
206
|
+
const isUpload = service === "git-upload-pack";
|
|
207
|
+
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 report-status no-done agent=${agent}`;
|
|
208
|
+
const parts = [pktLine(`# service=${service}
|
|
209
|
+
`), FLUSH];
|
|
210
|
+
if (refs.length === 0) {
|
|
211
|
+
parts.push(
|
|
212
|
+
pktLine(
|
|
213
|
+
`0000000000000000000000000000000000000000 capabilities^{}\0${caps}
|
|
214
|
+
`
|
|
215
|
+
)
|
|
216
|
+
);
|
|
217
|
+
} else {
|
|
218
|
+
let first = true;
|
|
219
|
+
for (const { name, oid } of refs) {
|
|
220
|
+
parts.push(
|
|
221
|
+
pktLine(first ? `${oid} ${name}\0${caps}
|
|
222
|
+
` : `${oid} ${name}
|
|
223
|
+
`)
|
|
224
|
+
);
|
|
225
|
+
first = false;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
parts.push(FLUSH);
|
|
229
|
+
return {
|
|
230
|
+
status: 200,
|
|
231
|
+
headers: {
|
|
232
|
+
"Content-Type": `application/x-${service}-advertisement`,
|
|
233
|
+
"Cache-Control": "no-cache"
|
|
234
|
+
},
|
|
235
|
+
body: concat(...parts)
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// src/http/reachability.ts
|
|
240
|
+
var import_isomorphic_git2 = __toESM(require("isomorphic-git"), 1);
|
|
241
|
+
var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
242
|
+
var MISSING_OBJECT_RETRY_DELAY_MS = 200;
|
|
243
|
+
async function collectReachableOids(repo, startOids, hooks) {
|
|
244
|
+
const seen = /* @__PURE__ */ new Set();
|
|
245
|
+
let complete = true;
|
|
246
|
+
const promises = /* @__PURE__ */ new Map();
|
|
247
|
+
async function readAndVisitChildren(oid) {
|
|
248
|
+
const obj = await import_isomorphic_git2.default.readObject({ ...repo, oid });
|
|
249
|
+
seen.add(oid);
|
|
250
|
+
let children = [];
|
|
251
|
+
if (obj.type === "commit") {
|
|
252
|
+
const { commit } = await import_isomorphic_git2.default.readCommit({ ...repo, oid });
|
|
253
|
+
children = [commit.tree, ...commit.parent];
|
|
254
|
+
} else if (obj.type === "tree") {
|
|
255
|
+
const { tree } = await import_isomorphic_git2.default.readTree({ ...repo, oid });
|
|
256
|
+
children = tree.map((e) => e.oid);
|
|
257
|
+
} else if (obj.type === "tag") {
|
|
258
|
+
const { tag } = await import_isomorphic_git2.default.readTag({ ...repo, oid });
|
|
259
|
+
children = [tag.object];
|
|
260
|
+
}
|
|
261
|
+
await Promise.all(children.map(visit));
|
|
262
|
+
}
|
|
263
|
+
function visit(oid) {
|
|
264
|
+
const existing = promises.get(oid);
|
|
265
|
+
if (existing) return existing;
|
|
266
|
+
const p = (async () => {
|
|
267
|
+
try {
|
|
268
|
+
await readAndVisitChildren(oid);
|
|
269
|
+
} catch {
|
|
270
|
+
try {
|
|
271
|
+
await delay(MISSING_OBJECT_RETRY_DELAY_MS);
|
|
272
|
+
await readAndVisitChildren(oid);
|
|
273
|
+
} catch (err) {
|
|
274
|
+
complete = false;
|
|
275
|
+
hooks?.onWarn?.(`missing object ${oid}`, err);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
})();
|
|
279
|
+
promises.set(oid, p);
|
|
280
|
+
return p;
|
|
281
|
+
}
|
|
282
|
+
await Promise.all(startOids.map(visit));
|
|
283
|
+
return { oids: Array.from(seen), complete };
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// src/http/receive-pack.ts
|
|
287
|
+
var import_isomorphic_git4 = __toESM(require("isomorphic-git"), 1);
|
|
288
|
+
|
|
289
|
+
// src/refs.ts
|
|
290
|
+
var BAD_REF_COMPONENT = (
|
|
291
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: control chars are exactly what git's own ref-name rules reject — this needs to match the same range.
|
|
292
|
+
/(^|[/.])([/.]|$)|^@$|@\{|[\x00-\x20\x7f~^:?*[\\]|\.lock(\/|$)/
|
|
293
|
+
);
|
|
294
|
+
function isSafeFullRefName(ref) {
|
|
295
|
+
if (!ref.startsWith("refs/heads/") && !ref.startsWith("refs/tags/")) {
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
return !BAD_REF_COMPONENT.test(ref);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// src/http/repack.ts
|
|
302
|
+
var import_isomorphic_git3 = __toESM(require("isomorphic-git"), 1);
|
|
303
|
+
var PACK_OBJECT_TYPE_BITS = {
|
|
304
|
+
commit: 16,
|
|
305
|
+
tree: 32,
|
|
306
|
+
blob: 48,
|
|
307
|
+
tag: 64
|
|
308
|
+
};
|
|
309
|
+
function encodePackObjectHeader(type, length) {
|
|
310
|
+
const bytes = [];
|
|
311
|
+
let more = length > 15;
|
|
312
|
+
bytes.push(
|
|
313
|
+
(more ? 128 : 0) | PACK_OBJECT_TYPE_BITS[type] | length & 15
|
|
314
|
+
);
|
|
315
|
+
length >>>= 4;
|
|
316
|
+
while (more) {
|
|
317
|
+
more = length > 127;
|
|
318
|
+
bytes.push((more ? 128 : 0) | length & 127);
|
|
319
|
+
length >>>= 7;
|
|
320
|
+
}
|
|
321
|
+
return new Uint8Array(bytes);
|
|
322
|
+
}
|
|
323
|
+
async function hashGitObject(type, content) {
|
|
324
|
+
const prefix = encodeUtf8(`${type} ${content.length}\0`);
|
|
325
|
+
return sha1(concat(prefix, content));
|
|
326
|
+
}
|
|
327
|
+
async function readAndVerifyObjects(repo, oids) {
|
|
328
|
+
const objects = /* @__PURE__ */ new Map();
|
|
329
|
+
const BATCH_SIZE = 100;
|
|
330
|
+
for (let i = 0; i < oids.length; i += BATCH_SIZE) {
|
|
331
|
+
const batch = oids.slice(i, i + BATCH_SIZE);
|
|
332
|
+
const entries = await Promise.all(
|
|
333
|
+
batch.map(async (oid) => {
|
|
334
|
+
const { type, object } = await import_isomorphic_git3.default.readObject({
|
|
335
|
+
...repo,
|
|
336
|
+
oid,
|
|
337
|
+
format: "content"
|
|
338
|
+
});
|
|
339
|
+
const content = object;
|
|
340
|
+
const actualOid = await hashGitObject(type, content);
|
|
341
|
+
if (actualOid !== oid) {
|
|
342
|
+
throw new Error(
|
|
343
|
+
`repack: object ${oid} failed independent SHA-1 verification (recomputed ${actualOid}) \u2014 refusing to trust this read, aborting repack`
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
return { oid, type, content };
|
|
347
|
+
})
|
|
348
|
+
);
|
|
349
|
+
for (const { oid, type, content } of entries) {
|
|
350
|
+
objects.set(oid, { type, content });
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return objects;
|
|
354
|
+
}
|
|
355
|
+
async function buildVerifiedPack(oids, objects) {
|
|
356
|
+
const header = new Uint8Array(12);
|
|
357
|
+
const view = new DataView(header.buffer);
|
|
358
|
+
header.set(encodeUtf8("PACK"), 0);
|
|
359
|
+
view.setUint32(4, 2, false);
|
|
360
|
+
view.setUint32(8, oids.length, false);
|
|
361
|
+
const chunks = [header];
|
|
362
|
+
const BATCH_SIZE = 100;
|
|
363
|
+
for (let i = 0; i < oids.length; i += BATCH_SIZE) {
|
|
364
|
+
const batch = oids.slice(i, i + BATCH_SIZE);
|
|
365
|
+
const encoded = await Promise.all(
|
|
366
|
+
batch.map(async (oid) => {
|
|
367
|
+
const entry = objects.get(oid);
|
|
368
|
+
if (!entry) {
|
|
369
|
+
throw new Error(`repack: missing verified object for ${oid}`);
|
|
370
|
+
}
|
|
371
|
+
const objHeader = encodePackObjectHeader(
|
|
372
|
+
entry.type,
|
|
373
|
+
entry.content.length
|
|
374
|
+
);
|
|
375
|
+
const compressed = await deflate(entry.content);
|
|
376
|
+
return concat(objHeader, compressed);
|
|
377
|
+
})
|
|
378
|
+
);
|
|
379
|
+
chunks.push(...encoded);
|
|
380
|
+
}
|
|
381
|
+
const packBody = concat(...chunks);
|
|
382
|
+
const trailer = fromHex(await sha1(packBody));
|
|
383
|
+
return concat(packBody, trailer);
|
|
384
|
+
}
|
|
385
|
+
function fromHex(hex) {
|
|
386
|
+
const bytes = new Uint8Array(hex.length / 2);
|
|
387
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
388
|
+
bytes[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
389
|
+
}
|
|
390
|
+
return bytes;
|
|
391
|
+
}
|
|
392
|
+
var REPACK_PACK_COUNT_THRESHOLD = 4;
|
|
393
|
+
async function countPacks(repo) {
|
|
394
|
+
try {
|
|
395
|
+
const entries = await rawFs(repo).readdir(`${repo.gitdir}/objects/pack`);
|
|
396
|
+
return entries.filter((f) => f.endsWith(".pack")).length;
|
|
397
|
+
} catch {
|
|
398
|
+
return 0;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
async function repackRepository(repo, options, hooks) {
|
|
402
|
+
const threshold = options?.threshold ?? REPACK_PACK_COUNT_THRESHOLD;
|
|
403
|
+
const fsp = rawFs(repo);
|
|
404
|
+
try {
|
|
405
|
+
if (await countPacks(repo) < threshold) {
|
|
406
|
+
return [];
|
|
407
|
+
}
|
|
408
|
+
const [branches, tags] = await Promise.all([
|
|
409
|
+
import_isomorphic_git3.default.listBranches(repo),
|
|
410
|
+
import_isomorphic_git3.default.listTags(repo)
|
|
411
|
+
]);
|
|
412
|
+
const refNames = [
|
|
413
|
+
...branches.map((b) => `refs/heads/${b}`),
|
|
414
|
+
...tags.map((t) => `refs/tags/${t}`)
|
|
415
|
+
];
|
|
416
|
+
const tipOids = (await Promise.all(
|
|
417
|
+
refNames.map(
|
|
418
|
+
(ref) => import_isomorphic_git3.default.resolveRef({ ...repo, ref }).catch(() => null)
|
|
419
|
+
)
|
|
420
|
+
)).filter((oid) => oid !== null);
|
|
421
|
+
if (tipOids.length === 0) return [];
|
|
422
|
+
const { oids, complete } = await collectReachableOids(repo, tipOids, hooks);
|
|
423
|
+
if (!complete || oids.length === 0) return [];
|
|
424
|
+
const objects = await readAndVerifyObjects(repo, oids);
|
|
425
|
+
const packBuffer = await buildVerifiedPack(oids, objects);
|
|
426
|
+
const packDir = `${repo.gitdir}/objects/pack`;
|
|
427
|
+
await fsp.mkdir(packDir, { recursive: true });
|
|
428
|
+
const newBase = `pack-${Date.now()}`;
|
|
429
|
+
const newPackFile = `${newBase}.pack`;
|
|
430
|
+
const newIdxFile = `${newBase}.idx`;
|
|
431
|
+
await fsp.writeFile(`${packDir}/${newPackFile}`, packBuffer);
|
|
432
|
+
const { oids: indexedOids } = await import_isomorphic_git3.default.indexPack({
|
|
433
|
+
...repo,
|
|
434
|
+
dir: packDir,
|
|
435
|
+
filepath: newPackFile
|
|
436
|
+
});
|
|
437
|
+
const expected = new Set(oids);
|
|
438
|
+
const indexed = new Set(indexedOids);
|
|
439
|
+
if (indexed.size !== expected.size || oids.some((oid) => !indexed.has(oid))) {
|
|
440
|
+
await fsp.unlink(`${packDir}/${newPackFile}`).catch(() => {
|
|
441
|
+
});
|
|
442
|
+
await fsp.unlink(`${packDir}/${newIdxFile}`).catch(() => {
|
|
443
|
+
});
|
|
444
|
+
throw new Error(
|
|
445
|
+
"repack: indexed pack's oid set didn't match the verified reachable set \u2014 aborting"
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
const allEntries = await fsp.readdir(packDir).catch(() => []);
|
|
449
|
+
const staleFiles = allEntries.filter(
|
|
450
|
+
(f) => f !== newPackFile && f !== newIdxFile && (f.endsWith(".pack") || f.endsWith(".idx") || f.endsWith(".keep"))
|
|
451
|
+
);
|
|
452
|
+
await Promise.all(
|
|
453
|
+
staleFiles.map((f) => fsp.unlink(`${packDir}/${f}`).catch(() => {
|
|
454
|
+
}))
|
|
455
|
+
);
|
|
456
|
+
return staleFiles.map((f) => `objects/pack/${f}`);
|
|
457
|
+
} catch (err) {
|
|
458
|
+
hooks?.onWarn?.("repack failed (non-fatal)", err);
|
|
459
|
+
return [];
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// src/http/receive-pack.ts
|
|
464
|
+
var ZERO_OID = "0".repeat(40);
|
|
465
|
+
function parseReceivePackBody(body) {
|
|
466
|
+
const refUpdates = [];
|
|
467
|
+
let pos = 0;
|
|
468
|
+
while (pos + 4 <= body.length) {
|
|
469
|
+
const len = Number.parseInt(decodeAscii(body.subarray(pos, pos + 4)), 16);
|
|
470
|
+
if (len === 0) {
|
|
471
|
+
pos += 4;
|
|
472
|
+
break;
|
|
473
|
+
}
|
|
474
|
+
if (len < 4) break;
|
|
475
|
+
const line = decodeUtf8(body.subarray(pos + 4, pos + len)).replace(/\n$/, "").split("\0")[0];
|
|
476
|
+
pos += len;
|
|
477
|
+
const parts = (line ?? "").split(" ");
|
|
478
|
+
const [oldOid, newOid, refName] = parts;
|
|
479
|
+
if (oldOid && newOid && refName) {
|
|
480
|
+
refUpdates.push({ oldOid, newOid, refName });
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
return { refUpdates, packData: body.subarray(pos) };
|
|
484
|
+
}
|
|
485
|
+
async function ensureRepoInitialized(repo, defaultBranch = "main") {
|
|
486
|
+
const fsp = rawFs(repo);
|
|
487
|
+
try {
|
|
488
|
+
await fsp.stat(`${repo.gitdir}/HEAD`);
|
|
489
|
+
} catch {
|
|
490
|
+
await fsp.mkdir(repo.gitdir, { recursive: true }).catch(() => {
|
|
491
|
+
});
|
|
492
|
+
await import_isomorphic_git4.default.init({ ...repo, dir: repo.gitdir, defaultBranch, bare: true });
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
async function indexIncomingPack(repo, packData, hooks) {
|
|
496
|
+
if (packData.length < 4) return;
|
|
497
|
+
await runStep(hooks, "write + indexPack incoming pack", async () => {
|
|
498
|
+
const fsp = rawFs(repo);
|
|
499
|
+
const packDir = `${repo.gitdir}/objects/pack`;
|
|
500
|
+
await fsp.mkdir(packDir, { recursive: true });
|
|
501
|
+
const packName = `recv-${Date.now()}`;
|
|
502
|
+
await fsp.writeFile(`${packDir}/${packName}.pack`, packData);
|
|
503
|
+
await import_isomorphic_git4.default.indexPack({
|
|
504
|
+
...repo,
|
|
505
|
+
dir: packDir,
|
|
506
|
+
filepath: `${packName}.pack`
|
|
507
|
+
});
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
async function applyRefUpdates(repo, refUpdates, hooks) {
|
|
511
|
+
return runStep(
|
|
512
|
+
hooks,
|
|
513
|
+
"apply ref updates",
|
|
514
|
+
() => Promise.all(
|
|
515
|
+
refUpdates.map(async ({ oldOid, newOid, refName }) => {
|
|
516
|
+
if (!isSafeFullRefName(refName)) {
|
|
517
|
+
return { refName, ok: false, reason: "invalid ref name" };
|
|
518
|
+
}
|
|
519
|
+
const currentOid = await import_isomorphic_git4.default.resolveRef({ ...repo, ref: refName }).catch(() => ZERO_OID);
|
|
520
|
+
if (currentOid !== oldOid) {
|
|
521
|
+
return {
|
|
522
|
+
refName,
|
|
523
|
+
ok: false,
|
|
524
|
+
reason: "non-fast-forward, ref updated by another push"
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
if (newOid === ZERO_OID) {
|
|
528
|
+
await import_isomorphic_git4.default.deleteRef({ ...repo, ref: refName }).catch(() => {
|
|
529
|
+
});
|
|
530
|
+
} else {
|
|
531
|
+
await import_isomorphic_git4.default.writeRef({
|
|
532
|
+
...repo,
|
|
533
|
+
ref: refName,
|
|
534
|
+
value: newOid,
|
|
535
|
+
force: true
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
return { refName, ok: true };
|
|
539
|
+
})
|
|
540
|
+
)
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
function receivePackResponse(results) {
|
|
544
|
+
const responseBody = concat(
|
|
545
|
+
pktLine("unpack ok\n"),
|
|
546
|
+
...results.map(
|
|
547
|
+
({ refName, ok, reason }) => pktLine(ok ? `ok ${refName}
|
|
548
|
+
` : `ng ${refName} ${reason}
|
|
549
|
+
`)
|
|
550
|
+
),
|
|
551
|
+
FLUSH
|
|
552
|
+
);
|
|
553
|
+
return {
|
|
554
|
+
status: 200,
|
|
555
|
+
headers: {
|
|
556
|
+
"Content-Type": "application/x-git-receive-pack-result",
|
|
557
|
+
"Cache-Control": "no-cache"
|
|
558
|
+
},
|
|
559
|
+
body: responseBody
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
async function applyReceivePack(repo, parsed, options, hooks) {
|
|
563
|
+
await ensureRepoInitialized(repo, options?.defaultBranch ?? "main");
|
|
564
|
+
await indexIncomingPack(repo, parsed.packData, hooks);
|
|
565
|
+
const results = await applyRefUpdates(repo, parsed.refUpdates, hooks);
|
|
566
|
+
const repackOptions = options?.repack;
|
|
567
|
+
const stalePackPaths = repackOptions === false ? [] : await runStep(
|
|
568
|
+
hooks,
|
|
569
|
+
"repack",
|
|
570
|
+
() => repackRepository(repo, repackOptions, hooks)
|
|
571
|
+
);
|
|
572
|
+
return { results, stalePackPaths };
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// src/http/upload-pack.ts
|
|
576
|
+
var import_isomorphic_git5 = __toESM(require("isomorphic-git"), 1);
|
|
577
|
+
var ZERO_OID2 = "0".repeat(40);
|
|
578
|
+
async function handleUploadPack(repo, body, options, hooks) {
|
|
579
|
+
const lines = parsePktLines(body);
|
|
580
|
+
const wants = [];
|
|
581
|
+
const haves = [];
|
|
582
|
+
let done = false;
|
|
583
|
+
for (const line of lines) {
|
|
584
|
+
if (!line) continue;
|
|
585
|
+
if (line.startsWith("want ")) {
|
|
586
|
+
wants.push(line.slice(5, 45));
|
|
587
|
+
}
|
|
588
|
+
if (line.startsWith("have ")) {
|
|
589
|
+
const sha = line.slice(5, 45);
|
|
590
|
+
if (sha !== ZERO_OID2) {
|
|
591
|
+
haves.push(sha);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
if (line.startsWith("done")) {
|
|
595
|
+
done = true;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
if (wants.length === 0) {
|
|
599
|
+
return {
|
|
600
|
+
status: 200,
|
|
601
|
+
headers: { "Content-Type": "application/x-git-upload-pack-result" },
|
|
602
|
+
body: concat(pktLine("NAK\n"))
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
if (haves.length > 0 && !done) {
|
|
606
|
+
return {
|
|
607
|
+
status: 200,
|
|
608
|
+
headers: { "Content-Type": "application/x-git-upload-pack-result" },
|
|
609
|
+
body: concat(pktLine("NAK\n"))
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
if (haves.length === 0) {
|
|
613
|
+
const packDirPath = `${repo.gitdir}/objects/pack`;
|
|
614
|
+
const entries = await runStep(
|
|
615
|
+
hooks,
|
|
616
|
+
"readdir objects/pack",
|
|
617
|
+
() => rawFs(repo).readdir(packDirPath).catch(() => [])
|
|
618
|
+
);
|
|
619
|
+
const packNames = entries.filter((f) => f.endsWith(".pack"));
|
|
620
|
+
const packName = packNames[0];
|
|
621
|
+
if (packNames.length === 1 && packName !== void 0) {
|
|
622
|
+
const packData = await runStep(
|
|
623
|
+
hooks,
|
|
624
|
+
"read consolidated pack (fast path)",
|
|
625
|
+
() => rawFs(repo).readFile(`${packDirPath}/${packName}`)
|
|
626
|
+
);
|
|
627
|
+
const bytes = packData instanceof Uint8Array ? packData : new TextEncoder().encode(packData);
|
|
628
|
+
return {
|
|
629
|
+
status: 200,
|
|
630
|
+
headers: {
|
|
631
|
+
"Content-Type": "application/x-git-upload-pack-result",
|
|
632
|
+
"Cache-Control": "no-cache"
|
|
633
|
+
},
|
|
634
|
+
body: concat(pktLine("NAK\n"), sideBandPackfile(bytes))
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
if (options?.beforeWalk) {
|
|
639
|
+
await runStep(hooks, "beforeWalk", options.beforeWalk);
|
|
640
|
+
}
|
|
641
|
+
const { oids: wantOids } = await runStep(
|
|
642
|
+
hooks,
|
|
643
|
+
"collectReachableOids(wants)",
|
|
644
|
+
() => collectReachableOids(repo, wants, hooks)
|
|
645
|
+
);
|
|
646
|
+
let oids = wantOids;
|
|
647
|
+
if (haves.length > 0) {
|
|
648
|
+
const { oids: haveOids } = await runStep(
|
|
649
|
+
hooks,
|
|
650
|
+
"collectReachableOids(haves)",
|
|
651
|
+
() => collectReachableOids(repo, haves, hooks)
|
|
652
|
+
);
|
|
653
|
+
const haveSet = new Set(haveOids);
|
|
654
|
+
oids = oids.filter((oid) => !haveSet.has(oid));
|
|
655
|
+
}
|
|
656
|
+
const { packfile } = await runStep(
|
|
657
|
+
hooks,
|
|
658
|
+
`packObjects (${oids.length} oids)`,
|
|
659
|
+
() => import_isomorphic_git5.default.packObjects({ ...repo, oids })
|
|
660
|
+
);
|
|
661
|
+
const packBytes = packfile instanceof Uint8Array ? packfile : new Uint8Array(packfile ?? []);
|
|
662
|
+
return {
|
|
663
|
+
status: 200,
|
|
664
|
+
headers: {
|
|
665
|
+
"Content-Type": "application/x-git-upload-pack-result",
|
|
666
|
+
"Cache-Control": "no-cache"
|
|
667
|
+
},
|
|
668
|
+
body: concat(pktLine("NAK\n"), sideBandPackfile(packBytes))
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
672
|
+
0 && (module.exports = {
|
|
673
|
+
FLUSH,
|
|
674
|
+
REPACK_PACK_COUNT_THRESHOLD,
|
|
675
|
+
applyReceivePack,
|
|
676
|
+
applyRefUpdates,
|
|
677
|
+
collectReachableOids,
|
|
678
|
+
ensureRepoInitialized,
|
|
679
|
+
handleInfoRefs,
|
|
680
|
+
handleUploadPack,
|
|
681
|
+
indexIncomingPack,
|
|
682
|
+
listAllRefs,
|
|
683
|
+
parsePktLines,
|
|
684
|
+
parseReceivePackBody,
|
|
685
|
+
pktLine,
|
|
686
|
+
pktLineBuffer,
|
|
687
|
+
rawFs,
|
|
688
|
+
receivePackResponse,
|
|
689
|
+
repackRepository,
|
|
690
|
+
sideBandPackfile
|
|
691
|
+
});
|
|
692
|
+
//# sourceMappingURL=http.cjs.map
|