git-fs-s3 0.13.0 → 0.15.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 +18 -1
- package/dist/chunk-HLYCSNJ2.js +53 -0
- package/dist/chunk-HLYCSNJ2.js.map +1 -0
- package/dist/chunk-IAKZRWBZ.js +598 -0
- package/dist/chunk-IAKZRWBZ.js.map +1 -0
- package/dist/{chunk-HO6OPXUS.js → chunk-OKBQISUC.js} +5 -1
- package/dist/chunk-OKBQISUC.js.map +1 -0
- package/dist/{chunk-G636FB7W.js → chunk-T5M5EYCZ.js} +2 -2
- package/dist/fetch-s3.js +174 -0
- package/dist/fetch-s3.js.map +1 -0
- package/dist/http.js +2 -2
- package/dist/index.js +42 -53
- package/dist/index.js.map +1 -1
- package/dist/node.js +74 -0
- package/dist/node.js.map +1 -0
- package/dist/ops.js +43 -313
- package/dist/ops.js.map +1 -1
- package/dist/types/git-errors.d.ts +2 -0
- package/dist/types/index.d.ts +3 -2
- package/dist/types/ops/branch.d.ts +9 -0
- package/dist/types/ops/history.d.ts +2 -0
- package/dist/types/ops/index.d.ts +2 -2
- package/dist/types/prefix.d.ts +23 -0
- package/dist/types/retry.d.ts +7 -0
- package/dist/types/stores/fetch-s3.d.ts +55 -0
- package/dist/types/stores/file.d.ts +16 -0
- package/package.json +14 -1
- package/dist/chunk-HO6OPXUS.js.map +0 -1
- package/dist/chunk-QH3HS7VI.js +0 -256
- package/dist/chunk-QH3HS7VI.js.map +0 -1
- /package/dist/{chunk-G636FB7W.js.map → chunk-T5M5EYCZ.js.map} +0 -0
|
@@ -0,0 +1,598 @@
|
|
|
1
|
+
import {
|
|
2
|
+
GitConflictError,
|
|
3
|
+
GitInvalidRequestError,
|
|
4
|
+
GitObjectNotFoundError,
|
|
5
|
+
GitPathNotFoundError,
|
|
6
|
+
GitRefNotFoundError,
|
|
7
|
+
concat,
|
|
8
|
+
deflate,
|
|
9
|
+
encodeUtf8,
|
|
10
|
+
fromHex,
|
|
11
|
+
isSafeBranchName,
|
|
12
|
+
packFileName,
|
|
13
|
+
qualifyBranchRef,
|
|
14
|
+
rawFs,
|
|
15
|
+
sha1,
|
|
16
|
+
toHex
|
|
17
|
+
} from "./chunk-OKBQISUC.js";
|
|
18
|
+
|
|
19
|
+
// src/ops/tree.ts
|
|
20
|
+
import git from "isomorphic-git";
|
|
21
|
+
var joinPath = (prefix, name) => prefix ? `${prefix.replace(/\/+$/, "")}/${name}` : name;
|
|
22
|
+
var BLOB_MODES = /* @__PURE__ */ new Set(["100644", "100755", "120000"]);
|
|
23
|
+
async function upsertTree(repo, treeOid, entries) {
|
|
24
|
+
const existing = treeOid ? (await git.readTree({ ...repo, oid: treeOid })).tree : [];
|
|
25
|
+
const byName = new Map(existing.map((e) => [e.path, e]));
|
|
26
|
+
const direct = /* @__PURE__ */ new Map();
|
|
27
|
+
const nested = /* @__PURE__ */ new Map();
|
|
28
|
+
for (const [filePath, blobOid] of entries) {
|
|
29
|
+
const slash = filePath.indexOf("/");
|
|
30
|
+
if (slash === -1) {
|
|
31
|
+
direct.set(filePath, blobOid);
|
|
32
|
+
} else {
|
|
33
|
+
const dir = filePath.slice(0, slash);
|
|
34
|
+
const rest = filePath.slice(slash + 1);
|
|
35
|
+
if (!nested.has(dir)) nested.set(dir, /* @__PURE__ */ new Map());
|
|
36
|
+
nested.get(dir)?.set(rest, blobOid);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
for (const [name, blobOid] of direct) {
|
|
40
|
+
const current = byName.get(name);
|
|
41
|
+
if (current && current.type !== "blob") {
|
|
42
|
+
throw new GitInvalidRequestError(`"${name}" is a directory, not a file`);
|
|
43
|
+
}
|
|
44
|
+
byName.set(name, {
|
|
45
|
+
mode: current && BLOB_MODES.has(current.mode) ? current.mode : "100644",
|
|
46
|
+
path: name,
|
|
47
|
+
oid: blobOid,
|
|
48
|
+
type: "blob"
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
const nestedResults = await Promise.all(
|
|
52
|
+
Array.from(nested, async ([dir, subEntries]) => {
|
|
53
|
+
const entry = byName.get(dir);
|
|
54
|
+
if (entry && entry.type !== "tree") {
|
|
55
|
+
throw new GitInvalidRequestError(`"${dir}" is a file, not a directory`);
|
|
56
|
+
}
|
|
57
|
+
const subtreeOid = entry?.oid;
|
|
58
|
+
const newOid = await upsertTree(repo, subtreeOid, subEntries);
|
|
59
|
+
return [dir, newOid];
|
|
60
|
+
})
|
|
61
|
+
);
|
|
62
|
+
for (const [dir, newOid] of nestedResults) {
|
|
63
|
+
byName.set(dir, { mode: "040000", path: dir, oid: newOid, type: "tree" });
|
|
64
|
+
}
|
|
65
|
+
return git.writeTree({ ...repo, tree: Array.from(byName.values()) });
|
|
66
|
+
}
|
|
67
|
+
async function deleteFromTree(repo, treeOid, filePath) {
|
|
68
|
+
const result = await deleteFromSubtree(repo, treeOid, filePath, filePath);
|
|
69
|
+
return result ?? git.writeTree({ ...repo, tree: [] });
|
|
70
|
+
}
|
|
71
|
+
async function deleteFromSubtree(repo, treeOid, filePath, fullPath) {
|
|
72
|
+
const existing = (await git.readTree({ ...repo, oid: treeOid })).tree;
|
|
73
|
+
const byName = new Map(existing.map((e) => [e.path, e]));
|
|
74
|
+
const slash = filePath.indexOf("/");
|
|
75
|
+
const name = slash === -1 ? filePath : filePath.slice(0, slash);
|
|
76
|
+
const entry = byName.get(name);
|
|
77
|
+
if (slash === -1) {
|
|
78
|
+
if (entry?.type !== "blob") {
|
|
79
|
+
throw new GitPathNotFoundError(`No such file: ${fullPath}`);
|
|
80
|
+
}
|
|
81
|
+
byName.delete(name);
|
|
82
|
+
} else {
|
|
83
|
+
if (entry?.type !== "tree") {
|
|
84
|
+
throw new GitPathNotFoundError(`No such file: ${fullPath}`);
|
|
85
|
+
}
|
|
86
|
+
const newOid = await deleteFromSubtree(
|
|
87
|
+
repo,
|
|
88
|
+
entry.oid,
|
|
89
|
+
filePath.slice(slash + 1),
|
|
90
|
+
fullPath
|
|
91
|
+
);
|
|
92
|
+
if (newOid === null) byName.delete(name);
|
|
93
|
+
else byName.set(name, { ...entry, oid: newOid });
|
|
94
|
+
}
|
|
95
|
+
if (byName.size === 0) return null;
|
|
96
|
+
return git.writeTree({ ...repo, tree: Array.from(byName.values()) });
|
|
97
|
+
}
|
|
98
|
+
async function findTreeEntry(repo, rootTreeOid, treePath) {
|
|
99
|
+
if (!treePath) {
|
|
100
|
+
return { path: "", mode: "040000", type: "tree", oid: rootTreeOid };
|
|
101
|
+
}
|
|
102
|
+
const parts = treePath.split("/").filter(Boolean);
|
|
103
|
+
let currentTreeOid = rootTreeOid;
|
|
104
|
+
let currentPath = "";
|
|
105
|
+
for (const [index, part] of parts.entries()) {
|
|
106
|
+
const tree = await git.readTree({ ...repo, oid: currentTreeOid });
|
|
107
|
+
const entry = tree.tree.find((candidate) => candidate.path === part);
|
|
108
|
+
if (!entry) return null;
|
|
109
|
+
currentPath = currentPath ? joinPath(currentPath, entry.path) : entry.path;
|
|
110
|
+
if (index === parts.length - 1) {
|
|
111
|
+
return {
|
|
112
|
+
path: currentPath,
|
|
113
|
+
mode: entry.mode,
|
|
114
|
+
type: entry.type,
|
|
115
|
+
oid: entry.oid
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
if (entry.type !== "tree") return null;
|
|
119
|
+
currentTreeOid = entry.oid;
|
|
120
|
+
}
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
async function listTreeEntries(repo, treeOid, prefix = "") {
|
|
124
|
+
const tree = await git.readTree({ ...repo, oid: treeOid });
|
|
125
|
+
return tree.tree.map((entry) => ({
|
|
126
|
+
path: prefix ? joinPath(prefix, entry.path) : entry.path,
|
|
127
|
+
mode: entry.mode,
|
|
128
|
+
type: entry.type,
|
|
129
|
+
oid: entry.oid
|
|
130
|
+
}));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// src/ops/types.ts
|
|
134
|
+
var runStep = (hooks, label, fn) => hooks?.step ? hooks.step(label, fn) : fn();
|
|
135
|
+
function resultKeyPrefixes(gitdir) {
|
|
136
|
+
return [
|
|
137
|
+
`commitlog:${gitdir}:`,
|
|
138
|
+
`tree:${gitdir}:`,
|
|
139
|
+
`last-commits:${gitdir}:`,
|
|
140
|
+
`file-history:${gitdir}:`
|
|
141
|
+
];
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// src/ops/history.ts
|
|
145
|
+
import git2 from "isomorphic-git";
|
|
146
|
+
var HISTORY_WALK_DEPTH = 400;
|
|
147
|
+
function summarizeCommit(commit) {
|
|
148
|
+
return {
|
|
149
|
+
sha: commit.oid,
|
|
150
|
+
message: commit.commit.message.trim(),
|
|
151
|
+
authorName: commit.commit.author.name,
|
|
152
|
+
authorEmail: commit.commit.author.email,
|
|
153
|
+
createdAt: new Date(commit.commit.author.timestamp * 1e3).toISOString()
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
var isNotFound = (err) => err?.code === "NotFoundError";
|
|
157
|
+
function wrapMissingObject(promise, context) {
|
|
158
|
+
return promise.catch((err) => {
|
|
159
|
+
if (isNotFound(err)) {
|
|
160
|
+
throw new GitObjectNotFoundError(
|
|
161
|
+
`Git data for ${context} is missing from storage`
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
throw err;
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
async function resolveRefOid(repo, ref) {
|
|
168
|
+
const qualified = qualifyBranchRef(ref);
|
|
169
|
+
const candidates = qualified === `refs/heads/${ref}` ? [qualified, `refs/tags/${ref}`] : [qualified];
|
|
170
|
+
for (const candidate of candidates) {
|
|
171
|
+
try {
|
|
172
|
+
return await peelToCommit(
|
|
173
|
+
repo,
|
|
174
|
+
await git2.resolveRef({ ...repo, ref: candidate })
|
|
175
|
+
);
|
|
176
|
+
} catch (err) {
|
|
177
|
+
if (!isNotFound(err)) throw err;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
throw new GitRefNotFoundError(`Ref ${ref} does not exist`);
|
|
181
|
+
}
|
|
182
|
+
async function peelToCommit(repo, oid) {
|
|
183
|
+
let current = oid;
|
|
184
|
+
for (let depth = 0; depth < 10; depth++) {
|
|
185
|
+
const { type, object } = await git2.readObject({
|
|
186
|
+
...repo,
|
|
187
|
+
oid: current,
|
|
188
|
+
format: "parsed"
|
|
189
|
+
});
|
|
190
|
+
if (type !== "tag") return current;
|
|
191
|
+
current = object.object;
|
|
192
|
+
}
|
|
193
|
+
throw new GitRefNotFoundError(`Tag chain at ${oid} is too deep`);
|
|
194
|
+
}
|
|
195
|
+
async function resolveCommit(repo, ref) {
|
|
196
|
+
const oid = await resolveRefOid(repo, ref);
|
|
197
|
+
const { commit } = await wrapMissingObject(
|
|
198
|
+
git2.readCommit({ ...repo, oid }),
|
|
199
|
+
`${repo.gitdir} commit ${oid}`
|
|
200
|
+
);
|
|
201
|
+
return { oid, commit };
|
|
202
|
+
}
|
|
203
|
+
async function getBlob(repo, sha) {
|
|
204
|
+
const { blob } = await wrapMissingObject(
|
|
205
|
+
git2.readBlob({ ...repo, oid: sha }),
|
|
206
|
+
`${repo.gitdir} blob ${sha}`
|
|
207
|
+
);
|
|
208
|
+
return blob;
|
|
209
|
+
}
|
|
210
|
+
async function getFileContent(repo, filePath, ref, hooks) {
|
|
211
|
+
if (hooks?.prefetch) await runStep(hooks, "prefetch", hooks.prefetch);
|
|
212
|
+
const { commit } = await resolveCommit(repo, ref);
|
|
213
|
+
const context = `${repo.gitdir}@${ref}:${filePath}`;
|
|
214
|
+
const entry = await wrapMissingObject(
|
|
215
|
+
findTreeEntry(repo, commit.tree, filePath),
|
|
216
|
+
context
|
|
217
|
+
);
|
|
218
|
+
if (entry?.type !== "blob") {
|
|
219
|
+
throw new GitPathNotFoundError(`File not found: ${filePath}`);
|
|
220
|
+
}
|
|
221
|
+
const { blob } = await wrapMissingObject(
|
|
222
|
+
git2.readBlob({ ...repo, oid: entry.oid }),
|
|
223
|
+
context
|
|
224
|
+
);
|
|
225
|
+
return blob;
|
|
226
|
+
}
|
|
227
|
+
async function getCommit(repo, sha) {
|
|
228
|
+
const result = await wrapMissingObject(
|
|
229
|
+
git2.readCommit({ ...repo, oid: sha }),
|
|
230
|
+
`${repo.gitdir} commit ${sha}`
|
|
231
|
+
);
|
|
232
|
+
return { oid: result.oid, commit: result.commit, payload: result.payload };
|
|
233
|
+
}
|
|
234
|
+
async function getCommitLog(repo, options, hooks) {
|
|
235
|
+
const { ref } = options;
|
|
236
|
+
const depth = options.depth ?? 50;
|
|
237
|
+
let headSha = options.knownHeadSha;
|
|
238
|
+
if (headSha === void 0) {
|
|
239
|
+
try {
|
|
240
|
+
headSha = await resolveRefOid(repo, ref);
|
|
241
|
+
} catch (err) {
|
|
242
|
+
if (err instanceof GitRefNotFoundError) return [];
|
|
243
|
+
throw err;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
const cacheKey = `commitlog:${repo.gitdir}:${headSha}`;
|
|
247
|
+
const cached = hooks?.resultCache?.get(cacheKey);
|
|
248
|
+
const fullyWalked = cached?.at(-1)?.commit.parent.length === 0;
|
|
249
|
+
if (cached && (cached.length >= depth || fullyWalked)) {
|
|
250
|
+
hooks?.onNote?.(`getCommitLog: cache hit ${cacheKey} (depth=${depth})`);
|
|
251
|
+
return cached.slice(0, depth);
|
|
252
|
+
}
|
|
253
|
+
hooks?.onNote?.(`getCommitLog: cache miss ${cacheKey} (depth=${depth})`);
|
|
254
|
+
if (hooks?.prefetch) await runStep(hooks, "prefetch", hooks.prefetch);
|
|
255
|
+
const log = await wrapMissingObject(
|
|
256
|
+
runStep(
|
|
257
|
+
hooks,
|
|
258
|
+
`git.log ${ref} depth=${depth}`,
|
|
259
|
+
() => git2.log({ ...repo, ref: headSha, depth })
|
|
260
|
+
),
|
|
261
|
+
`${repo.gitdir}@${ref} history`
|
|
262
|
+
);
|
|
263
|
+
const result = log.map(({ oid, commit, payload }) => ({
|
|
264
|
+
oid,
|
|
265
|
+
commit,
|
|
266
|
+
payload: payload ?? ""
|
|
267
|
+
}));
|
|
268
|
+
if (!cached || result.length > cached.length) {
|
|
269
|
+
hooks?.resultCache?.set(cacheKey, result);
|
|
270
|
+
}
|
|
271
|
+
return result;
|
|
272
|
+
}
|
|
273
|
+
async function getTreeFromRef(repo, options, hooks) {
|
|
274
|
+
const { ref } = options;
|
|
275
|
+
const treePath = options.treePath ?? "";
|
|
276
|
+
const { commit, oid: headSha } = await resolveCommit(repo, ref);
|
|
277
|
+
const cacheKey = `tree:${repo.gitdir}:${headSha}:${treePath}`;
|
|
278
|
+
const cached = hooks?.resultCache?.get(cacheKey);
|
|
279
|
+
if (cached) {
|
|
280
|
+
hooks?.onNote?.(`getTreeFromRef: cache hit ${cacheKey}`);
|
|
281
|
+
return cached;
|
|
282
|
+
}
|
|
283
|
+
hooks?.onNote?.(`getTreeFromRef: cache miss ${cacheKey}`);
|
|
284
|
+
if (hooks?.prefetch) await runStep(hooks, "prefetch", hooks.prefetch);
|
|
285
|
+
const context = `${repo.gitdir}@${ref}:${treePath || "/"}`;
|
|
286
|
+
const dir = treePath ? await wrapMissingObject(
|
|
287
|
+
runStep(
|
|
288
|
+
hooks,
|
|
289
|
+
`findTreeEntry ${treePath}`,
|
|
290
|
+
() => findTreeEntry(repo, commit.tree, treePath)
|
|
291
|
+
),
|
|
292
|
+
context
|
|
293
|
+
) : { path: "", type: "tree", oid: commit.tree };
|
|
294
|
+
if (!dir) {
|
|
295
|
+
throw new GitPathNotFoundError(
|
|
296
|
+
`Path "${treePath}" does not exist at ${ref}`
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
const result = dir.type === "tree" ? await wrapMissingObject(
|
|
300
|
+
runStep(
|
|
301
|
+
hooks,
|
|
302
|
+
`listTreeEntries ${treePath || "/"}`,
|
|
303
|
+
() => listTreeEntries(repo, dir.oid, dir.path)
|
|
304
|
+
),
|
|
305
|
+
context
|
|
306
|
+
) : [];
|
|
307
|
+
hooks?.resultCache?.set(cacheKey, result);
|
|
308
|
+
return result;
|
|
309
|
+
}
|
|
310
|
+
async function getCommitHistory(repo, options, hooks) {
|
|
311
|
+
const limit = options.limit ?? 50;
|
|
312
|
+
const skip = options.skip ?? 0;
|
|
313
|
+
const log = await getCommitLog(
|
|
314
|
+
repo,
|
|
315
|
+
{ ref: options.ref, depth: limit + skip },
|
|
316
|
+
hooks
|
|
317
|
+
);
|
|
318
|
+
return log.slice(skip, skip + limit);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// src/ops/branch.ts
|
|
322
|
+
import git3 from "isomorphic-git";
|
|
323
|
+
function assertSafeBranchName(name) {
|
|
324
|
+
if (!isSafeBranchName(name)) {
|
|
325
|
+
throw new GitInvalidRequestError(`Invalid branch name: ${name}`);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
var FULL_SHA_RE = /^[0-9a-f]{40}$/i;
|
|
329
|
+
function hasRecursiveFileLister(fs) {
|
|
330
|
+
return typeof fs === "object" && fs !== null && "listFilesRecursively" in fs && typeof fs.listFilesRecursively === "function";
|
|
331
|
+
}
|
|
332
|
+
function getPromisesFs(fs) {
|
|
333
|
+
const promises = fs.promises;
|
|
334
|
+
return promises ?? null;
|
|
335
|
+
}
|
|
336
|
+
function decodeFileContent(content) {
|
|
337
|
+
return typeof content === "string" ? content : new TextDecoder().decode(content);
|
|
338
|
+
}
|
|
339
|
+
async function listLooseBranches(repo) {
|
|
340
|
+
return listLooseRefNames(repo, "refs/heads");
|
|
341
|
+
}
|
|
342
|
+
async function listLooseRefNames(repo, dir) {
|
|
343
|
+
if (!hasRecursiveFileLister(repo.fs)) return null;
|
|
344
|
+
const names = await repo.fs.listFilesRecursively(`${repo.gitdir}/${dir}`);
|
|
345
|
+
return names.filter(isSafeBranchName);
|
|
346
|
+
}
|
|
347
|
+
async function readPackedBranches(repo) {
|
|
348
|
+
if (!hasRecursiveFileLister(repo.fs)) return /* @__PURE__ */ new Map();
|
|
349
|
+
const promisesFs = getPromisesFs(repo.fs);
|
|
350
|
+
if (!promisesFs) return /* @__PURE__ */ new Map();
|
|
351
|
+
try {
|
|
352
|
+
const content = await promisesFs.readFile(
|
|
353
|
+
`${repo.gitdir}/packed-refs`,
|
|
354
|
+
"utf8"
|
|
355
|
+
);
|
|
356
|
+
const refs = /* @__PURE__ */ new Map();
|
|
357
|
+
for (const line of decodeFileContent(content).split("\n")) {
|
|
358
|
+
const match = /^([0-9a-f]{40}) refs\/heads\/(.+)$/i.exec(line);
|
|
359
|
+
if (match?.[1] && match[2] && isSafeBranchName(match[2])) {
|
|
360
|
+
refs.set(match[2], match[1]);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return refs;
|
|
364
|
+
} catch {
|
|
365
|
+
return /* @__PURE__ */ new Map();
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
async function getCurrentLooseBranch(repo) {
|
|
369
|
+
if (!hasRecursiveFileLister(repo.fs)) return null;
|
|
370
|
+
const promisesFs = getPromisesFs(repo.fs);
|
|
371
|
+
if (!promisesFs) return null;
|
|
372
|
+
try {
|
|
373
|
+
const content = await promisesFs.readFile(`${repo.gitdir}/HEAD`, "utf8");
|
|
374
|
+
const head = decodeFileContent(content).trim();
|
|
375
|
+
const match = /^ref: refs\/heads\/(.+)$/.exec(head);
|
|
376
|
+
return match?.[1] && isSafeBranchName(match[1]) ? match[1] : null;
|
|
377
|
+
} catch {
|
|
378
|
+
return null;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
async function resolveLooseRefFast(repo, ref) {
|
|
382
|
+
const promisesFs = getPromisesFs(repo.fs);
|
|
383
|
+
if (promisesFs) {
|
|
384
|
+
try {
|
|
385
|
+
const content = await promisesFs.readFile(
|
|
386
|
+
`${repo.gitdir}/${ref}`,
|
|
387
|
+
"utf8"
|
|
388
|
+
);
|
|
389
|
+
const oid = decodeFileContent(content).trim();
|
|
390
|
+
if (FULL_SHA_RE.test(oid)) return oid;
|
|
391
|
+
} catch {
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return git3.resolveRef({ ...repo, ref });
|
|
395
|
+
}
|
|
396
|
+
async function listBranches(repo) {
|
|
397
|
+
try {
|
|
398
|
+
const [looseBranches, looseCurrentBranch, packedBranches] = await Promise.all([
|
|
399
|
+
listLooseBranches(repo),
|
|
400
|
+
getCurrentLooseBranch(repo),
|
|
401
|
+
readPackedBranches(repo)
|
|
402
|
+
]);
|
|
403
|
+
const branches = looseBranches === null ? await git3.listBranches(repo) : [.../* @__PURE__ */ new Set([...looseBranches, ...packedBranches.keys()])].sort();
|
|
404
|
+
const currentBranch = looseBranches === null ? await git3.currentBranch({ ...repo, fullname: false }).catch(() => null) : looseCurrentBranch;
|
|
405
|
+
return Promise.all(
|
|
406
|
+
branches.map(async (branch) => {
|
|
407
|
+
const packedCommit = packedBranches.get(branch);
|
|
408
|
+
return {
|
|
409
|
+
name: branch,
|
|
410
|
+
commit: packedCommit && !looseBranches?.includes(branch) ? packedCommit : await resolveLooseRefFast(repo, `refs/heads/${branch}`),
|
|
411
|
+
isDefault: branch === currentBranch
|
|
412
|
+
};
|
|
413
|
+
})
|
|
414
|
+
);
|
|
415
|
+
} catch (err) {
|
|
416
|
+
if (err.code === "NotFoundError") return [];
|
|
417
|
+
throw err;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
async function createBranchFrom(repo, name, startPoint) {
|
|
421
|
+
assertSafeBranchName(name);
|
|
422
|
+
assertSafeBranchName(startPoint);
|
|
423
|
+
const object = await resolveLooseRefFast(repo, `refs/heads/${startPoint}`);
|
|
424
|
+
try {
|
|
425
|
+
await git3.branch({ ...repo, ref: name, checkout: false, object });
|
|
426
|
+
} catch (err) {
|
|
427
|
+
if (err.code === "AlreadyExistsError") {
|
|
428
|
+
throw new GitConflictError(`Branch ${name} already exists`);
|
|
429
|
+
}
|
|
430
|
+
throw err;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
async function deleteBranchByName(repo, name) {
|
|
434
|
+
assertSafeBranchName(name);
|
|
435
|
+
const current = await getCurrentLooseBranch(repo) ?? await git3.currentBranch({ ...repo, fullname: false }).catch(() => null);
|
|
436
|
+
if (current === name) {
|
|
437
|
+
throw new GitInvalidRequestError(
|
|
438
|
+
`Cannot delete ${name}: it is the default branch`
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
await git3.deleteBranch({ ...repo, ref: name });
|
|
442
|
+
}
|
|
443
|
+
async function assertBranchExists(repo, name) {
|
|
444
|
+
assertSafeBranchName(name);
|
|
445
|
+
await resolveLooseRefFast(repo, `refs/heads/${name}`);
|
|
446
|
+
}
|
|
447
|
+
async function listTags(repo) {
|
|
448
|
+
const names = await listLooseRefNames(repo, "refs/tags") ?? [];
|
|
449
|
+
const all = /* @__PURE__ */ new Set([
|
|
450
|
+
...names,
|
|
451
|
+
...await git3.listTags(repo).catch(() => [])
|
|
452
|
+
]);
|
|
453
|
+
const tags = await Promise.all(
|
|
454
|
+
[...all].filter(isSafeBranchName).map(async (name) => {
|
|
455
|
+
try {
|
|
456
|
+
const oid = await resolveLooseRefFast(repo, `refs/tags/${name}`);
|
|
457
|
+
const commit = await peelToCommit(repo, oid);
|
|
458
|
+
return commit === oid ? { name, commit } : { name, commit, tagObject: oid };
|
|
459
|
+
} catch {
|
|
460
|
+
return null;
|
|
461
|
+
}
|
|
462
|
+
})
|
|
463
|
+
);
|
|
464
|
+
return tags.filter((tag) => tag !== null).sort((a, b) => a.name.localeCompare(b.name, void 0, { numeric: true }));
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// src/pack-write.ts
|
|
468
|
+
import git4 from "isomorphic-git";
|
|
469
|
+
var PACK_OBJECT_TYPE_BITS = {
|
|
470
|
+
commit: 16,
|
|
471
|
+
tree: 32,
|
|
472
|
+
blob: 48,
|
|
473
|
+
tag: 64
|
|
474
|
+
};
|
|
475
|
+
function encodePackObjectHeader(type, length) {
|
|
476
|
+
const bytes = [];
|
|
477
|
+
let more = length > 15;
|
|
478
|
+
bytes.push(
|
|
479
|
+
(more ? 128 : 0) | PACK_OBJECT_TYPE_BITS[type] | length & 15
|
|
480
|
+
);
|
|
481
|
+
let rest = Math.floor(length / 16);
|
|
482
|
+
while (more) {
|
|
483
|
+
more = rest > 127;
|
|
484
|
+
bytes.push((more ? 128 : 0) | rest & 127);
|
|
485
|
+
rest = Math.floor(rest / 128);
|
|
486
|
+
}
|
|
487
|
+
return new Uint8Array(bytes);
|
|
488
|
+
}
|
|
489
|
+
function hashGitObject(type, content) {
|
|
490
|
+
return sha1(concat(encodeUtf8(`${type} ${content.length}\0`), content));
|
|
491
|
+
}
|
|
492
|
+
async function encodePack(objects) {
|
|
493
|
+
const header = new Uint8Array(12);
|
|
494
|
+
const view = new DataView(header.buffer);
|
|
495
|
+
header.set(encodeUtf8("PACK"), 0);
|
|
496
|
+
view.setUint32(4, 2, false);
|
|
497
|
+
view.setUint32(8, objects.length, false);
|
|
498
|
+
const chunks = [header];
|
|
499
|
+
const BATCH_SIZE = 100;
|
|
500
|
+
for (let i = 0; i < objects.length; i += BATCH_SIZE) {
|
|
501
|
+
const encoded = await Promise.all(
|
|
502
|
+
objects.slice(i, i + BATCH_SIZE).map(
|
|
503
|
+
async (object) => concat(
|
|
504
|
+
encodePackObjectHeader(object.type, object.content.length),
|
|
505
|
+
await deflate(object.content)
|
|
506
|
+
)
|
|
507
|
+
)
|
|
508
|
+
);
|
|
509
|
+
chunks.push(...encoded);
|
|
510
|
+
}
|
|
511
|
+
const body = concat(...chunks);
|
|
512
|
+
return concat(body, fromHex(await sha1(body)));
|
|
513
|
+
}
|
|
514
|
+
function packBaseName(pack) {
|
|
515
|
+
return `pack-${toHex(pack.subarray(pack.length - 20))}`;
|
|
516
|
+
}
|
|
517
|
+
async function storePack(repo, pack) {
|
|
518
|
+
const fsp = rawFs(repo);
|
|
519
|
+
const packDir = `${repo.gitdir}/objects/pack`;
|
|
520
|
+
const base = packBaseName(pack);
|
|
521
|
+
await fsp.mkdir(packDir, { recursive: true });
|
|
522
|
+
await fsp.writeFile(`${packDir}/${packFileName(base, "pack")}`, pack);
|
|
523
|
+
const { oids } = await git4.indexPack({
|
|
524
|
+
...repo,
|
|
525
|
+
dir: packDir,
|
|
526
|
+
filepath: packFileName(base, "pack")
|
|
527
|
+
});
|
|
528
|
+
return { base, oids };
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// src/semaphore.ts
|
|
532
|
+
function createSemaphore(limit) {
|
|
533
|
+
if (!Number.isInteger(limit) || limit < 1) {
|
|
534
|
+
throw new Error(
|
|
535
|
+
`createSemaphore: limit must be an integer >= 1, got ${limit}`
|
|
536
|
+
);
|
|
537
|
+
}
|
|
538
|
+
let active = 0;
|
|
539
|
+
const waiting = [];
|
|
540
|
+
function release() {
|
|
541
|
+
const next = waiting.shift();
|
|
542
|
+
if (next) next();
|
|
543
|
+
else active--;
|
|
544
|
+
}
|
|
545
|
+
function acquire() {
|
|
546
|
+
if (active < limit) {
|
|
547
|
+
active++;
|
|
548
|
+
return Promise.resolve();
|
|
549
|
+
}
|
|
550
|
+
return new Promise((resolve) => {
|
|
551
|
+
waiting.push(resolve);
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
return {
|
|
555
|
+
run(fn) {
|
|
556
|
+
return acquire().then(async () => {
|
|
557
|
+
try {
|
|
558
|
+
return await fn();
|
|
559
|
+
} finally {
|
|
560
|
+
release();
|
|
561
|
+
}
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
export {
|
|
568
|
+
upsertTree,
|
|
569
|
+
deleteFromTree,
|
|
570
|
+
findTreeEntry,
|
|
571
|
+
listTreeEntries,
|
|
572
|
+
runStep,
|
|
573
|
+
resultKeyPrefixes,
|
|
574
|
+
HISTORY_WALK_DEPTH,
|
|
575
|
+
summarizeCommit,
|
|
576
|
+
peelToCommit,
|
|
577
|
+
resolveCommit,
|
|
578
|
+
getBlob,
|
|
579
|
+
getFileContent,
|
|
580
|
+
getCommit,
|
|
581
|
+
getCommitLog,
|
|
582
|
+
getTreeFromRef,
|
|
583
|
+
getCommitHistory,
|
|
584
|
+
assertSafeBranchName,
|
|
585
|
+
listLooseRefNames,
|
|
586
|
+
resolveLooseRefFast,
|
|
587
|
+
listBranches,
|
|
588
|
+
createBranchFrom,
|
|
589
|
+
deleteBranchByName,
|
|
590
|
+
assertBranchExists,
|
|
591
|
+
listTags,
|
|
592
|
+
hashGitObject,
|
|
593
|
+
encodePack,
|
|
594
|
+
packBaseName,
|
|
595
|
+
storePack,
|
|
596
|
+
createSemaphore
|
|
597
|
+
};
|
|
598
|
+
//# sourceMappingURL=chunk-IAKZRWBZ.js.map
|