@hallaxius/forge 0.1.3 → 0.1.4
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 +160 -158
- package/bin/forge.js +2 -2
- package/dist/cli.js +12764 -13800
- package/package.json +75 -75
- package/src/cli.ts +80 -78
- package/src/commands/account.ts +80 -0
- package/src/commands/alias.ts +66 -66
- package/src/commands/branch.ts +46 -46
- package/src/commands/ci.ts +28 -28
- package/src/commands/clone.ts +100 -100
- package/src/commands/commit.ts +88 -88
- package/src/commands/config.ts +47 -48
- package/src/commands/diff.ts +26 -26
- package/src/commands/fetch.ts +20 -20
- package/src/commands/help.ts +58 -58
- package/src/commands/init.ts +32 -33
- package/src/commands/issue.ts +63 -63
- package/src/commands/log.ts +29 -29
- package/src/commands/merge.ts +37 -37
- package/src/commands/pr.ts +65 -65
- package/src/commands/push.ts +35 -35
- package/src/commands/release.ts +26 -26
- package/src/commands/remote.ts +107 -107
- package/src/commands/reset.ts +30 -30
- package/src/commands/setup.ts +93 -94
- package/src/commands/stash.ts +44 -44
- package/src/commands/status.ts +74 -74
- package/src/commands/sync.ts +20 -20
- package/src/commands/tag.ts +41 -41
- package/src/commands/undo.ts +27 -27
- package/src/commands/version.ts +12 -12
- package/src/constants/colors.ts +7 -7
- package/src/constants/commit-types.ts +24 -24
- package/src/constants/messages.ts +13 -23
- package/src/lib/auth.ts +172 -172
- package/src/lib/config.ts +108 -108
- package/src/lib/git.ts +543 -543
- package/src/lib/github.ts +202 -160
- package/src/lib/logger.ts +18 -31
- package/src/lib/ui.ts +122 -156
- package/src/lib/validators.ts +16 -16
- package/src/templates/commit-types.json +9 -9
- package/src/utils/files.ts +21 -21
- package/src/utils/strings.ts +19 -19
- package/src/version.const.ts +1 -1
package/src/lib/git.ts
CHANGED
|
@@ -1,543 +1,543 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { diffLines } from "diff";
|
|
4
|
-
import git, {
|
|
5
|
-
currentBranch,
|
|
6
|
-
log as gitLog,
|
|
7
|
-
listBranches,
|
|
8
|
-
listRemotes,
|
|
9
|
-
listTags,
|
|
10
|
-
statusMatrix,
|
|
11
|
-
} from "isomorphic-git";
|
|
12
|
-
import http from "isomorphic-git/http/node";
|
|
13
|
-
import { resolveToken } from "./auth.js";
|
|
14
|
-
|
|
15
|
-
const dir = process.cwd();
|
|
16
|
-
|
|
17
|
-
interface StatusResult {
|
|
18
|
-
current: string;
|
|
19
|
-
tracking: string;
|
|
20
|
-
ahead: number;
|
|
21
|
-
behind: number;
|
|
22
|
-
files: { path: string; working_dir: string; index: string }[];
|
|
23
|
-
recentCommits: { hash: string; date: string; message: string }[];
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
interface LogEntry {
|
|
27
|
-
hash: string;
|
|
28
|
-
date: string;
|
|
29
|
-
message: string;
|
|
30
|
-
author: string;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
interface BranchesResult {
|
|
34
|
-
current: string;
|
|
35
|
-
branches: string[];
|
|
36
|
-
all: string[];
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
interface StashEntry {
|
|
40
|
-
index: number;
|
|
41
|
-
description: string;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function statusChar(head: number, workdir: number): string {
|
|
45
|
-
if (workdir === 0) return "D";
|
|
46
|
-
if (head === 0 && workdir === 2) return "?";
|
|
47
|
-
if (workdir === 2) return "M";
|
|
48
|
-
return " ";
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function indexChar(head: number, stage: number): string {
|
|
52
|
-
if (head === 0 && stage === 2) return "A";
|
|
53
|
-
if (stage === 0 && head === 1) return "D";
|
|
54
|
-
if (stage === 2 && head === 1) return "M";
|
|
55
|
-
return " ";
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export async function getStatus(): Promise<StatusResult> {
|
|
59
|
-
const matrix = await statusMatrix({ fs, dir });
|
|
60
|
-
|
|
61
|
-
const current = (await currentBranch({ fs, dir, fullname: false })) || "";
|
|
62
|
-
const logResult = await gitLog({ fs, dir, depth: 5 });
|
|
63
|
-
|
|
64
|
-
const files = matrix
|
|
65
|
-
.filter(
|
|
66
|
-
([_f, head, workdir, stage]) =>
|
|
67
|
-
head !== 1 || workdir !== 1 || stage !== 1,
|
|
68
|
-
)
|
|
69
|
-
.map(([filepath, head, workdir, stage]) => ({
|
|
70
|
-
path: filepath,
|
|
71
|
-
working_dir: statusChar(head as number, workdir as number),
|
|
72
|
-
index: indexChar(head as number, stage as number),
|
|
73
|
-
}));
|
|
74
|
-
|
|
75
|
-
return {
|
|
76
|
-
current,
|
|
77
|
-
tracking: "",
|
|
78
|
-
ahead: 0,
|
|
79
|
-
behind: 0,
|
|
80
|
-
files,
|
|
81
|
-
recentCommits: logResult.map((c) => ({
|
|
82
|
-
hash: c.oid,
|
|
83
|
-
date: String(c.commit.author.timestamp),
|
|
84
|
-
message: c.commit.message,
|
|
85
|
-
})),
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export async function commit(message: string): Promise<string> {
|
|
90
|
-
const name =
|
|
91
|
-
(await git.getConfig({ fs, dir, path: "user.name" })) || "Unknown";
|
|
92
|
-
const email =
|
|
93
|
-
(await git.getConfig({ fs, dir, path: "user.email" })) ||
|
|
94
|
-
"unknown@localhost";
|
|
95
|
-
|
|
96
|
-
const result = await git.commit({
|
|
97
|
-
fs,
|
|
98
|
-
dir,
|
|
99
|
-
message,
|
|
100
|
-
author: { name, email },
|
|
101
|
-
});
|
|
102
|
-
return result;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
export async function amendCommit(): Promise<void> {
|
|
106
|
-
const commits = await gitLog({ fs, dir, depth: 1 });
|
|
107
|
-
if (commits.length === 0) throw new Error("No commits to amend");
|
|
108
|
-
const { commit: c } = commits[0];
|
|
109
|
-
|
|
110
|
-
const name =
|
|
111
|
-
(await git.getConfig({ fs, dir, path: "user.name" })) || "Unknown";
|
|
112
|
-
const email =
|
|
113
|
-
(await git.getConfig({ fs, dir, path: "user.email" })) ||
|
|
114
|
-
"unknown@localhost";
|
|
115
|
-
|
|
116
|
-
await git.commit({
|
|
117
|
-
fs,
|
|
118
|
-
dir,
|
|
119
|
-
message: c.message,
|
|
120
|
-
author: c.author,
|
|
121
|
-
committer: { name, email },
|
|
122
|
-
tree: c.tree,
|
|
123
|
-
parent: c.parent.map((p) => p.toString()),
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
export async function add(filepaths: string | string[]): Promise<void> {
|
|
128
|
-
const files = Array.isArray(filepaths) ? filepaths : [filepaths];
|
|
129
|
-
for (const filepath of files) {
|
|
130
|
-
await git.add({ fs, dir, filepath });
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
export async function push(force: boolean = false): Promise<string> {
|
|
135
|
-
const token = await resolveToken();
|
|
136
|
-
const result = await git.push({
|
|
137
|
-
fs,
|
|
138
|
-
http,
|
|
139
|
-
dir,
|
|
140
|
-
force,
|
|
141
|
-
token,
|
|
142
|
-
oauth2format: "github",
|
|
143
|
-
});
|
|
144
|
-
return result;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
export async function pullRebase(): Promise<string> {
|
|
148
|
-
const token = await resolveToken();
|
|
149
|
-
await git.fetch({ fs, http, dir, token, oauth2format: "github" });
|
|
150
|
-
const current = (await currentBranch({ fs, dir })) || "";
|
|
151
|
-
const result = await git.merge({
|
|
152
|
-
fs,
|
|
153
|
-
dir,
|
|
154
|
-
theirs: current,
|
|
155
|
-
fastForwardOnly: true,
|
|
156
|
-
});
|
|
157
|
-
return result;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
export async function log(maxCount: number = 10): Promise<LogEntry[]> {
|
|
161
|
-
const result = await gitLog({ fs, dir, depth: maxCount, ref: "HEAD" });
|
|
162
|
-
return result.map((c) => ({
|
|
163
|
-
hash: c.oid,
|
|
164
|
-
date: String(c.commit.author.timestamp),
|
|
165
|
-
message: c.commit.message,
|
|
166
|
-
author: c.commit.author.name,
|
|
167
|
-
}));
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
export async function diff(): Promise<string> {
|
|
171
|
-
const matrix = await statusMatrix({ fs, dir });
|
|
172
|
-
const changed = matrix.filter(
|
|
173
|
-
([_f, head, workdir, _stage]) => head !== 1 || workdir !== 1,
|
|
174
|
-
);
|
|
175
|
-
if (changed.length === 0) return "";
|
|
176
|
-
|
|
177
|
-
const headOid = await git.resolveRef({ fs, dir, ref: "HEAD" });
|
|
178
|
-
const output: string[] = [];
|
|
179
|
-
|
|
180
|
-
for (const [filepath, head, workdir] of changed) {
|
|
181
|
-
let oldContent = "";
|
|
182
|
-
let newContent = "";
|
|
183
|
-
|
|
184
|
-
if ((head as number) !== 0) {
|
|
185
|
-
try {
|
|
186
|
-
const blob = await git.readBlob({
|
|
187
|
-
fs,
|
|
188
|
-
dir,
|
|
189
|
-
oid: headOid,
|
|
190
|
-
filepath,
|
|
191
|
-
});
|
|
192
|
-
oldContent = new TextDecoder().decode(blob.blob);
|
|
193
|
-
} catch {}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
if ((workdir as number) !== 0) {
|
|
197
|
-
try {
|
|
198
|
-
newContent = readFileSync(join(dir, filepath), "utf-8");
|
|
199
|
-
} catch {}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
output.push(`diff --git a/${filepath} b/${filepath}`);
|
|
203
|
-
const patch = diffLines(oldContent, newContent);
|
|
204
|
-
for (const part of patch) {
|
|
205
|
-
if (part.added) {
|
|
206
|
-
for (const line of part.value.split("\n").filter(Boolean)) {
|
|
207
|
-
output.push(`+${line}`);
|
|
208
|
-
}
|
|
209
|
-
} else if (part.removed) {
|
|
210
|
-
for (const line of part.value.split("\n").filter(Boolean)) {
|
|
211
|
-
output.push(`-${line}`);
|
|
212
|
-
}
|
|
213
|
-
} else {
|
|
214
|
-
for (const line of part.value.split("\n").filter(Boolean)) {
|
|
215
|
-
output.push(` ${line}`);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
return output.join("\n");
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
export async function diffStaged(): Promise<string> {
|
|
225
|
-
const matrix = await statusMatrix({ fs, dir });
|
|
226
|
-
const changed = matrix.filter(
|
|
227
|
-
([_f, head, _workdir, stage]) => head !== 1 || (stage as number) !== 1,
|
|
228
|
-
);
|
|
229
|
-
if (changed.length === 0) return "";
|
|
230
|
-
|
|
231
|
-
const headOid = await git.resolveRef({ fs, dir, ref: "HEAD" });
|
|
232
|
-
const output: string[] = [];
|
|
233
|
-
|
|
234
|
-
for (const [filepath, head, _workdir, stage] of changed) {
|
|
235
|
-
let oldContent = "";
|
|
236
|
-
let newContent = "";
|
|
237
|
-
|
|
238
|
-
if ((head as number) !== 0) {
|
|
239
|
-
try {
|
|
240
|
-
const blob = await git.readBlob({
|
|
241
|
-
fs,
|
|
242
|
-
dir,
|
|
243
|
-
oid: headOid,
|
|
244
|
-
filepath,
|
|
245
|
-
});
|
|
246
|
-
oldContent = new TextDecoder().decode(blob.blob);
|
|
247
|
-
} catch {}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
if ((stage as number) !== 0) {
|
|
251
|
-
try {
|
|
252
|
-
const blob = await git.readBlob({
|
|
253
|
-
fs,
|
|
254
|
-
dir,
|
|
255
|
-
oid: headOid,
|
|
256
|
-
filepath,
|
|
257
|
-
});
|
|
258
|
-
newContent = new TextDecoder().decode(blob.blob);
|
|
259
|
-
} catch {}
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
output.push(`diff --git a/${filepath} b/${filepath} (staged)`);
|
|
263
|
-
const patch = diffLines(oldContent, newContent);
|
|
264
|
-
for (const part of patch) {
|
|
265
|
-
if (part.added) {
|
|
266
|
-
for (const line of part.value.split("\n").filter(Boolean)) {
|
|
267
|
-
output.push(`+${line}`);
|
|
268
|
-
}
|
|
269
|
-
} else if (part.removed) {
|
|
270
|
-
for (const line of part.value.split("\n").filter(Boolean)) {
|
|
271
|
-
output.push(`-${line}`);
|
|
272
|
-
}
|
|
273
|
-
} else {
|
|
274
|
-
for (const line of part.value.split("\n").filter(Boolean)) {
|
|
275
|
-
output.push(` ${line}`);
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
return output.join("\n");
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
export async function getBranches(): Promise<BranchesResult> {
|
|
285
|
-
const all = await listBranches({ fs, dir });
|
|
286
|
-
const current = (await currentBranch({ fs, dir, fullname: false })) || "";
|
|
287
|
-
return {
|
|
288
|
-
current,
|
|
289
|
-
branches: all.filter((b) => b !== current),
|
|
290
|
-
all,
|
|
291
|
-
};
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
export async function createBranch(name: string): Promise<void> {
|
|
295
|
-
await git.branch({ fs, dir, ref: name });
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
export async function deleteBranch(
|
|
299
|
-
name: string,
|
|
300
|
-
force: boolean = false,
|
|
301
|
-
): Promise<void> {
|
|
302
|
-
if (force) {
|
|
303
|
-
await git.deleteBranch({ fs, dir, ref: name });
|
|
304
|
-
} else {
|
|
305
|
-
await git.deleteBranch({ fs, dir, ref: name });
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
export async function switchBranch(name: string): Promise<void> {
|
|
310
|
-
await git.checkout({ fs, dir, ref: name });
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
export async function stash(): Promise<string> {
|
|
314
|
-
await git.stash({ fs, dir });
|
|
315
|
-
return "Stash saved";
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
export async function stashPop(): Promise<string> {
|
|
319
|
-
const stashes = await stashList();
|
|
320
|
-
if (stashes.length === 0) throw new Error("No stashes to pop");
|
|
321
|
-
|
|
322
|
-
try {
|
|
323
|
-
await git.stash({ fs, dir });
|
|
324
|
-
} catch {
|
|
325
|
-
throw new Error("Failed to apply stash");
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
const refs = await git.listRefs({ fs, dir, prefix: "refs/stash" });
|
|
329
|
-
if (refs.length > 0) {
|
|
330
|
-
await git.deleteRef({ fs, dir, ref: refs[0] });
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
return "Stash popped";
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
export async function stashList(): Promise<StashEntry[]> {
|
|
337
|
-
try {
|
|
338
|
-
const stashLog = await gitLog({ fs, dir, ref: "refs/stash" });
|
|
339
|
-
return stashLog.map((c, i) => ({
|
|
340
|
-
index: i + 1,
|
|
341
|
-
description: c.commit.message,
|
|
342
|
-
}));
|
|
343
|
-
} catch {
|
|
344
|
-
return [];
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
export async function tag(name: string, message?: string): Promise<string> {
|
|
349
|
-
if (message) {
|
|
350
|
-
await git.tag({ fs, dir, ref: name, message });
|
|
351
|
-
} else {
|
|
352
|
-
await git.tag({ fs, dir, ref: name });
|
|
353
|
-
}
|
|
354
|
-
return name;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
export async function tagList(): Promise<string[]> {
|
|
358
|
-
return listTags({ fs, dir });
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
export async function fetch(): Promise<string> {
|
|
362
|
-
const token = await resolveToken();
|
|
363
|
-
const result = await git.fetch({
|
|
364
|
-
fs,
|
|
365
|
-
http,
|
|
366
|
-
dir,
|
|
367
|
-
token,
|
|
368
|
-
oauth2format: "github",
|
|
369
|
-
});
|
|
370
|
-
return result;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
export async function undo(): Promise<string> {
|
|
374
|
-
const commits = await gitLog({ fs, dir, depth: 1, ref: "HEAD" });
|
|
375
|
-
if (commits.length === 0) throw new Error("No commits to undo");
|
|
376
|
-
const c = commits[0];
|
|
377
|
-
if (c.commit.parent.length === 0) throw new Error("Cannot undo root commit");
|
|
378
|
-
|
|
379
|
-
const parentOid = c.commit.parent[0];
|
|
380
|
-
await git.writeRef({
|
|
381
|
-
fs,
|
|
382
|
-
dir,
|
|
383
|
-
ref: "HEAD",
|
|
384
|
-
value: parentOid.toString(),
|
|
385
|
-
force: true,
|
|
386
|
-
});
|
|
387
|
-
return `Undone to ${parentOid.toString().slice(0, 7)}`;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
export async function getCurrentBranch(): Promise<string> {
|
|
391
|
-
return (await currentBranch({ fs, dir })) || "";
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
export async function clone(
|
|
395
|
-
url: string,
|
|
396
|
-
targetDir?: string,
|
|
397
|
-
options?: {
|
|
398
|
-
depth?: number;
|
|
399
|
-
branch?: string;
|
|
400
|
-
recurseSubmodules?: boolean;
|
|
401
|
-
},
|
|
402
|
-
): Promise<string> {
|
|
403
|
-
const token = await resolveToken();
|
|
404
|
-
const cloneDir =
|
|
405
|
-
targetDir || url.split("/").pop()?.replace(".git", "") || "repo";
|
|
406
|
-
|
|
407
|
-
await git.clone({
|
|
408
|
-
fs,
|
|
409
|
-
http,
|
|
410
|
-
dir: cloneDir,
|
|
411
|
-
url,
|
|
412
|
-
singleBranch: !!options?.branch,
|
|
413
|
-
ref: options?.branch,
|
|
414
|
-
depth: options?.depth,
|
|
415
|
-
token,
|
|
416
|
-
oauth2format: "github",
|
|
417
|
-
});
|
|
418
|
-
|
|
419
|
-
return cloneDir;
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
export async function init(
|
|
423
|
-
targetDir?: string,
|
|
424
|
-
options?: { initialBranch?: string },
|
|
425
|
-
): Promise<void> {
|
|
426
|
-
const initDir = targetDir || ".";
|
|
427
|
-
await git.init({
|
|
428
|
-
fs,
|
|
429
|
-
dir: initDir,
|
|
430
|
-
defaultBranch: options?.initialBranch,
|
|
431
|
-
});
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
export async function remoteAdd(name: string, url: string): Promise<void> {
|
|
435
|
-
await git.addRemote({ fs, dir, remote: name, url });
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
export async function remoteRemove(name: string): Promise<void> {
|
|
439
|
-
await git.deleteRemote({ fs, dir, remote: name });
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
export async function remoteSetUrl(
|
|
443
|
-
name: string,
|
|
444
|
-
newUrl: string,
|
|
445
|
-
): Promise<void> {
|
|
446
|
-
await git.setConfig({
|
|
447
|
-
fs,
|
|
448
|
-
dir,
|
|
449
|
-
path: `remote.${name}.url`,
|
|
450
|
-
value: newUrl,
|
|
451
|
-
});
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
export async function remoteRename(
|
|
455
|
-
oldName: string,
|
|
456
|
-
newName: string,
|
|
457
|
-
): Promise<void> {
|
|
458
|
-
const url = await git.getConfig({
|
|
459
|
-
fs,
|
|
460
|
-
dir,
|
|
461
|
-
path: `remote.${oldName}.url`,
|
|
462
|
-
});
|
|
463
|
-
if (!url) throw new Error(`Remote '${oldName}' not found`);
|
|
464
|
-
|
|
465
|
-
await remoteAdd(newName, url);
|
|
466
|
-
await remoteRemove(oldName);
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
export async function remoteGetUrl(name: string): Promise<string> {
|
|
470
|
-
const url = await git.getConfig({
|
|
471
|
-
fs,
|
|
472
|
-
dir,
|
|
473
|
-
path: `remote.${name}.url`,
|
|
474
|
-
});
|
|
475
|
-
if (!url) throw new Error(`Remote '${name}' not found`);
|
|
476
|
-
return url;
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
export async function remoteList(): Promise<{ name: string; url: string }[]> {
|
|
480
|
-
const remotes = await listRemotes({ fs, dir });
|
|
481
|
-
return remotes.map((r) => ({ name: r.remote, url: r.url }));
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
export async function merge(
|
|
485
|
-
branch: string,
|
|
486
|
-
options?: {
|
|
487
|
-
noFF?: boolean;
|
|
488
|
-
squash?: boolean;
|
|
489
|
-
noCommit?: boolean;
|
|
490
|
-
},
|
|
491
|
-
): Promise<string> {
|
|
492
|
-
const name =
|
|
493
|
-
(await git.getConfig({ fs, dir, path: "user.name" })) || "Unknown";
|
|
494
|
-
const email =
|
|
495
|
-
(await git.getConfig({ fs, dir, path: "user.email" })) ||
|
|
496
|
-
"unknown@localhost";
|
|
497
|
-
|
|
498
|
-
const result = await git.merge({
|
|
499
|
-
fs,
|
|
500
|
-
dir,
|
|
501
|
-
theirs: branch,
|
|
502
|
-
ours: undefined,
|
|
503
|
-
noCommit: options?.noCommit,
|
|
504
|
-
squash: options?.squash,
|
|
505
|
-
fastForward: !options?.noFF,
|
|
506
|
-
author: { name, email },
|
|
507
|
-
committer: { name, email },
|
|
508
|
-
});
|
|
509
|
-
return result;
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
export default {
|
|
513
|
-
getStatus,
|
|
514
|
-
commit,
|
|
515
|
-
amendCommit,
|
|
516
|
-
add,
|
|
517
|
-
push,
|
|
518
|
-
pullRebase,
|
|
519
|
-
log,
|
|
520
|
-
diff,
|
|
521
|
-
diffStaged,
|
|
522
|
-
getBranches,
|
|
523
|
-
createBranch,
|
|
524
|
-
deleteBranch,
|
|
525
|
-
switchBranch,
|
|
526
|
-
stash,
|
|
527
|
-
stashPop,
|
|
528
|
-
stashList,
|
|
529
|
-
tag,
|
|
530
|
-
tagList,
|
|
531
|
-
fetch,
|
|
532
|
-
undo,
|
|
533
|
-
getCurrentBranch,
|
|
534
|
-
clone,
|
|
535
|
-
init,
|
|
536
|
-
remoteAdd,
|
|
537
|
-
remoteRemove,
|
|
538
|
-
remoteSetUrl,
|
|
539
|
-
remoteRename,
|
|
540
|
-
remoteGetUrl,
|
|
541
|
-
remoteList,
|
|
542
|
-
merge,
|
|
543
|
-
};
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { diffLines } from "diff";
|
|
4
|
+
import git, {
|
|
5
|
+
currentBranch,
|
|
6
|
+
log as gitLog,
|
|
7
|
+
listBranches,
|
|
8
|
+
listRemotes,
|
|
9
|
+
listTags,
|
|
10
|
+
statusMatrix,
|
|
11
|
+
} from "isomorphic-git";
|
|
12
|
+
import http from "isomorphic-git/http/node";
|
|
13
|
+
import { resolveToken } from "./auth.js";
|
|
14
|
+
|
|
15
|
+
const dir = process.cwd();
|
|
16
|
+
|
|
17
|
+
interface StatusResult {
|
|
18
|
+
current: string;
|
|
19
|
+
tracking: string;
|
|
20
|
+
ahead: number;
|
|
21
|
+
behind: number;
|
|
22
|
+
files: { path: string; working_dir: string; index: string }[];
|
|
23
|
+
recentCommits: { hash: string; date: string; message: string }[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface LogEntry {
|
|
27
|
+
hash: string;
|
|
28
|
+
date: string;
|
|
29
|
+
message: string;
|
|
30
|
+
author: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface BranchesResult {
|
|
34
|
+
current: string;
|
|
35
|
+
branches: string[];
|
|
36
|
+
all: string[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface StashEntry {
|
|
40
|
+
index: number;
|
|
41
|
+
description: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function statusChar(head: number, workdir: number): string {
|
|
45
|
+
if (workdir === 0) return "D";
|
|
46
|
+
if (head === 0 && workdir === 2) return "?";
|
|
47
|
+
if (workdir === 2) return "M";
|
|
48
|
+
return " ";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function indexChar(head: number, stage: number): string {
|
|
52
|
+
if (head === 0 && stage === 2) return "A";
|
|
53
|
+
if (stage === 0 && head === 1) return "D";
|
|
54
|
+
if (stage === 2 && head === 1) return "M";
|
|
55
|
+
return " ";
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function getStatus(): Promise<StatusResult> {
|
|
59
|
+
const matrix = await statusMatrix({ fs, dir });
|
|
60
|
+
|
|
61
|
+
const current = (await currentBranch({ fs, dir, fullname: false })) || "";
|
|
62
|
+
const logResult = await gitLog({ fs, dir, depth: 5 });
|
|
63
|
+
|
|
64
|
+
const files = matrix
|
|
65
|
+
.filter(
|
|
66
|
+
([_f, head, workdir, stage]) =>
|
|
67
|
+
head !== 1 || workdir !== 1 || stage !== 1,
|
|
68
|
+
)
|
|
69
|
+
.map(([filepath, head, workdir, stage]) => ({
|
|
70
|
+
path: filepath,
|
|
71
|
+
working_dir: statusChar(head as number, workdir as number),
|
|
72
|
+
index: indexChar(head as number, stage as number),
|
|
73
|
+
}));
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
current,
|
|
77
|
+
tracking: "",
|
|
78
|
+
ahead: 0,
|
|
79
|
+
behind: 0,
|
|
80
|
+
files,
|
|
81
|
+
recentCommits: logResult.map((c) => ({
|
|
82
|
+
hash: c.oid,
|
|
83
|
+
date: String(c.commit.author.timestamp),
|
|
84
|
+
message: c.commit.message,
|
|
85
|
+
})),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export async function commit(message: string): Promise<string> {
|
|
90
|
+
const name =
|
|
91
|
+
(await git.getConfig({ fs, dir, path: "user.name" })) || "Unknown";
|
|
92
|
+
const email =
|
|
93
|
+
(await git.getConfig({ fs, dir, path: "user.email" })) ||
|
|
94
|
+
"unknown@localhost";
|
|
95
|
+
|
|
96
|
+
const result = await git.commit({
|
|
97
|
+
fs,
|
|
98
|
+
dir,
|
|
99
|
+
message,
|
|
100
|
+
author: { name, email },
|
|
101
|
+
});
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export async function amendCommit(): Promise<void> {
|
|
106
|
+
const commits = await gitLog({ fs, dir, depth: 1 });
|
|
107
|
+
if (commits.length === 0) throw new Error("No commits to amend");
|
|
108
|
+
const { commit: c } = commits[0];
|
|
109
|
+
|
|
110
|
+
const name =
|
|
111
|
+
(await git.getConfig({ fs, dir, path: "user.name" })) || "Unknown";
|
|
112
|
+
const email =
|
|
113
|
+
(await git.getConfig({ fs, dir, path: "user.email" })) ||
|
|
114
|
+
"unknown@localhost";
|
|
115
|
+
|
|
116
|
+
await git.commit({
|
|
117
|
+
fs,
|
|
118
|
+
dir,
|
|
119
|
+
message: c.message,
|
|
120
|
+
author: c.author,
|
|
121
|
+
committer: { name, email },
|
|
122
|
+
tree: c.tree,
|
|
123
|
+
parent: c.parent.map((p) => p.toString()),
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export async function add(filepaths: string | string[]): Promise<void> {
|
|
128
|
+
const files = Array.isArray(filepaths) ? filepaths : [filepaths];
|
|
129
|
+
for (const filepath of files) {
|
|
130
|
+
await git.add({ fs, dir, filepath });
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export async function push(force: boolean = false): Promise<string> {
|
|
135
|
+
const token = await resolveToken();
|
|
136
|
+
const result = await git.push({
|
|
137
|
+
fs,
|
|
138
|
+
http,
|
|
139
|
+
dir,
|
|
140
|
+
force,
|
|
141
|
+
token,
|
|
142
|
+
oauth2format: "github",
|
|
143
|
+
});
|
|
144
|
+
return result;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export async function pullRebase(): Promise<string> {
|
|
148
|
+
const token = await resolveToken();
|
|
149
|
+
await git.fetch({ fs, http, dir, token, oauth2format: "github" });
|
|
150
|
+
const current = (await currentBranch({ fs, dir })) || "";
|
|
151
|
+
const result = await git.merge({
|
|
152
|
+
fs,
|
|
153
|
+
dir,
|
|
154
|
+
theirs: current,
|
|
155
|
+
fastForwardOnly: true,
|
|
156
|
+
});
|
|
157
|
+
return result;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export async function log(maxCount: number = 10): Promise<LogEntry[]> {
|
|
161
|
+
const result = await gitLog({ fs, dir, depth: maxCount, ref: "HEAD" });
|
|
162
|
+
return result.map((c) => ({
|
|
163
|
+
hash: c.oid,
|
|
164
|
+
date: String(c.commit.author.timestamp),
|
|
165
|
+
message: c.commit.message,
|
|
166
|
+
author: c.commit.author.name,
|
|
167
|
+
}));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export async function diff(): Promise<string> {
|
|
171
|
+
const matrix = await statusMatrix({ fs, dir });
|
|
172
|
+
const changed = matrix.filter(
|
|
173
|
+
([_f, head, workdir, _stage]) => head !== 1 || workdir !== 1,
|
|
174
|
+
);
|
|
175
|
+
if (changed.length === 0) return "";
|
|
176
|
+
|
|
177
|
+
const headOid = await git.resolveRef({ fs, dir, ref: "HEAD" });
|
|
178
|
+
const output: string[] = [];
|
|
179
|
+
|
|
180
|
+
for (const [filepath, head, workdir] of changed) {
|
|
181
|
+
let oldContent = "";
|
|
182
|
+
let newContent = "";
|
|
183
|
+
|
|
184
|
+
if ((head as number) !== 0) {
|
|
185
|
+
try {
|
|
186
|
+
const blob = await git.readBlob({
|
|
187
|
+
fs,
|
|
188
|
+
dir,
|
|
189
|
+
oid: headOid,
|
|
190
|
+
filepath,
|
|
191
|
+
});
|
|
192
|
+
oldContent = new TextDecoder().decode(blob.blob);
|
|
193
|
+
} catch {}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if ((workdir as number) !== 0) {
|
|
197
|
+
try {
|
|
198
|
+
newContent = readFileSync(join(dir, filepath), "utf-8");
|
|
199
|
+
} catch {}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
output.push(`diff --git a/${filepath} b/${filepath}`);
|
|
203
|
+
const patch = diffLines(oldContent, newContent);
|
|
204
|
+
for (const part of patch) {
|
|
205
|
+
if (part.added) {
|
|
206
|
+
for (const line of part.value.split("\n").filter(Boolean)) {
|
|
207
|
+
output.push(`+${line}`);
|
|
208
|
+
}
|
|
209
|
+
} else if (part.removed) {
|
|
210
|
+
for (const line of part.value.split("\n").filter(Boolean)) {
|
|
211
|
+
output.push(`-${line}`);
|
|
212
|
+
}
|
|
213
|
+
} else {
|
|
214
|
+
for (const line of part.value.split("\n").filter(Boolean)) {
|
|
215
|
+
output.push(` ${line}`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return output.join("\n");
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export async function diffStaged(): Promise<string> {
|
|
225
|
+
const matrix = await statusMatrix({ fs, dir });
|
|
226
|
+
const changed = matrix.filter(
|
|
227
|
+
([_f, head, _workdir, stage]) => head !== 1 || (stage as number) !== 1,
|
|
228
|
+
);
|
|
229
|
+
if (changed.length === 0) return "";
|
|
230
|
+
|
|
231
|
+
const headOid = await git.resolveRef({ fs, dir, ref: "HEAD" });
|
|
232
|
+
const output: string[] = [];
|
|
233
|
+
|
|
234
|
+
for (const [filepath, head, _workdir, stage] of changed) {
|
|
235
|
+
let oldContent = "";
|
|
236
|
+
let newContent = "";
|
|
237
|
+
|
|
238
|
+
if ((head as number) !== 0) {
|
|
239
|
+
try {
|
|
240
|
+
const blob = await git.readBlob({
|
|
241
|
+
fs,
|
|
242
|
+
dir,
|
|
243
|
+
oid: headOid,
|
|
244
|
+
filepath,
|
|
245
|
+
});
|
|
246
|
+
oldContent = new TextDecoder().decode(blob.blob);
|
|
247
|
+
} catch {}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if ((stage as number) !== 0) {
|
|
251
|
+
try {
|
|
252
|
+
const blob = await git.readBlob({
|
|
253
|
+
fs,
|
|
254
|
+
dir,
|
|
255
|
+
oid: headOid,
|
|
256
|
+
filepath,
|
|
257
|
+
});
|
|
258
|
+
newContent = new TextDecoder().decode(blob.blob);
|
|
259
|
+
} catch {}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
output.push(`diff --git a/${filepath} b/${filepath} (staged)`);
|
|
263
|
+
const patch = diffLines(oldContent, newContent);
|
|
264
|
+
for (const part of patch) {
|
|
265
|
+
if (part.added) {
|
|
266
|
+
for (const line of part.value.split("\n").filter(Boolean)) {
|
|
267
|
+
output.push(`+${line}`);
|
|
268
|
+
}
|
|
269
|
+
} else if (part.removed) {
|
|
270
|
+
for (const line of part.value.split("\n").filter(Boolean)) {
|
|
271
|
+
output.push(`-${line}`);
|
|
272
|
+
}
|
|
273
|
+
} else {
|
|
274
|
+
for (const line of part.value.split("\n").filter(Boolean)) {
|
|
275
|
+
output.push(` ${line}`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return output.join("\n");
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export async function getBranches(): Promise<BranchesResult> {
|
|
285
|
+
const all = await listBranches({ fs, dir });
|
|
286
|
+
const current = (await currentBranch({ fs, dir, fullname: false })) || "";
|
|
287
|
+
return {
|
|
288
|
+
current,
|
|
289
|
+
branches: all.filter((b) => b !== current),
|
|
290
|
+
all,
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export async function createBranch(name: string): Promise<void> {
|
|
295
|
+
await git.branch({ fs, dir, ref: name });
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export async function deleteBranch(
|
|
299
|
+
name: string,
|
|
300
|
+
force: boolean = false,
|
|
301
|
+
): Promise<void> {
|
|
302
|
+
if (force) {
|
|
303
|
+
await git.deleteBranch({ fs, dir, ref: name });
|
|
304
|
+
} else {
|
|
305
|
+
await git.deleteBranch({ fs, dir, ref: name });
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export async function switchBranch(name: string): Promise<void> {
|
|
310
|
+
await git.checkout({ fs, dir, ref: name });
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
export async function stash(): Promise<string> {
|
|
314
|
+
await git.stash({ fs, dir });
|
|
315
|
+
return "Stash saved";
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export async function stashPop(): Promise<string> {
|
|
319
|
+
const stashes = await stashList();
|
|
320
|
+
if (stashes.length === 0) throw new Error("No stashes to pop");
|
|
321
|
+
|
|
322
|
+
try {
|
|
323
|
+
await git.stash({ fs, dir });
|
|
324
|
+
} catch {
|
|
325
|
+
throw new Error("Failed to apply stash");
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const refs = await git.listRefs({ fs, dir, prefix: "refs/stash" });
|
|
329
|
+
if (refs.length > 0) {
|
|
330
|
+
await git.deleteRef({ fs, dir, ref: refs[0] });
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return "Stash popped";
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
export async function stashList(): Promise<StashEntry[]> {
|
|
337
|
+
try {
|
|
338
|
+
const stashLog = await gitLog({ fs, dir, ref: "refs/stash" });
|
|
339
|
+
return stashLog.map((c, i) => ({
|
|
340
|
+
index: i + 1,
|
|
341
|
+
description: c.commit.message,
|
|
342
|
+
}));
|
|
343
|
+
} catch {
|
|
344
|
+
return [];
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
export async function tag(name: string, message?: string): Promise<string> {
|
|
349
|
+
if (message) {
|
|
350
|
+
await git.tag({ fs, dir, ref: name, message });
|
|
351
|
+
} else {
|
|
352
|
+
await git.tag({ fs, dir, ref: name });
|
|
353
|
+
}
|
|
354
|
+
return name;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
export async function tagList(): Promise<string[]> {
|
|
358
|
+
return listTags({ fs, dir });
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
export async function fetch(): Promise<string> {
|
|
362
|
+
const token = await resolveToken();
|
|
363
|
+
const result = await git.fetch({
|
|
364
|
+
fs,
|
|
365
|
+
http,
|
|
366
|
+
dir,
|
|
367
|
+
token,
|
|
368
|
+
oauth2format: "github",
|
|
369
|
+
});
|
|
370
|
+
return result;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
export async function undo(): Promise<string> {
|
|
374
|
+
const commits = await gitLog({ fs, dir, depth: 1, ref: "HEAD" });
|
|
375
|
+
if (commits.length === 0) throw new Error("No commits to undo");
|
|
376
|
+
const c = commits[0];
|
|
377
|
+
if (c.commit.parent.length === 0) throw new Error("Cannot undo root commit");
|
|
378
|
+
|
|
379
|
+
const parentOid = c.commit.parent[0];
|
|
380
|
+
await git.writeRef({
|
|
381
|
+
fs,
|
|
382
|
+
dir,
|
|
383
|
+
ref: "HEAD",
|
|
384
|
+
value: parentOid.toString(),
|
|
385
|
+
force: true,
|
|
386
|
+
});
|
|
387
|
+
return `Undone to ${parentOid.toString().slice(0, 7)}`;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
export async function getCurrentBranch(): Promise<string> {
|
|
391
|
+
return (await currentBranch({ fs, dir })) || "";
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
export async function clone(
|
|
395
|
+
url: string,
|
|
396
|
+
targetDir?: string,
|
|
397
|
+
options?: {
|
|
398
|
+
depth?: number;
|
|
399
|
+
branch?: string;
|
|
400
|
+
recurseSubmodules?: boolean;
|
|
401
|
+
},
|
|
402
|
+
): Promise<string> {
|
|
403
|
+
const token = await resolveToken();
|
|
404
|
+
const cloneDir =
|
|
405
|
+
targetDir || url.split("/").pop()?.replace(".git", "") || "repo";
|
|
406
|
+
|
|
407
|
+
await git.clone({
|
|
408
|
+
fs,
|
|
409
|
+
http,
|
|
410
|
+
dir: cloneDir,
|
|
411
|
+
url,
|
|
412
|
+
singleBranch: !!options?.branch,
|
|
413
|
+
ref: options?.branch,
|
|
414
|
+
depth: options?.depth,
|
|
415
|
+
token,
|
|
416
|
+
oauth2format: "github",
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
return cloneDir;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
export async function init(
|
|
423
|
+
targetDir?: string,
|
|
424
|
+
options?: { initialBranch?: string },
|
|
425
|
+
): Promise<void> {
|
|
426
|
+
const initDir = targetDir || ".";
|
|
427
|
+
await git.init({
|
|
428
|
+
fs,
|
|
429
|
+
dir: initDir,
|
|
430
|
+
defaultBranch: options?.initialBranch,
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
export async function remoteAdd(name: string, url: string): Promise<void> {
|
|
435
|
+
await git.addRemote({ fs, dir, remote: name, url });
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
export async function remoteRemove(name: string): Promise<void> {
|
|
439
|
+
await git.deleteRemote({ fs, dir, remote: name });
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
export async function remoteSetUrl(
|
|
443
|
+
name: string,
|
|
444
|
+
newUrl: string,
|
|
445
|
+
): Promise<void> {
|
|
446
|
+
await git.setConfig({
|
|
447
|
+
fs,
|
|
448
|
+
dir,
|
|
449
|
+
path: `remote.${name}.url`,
|
|
450
|
+
value: newUrl,
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
export async function remoteRename(
|
|
455
|
+
oldName: string,
|
|
456
|
+
newName: string,
|
|
457
|
+
): Promise<void> {
|
|
458
|
+
const url = await git.getConfig({
|
|
459
|
+
fs,
|
|
460
|
+
dir,
|
|
461
|
+
path: `remote.${oldName}.url`,
|
|
462
|
+
});
|
|
463
|
+
if (!url) throw new Error(`Remote '${oldName}' not found`);
|
|
464
|
+
|
|
465
|
+
await remoteAdd(newName, url);
|
|
466
|
+
await remoteRemove(oldName);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
export async function remoteGetUrl(name: string): Promise<string> {
|
|
470
|
+
const url = await git.getConfig({
|
|
471
|
+
fs,
|
|
472
|
+
dir,
|
|
473
|
+
path: `remote.${name}.url`,
|
|
474
|
+
});
|
|
475
|
+
if (!url) throw new Error(`Remote '${name}' not found`);
|
|
476
|
+
return url;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
export async function remoteList(): Promise<{ name: string; url: string }[]> {
|
|
480
|
+
const remotes = await listRemotes({ fs, dir });
|
|
481
|
+
return remotes.map((r) => ({ name: r.remote, url: r.url }));
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
export async function merge(
|
|
485
|
+
branch: string,
|
|
486
|
+
options?: {
|
|
487
|
+
noFF?: boolean;
|
|
488
|
+
squash?: boolean;
|
|
489
|
+
noCommit?: boolean;
|
|
490
|
+
},
|
|
491
|
+
): Promise<string> {
|
|
492
|
+
const name =
|
|
493
|
+
(await git.getConfig({ fs, dir, path: "user.name" })) || "Unknown";
|
|
494
|
+
const email =
|
|
495
|
+
(await git.getConfig({ fs, dir, path: "user.email" })) ||
|
|
496
|
+
"unknown@localhost";
|
|
497
|
+
|
|
498
|
+
const result = await git.merge({
|
|
499
|
+
fs,
|
|
500
|
+
dir,
|
|
501
|
+
theirs: branch,
|
|
502
|
+
ours: undefined,
|
|
503
|
+
noCommit: options?.noCommit,
|
|
504
|
+
squash: options?.squash,
|
|
505
|
+
fastForward: !options?.noFF,
|
|
506
|
+
author: { name, email },
|
|
507
|
+
committer: { name, email },
|
|
508
|
+
});
|
|
509
|
+
return result;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
export default {
|
|
513
|
+
getStatus,
|
|
514
|
+
commit,
|
|
515
|
+
amendCommit,
|
|
516
|
+
add,
|
|
517
|
+
push,
|
|
518
|
+
pullRebase,
|
|
519
|
+
log,
|
|
520
|
+
diff,
|
|
521
|
+
diffStaged,
|
|
522
|
+
getBranches,
|
|
523
|
+
createBranch,
|
|
524
|
+
deleteBranch,
|
|
525
|
+
switchBranch,
|
|
526
|
+
stash,
|
|
527
|
+
stashPop,
|
|
528
|
+
stashList,
|
|
529
|
+
tag,
|
|
530
|
+
tagList,
|
|
531
|
+
fetch,
|
|
532
|
+
undo,
|
|
533
|
+
getCurrentBranch,
|
|
534
|
+
clone,
|
|
535
|
+
init,
|
|
536
|
+
remoteAdd,
|
|
537
|
+
remoteRemove,
|
|
538
|
+
remoteSetUrl,
|
|
539
|
+
remoteRename,
|
|
540
|
+
remoteGetUrl,
|
|
541
|
+
remoteList,
|
|
542
|
+
merge,
|
|
543
|
+
};
|