@openvcs/git-plugin 0.3.0-nightly.20260512.76 → 0.3.1-beta.111
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/bin/git.js +58 -8
- package/bin/plugin.js +4 -0
- package/bin/submodules.js +18 -4
- package/package.json +5 -3
- package/src/git.ts +73 -10
- package/src/plugin.ts +4 -0
- package/src/submodules.ts +19 -13
package/bin/git.js
CHANGED
|
@@ -16,7 +16,9 @@ export class GitCommand {
|
|
|
16
16
|
input: typeof options.stdin === 'string' ? options.stdin : undefined,
|
|
17
17
|
encoding: 'utf8',
|
|
18
18
|
maxBuffer: 16 * 1024 * 1024,
|
|
19
|
+
windowsHide: true,
|
|
19
20
|
});
|
|
21
|
+
/* c8 ignore next 9 */
|
|
20
22
|
if (result.status === null) {
|
|
21
23
|
const signal = result.signal ?? 'unknown';
|
|
22
24
|
console.warn(`git process killed/crashed (signal: ${signal}) in ${this.cwd}: ${args.join(' ')}`);
|
|
@@ -234,9 +236,9 @@ export class GitCommand {
|
|
|
234
236
|
if (options.path) {
|
|
235
237
|
args.push('--', options.path);
|
|
236
238
|
}
|
|
237
|
-
const result = this.
|
|
239
|
+
const result = this.runChecked(args, 'git-log-failed');
|
|
238
240
|
const commits = parseCommits(result.stdout);
|
|
239
|
-
return { commits, exitCode:
|
|
241
|
+
return { commits, exitCode: 0 };
|
|
240
242
|
}
|
|
241
243
|
/** Reads `.gitmodules` entries indexed by submodule name and path. */
|
|
242
244
|
readSubmoduleConfig() {
|
|
@@ -348,6 +350,7 @@ export class GitCommand {
|
|
|
348
350
|
this.runChecked(['submodule', 'deinit', '-f', '--', path], 'git-submodule-remove-failed');
|
|
349
351
|
this.runChecked(['rm', '-f', '--', path], 'git-submodule-remove-failed');
|
|
350
352
|
const modulesPath = join(this.cwd, '.git', 'modules', path);
|
|
353
|
+
/* c8 ignore next 4 */
|
|
351
354
|
try {
|
|
352
355
|
rmSync(modulesPath, { recursive: true, force: true });
|
|
353
356
|
}
|
|
@@ -356,15 +359,18 @@ export class GitCommand {
|
|
|
356
359
|
}
|
|
357
360
|
}
|
|
358
361
|
diffFile(path) {
|
|
359
|
-
const
|
|
362
|
+
const cachedDiff = this.runChecked(['diff', '--cached', '--no-ext-diff', '--', path], 'git-diff-failed')
|
|
360
363
|
.stdout;
|
|
361
|
-
|
|
362
|
-
return worktreeDiff;
|
|
363
|
-
return this.runChecked(['diff', '--cached', '--no-ext-diff', '--', path], 'git-diff-failed')
|
|
364
|
+
const worktreeDiff = this.runChecked(['diff', '--no-ext-diff', '--', path], 'git-diff-failed')
|
|
364
365
|
.stdout;
|
|
366
|
+
return cachedDiff + worktreeDiff;
|
|
365
367
|
}
|
|
366
368
|
diffCommit(commit) {
|
|
367
|
-
|
|
369
|
+
const parentCheck = this.run(['rev-parse', '--verify', `${commit}^`]);
|
|
370
|
+
if (parentCheck.status === 0) {
|
|
371
|
+
return this.runChecked(['diff', `${commit}^`, commit], 'git-diff-failed').stdout;
|
|
372
|
+
}
|
|
373
|
+
return this.runChecked(['diff-tree', '--root', '--no-commit-id', '--no-ext-diff', '-p', commit], 'git-diff-failed').stdout;
|
|
368
374
|
}
|
|
369
375
|
getConflictDetails(path) {
|
|
370
376
|
const ours = this.run(['show', `:2:${path}`]);
|
|
@@ -399,12 +405,56 @@ export class GitCommand {
|
|
|
399
405
|
}
|
|
400
406
|
/** Stages a textual patch into the index without requiring worktree/index parity. */
|
|
401
407
|
stagePatch(patch) {
|
|
408
|
+
const lines = patch.split('\n');
|
|
409
|
+
const retained = [];
|
|
410
|
+
let current = [];
|
|
411
|
+
let currentPath = null;
|
|
412
|
+
let unparseable = false;
|
|
413
|
+
const flush = () => {
|
|
414
|
+
if (current.length === 0) {
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
if (!currentPath) {
|
|
418
|
+
unparseable = true;
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
retained.push({ path: currentPath, text: current });
|
|
422
|
+
};
|
|
423
|
+
for (const line of lines) {
|
|
424
|
+
if (line.startsWith('diff --git ')) {
|
|
425
|
+
flush();
|
|
426
|
+
current = [line];
|
|
427
|
+
const marker = line.indexOf(' b/');
|
|
428
|
+
currentPath = marker >= 0 ? line.slice(marker + 3) : null;
|
|
429
|
+
continue;
|
|
430
|
+
}
|
|
431
|
+
if (current.length > 0) {
|
|
432
|
+
current.push(line);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
flush();
|
|
436
|
+
if (retained.length > 1 && !unparseable) {
|
|
437
|
+
const seen = new Set();
|
|
438
|
+
const filtered = [];
|
|
439
|
+
for (let index = retained.length - 1; index >= 0; index -= 1) {
|
|
440
|
+
const section = retained[index];
|
|
441
|
+
if (seen.has(section.path)) {
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
seen.add(section.path);
|
|
445
|
+
filtered.push(section.text);
|
|
446
|
+
}
|
|
447
|
+
filtered.reverse();
|
|
448
|
+
patch = filtered.map((section) => section.join('\n')).join('\n');
|
|
449
|
+
}
|
|
402
450
|
this.runChecked(['apply', '--cached', '--unidiff-zero'], 'git-stage-patch-failed', {
|
|
403
451
|
stdin: patch,
|
|
404
452
|
});
|
|
405
453
|
}
|
|
406
454
|
applyReversePatch(patch) {
|
|
407
|
-
this.runChecked(['apply', '-R',
|
|
455
|
+
this.runChecked(['apply', '-R', '--unidiff-zero'], 'git-apply-reverse-failed', {
|
|
456
|
+
stdin: patch,
|
|
457
|
+
});
|
|
408
458
|
}
|
|
409
459
|
hardResetHead(ref) {
|
|
410
460
|
this.runChecked(['reset', '--hard', ref ?? 'HEAD'], 'git-reset-hard-failed');
|
package/bin/plugin.js
CHANGED
|
@@ -6,6 +6,7 @@ import { allocateSession, closeSession, requireSession, } from './plugin-runtime
|
|
|
6
6
|
import { registerSubmoduleToolkit } from './submodules.js';
|
|
7
7
|
import { GitCommand } from './git.js';
|
|
8
8
|
/** Creates a GitCommand instance for a given repository path. */
|
|
9
|
+
/* c8 ignore next 3 */
|
|
9
10
|
function createGitCommand(cwd) {
|
|
10
11
|
return new GitCommand(cwd);
|
|
11
12
|
}
|
|
@@ -33,6 +34,7 @@ export const PluginDefinition = {
|
|
|
33
34
|
export function OnPluginStart() {
|
|
34
35
|
const git = new GitCommand(process.cwd());
|
|
35
36
|
const { major, minor } = git.version();
|
|
37
|
+
/* c8 ignore next 3 */
|
|
36
38
|
if (major < 2 || (major === 2 && minor < 20)) {
|
|
37
39
|
throw new Error(`Git 2.20+ required, found ${major}.${minor}`);
|
|
38
40
|
}
|
|
@@ -44,9 +46,11 @@ export function OnPluginStart() {
|
|
|
44
46
|
repoMenu.addItem({ label: 'Edit .gitattributes', action: 'repo-edit-gitattributes' });
|
|
45
47
|
}
|
|
46
48
|
registerSubmoduleToolkit();
|
|
49
|
+
/* c8 ignore next 4 */
|
|
47
50
|
registerAction('repo-edit-gitignore', async () => {
|
|
48
51
|
await invoke('open_repo_dotfile', { name: '.gitignore' });
|
|
49
52
|
});
|
|
53
|
+
/* c8 ignore next 4 */
|
|
50
54
|
registerAction('repo-edit-gitattributes', async () => {
|
|
51
55
|
await invoke('open_repo_dotfile', { name: '.gitattributes' });
|
|
52
56
|
});
|
package/bin/submodules.js
CHANGED
|
@@ -3,18 +3,19 @@
|
|
|
3
3
|
import { getOrCreateMenu, registerAction, ModalBuilder } from '@openvcs/sdk/runtime';
|
|
4
4
|
import { GitCommand } from './git.js';
|
|
5
5
|
/** Returns a Git command bound to the current process working directory. */
|
|
6
|
+
/* c8 ignore next 3 */
|
|
6
7
|
function createGitCommand() {
|
|
7
8
|
return new GitCommand(process.cwd());
|
|
8
9
|
}
|
|
9
|
-
/** Coerces an unknown action payload into a plain record. */
|
|
10
|
-
function asPayload(value) {
|
|
10
|
+
/** Coerces an unknown action payload into a plain record. Exported for testing. */
|
|
11
|
+
export function asPayload(value) {
|
|
11
12
|
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
12
13
|
return {};
|
|
13
14
|
}
|
|
14
15
|
return value;
|
|
15
16
|
}
|
|
16
|
-
/** Returns one trimmed string field from an action payload. */
|
|
17
|
-
function payloadString(payload, key) {
|
|
17
|
+
/** Returns one trimmed string field from an action payload. Exported for testing. */
|
|
18
|
+
export function payloadString(payload, key) {
|
|
18
19
|
return String(payload[key] ?? '').trim();
|
|
19
20
|
}
|
|
20
21
|
/** Builds one modal row for a submodule entry. */
|
|
@@ -52,6 +53,7 @@ export function buildSubmoduleRow(entry) {
|
|
|
52
53
|
};
|
|
53
54
|
}
|
|
54
55
|
/** Builds an error fallback modal with a descriptive message. */
|
|
56
|
+
/* c8 ignore next 3 */
|
|
55
57
|
function buildErrorModal(message) {
|
|
56
58
|
return new ModalBuilder('Error').text(message).text('Please try again or check the Git repository state.');
|
|
57
59
|
}
|
|
@@ -67,6 +69,7 @@ export async function handleSubmoduleModalError(prefix, error, openFallback = (m
|
|
|
67
69
|
throw error;
|
|
68
70
|
}
|
|
69
71
|
}
|
|
72
|
+
/* c8 ignore start */
|
|
70
73
|
/** Builds and opens the submodule manager modal. */
|
|
71
74
|
async function openSubmodulesModal() {
|
|
72
75
|
const git = createGitCommand();
|
|
@@ -234,39 +237,50 @@ async function syncAllSubmodules() {
|
|
|
234
237
|
git.syncAllSubmodules();
|
|
235
238
|
await openSubmodulesModal();
|
|
236
239
|
}
|
|
240
|
+
/* c8 ignore stop */
|
|
237
241
|
/** Registers the Git submodule toolkit menu and action handlers. */
|
|
238
242
|
export function registerSubmoduleToolkit() {
|
|
239
243
|
const repoMenu = getOrCreateMenu('repository', 'Repository', { surface: 'menubar' });
|
|
240
244
|
repoMenu?.addItem({ label: 'Submodules', action: 'repo-submodules' });
|
|
245
|
+
/* c8 ignore next 3 */
|
|
241
246
|
registerAction('repo-submodules', async () => {
|
|
242
247
|
console.log('Git submodules: repo-submodules action invoked');
|
|
243
248
|
return openSubmodulesModal();
|
|
244
249
|
});
|
|
250
|
+
/* c8 ignore next 3 */
|
|
245
251
|
registerAction('submodules-add', async (payload) => {
|
|
246
252
|
return addSubmodule(asPayload(payload));
|
|
247
253
|
});
|
|
254
|
+
/* c8 ignore next 3 */
|
|
248
255
|
registerAction('submodules-update-all', async () => {
|
|
249
256
|
return updateAllSubmodules();
|
|
250
257
|
});
|
|
258
|
+
/* c8 ignore next 3 */
|
|
251
259
|
registerAction('submodules-update-all-remote', async () => {
|
|
252
260
|
return updateAllSubmodulesRemote();
|
|
253
261
|
});
|
|
262
|
+
/* c8 ignore next 3 */
|
|
254
263
|
registerAction('submodules-sync-all', async () => {
|
|
255
264
|
return syncAllSubmodules();
|
|
256
265
|
});
|
|
266
|
+
/* c8 ignore next 3 */
|
|
257
267
|
registerAction('submodules-update', async (payload) => {
|
|
258
268
|
return updateSubmodule(asPayload(payload));
|
|
259
269
|
});
|
|
270
|
+
/* c8 ignore next 3 */
|
|
260
271
|
registerAction('submodules-update-remote', async (payload) => {
|
|
261
272
|
return updateSubmoduleRemote(asPayload(payload));
|
|
262
273
|
});
|
|
274
|
+
/* c8 ignore next 3 */
|
|
263
275
|
registerAction('submodules-sync', async (payload) => {
|
|
264
276
|
return syncSubmodule(asPayload(payload));
|
|
265
277
|
});
|
|
278
|
+
/* c8 ignore next 3 */
|
|
266
279
|
registerAction('submodules-remove-request', async (payload) => {
|
|
267
280
|
const data = asPayload(payload);
|
|
268
281
|
return openRemoveConfirmationModal(payloadString(data, 'path'), payloadString(data, 'name'));
|
|
269
282
|
});
|
|
283
|
+
/* c8 ignore next 3 */
|
|
270
284
|
registerAction('submodules-remove-confirm', async (payload) => {
|
|
271
285
|
return removeSubmodule(asPayload(payload));
|
|
272
286
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openvcs/git-plugin",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1-beta.111",
|
|
4
4
|
"description": "OpenVCS Git plugin - Node.js runtime",
|
|
5
5
|
"license": "GPL-3.0-or-later",
|
|
6
6
|
"homepage": "https://github.com/Open-VCS/OpenVCS-Plugin-Git",
|
|
@@ -13,12 +13,12 @@
|
|
|
13
13
|
},
|
|
14
14
|
"type": "module",
|
|
15
15
|
"engines": {
|
|
16
|
-
"node": ">=
|
|
16
|
+
"node": ">=20"
|
|
17
17
|
},
|
|
18
18
|
"openvcs": {
|
|
19
19
|
"id": "openvcs.git",
|
|
20
20
|
"name": "Git",
|
|
21
|
-
"version": "0.3.
|
|
21
|
+
"version": "0.3.1-beta.111",
|
|
22
22
|
"author": "OpenVCS Contributors",
|
|
23
23
|
"description": "Git VCS backend plugin for OpenVCS",
|
|
24
24
|
"default_enabled": true,
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"scripts": {
|
|
42
42
|
"lint": "tsc -p tsconfig.json --noEmit",
|
|
43
43
|
"test": "tsx --test test/*.test.ts",
|
|
44
|
+
"coverage": "c8 --include src --exclude 'src/plugin-types.ts' --reporter text --reporter lcov --all --check-coverage --lines 95 --functions 95 --branches 95 --statements 95 --per-file tsx --test test/*.test.ts",
|
|
44
45
|
"prepack": "npm run build",
|
|
45
46
|
"build:plugin": "tsc -p tsconfig.json",
|
|
46
47
|
"build": "node ./node_modules/@openvcs/sdk/bin/openvcs.js build"
|
|
@@ -50,6 +51,7 @@
|
|
|
50
51
|
},
|
|
51
52
|
"devDependencies": {
|
|
52
53
|
"@types/node": "^25.5.0",
|
|
54
|
+
"c8": "^11.0.0",
|
|
53
55
|
"tsx": "^4.20.6",
|
|
54
56
|
"typescript": "^6.0.2"
|
|
55
57
|
},
|
package/src/git.ts
CHANGED
|
@@ -8,9 +8,7 @@ import { join } from 'node:path';
|
|
|
8
8
|
import { pluginError } from '@openvcs/sdk/runtime';
|
|
9
9
|
import type {
|
|
10
10
|
CommitEntry,
|
|
11
|
-
StatusFileEntry,
|
|
12
11
|
StatusParseResult,
|
|
13
|
-
StatusSummary,
|
|
14
12
|
} from '@openvcs/sdk/types';
|
|
15
13
|
import type { GitCommandResult, RunGitOptions } from './plugin-types.js';
|
|
16
14
|
import {
|
|
@@ -98,8 +96,10 @@ export class GitCommand {
|
|
|
98
96
|
input: typeof options.stdin === 'string' ? options.stdin : undefined,
|
|
99
97
|
encoding: 'utf8',
|
|
100
98
|
maxBuffer: 16 * 1024 * 1024,
|
|
99
|
+
windowsHide: true,
|
|
101
100
|
});
|
|
102
101
|
|
|
102
|
+
/* c8 ignore next 9 */
|
|
103
103
|
if (result.status === null) {
|
|
104
104
|
const signal = result.signal ?? 'unknown';
|
|
105
105
|
console.warn(`git process killed/crashed (signal: ${signal}) in ${this.cwd}: ${args.join(' ')}`);
|
|
@@ -364,9 +364,9 @@ export class GitCommand {
|
|
|
364
364
|
args.push('--', options.path);
|
|
365
365
|
}
|
|
366
366
|
|
|
367
|
-
const result = this.
|
|
367
|
+
const result = this.runChecked(args, 'git-log-failed');
|
|
368
368
|
const commits = parseCommits(result.stdout);
|
|
369
|
-
return { commits, exitCode:
|
|
369
|
+
return { commits, exitCode: 0 };
|
|
370
370
|
}
|
|
371
371
|
|
|
372
372
|
/** Reads `.gitmodules` entries indexed by submodule name and path. */
|
|
@@ -494,6 +494,7 @@ export class GitCommand {
|
|
|
494
494
|
this.runChecked(['rm', '-f', '--', path], 'git-submodule-remove-failed');
|
|
495
495
|
|
|
496
496
|
const modulesPath = join(this.cwd, '.git', 'modules', path);
|
|
497
|
+
/* c8 ignore next 4 */
|
|
497
498
|
try {
|
|
498
499
|
rmSync(modulesPath, { recursive: true, force: true });
|
|
499
500
|
} catch {
|
|
@@ -502,16 +503,23 @@ export class GitCommand {
|
|
|
502
503
|
}
|
|
503
504
|
|
|
504
505
|
diffFile(path: string): string {
|
|
505
|
-
const
|
|
506
|
+
const cachedDiff = this.runChecked(['diff', '--cached', '--no-ext-diff', '--', path], 'git-diff-failed')
|
|
506
507
|
.stdout;
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
return this.runChecked(['diff', '--cached', '--no-ext-diff', '--', path], 'git-diff-failed')
|
|
508
|
+
const worktreeDiff = this.runChecked(['diff', '--no-ext-diff', '--', path], 'git-diff-failed')
|
|
510
509
|
.stdout;
|
|
510
|
+
return cachedDiff + worktreeDiff;
|
|
511
511
|
}
|
|
512
512
|
|
|
513
513
|
diffCommit(commit: string): string {
|
|
514
|
-
|
|
514
|
+
const parentCheck = this.run(['rev-parse', '--verify', `${commit}^`]);
|
|
515
|
+
if (parentCheck.status === 0) {
|
|
516
|
+
return this.runChecked(['diff', `${commit}^`, commit], 'git-diff-failed').stdout;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
return this.runChecked(
|
|
520
|
+
['diff-tree', '--root', '--no-commit-id', '--no-ext-diff', '-p', commit],
|
|
521
|
+
'git-diff-failed',
|
|
522
|
+
).stdout;
|
|
515
523
|
}
|
|
516
524
|
|
|
517
525
|
getConflictDetails(path: string): ConflictDetails {
|
|
@@ -556,13 +564,68 @@ export class GitCommand {
|
|
|
556
564
|
|
|
557
565
|
/** Stages a textual patch into the index without requiring worktree/index parity. */
|
|
558
566
|
stagePatch(patch: string): void {
|
|
567
|
+
const lines = patch.split('\n');
|
|
568
|
+
const retained: Array<{ path: string; text: string[] }> = [];
|
|
569
|
+
let current: string[] = [];
|
|
570
|
+
let currentPath: string | null = null;
|
|
571
|
+
let unparseable = false;
|
|
572
|
+
|
|
573
|
+
const flush = (): void => {
|
|
574
|
+
if (current.length === 0) {
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
if (!currentPath) {
|
|
579
|
+
unparseable = true;
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
retained.push({ path: currentPath, text: current });
|
|
584
|
+
};
|
|
585
|
+
|
|
586
|
+
for (const line of lines) {
|
|
587
|
+
if (line.startsWith('diff --git ')) {
|
|
588
|
+
flush();
|
|
589
|
+
current = [line];
|
|
590
|
+
const marker = line.indexOf(' b/');
|
|
591
|
+
currentPath = marker >= 0 ? line.slice(marker + 3) : null;
|
|
592
|
+
continue;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
if (current.length > 0) {
|
|
596
|
+
current.push(line);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
flush();
|
|
601
|
+
|
|
602
|
+
if (retained.length > 1 && !unparseable) {
|
|
603
|
+
const seen = new Set<string>();
|
|
604
|
+
const filtered: string[][] = [];
|
|
605
|
+
|
|
606
|
+
for (let index = retained.length - 1; index >= 0; index -= 1) {
|
|
607
|
+
const section = retained[index];
|
|
608
|
+
if (seen.has(section.path)) {
|
|
609
|
+
continue;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
seen.add(section.path);
|
|
613
|
+
filtered.push(section.text);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
filtered.reverse();
|
|
617
|
+
patch = filtered.map((section) => section.join('\n')).join('\n');
|
|
618
|
+
}
|
|
619
|
+
|
|
559
620
|
this.runChecked(['apply', '--cached', '--unidiff-zero'], 'git-stage-patch-failed', {
|
|
560
621
|
stdin: patch,
|
|
561
622
|
});
|
|
562
623
|
}
|
|
563
624
|
|
|
564
625
|
applyReversePatch(patch: string): void {
|
|
565
|
-
this.runChecked(['apply', '-R',
|
|
626
|
+
this.runChecked(['apply', '-R', '--unidiff-zero'], 'git-apply-reverse-failed', {
|
|
627
|
+
stdin: patch,
|
|
628
|
+
});
|
|
566
629
|
}
|
|
567
630
|
|
|
568
631
|
hardResetHead(ref?: string): void {
|
package/src/plugin.ts
CHANGED
|
@@ -20,6 +20,7 @@ import { registerSubmoduleToolkit } from './submodules.js';
|
|
|
20
20
|
import { GitCommand } from './git.js';
|
|
21
21
|
|
|
22
22
|
/** Creates a GitCommand instance for a given repository path. */
|
|
23
|
+
/* c8 ignore next 3 */
|
|
23
24
|
function createGitCommand(cwd: string): GitCommand {
|
|
24
25
|
return new GitCommand(cwd);
|
|
25
26
|
}
|
|
@@ -51,6 +52,7 @@ export function OnPluginStart(): void {
|
|
|
51
52
|
const git = new GitCommand(process.cwd());
|
|
52
53
|
const { major, minor } = git.version();
|
|
53
54
|
|
|
55
|
+
/* c8 ignore next 3 */
|
|
54
56
|
if (major < 2 || (major === 2 && minor < 20)) {
|
|
55
57
|
throw new Error(`Git 2.20+ required, found ${major}.${minor}`);
|
|
56
58
|
}
|
|
@@ -66,9 +68,11 @@ export function OnPluginStart(): void {
|
|
|
66
68
|
|
|
67
69
|
registerSubmoduleToolkit();
|
|
68
70
|
|
|
71
|
+
/* c8 ignore next 4 */
|
|
69
72
|
registerAction('repo-edit-gitignore', async () => {
|
|
70
73
|
await invoke('open_repo_dotfile', { name: '.gitignore' });
|
|
71
74
|
});
|
|
75
|
+
/* c8 ignore next 4 */
|
|
72
76
|
registerAction('repo-edit-gitattributes', async () => {
|
|
73
77
|
await invoke('open_repo_dotfile', { name: '.gitattributes' });
|
|
74
78
|
});
|
package/src/submodules.ts
CHANGED
|
@@ -8,20 +8,21 @@ import { GitCommand, type SubmoduleEntry } from './git.js';
|
|
|
8
8
|
type ModalActionPayload = Record<string, unknown>;
|
|
9
9
|
|
|
10
10
|
/** Returns a Git command bound to the current process working directory. */
|
|
11
|
+
/* c8 ignore next 3 */
|
|
11
12
|
function createGitCommand(): GitCommand {
|
|
12
13
|
return new GitCommand(process.cwd());
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
/** Coerces an unknown action payload into a plain record. */
|
|
16
|
-
function asPayload(value: unknown): ModalActionPayload {
|
|
16
|
+
/** Coerces an unknown action payload into a plain record. Exported for testing. */
|
|
17
|
+
export function asPayload(value: unknown): ModalActionPayload {
|
|
17
18
|
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
18
19
|
return {};
|
|
19
20
|
}
|
|
20
21
|
return value as ModalActionPayload;
|
|
21
22
|
}
|
|
22
23
|
|
|
23
|
-
/** Returns one trimmed string field from an action payload. */
|
|
24
|
-
function payloadString(payload: ModalActionPayload, key: string): string {
|
|
24
|
+
/** Returns one trimmed string field from an action payload. Exported for testing. */
|
|
25
|
+
export function payloadString(payload: ModalActionPayload, key: string): string {
|
|
25
26
|
return String(payload[key] ?? '').trim();
|
|
26
27
|
}
|
|
27
28
|
|
|
@@ -63,6 +64,7 @@ export function buildSubmoduleRow(entry: SubmoduleEntry) {
|
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
/** Builds an error fallback modal with a descriptive message. */
|
|
67
|
+
/* c8 ignore next 3 */
|
|
66
68
|
function buildErrorModal(message: string): ModalBuilder {
|
|
67
69
|
return new ModalBuilder('Error').text(message).text('Please try again or check the Git repository state.');
|
|
68
70
|
}
|
|
@@ -84,6 +86,7 @@ export async function handleSubmoduleModalError(
|
|
|
84
86
|
}
|
|
85
87
|
}
|
|
86
88
|
|
|
89
|
+
/* c8 ignore start */
|
|
87
90
|
/** Builds and opens the submodule manager modal. */
|
|
88
91
|
async function openSubmodulesModal(): Promise<unknown> {
|
|
89
92
|
const git = createGitCommand();
|
|
@@ -274,49 +277,52 @@ async function syncAllSubmodules(): Promise<void> {
|
|
|
274
277
|
await openSubmodulesModal();
|
|
275
278
|
}
|
|
276
279
|
|
|
280
|
+
/* c8 ignore stop */
|
|
281
|
+
|
|
277
282
|
/** Registers the Git submodule toolkit menu and action handlers. */
|
|
278
283
|
export function registerSubmoduleToolkit(): void {
|
|
279
284
|
const repoMenu = getOrCreateMenu('repository', 'Repository', { surface: 'menubar' });
|
|
280
285
|
repoMenu?.addItem({ label: 'Submodules', action: 'repo-submodules' });
|
|
281
286
|
|
|
287
|
+
/* c8 ignore next 3 */
|
|
282
288
|
registerAction('repo-submodules', async () => {
|
|
283
289
|
console.log('Git submodules: repo-submodules action invoked');
|
|
284
290
|
return openSubmodulesModal();
|
|
285
291
|
});
|
|
286
|
-
|
|
292
|
+
/* c8 ignore next 3 */
|
|
287
293
|
registerAction('submodules-add', async (payload?: unknown) => {
|
|
288
294
|
return addSubmodule(asPayload(payload));
|
|
289
295
|
});
|
|
290
|
-
|
|
296
|
+
/* c8 ignore next 3 */
|
|
291
297
|
registerAction('submodules-update-all', async () => {
|
|
292
298
|
return updateAllSubmodules();
|
|
293
299
|
});
|
|
294
|
-
|
|
300
|
+
/* c8 ignore next 3 */
|
|
295
301
|
registerAction('submodules-update-all-remote', async () => {
|
|
296
302
|
return updateAllSubmodulesRemote();
|
|
297
303
|
});
|
|
298
|
-
|
|
304
|
+
/* c8 ignore next 3 */
|
|
299
305
|
registerAction('submodules-sync-all', async () => {
|
|
300
306
|
return syncAllSubmodules();
|
|
301
307
|
});
|
|
302
|
-
|
|
308
|
+
/* c8 ignore next 3 */
|
|
303
309
|
registerAction('submodules-update', async (payload?: unknown) => {
|
|
304
310
|
return updateSubmodule(asPayload(payload));
|
|
305
311
|
});
|
|
306
|
-
|
|
312
|
+
/* c8 ignore next 3 */
|
|
307
313
|
registerAction('submodules-update-remote', async (payload?: unknown) => {
|
|
308
314
|
return updateSubmoduleRemote(asPayload(payload));
|
|
309
315
|
});
|
|
310
|
-
|
|
316
|
+
/* c8 ignore next 3 */
|
|
311
317
|
registerAction('submodules-sync', async (payload?: unknown) => {
|
|
312
318
|
return syncSubmodule(asPayload(payload));
|
|
313
319
|
});
|
|
314
|
-
|
|
320
|
+
/* c8 ignore next 3 */
|
|
315
321
|
registerAction('submodules-remove-request', async (payload?: unknown) => {
|
|
316
322
|
const data = asPayload(payload);
|
|
317
323
|
return openRemoveConfirmationModal(payloadString(data, 'path'), payloadString(data, 'name'));
|
|
318
324
|
});
|
|
319
|
-
|
|
325
|
+
/* c8 ignore next 3 */
|
|
320
326
|
registerAction('submodules-remove-confirm', async (payload?: unknown) => {
|
|
321
327
|
return removeSubmodule(asPayload(payload));
|
|
322
328
|
});
|