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/ops.js
ADDED
|
@@ -0,0 +1,889 @@
|
|
|
1
|
+
import {
|
|
2
|
+
GitInvalidRequestError,
|
|
3
|
+
GitObjectNotFoundError,
|
|
4
|
+
GitPathNotFoundError
|
|
5
|
+
} from "./chunk-T5NHPY7U.js";
|
|
6
|
+
import {
|
|
7
|
+
decodeUtf8,
|
|
8
|
+
hasNullByte,
|
|
9
|
+
isSafeBranchName,
|
|
10
|
+
qualifyBranchRef,
|
|
11
|
+
readBlobContent,
|
|
12
|
+
toBase64
|
|
13
|
+
} from "./chunk-4QPWSRYC.js";
|
|
14
|
+
|
|
15
|
+
// src/ops/branch.ts
|
|
16
|
+
import git from "isomorphic-git";
|
|
17
|
+
function assertSafeBranchName(name) {
|
|
18
|
+
if (!isSafeBranchName(name)) {
|
|
19
|
+
throw new GitInvalidRequestError(`Invalid branch name: ${name}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
async function listBranches(repo) {
|
|
23
|
+
try {
|
|
24
|
+
const [branches, currentBranch] = await Promise.all([
|
|
25
|
+
git.listBranches(repo),
|
|
26
|
+
git.currentBranch({ ...repo, fullname: false }).catch(() => null)
|
|
27
|
+
]);
|
|
28
|
+
return Promise.all(
|
|
29
|
+
branches.map(async (branch) => ({
|
|
30
|
+
name: branch,
|
|
31
|
+
commit: await git.resolveRef({ ...repo, ref: `refs/heads/${branch}` }),
|
|
32
|
+
isDefault: branch === currentBranch
|
|
33
|
+
}))
|
|
34
|
+
);
|
|
35
|
+
} catch (err) {
|
|
36
|
+
if (err.code === "NotFoundError") return [];
|
|
37
|
+
throw err;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async function createBranchFrom(repo, name, startPoint = "main") {
|
|
41
|
+
assertSafeBranchName(name);
|
|
42
|
+
assertSafeBranchName(startPoint);
|
|
43
|
+
const object = await git.resolveRef({
|
|
44
|
+
...repo,
|
|
45
|
+
ref: `refs/heads/${startPoint}`
|
|
46
|
+
});
|
|
47
|
+
await git.branch({ ...repo, ref: name, checkout: false, object });
|
|
48
|
+
}
|
|
49
|
+
async function deleteBranchByName(repo, name) {
|
|
50
|
+
assertSafeBranchName(name);
|
|
51
|
+
await git.deleteBranch({ ...repo, ref: name });
|
|
52
|
+
}
|
|
53
|
+
async function assertBranchExists(repo, name) {
|
|
54
|
+
assertSafeBranchName(name);
|
|
55
|
+
await git.resolveRef({ ...repo, ref: `refs/heads/${name}` });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// src/ops/commit.ts
|
|
59
|
+
import git3 from "isomorphic-git";
|
|
60
|
+
|
|
61
|
+
// src/ops/tree.ts
|
|
62
|
+
import git2 from "isomorphic-git";
|
|
63
|
+
var joinPath = (prefix, name) => prefix ? `${prefix.replace(/\/+$/, "")}/${name}` : name;
|
|
64
|
+
async function upsertTree(repo, treeOid, entries) {
|
|
65
|
+
const existing = treeOid ? (await git2.readTree({ ...repo, oid: treeOid })).tree : [];
|
|
66
|
+
const byName = new Map(existing.map((e) => [e.path, e]));
|
|
67
|
+
const direct = /* @__PURE__ */ new Map();
|
|
68
|
+
const nested = /* @__PURE__ */ new Map();
|
|
69
|
+
for (const [filePath, blobOid] of entries) {
|
|
70
|
+
const slash = filePath.indexOf("/");
|
|
71
|
+
if (slash === -1) {
|
|
72
|
+
direct.set(filePath, blobOid);
|
|
73
|
+
} else {
|
|
74
|
+
const dir = filePath.slice(0, slash);
|
|
75
|
+
const rest = filePath.slice(slash + 1);
|
|
76
|
+
if (!nested.has(dir)) nested.set(dir, /* @__PURE__ */ new Map());
|
|
77
|
+
nested.get(dir)?.set(rest, blobOid);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
for (const [name, blobOid] of direct) {
|
|
81
|
+
byName.set(name, {
|
|
82
|
+
mode: "100644",
|
|
83
|
+
path: name,
|
|
84
|
+
oid: blobOid,
|
|
85
|
+
type: "blob"
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
const nestedResults = await Promise.all(
|
|
89
|
+
Array.from(nested, async ([dir, subEntries]) => {
|
|
90
|
+
const entry = byName.get(dir);
|
|
91
|
+
const subtreeOid = entry?.type === "tree" ? entry.oid : void 0;
|
|
92
|
+
const newOid = await upsertTree(repo, subtreeOid, subEntries);
|
|
93
|
+
return [dir, newOid];
|
|
94
|
+
})
|
|
95
|
+
);
|
|
96
|
+
for (const [dir, newOid] of nestedResults) {
|
|
97
|
+
byName.set(dir, { mode: "040000", path: dir, oid: newOid, type: "tree" });
|
|
98
|
+
}
|
|
99
|
+
return git2.writeTree({ ...repo, tree: Array.from(byName.values()) });
|
|
100
|
+
}
|
|
101
|
+
async function deleteFromTree(repo, treeOid, filePath) {
|
|
102
|
+
const existing = (await git2.readTree({ ...repo, oid: treeOid })).tree;
|
|
103
|
+
const byName = new Map(existing.map((e) => [e.path, e]));
|
|
104
|
+
const slash = filePath.indexOf("/");
|
|
105
|
+
if (slash === -1) {
|
|
106
|
+
byName.delete(filePath);
|
|
107
|
+
} else {
|
|
108
|
+
const dir = filePath.slice(0, slash);
|
|
109
|
+
const rest = filePath.slice(slash + 1);
|
|
110
|
+
const entry = byName.get(dir);
|
|
111
|
+
if (entry?.type === "tree") {
|
|
112
|
+
const newOid = await deleteFromTree(repo, entry.oid, rest);
|
|
113
|
+
byName.set(dir, { ...entry, oid: newOid });
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return git2.writeTree({ ...repo, tree: Array.from(byName.values()) });
|
|
117
|
+
}
|
|
118
|
+
async function findTreeEntry(repo, rootTreeOid, treePath) {
|
|
119
|
+
if (!treePath) {
|
|
120
|
+
return { path: "", mode: "040000", type: "tree", oid: rootTreeOid };
|
|
121
|
+
}
|
|
122
|
+
const parts = treePath.split("/").filter(Boolean);
|
|
123
|
+
let currentTreeOid = rootTreeOid;
|
|
124
|
+
let currentPath = "";
|
|
125
|
+
for (const [index, part] of parts.entries()) {
|
|
126
|
+
const tree = await git2.readTree({ ...repo, oid: currentTreeOid });
|
|
127
|
+
const entry = tree.tree.find((candidate) => candidate.path === part);
|
|
128
|
+
if (!entry) return null;
|
|
129
|
+
currentPath = currentPath ? joinPath(currentPath, entry.path) : entry.path;
|
|
130
|
+
if (index === parts.length - 1) {
|
|
131
|
+
return {
|
|
132
|
+
path: currentPath,
|
|
133
|
+
mode: entry.mode,
|
|
134
|
+
type: entry.type,
|
|
135
|
+
oid: entry.oid
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
if (entry.type !== "tree") return null;
|
|
139
|
+
currentTreeOid = entry.oid;
|
|
140
|
+
}
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
async function listTreeEntries(repo, treeOid, prefix = "") {
|
|
144
|
+
const tree = await git2.readTree({ ...repo, oid: treeOid });
|
|
145
|
+
return tree.tree.map((entry) => ({
|
|
146
|
+
path: prefix ? joinPath(prefix, entry.path) : entry.path,
|
|
147
|
+
mode: entry.mode,
|
|
148
|
+
type: entry.type,
|
|
149
|
+
oid: entry.oid
|
|
150
|
+
}));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// src/ops/commit.ts
|
|
154
|
+
function authorNow(name, email) {
|
|
155
|
+
return {
|
|
156
|
+
name,
|
|
157
|
+
email,
|
|
158
|
+
timestamp: Math.floor(Date.now() / 1e3),
|
|
159
|
+
timezoneOffset: 0
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
async function writeCommitToBare(repo, options) {
|
|
163
|
+
assertSafeBranchName(options.branch);
|
|
164
|
+
let parentOid;
|
|
165
|
+
let parentTreeOid;
|
|
166
|
+
try {
|
|
167
|
+
parentOid = await git3.resolveRef({
|
|
168
|
+
...repo,
|
|
169
|
+
ref: `refs/heads/${options.branch}`
|
|
170
|
+
});
|
|
171
|
+
const { commit } = await git3.readCommit({ ...repo, oid: parentOid });
|
|
172
|
+
parentTreeOid = commit.tree;
|
|
173
|
+
} catch (err) {
|
|
174
|
+
if (err?.code !== "NotFoundError") {
|
|
175
|
+
throw err;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const treeOid = await options.buildTree(parentTreeOid);
|
|
179
|
+
const commitOid = await git3.writeCommit({
|
|
180
|
+
...repo,
|
|
181
|
+
commit: {
|
|
182
|
+
message: options.message,
|
|
183
|
+
tree: treeOid,
|
|
184
|
+
parent: parentOid ? [parentOid] : [],
|
|
185
|
+
author: options.author,
|
|
186
|
+
committer: options.author
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
await git3.writeRef({
|
|
190
|
+
...repo,
|
|
191
|
+
ref: `refs/heads/${options.branch}`,
|
|
192
|
+
value: commitOid,
|
|
193
|
+
force: true
|
|
194
|
+
});
|
|
195
|
+
return commitOid;
|
|
196
|
+
}
|
|
197
|
+
function commitFilesToBare(repo, options) {
|
|
198
|
+
return writeCommitToBare(repo, {
|
|
199
|
+
branch: options.branch,
|
|
200
|
+
message: options.message,
|
|
201
|
+
author: options.author,
|
|
202
|
+
buildTree: async (parentTreeOid) => {
|
|
203
|
+
const blobs = /* @__PURE__ */ new Map();
|
|
204
|
+
await Promise.all(
|
|
205
|
+
options.files.map(async (file) => {
|
|
206
|
+
const content = typeof file.content === "string" ? new TextEncoder().encode(file.content) : file.content;
|
|
207
|
+
const oid = await git3.writeBlob({ ...repo, blob: content });
|
|
208
|
+
blobs.set(file.path, oid);
|
|
209
|
+
})
|
|
210
|
+
);
|
|
211
|
+
return upsertTree(repo, parentTreeOid, blobs);
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
function deleteFileFromBare(repo, options) {
|
|
216
|
+
return writeCommitToBare(repo, {
|
|
217
|
+
branch: options.branch,
|
|
218
|
+
message: options.message,
|
|
219
|
+
author: options.author,
|
|
220
|
+
buildTree: async (parentTreeOid) => {
|
|
221
|
+
if (!parentTreeOid) {
|
|
222
|
+
throw new Error(`Branch ${options.branch} is empty`);
|
|
223
|
+
}
|
|
224
|
+
return deleteFromTree(repo, parentTreeOid, options.filePath);
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// src/ops/diff.ts
|
|
230
|
+
import { createTwoFilesPatch } from "diff";
|
|
231
|
+
import git5 from "isomorphic-git";
|
|
232
|
+
|
|
233
|
+
// src/ops/history.ts
|
|
234
|
+
import git4 from "isomorphic-git";
|
|
235
|
+
|
|
236
|
+
// src/ops/types.ts
|
|
237
|
+
var runStep = (hooks, label, fn) => hooks?.step ? hooks.step(label, fn) : fn();
|
|
238
|
+
function resultKeyPrefixes(gitdir) {
|
|
239
|
+
return [
|
|
240
|
+
`commitlog:${gitdir}:`,
|
|
241
|
+
`tree:${gitdir}:`,
|
|
242
|
+
`commits:${gitdir}:`,
|
|
243
|
+
`last-commits:${gitdir}:`,
|
|
244
|
+
`file-history:${gitdir}:`
|
|
245
|
+
];
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// src/ops/history.ts
|
|
249
|
+
function wrapMissingObject(promise, context) {
|
|
250
|
+
return promise.catch((err) => {
|
|
251
|
+
if (err?.code === "NotFoundError") {
|
|
252
|
+
throw new GitObjectNotFoundError(
|
|
253
|
+
`Git data for ${context} is missing from storage. The repository may need to be re-pushed to repair it.`
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
throw err;
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
var isNotFound = (err) => err?.code === "NotFoundError";
|
|
260
|
+
async function resolveCommit(repo, ref) {
|
|
261
|
+
const oid = await git4.resolveRef({ ...repo, ref: qualifyBranchRef(ref) });
|
|
262
|
+
const result = await wrapMissingObject(
|
|
263
|
+
git4.readCommit({ ...repo, oid }),
|
|
264
|
+
`${repo.gitdir} commit ${oid}`
|
|
265
|
+
);
|
|
266
|
+
return { oid, commit: result.commit };
|
|
267
|
+
}
|
|
268
|
+
async function getBlob(repo, sha) {
|
|
269
|
+
const { blob } = await wrapMissingObject(
|
|
270
|
+
git4.readBlob({ ...repo, oid: sha }),
|
|
271
|
+
`${repo.gitdir} blob ${sha}`
|
|
272
|
+
);
|
|
273
|
+
return blob;
|
|
274
|
+
}
|
|
275
|
+
async function getFileContent(repo, filePath, ref = "main") {
|
|
276
|
+
const { commit } = await resolveCommit(repo, ref);
|
|
277
|
+
const context = `${repo.gitdir}@${ref}:${filePath}`;
|
|
278
|
+
const entry = await wrapMissingObject(
|
|
279
|
+
findTreeEntry(repo, commit.tree, filePath),
|
|
280
|
+
context
|
|
281
|
+
);
|
|
282
|
+
if (entry?.type !== "blob") {
|
|
283
|
+
throw new GitPathNotFoundError(`File not found: ${filePath}`);
|
|
284
|
+
}
|
|
285
|
+
const { blob } = await wrapMissingObject(
|
|
286
|
+
git4.readBlob({ ...repo, oid: entry.oid }),
|
|
287
|
+
context
|
|
288
|
+
);
|
|
289
|
+
return blob;
|
|
290
|
+
}
|
|
291
|
+
async function getCommit(repo, sha) {
|
|
292
|
+
const result = await wrapMissingObject(
|
|
293
|
+
git4.readCommit({ ...repo, oid: sha }),
|
|
294
|
+
`${repo.gitdir} commit ${sha}`
|
|
295
|
+
);
|
|
296
|
+
return { oid: result.oid, commit: result.commit, payload: result.payload };
|
|
297
|
+
}
|
|
298
|
+
function isFullyWalked(commits) {
|
|
299
|
+
const last = commits[commits.length - 1];
|
|
300
|
+
return !!last && last.commit.parent.length === 0;
|
|
301
|
+
}
|
|
302
|
+
async function getCommitLog(repo, options = {}, hooks) {
|
|
303
|
+
const ref = options.ref ?? "main";
|
|
304
|
+
const depth = options.depth ?? 50;
|
|
305
|
+
let headSha;
|
|
306
|
+
if (options.knownHeadSha) {
|
|
307
|
+
headSha = options.knownHeadSha;
|
|
308
|
+
} else {
|
|
309
|
+
try {
|
|
310
|
+
headSha = await git4.resolveRef({ ...repo, ref: qualifyBranchRef(ref) });
|
|
311
|
+
} catch (err) {
|
|
312
|
+
if (isNotFound(err)) return [];
|
|
313
|
+
throw err;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
const cacheKey = `commitlog:${repo.gitdir}:${headSha}`;
|
|
317
|
+
const cached = hooks?.resultCache?.get(cacheKey);
|
|
318
|
+
if (cached && (cached.length >= depth || isFullyWalked(cached))) {
|
|
319
|
+
hooks?.onNote?.(
|
|
320
|
+
`getCommitLog: result-cache HIT for ${cacheKey} (depth=${depth})`
|
|
321
|
+
);
|
|
322
|
+
return cached.slice(0, depth);
|
|
323
|
+
}
|
|
324
|
+
hooks?.onNote?.(
|
|
325
|
+
`getCommitLog: result-cache MISS for ${cacheKey} (depth=${depth})`
|
|
326
|
+
);
|
|
327
|
+
if (hooks?.prefetch && depth >= (hooks.prefetchMinDepth ?? 5)) {
|
|
328
|
+
await runStep(hooks, "prefetch", hooks.prefetch);
|
|
329
|
+
}
|
|
330
|
+
const commits = await wrapMissingObject(
|
|
331
|
+
runStep(
|
|
332
|
+
hooks,
|
|
333
|
+
`git.log ${ref} depth=${depth}`,
|
|
334
|
+
() => git4.log({ ...repo, ref: headSha, depth })
|
|
335
|
+
),
|
|
336
|
+
`${repo.gitdir}@${ref} history`
|
|
337
|
+
);
|
|
338
|
+
const result = commits.map((commit) => ({
|
|
339
|
+
oid: commit.oid,
|
|
340
|
+
commit: commit.commit,
|
|
341
|
+
payload: commit.payload || ""
|
|
342
|
+
}));
|
|
343
|
+
if (!cached || result.length > cached.length) {
|
|
344
|
+
hooks?.resultCache?.set(cacheKey, result);
|
|
345
|
+
}
|
|
346
|
+
return result;
|
|
347
|
+
}
|
|
348
|
+
async function getFileFromRef(repo, filePath, ref) {
|
|
349
|
+
const bytes = await getFileContent(repo, filePath, ref);
|
|
350
|
+
const isBinary = hasNullByte(bytes);
|
|
351
|
+
return {
|
|
352
|
+
content: isBinary ? toBase64(bytes) : decodeUtf8(bytes),
|
|
353
|
+
size: bytes.length,
|
|
354
|
+
isBinary
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
async function getTreeFromRef(repo, options = {}, hooks) {
|
|
358
|
+
const ref = options.ref ?? "main";
|
|
359
|
+
const treePath = options.treePath ?? "";
|
|
360
|
+
let commit;
|
|
361
|
+
let headSha;
|
|
362
|
+
try {
|
|
363
|
+
const resolved = await resolveCommit(repo, ref);
|
|
364
|
+
commit = resolved.commit;
|
|
365
|
+
headSha = resolved.oid;
|
|
366
|
+
} catch (err) {
|
|
367
|
+
if (isNotFound(err)) return [];
|
|
368
|
+
throw err;
|
|
369
|
+
}
|
|
370
|
+
const cacheKey = `tree:${repo.gitdir}:${headSha}:${treePath}`;
|
|
371
|
+
const cached = hooks?.resultCache?.get(cacheKey);
|
|
372
|
+
if (cached) {
|
|
373
|
+
hooks?.onNote?.(`getTreeFromRef: result-cache HIT for ${cacheKey}`);
|
|
374
|
+
return cached;
|
|
375
|
+
}
|
|
376
|
+
hooks?.onNote?.(`getTreeFromRef: result-cache MISS for ${cacheKey}`);
|
|
377
|
+
if (hooks?.prefetch) {
|
|
378
|
+
await runStep(hooks, "prefetch", hooks.prefetch);
|
|
379
|
+
}
|
|
380
|
+
const context = `${repo.gitdir}@${ref}:${treePath || "/"}`;
|
|
381
|
+
let result;
|
|
382
|
+
if (!treePath) {
|
|
383
|
+
result = await wrapMissingObject(
|
|
384
|
+
runStep(
|
|
385
|
+
hooks,
|
|
386
|
+
"listTreeEntries (root)",
|
|
387
|
+
() => listTreeEntries(repo, commit.tree)
|
|
388
|
+
),
|
|
389
|
+
context
|
|
390
|
+
);
|
|
391
|
+
} else {
|
|
392
|
+
const entry = await wrapMissingObject(
|
|
393
|
+
runStep(
|
|
394
|
+
hooks,
|
|
395
|
+
`findTreeEntry ${treePath}`,
|
|
396
|
+
() => findTreeEntry(repo, commit.tree, treePath)
|
|
397
|
+
),
|
|
398
|
+
context
|
|
399
|
+
);
|
|
400
|
+
if (!entry) {
|
|
401
|
+
throw new GitPathNotFoundError(
|
|
402
|
+
`Path "${treePath}" does not exist at ${ref}`
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
result = entry.type !== "tree" ? [] : await wrapMissingObject(
|
|
406
|
+
runStep(
|
|
407
|
+
hooks,
|
|
408
|
+
`listTreeEntries ${treePath}`,
|
|
409
|
+
() => listTreeEntries(repo, entry.oid, entry.path)
|
|
410
|
+
),
|
|
411
|
+
context
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
hooks?.resultCache?.set(cacheKey, result);
|
|
415
|
+
return result;
|
|
416
|
+
}
|
|
417
|
+
async function getCommitHistory(repo, options, hooks) {
|
|
418
|
+
const limit = options.limit ?? 50;
|
|
419
|
+
const skip = options.skip ?? 0;
|
|
420
|
+
const headSha = await git4.resolveRef({ ...repo, ref: qualifyBranchRef(options.ref) }).catch(() => null);
|
|
421
|
+
const cacheKey = headSha ? `commits:${repo.gitdir}:${headSha}:${limit}:${skip}` : null;
|
|
422
|
+
if (cacheKey) {
|
|
423
|
+
const cached = hooks?.resultCache?.get(cacheKey);
|
|
424
|
+
if (cached) {
|
|
425
|
+
hooks?.onNote?.(`getCommitHistory: result-cache HIT for ${cacheKey}`);
|
|
426
|
+
return cached;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
hooks?.onNote?.(
|
|
430
|
+
`getCommitHistory: result-cache MISS for ${cacheKey ?? "(no head)"}`
|
|
431
|
+
);
|
|
432
|
+
if (!headSha) return [];
|
|
433
|
+
const all = await getCommitLog(
|
|
434
|
+
repo,
|
|
435
|
+
{ ref: options.ref, depth: limit + skip, knownHeadSha: headSha },
|
|
436
|
+
hooks
|
|
437
|
+
);
|
|
438
|
+
const result = all.slice(skip, skip + limit);
|
|
439
|
+
if (cacheKey) hooks?.resultCache?.set(cacheKey, result);
|
|
440
|
+
return result;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// src/ops/diff.ts
|
|
444
|
+
function detectBlobContent(blob) {
|
|
445
|
+
return readBlobContent(blob);
|
|
446
|
+
}
|
|
447
|
+
function countContentLines(content) {
|
|
448
|
+
if (content.length === 0) return 0;
|
|
449
|
+
const lines = content.split("\n");
|
|
450
|
+
if (lines[lines.length - 1] === "") lines.pop();
|
|
451
|
+
return lines.length;
|
|
452
|
+
}
|
|
453
|
+
function createUnifiedPatch(params) {
|
|
454
|
+
const oldPath = params.oldPath ?? `a/${params.path}`;
|
|
455
|
+
const newPath = params.newPath ?? `b/${params.path}`;
|
|
456
|
+
const patchBody = createTwoFilesPatch(
|
|
457
|
+
oldPath,
|
|
458
|
+
newPath,
|
|
459
|
+
params.before,
|
|
460
|
+
params.after,
|
|
461
|
+
"",
|
|
462
|
+
"",
|
|
463
|
+
{ context: 3 }
|
|
464
|
+
).replace(/^=+\n/, "");
|
|
465
|
+
return `diff --git a/${params.path} b/${params.path}
|
|
466
|
+
${patchBody}`;
|
|
467
|
+
}
|
|
468
|
+
function summarizeDiff(files) {
|
|
469
|
+
return {
|
|
470
|
+
files,
|
|
471
|
+
totalAdditions: files.reduce((sum, f) => sum + f.additions, 0),
|
|
472
|
+
totalDeletions: files.reduce((sum, f) => sum + f.deletions, 0),
|
|
473
|
+
totalFiles: files.length
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
async function walkTreeDiff(repo, oldOid, newOid) {
|
|
477
|
+
const changes = await git5.walk({
|
|
478
|
+
...repo,
|
|
479
|
+
trees: [git5.TREE({ ref: oldOid }), git5.TREE({ ref: newOid })],
|
|
480
|
+
map: async (filepath, [A, B]) => {
|
|
481
|
+
const [typeA, typeB] = await Promise.all([A?.type(), B?.type()]);
|
|
482
|
+
if (typeA === "tree" || typeB === "tree") return;
|
|
483
|
+
if (typeA && !typeB) {
|
|
484
|
+
const oidA2 = A ? await A.oid() : "";
|
|
485
|
+
const { blob } = await git5.readBlob({ ...repo, oid: oidA2 });
|
|
486
|
+
const before = detectBlobContent(blob);
|
|
487
|
+
return {
|
|
488
|
+
path: filepath,
|
|
489
|
+
status: "deleted",
|
|
490
|
+
additions: 0,
|
|
491
|
+
deletions: before.isBinary ? 0 : countContentLines(before.text),
|
|
492
|
+
patch: before.isBinary ? "" : createUnifiedPatch({
|
|
493
|
+
path: filepath,
|
|
494
|
+
before: before.text,
|
|
495
|
+
after: "",
|
|
496
|
+
newPath: "/dev/null"
|
|
497
|
+
}),
|
|
498
|
+
isBinary: before.isBinary,
|
|
499
|
+
oldContent: before.isBinary ? toBase64(before.bytes) : void 0,
|
|
500
|
+
oldSize: before.bytes.length
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
if (!typeA && typeB) {
|
|
504
|
+
const oidB2 = B ? await B.oid() : "";
|
|
505
|
+
const { blob } = await git5.readBlob({ ...repo, oid: oidB2 });
|
|
506
|
+
const after = detectBlobContent(blob);
|
|
507
|
+
return {
|
|
508
|
+
path: filepath,
|
|
509
|
+
status: "added",
|
|
510
|
+
additions: after.isBinary ? 0 : countContentLines(after.text),
|
|
511
|
+
deletions: 0,
|
|
512
|
+
patch: after.isBinary ? "" : createUnifiedPatch({
|
|
513
|
+
path: filepath,
|
|
514
|
+
before: "",
|
|
515
|
+
after: after.text,
|
|
516
|
+
oldPath: "/dev/null"
|
|
517
|
+
}),
|
|
518
|
+
isBinary: after.isBinary,
|
|
519
|
+
newContent: after.isBinary ? toBase64(after.bytes) : void 0,
|
|
520
|
+
newSize: after.bytes.length
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
const [oidA, oidB] = await Promise.all([
|
|
524
|
+
A ? A.oid() : Promise.resolve(""),
|
|
525
|
+
B ? B.oid() : Promise.resolve("")
|
|
526
|
+
]);
|
|
527
|
+
if (oidA !== oidB) {
|
|
528
|
+
const [{ blob: blobA }, { blob: blobB }] = await Promise.all([
|
|
529
|
+
git5.readBlob({ ...repo, oid: oidA }),
|
|
530
|
+
git5.readBlob({ ...repo, oid: oidB })
|
|
531
|
+
]);
|
|
532
|
+
const before = detectBlobContent(blobA);
|
|
533
|
+
const after = detectBlobContent(blobB);
|
|
534
|
+
const isBinary = before.isBinary || after.isBinary;
|
|
535
|
+
return {
|
|
536
|
+
path: filepath,
|
|
537
|
+
status: "modified",
|
|
538
|
+
additions: isBinary ? 0 : countContentLines(after.text),
|
|
539
|
+
deletions: isBinary ? 0 : countContentLines(before.text),
|
|
540
|
+
patch: isBinary ? "" : createUnifiedPatch({
|
|
541
|
+
path: filepath,
|
|
542
|
+
before: before.text,
|
|
543
|
+
after: after.text
|
|
544
|
+
}),
|
|
545
|
+
isBinary,
|
|
546
|
+
oldContent: isBinary ? toBase64(before.bytes) : void 0,
|
|
547
|
+
newContent: isBinary ? toBase64(after.bytes) : void 0,
|
|
548
|
+
oldSize: before.bytes.length,
|
|
549
|
+
newSize: after.bytes.length
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
return null;
|
|
553
|
+
}
|
|
554
|
+
});
|
|
555
|
+
return (changes ?? []).filter(
|
|
556
|
+
(c) => c !== null && c !== void 0
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
async function getCommitDiff(repo, commitSha) {
|
|
560
|
+
try {
|
|
561
|
+
const commit = await getCommit(repo, commitSha);
|
|
562
|
+
const parent = commit.commit.parent[0];
|
|
563
|
+
if (!parent) {
|
|
564
|
+
const entries = [];
|
|
565
|
+
const stack = [
|
|
566
|
+
{ treeOid: commit.commit.tree, prefix: "" }
|
|
567
|
+
];
|
|
568
|
+
while (stack.length) {
|
|
569
|
+
const { treeOid, prefix } = stack.pop();
|
|
570
|
+
const { tree } = await git5.readTree({ ...repo, oid: treeOid });
|
|
571
|
+
for (const entry of tree) {
|
|
572
|
+
const full = prefix ? `${prefix}/${entry.path}` : entry.path;
|
|
573
|
+
if (entry.type === "tree") {
|
|
574
|
+
stack.push({ treeOid: entry.oid, prefix: full });
|
|
575
|
+
} else if (entry.type === "blob") {
|
|
576
|
+
entries.push({ path: full, oid: entry.oid });
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
const files2 = await Promise.all(
|
|
581
|
+
entries.map(async ({ path, oid }) => {
|
|
582
|
+
const { blob } = await git5.readBlob({ ...repo, oid });
|
|
583
|
+
const after = detectBlobContent(blob);
|
|
584
|
+
return {
|
|
585
|
+
path,
|
|
586
|
+
status: "added",
|
|
587
|
+
additions: after.isBinary ? 0 : countContentLines(after.text),
|
|
588
|
+
deletions: 0,
|
|
589
|
+
patch: after.isBinary ? "" : createUnifiedPatch({
|
|
590
|
+
path,
|
|
591
|
+
before: "",
|
|
592
|
+
after: after.text,
|
|
593
|
+
oldPath: "/dev/null"
|
|
594
|
+
}),
|
|
595
|
+
isBinary: after.isBinary,
|
|
596
|
+
newContent: after.isBinary ? toBase64(after.bytes) : void 0,
|
|
597
|
+
newSize: after.bytes.length
|
|
598
|
+
};
|
|
599
|
+
})
|
|
600
|
+
);
|
|
601
|
+
return summarizeDiff(files2);
|
|
602
|
+
}
|
|
603
|
+
const files = await walkTreeDiff(repo, parent, commitSha);
|
|
604
|
+
return summarizeDiff(files);
|
|
605
|
+
} catch (error) {
|
|
606
|
+
throw new Error(`Failed to get commit diff: ${error}`);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
async function getDiffBetweenRefs(repo, baseRef, compareRef) {
|
|
610
|
+
const [baseOid, compareOid] = await Promise.all([
|
|
611
|
+
git5.resolveRef({ ...repo, ref: qualifyBranchRef(baseRef) }),
|
|
612
|
+
git5.resolveRef({ ...repo, ref: qualifyBranchRef(compareRef) })
|
|
613
|
+
]);
|
|
614
|
+
const files = await walkTreeDiff(repo, baseOid, compareOid);
|
|
615
|
+
return summarizeDiff(files);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// src/ops/file-history.ts
|
|
619
|
+
var HISTORY_WALK_DEPTH = 400;
|
|
620
|
+
var BANNER_WALK_DEPTH = 60;
|
|
621
|
+
var PREFETCH_WINDOW = 24;
|
|
622
|
+
function toEntry(commit) {
|
|
623
|
+
return {
|
|
624
|
+
sha: commit.oid,
|
|
625
|
+
message: commit.commit.message.trim(),
|
|
626
|
+
authorName: commit.commit.author.name,
|
|
627
|
+
authorEmail: commit.commit.author.email,
|
|
628
|
+
createdAt: new Date(commit.commit.author.timestamp * 1e3).toISOString()
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
async function getFileHistory(repo, options, hooks) {
|
|
632
|
+
const limit = options.limit ?? 30;
|
|
633
|
+
const maxDepth = options.maxDepth ?? HISTORY_WALK_DEPTH;
|
|
634
|
+
const walkDepth = Math.max(maxDepth, limit);
|
|
635
|
+
const commits = await runStep(
|
|
636
|
+
hooks,
|
|
637
|
+
`getCommitLog depth=${walkDepth}`,
|
|
638
|
+
() => getCommitLog(repo, { ref: options.ref, depth: walkDepth }, hooks)
|
|
639
|
+
);
|
|
640
|
+
const head = commits[0];
|
|
641
|
+
if (!head) return { entries: [], truncated: false };
|
|
642
|
+
const cacheKey = `file-history:${repo.gitdir}:${head.oid}:${options.filePath}:${limit}:${maxDepth}`;
|
|
643
|
+
const cached = hooks?.resultCache?.get(cacheKey);
|
|
644
|
+
if (cached) {
|
|
645
|
+
hooks?.onNote?.("getFileHistory: result-cache HIT, skipping history walk");
|
|
646
|
+
return cached;
|
|
647
|
+
}
|
|
648
|
+
hooks?.onNote?.("getFileHistory: result-cache MISS, walking history");
|
|
649
|
+
const byOid = new Map(commits.map((commit) => [commit.oid, commit]));
|
|
650
|
+
const oidByCommitTree = /* @__PURE__ */ new Map();
|
|
651
|
+
async function resolveOid(commitTreeOid) {
|
|
652
|
+
const cachedOid = oidByCommitTree.get(commitTreeOid);
|
|
653
|
+
if (cachedOid !== void 0) return cachedOid;
|
|
654
|
+
const entry = await findTreeEntry(repo, commitTreeOid, options.filePath);
|
|
655
|
+
const oid = entry?.type === "blob" ? entry.oid : null;
|
|
656
|
+
oidByCommitTree.set(commitTreeOid, oid);
|
|
657
|
+
return oid;
|
|
658
|
+
}
|
|
659
|
+
const entries = [];
|
|
660
|
+
let truncated = false;
|
|
661
|
+
outer: for (let windowStart = 0; windowStart < commits.length; windowStart += PREFETCH_WINDOW) {
|
|
662
|
+
const windowEnd = Math.min(windowStart + PREFETCH_WINDOW, commits.length);
|
|
663
|
+
const prefetchEnd = Math.min(windowEnd + 1, commits.length);
|
|
664
|
+
await Promise.all(
|
|
665
|
+
commits.slice(windowStart, prefetchEnd).map((commit) => resolveOid(commit.commit.tree))
|
|
666
|
+
);
|
|
667
|
+
for (let i = windowStart; i < windowEnd; i++) {
|
|
668
|
+
const commit = commits[i];
|
|
669
|
+
if (!commit) break outer;
|
|
670
|
+
const parentSha = commit.commit.parent[0];
|
|
671
|
+
const parentCommit = parentSha ? byOid.get(parentSha) : void 0;
|
|
672
|
+
if (parentSha && !parentCommit) {
|
|
673
|
+
truncated = true;
|
|
674
|
+
break outer;
|
|
675
|
+
}
|
|
676
|
+
const [oid, parentOid] = await Promise.all([
|
|
677
|
+
resolveOid(commit.commit.tree),
|
|
678
|
+
parentCommit ? resolveOid(parentCommit.commit.tree) : Promise.resolve(null)
|
|
679
|
+
]);
|
|
680
|
+
if (oid !== parentOid) {
|
|
681
|
+
entries.push(toEntry(commit));
|
|
682
|
+
if (entries.length >= limit) {
|
|
683
|
+
truncated = i < commits.length - 1;
|
|
684
|
+
break outer;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
const result = { entries, truncated };
|
|
690
|
+
hooks?.resultCache?.set(cacheKey, result);
|
|
691
|
+
return result;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// src/ops/last-commit.ts
|
|
695
|
+
var HISTORY_WALK_DEPTH2 = 400;
|
|
696
|
+
var PREFETCH_WINDOW2 = 24;
|
|
697
|
+
function toLastCommitInfo(commit) {
|
|
698
|
+
return {
|
|
699
|
+
sha: commit.oid,
|
|
700
|
+
message: commit.commit.message.trim(),
|
|
701
|
+
authorName: commit.commit.author.name,
|
|
702
|
+
authorEmail: commit.commit.author.email,
|
|
703
|
+
createdAt: new Date(commit.commit.author.timestamp * 1e3).toISOString()
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
async function getLastCommitsForTree(repo, options, hooks) {
|
|
707
|
+
const treePath = options.treePath ?? "";
|
|
708
|
+
const depth = options.depth ?? HISTORY_WALK_DEPTH2;
|
|
709
|
+
const commits = await runStep(
|
|
710
|
+
hooks,
|
|
711
|
+
`getCommitLog depth=${depth}`,
|
|
712
|
+
() => getCommitLog(repo, { ref: options.ref, depth }, hooks)
|
|
713
|
+
);
|
|
714
|
+
hooks?.onNote?.(`getLastCommitsForTree: ${commits.length} commits in log`);
|
|
715
|
+
const head = commits[0];
|
|
716
|
+
if (!head) return {};
|
|
717
|
+
const cacheKey = `last-commits:${repo.gitdir}:${head.oid}:${treePath}`;
|
|
718
|
+
const cachedResult = hooks?.resultCache?.get(cacheKey);
|
|
719
|
+
if (cachedResult) {
|
|
720
|
+
hooks?.onNote?.(
|
|
721
|
+
"getLastCommitsForTree: result-cache HIT, skipping history walk"
|
|
722
|
+
);
|
|
723
|
+
return cachedResult;
|
|
724
|
+
}
|
|
725
|
+
hooks?.onNote?.("getLastCommitsForTree: result-cache MISS, walking history");
|
|
726
|
+
const byOid = new Map(commits.map((commit) => [commit.oid, commit]));
|
|
727
|
+
const dirOidByCommitTree = /* @__PURE__ */ new Map();
|
|
728
|
+
const childrenByDirOid = /* @__PURE__ */ new Map();
|
|
729
|
+
async function resolveDirOid(commitTreeOid) {
|
|
730
|
+
const cached = dirOidByCommitTree.get(commitTreeOid);
|
|
731
|
+
if (cached !== void 0) return cached;
|
|
732
|
+
const entry = await findTreeEntry(repo, commitTreeOid, treePath);
|
|
733
|
+
const dirOid = entry?.type === "tree" ? entry.oid : null;
|
|
734
|
+
dirOidByCommitTree.set(commitTreeOid, dirOid);
|
|
735
|
+
return dirOid;
|
|
736
|
+
}
|
|
737
|
+
async function resolveChildren(dirOid) {
|
|
738
|
+
if (dirOid === null) return [];
|
|
739
|
+
const cached = childrenByDirOid.get(dirOid);
|
|
740
|
+
if (cached) return cached;
|
|
741
|
+
const children = await listTreeEntries(repo, dirOid, treePath);
|
|
742
|
+
childrenByDirOid.set(dirOid, children);
|
|
743
|
+
return children;
|
|
744
|
+
}
|
|
745
|
+
const headDirOid = await resolveDirOid(head.commit.tree);
|
|
746
|
+
if (headDirOid === null) return {};
|
|
747
|
+
const headChildren = await resolveChildren(headDirOid);
|
|
748
|
+
const remaining = new Set(headChildren.map((entry) => entry.path));
|
|
749
|
+
const result = {};
|
|
750
|
+
let commitsWalked = 0;
|
|
751
|
+
let prefetchWindows = 0;
|
|
752
|
+
const walkStart = performance.now();
|
|
753
|
+
outer: for (let windowStart = 0; windowStart < commits.length && remaining.size > 0; windowStart += PREFETCH_WINDOW2) {
|
|
754
|
+
const windowEnd = Math.min(windowStart + PREFETCH_WINDOW2, commits.length);
|
|
755
|
+
const prefetchEnd = Math.min(windowEnd + 1, commits.length);
|
|
756
|
+
prefetchWindows++;
|
|
757
|
+
const dirOids = await Promise.all(
|
|
758
|
+
commits.slice(windowStart, prefetchEnd).map((commit) => resolveDirOid(commit.commit.tree))
|
|
759
|
+
);
|
|
760
|
+
await Promise.all(
|
|
761
|
+
[...new Set(dirOids)].map((dirOid) => resolveChildren(dirOid))
|
|
762
|
+
);
|
|
763
|
+
for (let i = windowStart; i < windowEnd; i++) {
|
|
764
|
+
if (remaining.size === 0) break outer;
|
|
765
|
+
const commit = commits[i];
|
|
766
|
+
if (!commit) break outer;
|
|
767
|
+
commitsWalked++;
|
|
768
|
+
const parentSha = commit.commit.parent[0];
|
|
769
|
+
const parentCommit = parentSha ? byOid.get(parentSha) : void 0;
|
|
770
|
+
if (parentSha && !parentCommit) break outer;
|
|
771
|
+
const [dirOid, parentDirOid] = await Promise.all([
|
|
772
|
+
resolveDirOid(commit.commit.tree),
|
|
773
|
+
parentCommit ? resolveDirOid(parentCommit.commit.tree) : Promise.resolve(null)
|
|
774
|
+
]);
|
|
775
|
+
if (dirOid === parentDirOid) continue;
|
|
776
|
+
const [children, parentChildren] = await Promise.all([
|
|
777
|
+
resolveChildren(dirOid),
|
|
778
|
+
resolveChildren(parentDirOid)
|
|
779
|
+
]);
|
|
780
|
+
const childByName = new Map(
|
|
781
|
+
children.map((entry) => [entry.path, entry.oid])
|
|
782
|
+
);
|
|
783
|
+
const parentChildByName = new Map(
|
|
784
|
+
parentChildren.map((entry) => [entry.path, entry.oid])
|
|
785
|
+
);
|
|
786
|
+
for (const name of remaining) {
|
|
787
|
+
if (childByName.get(name) !== parentChildByName.get(name)) {
|
|
788
|
+
result[name] = toLastCommitInfo(commit);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
for (const name of Object.keys(result)) {
|
|
792
|
+
remaining.delete(name);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
hooks?.onNote?.(
|
|
797
|
+
`getLastCommitsForTree: walked ${commitsWalked}/${commits.length} commits across ${prefetchWindows} prefetch windows in ${(performance.now() - walkStart).toFixed(1)}ms, ${dirOidByCommitTree.size} unique tree lookups, ${remaining.size} entries never resolved`
|
|
798
|
+
);
|
|
799
|
+
hooks?.resultCache?.set(cacheKey, result);
|
|
800
|
+
return result;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
// src/ops/merge.ts
|
|
804
|
+
import git6 from "isomorphic-git";
|
|
805
|
+
async function analyzeMerge(repo, sourceBranch, targetBranch) {
|
|
806
|
+
assertSafeBranchName(sourceBranch);
|
|
807
|
+
assertSafeBranchName(targetBranch);
|
|
808
|
+
try {
|
|
809
|
+
const [sourceOid, targetOid] = await Promise.all([
|
|
810
|
+
git6.resolveRef({ ...repo, ref: qualifyBranchRef(sourceBranch) }),
|
|
811
|
+
git6.resolveRef({ ...repo, ref: qualifyBranchRef(targetBranch) })
|
|
812
|
+
]);
|
|
813
|
+
const isDescendant = await git6.isDescendent({
|
|
814
|
+
...repo,
|
|
815
|
+
oid: sourceOid,
|
|
816
|
+
ancestor: targetOid
|
|
817
|
+
});
|
|
818
|
+
return {
|
|
819
|
+
canMerge: true,
|
|
820
|
+
hasConflicts: false,
|
|
821
|
+
conflictingFiles: [],
|
|
822
|
+
fastForward: isDescendant
|
|
823
|
+
};
|
|
824
|
+
} catch (err) {
|
|
825
|
+
if (err?.code !== "NotFoundError") {
|
|
826
|
+
throw err;
|
|
827
|
+
}
|
|
828
|
+
return {
|
|
829
|
+
canMerge: false,
|
|
830
|
+
hasConflicts: true,
|
|
831
|
+
conflictingFiles: [],
|
|
832
|
+
fastForward: false
|
|
833
|
+
};
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
async function fastForwardMerge(repo, sourceBranch, targetBranch) {
|
|
837
|
+
assertSafeBranchName(sourceBranch);
|
|
838
|
+
assertSafeBranchName(targetBranch);
|
|
839
|
+
const [sourceOid, targetOid] = await Promise.all([
|
|
840
|
+
git6.resolveRef({ ...repo, ref: `refs/heads/${sourceBranch}` }),
|
|
841
|
+
git6.resolveRef({ ...repo, ref: `refs/heads/${targetBranch}` })
|
|
842
|
+
]);
|
|
843
|
+
const isFF = await git6.isDescendent({
|
|
844
|
+
...repo,
|
|
845
|
+
oid: sourceOid,
|
|
846
|
+
ancestor: targetOid
|
|
847
|
+
});
|
|
848
|
+
if (!isFF) return null;
|
|
849
|
+
await git6.writeRef({
|
|
850
|
+
...repo,
|
|
851
|
+
ref: `refs/heads/${targetBranch}`,
|
|
852
|
+
value: sourceOid,
|
|
853
|
+
force: true
|
|
854
|
+
});
|
|
855
|
+
return { success: true, commitSha: sourceOid };
|
|
856
|
+
}
|
|
857
|
+
export {
|
|
858
|
+
BANNER_WALK_DEPTH,
|
|
859
|
+
HISTORY_WALK_DEPTH,
|
|
860
|
+
analyzeMerge,
|
|
861
|
+
assertBranchExists,
|
|
862
|
+
assertSafeBranchName,
|
|
863
|
+
authorNow,
|
|
864
|
+
commitFilesToBare,
|
|
865
|
+
createBranchFrom,
|
|
866
|
+
deleteBranchByName,
|
|
867
|
+
deleteFileFromBare,
|
|
868
|
+
deleteFromTree,
|
|
869
|
+
fastForwardMerge,
|
|
870
|
+
findTreeEntry,
|
|
871
|
+
getBlob,
|
|
872
|
+
getCommit,
|
|
873
|
+
getCommitDiff,
|
|
874
|
+
getCommitHistory,
|
|
875
|
+
getCommitLog,
|
|
876
|
+
getDiffBetweenRefs,
|
|
877
|
+
getFileContent,
|
|
878
|
+
getFileFromRef,
|
|
879
|
+
getFileHistory,
|
|
880
|
+
getLastCommitsForTree,
|
|
881
|
+
getTreeFromRef,
|
|
882
|
+
listBranches,
|
|
883
|
+
listTreeEntries,
|
|
884
|
+
resolveCommit,
|
|
885
|
+
resultKeyPrefixes,
|
|
886
|
+
upsertTree,
|
|
887
|
+
writeCommitToBare
|
|
888
|
+
};
|
|
889
|
+
//# sourceMappingURL=ops.js.map
|