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