@openvcs/git-plugin 0.1.0 → 0.2.0-edge.20260426.39
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/ARCHITECTURE.md +8 -0
- package/README.md +12 -1
- package/bin/git.js +64 -27
- package/bin/plugin-helpers.js +44 -0
- package/bin/plugin-request-handler.js +97 -11
- package/bin/plugin.js +1 -1
- package/bin/submodules.js +123 -40
- package/package.json +11 -3
- package/src/git.ts +79 -29
- package/src/plugin-helpers.ts +58 -0
- package/src/plugin-request-handler.ts +141 -10
- package/src/plugin.ts +1 -1
- package/src/submodules.ts +146 -42
|
@@ -14,10 +14,93 @@ import {
|
|
|
14
14
|
asString,
|
|
15
15
|
asStringArray,
|
|
16
16
|
asTrimmedString,
|
|
17
|
+
buildCloneArgs,
|
|
18
|
+
parseStatusOutput,
|
|
17
19
|
} from './plugin-helpers.js';
|
|
18
20
|
import { GitCommand } from './git.js';
|
|
19
21
|
import type { GitSession } from './plugin-types.js';
|
|
20
22
|
|
|
23
|
+
/** Describes the Git operations needed to discard a set of paths safely. */
|
|
24
|
+
export interface DiscardPathPlan {
|
|
25
|
+
/** Restores tracked paths from HEAD in both the index and worktree. */
|
|
26
|
+
restore: string[];
|
|
27
|
+
/** Removes newly added index entries before deleting their worktree files. */
|
|
28
|
+
unstageThenRemove: string[];
|
|
29
|
+
/** Deletes untracked worktree paths after index state has been corrected. */
|
|
30
|
+
clean: string[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Returns an optional boolean only when the input is already a boolean. */
|
|
34
|
+
function asOptionalBoolean(value: unknown): boolean | undefined {
|
|
35
|
+
return typeof value === 'boolean' ? value : undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Reduces a file status string to the primary status code needed for discard routing. */
|
|
39
|
+
function getPrimaryDiscardStatus(status: string): string {
|
|
40
|
+
const normalized = asTrimmedString(status);
|
|
41
|
+
if (!normalized) {
|
|
42
|
+
return 'M';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
for (const candidate of ['?', 'R', 'C', 'A', 'D', 'U', 'T', 'S', 'M']) {
|
|
46
|
+
if (normalized.includes(candidate)) {
|
|
47
|
+
return candidate;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return normalized[0] ?? 'M';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Builds a discard plan that handles tracked, added, copied, and renamed paths. */
|
|
55
|
+
export function planDiscardPaths(statusOutput: string): DiscardPathPlan {
|
|
56
|
+
const restore = new Set<string>();
|
|
57
|
+
const unstageThenRemove = new Set<string>();
|
|
58
|
+
const clean = new Set<string>();
|
|
59
|
+
const parsed = parseStatusOutput(statusOutput);
|
|
60
|
+
|
|
61
|
+
for (const file of parsed.payload.files) {
|
|
62
|
+
const path = asTrimmedString(file.path);
|
|
63
|
+
const oldPath = asTrimmedString(file.old_path);
|
|
64
|
+
const primaryStatus = getPrimaryDiscardStatus(file.status);
|
|
65
|
+
|
|
66
|
+
if (!path) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (primaryStatus === '?') {
|
|
71
|
+
clean.add(path);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (primaryStatus === 'R') {
|
|
76
|
+
if (oldPath) {
|
|
77
|
+
restore.add(oldPath);
|
|
78
|
+
}
|
|
79
|
+
if (file.staged) {
|
|
80
|
+
unstageThenRemove.add(path);
|
|
81
|
+
}
|
|
82
|
+
clean.add(path);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (primaryStatus === 'C' || primaryStatus === 'A') {
|
|
87
|
+
if (file.staged) {
|
|
88
|
+
unstageThenRemove.add(path);
|
|
89
|
+
}
|
|
90
|
+
clean.add(path);
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
restore.add(path);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
restore: Array.from(restore),
|
|
99
|
+
unstageThenRemove: Array.from(unstageThenRemove),
|
|
100
|
+
clean: Array.from(clean),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
21
104
|
/** Describes the Git runtime services consumed by the VCS delegates. */
|
|
22
105
|
export interface GitRuntimeDependencies {
|
|
23
106
|
/** Allocates a new repository session and returns its id. */
|
|
@@ -92,7 +175,7 @@ export class GitVcsDelegates extends VcsDelegateBase<GitRuntimeDependencies> {
|
|
|
92
175
|
}
|
|
93
176
|
|
|
94
177
|
const git = this.deps.createGitCommand(process.cwd());
|
|
95
|
-
const output = git.runChecked(
|
|
178
|
+
const output = git.runChecked(buildCloneArgs({ url, dest: destination }), 'vcs-clone-failed');
|
|
96
179
|
const lines = `${output.stdout}\n${output.stderr}`
|
|
97
180
|
.split(/\r?\n/g)
|
|
98
181
|
.map((line) => line.trim())
|
|
@@ -270,9 +353,13 @@ export class GitVcsDelegates extends VcsDelegateBase<GitRuntimeDependencies> {
|
|
|
270
353
|
_context: PluginRuntimeContext,
|
|
271
354
|
): string {
|
|
272
355
|
const git = this.requireGit(params.session_id);
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
356
|
+
git.commit(
|
|
357
|
+
asTrimmedString(params.message),
|
|
358
|
+
asTrimmedString(params.name),
|
|
359
|
+
asTrimmedString(params.email),
|
|
360
|
+
asStringArray(params.paths),
|
|
361
|
+
);
|
|
362
|
+
return git.currentHead();
|
|
276
363
|
}
|
|
277
364
|
|
|
278
365
|
override commitIndex(
|
|
@@ -280,14 +367,12 @@ export class GitVcsDelegates extends VcsDelegateBase<GitRuntimeDependencies> {
|
|
|
280
367
|
_context: PluginRuntimeContext,
|
|
281
368
|
): string {
|
|
282
369
|
const git = this.requireGit(params.session_id);
|
|
283
|
-
const result = git.runChecked(['rev-parse', 'HEAD'], 'git-commit-failed');
|
|
284
370
|
git.commitIndex(
|
|
285
371
|
asTrimmedString(params.message),
|
|
286
372
|
asTrimmedString(params.name),
|
|
287
373
|
asTrimmedString(params.email),
|
|
288
|
-
asStringArray(params.paths),
|
|
289
374
|
);
|
|
290
|
-
return
|
|
375
|
+
return git.currentHead();
|
|
291
376
|
}
|
|
292
377
|
|
|
293
378
|
override getStatusSummary(
|
|
@@ -316,8 +401,8 @@ export class GitVcsDelegates extends VcsDelegateBase<GitRuntimeDependencies> {
|
|
|
316
401
|
branch: asTrimmedString(query.rev) || undefined,
|
|
317
402
|
skip: asNumber(query.skip, 0) || undefined,
|
|
318
403
|
limit: asNumber(query.limit, 0),
|
|
319
|
-
topo_order: query.topo_order
|
|
320
|
-
include_merges: query.include_merges
|
|
404
|
+
topo_order: asOptionalBoolean(query.topo_order),
|
|
405
|
+
include_merges: asOptionalBoolean(query.include_merges),
|
|
321
406
|
author_contains: asTrimmedString(query.author_contains) || undefined,
|
|
322
407
|
since_utc: asTrimmedString(query.since_utc) || undefined,
|
|
323
408
|
until_utc: asTrimmedString(query.until_utc) || undefined,
|
|
@@ -385,6 +470,15 @@ export class GitVcsDelegates extends VcsDelegateBase<GitRuntimeDependencies> {
|
|
|
385
470
|
return null;
|
|
386
471
|
}
|
|
387
472
|
|
|
473
|
+
override stagePaths(
|
|
474
|
+
params: OpenVcs.VcsStagePathsParams,
|
|
475
|
+
_context: PluginRuntimeContext,
|
|
476
|
+
): null {
|
|
477
|
+
const git = this.requireGit(params.session_id);
|
|
478
|
+
git.stagePaths(asStringArray(params.paths));
|
|
479
|
+
return null;
|
|
480
|
+
}
|
|
481
|
+
|
|
388
482
|
override discardPaths(
|
|
389
483
|
params: OpenVcs.VcsDiscardPathsParams,
|
|
390
484
|
_context: PluginRuntimeContext,
|
|
@@ -395,7 +489,44 @@ export class GitVcsDelegates extends VcsDelegateBase<GitRuntimeDependencies> {
|
|
|
395
489
|
return null;
|
|
396
490
|
}
|
|
397
491
|
|
|
398
|
-
git.runChecked(
|
|
492
|
+
const status = git.runChecked(
|
|
493
|
+
['status', '--porcelain=1', '-z', '-uall', '--', ...paths],
|
|
494
|
+
'git-discard-paths-failed',
|
|
495
|
+
);
|
|
496
|
+
const discardPlan = planDiscardPaths(status.stdout);
|
|
497
|
+
|
|
498
|
+
let failure: unknown;
|
|
499
|
+
|
|
500
|
+
if (discardPlan.unstageThenRemove.length > 0) {
|
|
501
|
+
try {
|
|
502
|
+
git.runChecked(
|
|
503
|
+
['rm', '-f', '--cached', '--', ...discardPlan.unstageThenRemove],
|
|
504
|
+
'git-discard-paths-failed',
|
|
505
|
+
);
|
|
506
|
+
} catch (error) {
|
|
507
|
+
failure = error;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
if (!failure && discardPlan.restore.length > 0) {
|
|
512
|
+
try {
|
|
513
|
+
git.runChecked(
|
|
514
|
+
['restore', '--source=HEAD', '--staged', '--worktree', '--', ...discardPlan.restore],
|
|
515
|
+
'git-discard-paths-failed',
|
|
516
|
+
);
|
|
517
|
+
} catch (error) {
|
|
518
|
+
failure = error;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
if (!failure && discardPlan.clean.length > 0) {
|
|
523
|
+
git.runChecked(['clean', '-f', '--', ...discardPlan.clean], 'git-discard-paths-failed');
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
if (failure) {
|
|
527
|
+
throw failure;
|
|
528
|
+
}
|
|
529
|
+
|
|
399
530
|
return null;
|
|
400
531
|
}
|
|
401
532
|
|
package/src/plugin.ts
CHANGED
|
@@ -58,7 +58,7 @@ export function OnPluginStart(): void {
|
|
|
58
58
|
const delegates = new GitVcsDelegates(createGitRuntimeDependencies());
|
|
59
59
|
PluginDefinition.vcs = delegates.toDelegates();
|
|
60
60
|
|
|
61
|
-
const repoMenu = getOrCreateMenu('repository', 'Repository');
|
|
61
|
+
const repoMenu = getOrCreateMenu('repository', 'Repository', { surface: 'menubar' });
|
|
62
62
|
if (repoMenu) {
|
|
63
63
|
repoMenu.addItem({ label: 'Edit .gitignore', action: 'repo-edit-gitignore' });
|
|
64
64
|
repoMenu.addItem({ label: 'Edit .gitattributes', action: 'repo-edit-gitattributes' });
|
package/src/submodules.ts
CHANGED
|
@@ -26,7 +26,7 @@ function payloadString(payload: ModalActionPayload, key: string): string {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
/** Builds one modal row for a submodule entry. */
|
|
29
|
-
function buildSubmoduleRow(entry: SubmoduleEntry) {
|
|
29
|
+
export function buildSubmoduleRow(entry: SubmoduleEntry) {
|
|
30
30
|
const metaBits = [entry.commit ? `commit ${entry.commit}` : '', entry.branch ? `branch ${entry.branch}` : '']
|
|
31
31
|
.map((part) => String(part || '').trim())
|
|
32
32
|
.filter(Boolean);
|
|
@@ -44,6 +44,12 @@ function buildSubmoduleRow(entry: SubmoduleEntry) {
|
|
|
44
44
|
description,
|
|
45
45
|
actions: [
|
|
46
46
|
{ type: 'button' as const, id: 'submodules-update', content: 'Update', payload: { path: entry.path } },
|
|
47
|
+
{
|
|
48
|
+
type: 'button' as const,
|
|
49
|
+
id: 'submodules-update-remote',
|
|
50
|
+
content: 'Update Remote',
|
|
51
|
+
payload: { path: entry.path },
|
|
52
|
+
},
|
|
47
53
|
{ type: 'button' as const, id: 'submodules-sync', content: 'Sync', payload: { path: entry.path } },
|
|
48
54
|
{
|
|
49
55
|
type: 'button' as const,
|
|
@@ -56,69 +62,143 @@ function buildSubmoduleRow(entry: SubmoduleEntry) {
|
|
|
56
62
|
};
|
|
57
63
|
}
|
|
58
64
|
|
|
65
|
+
/** Builds an error fallback modal with a descriptive message. */
|
|
66
|
+
function buildErrorModal(message: string): ModalBuilder {
|
|
67
|
+
return new ModalBuilder('Error').text(message).text('Please try again or check the Git repository state.');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Opens a fallback modal for a submodule UI failure and preserves the original error on fallback failure. */
|
|
71
|
+
export async function handleSubmoduleModalError(
|
|
72
|
+
prefix: string,
|
|
73
|
+
error: unknown,
|
|
74
|
+
openFallback: (message: string) => Promise<unknown> = (message) => buildErrorModal(message).open(),
|
|
75
|
+
): Promise<unknown> {
|
|
76
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
77
|
+
const fullMessage = `${prefix}: ${message}`;
|
|
78
|
+
console.error(`Git submodules: ${fullMessage}`);
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
return await openFallback(fullMessage);
|
|
82
|
+
} catch {
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
59
87
|
/** Builds and opens the submodule manager modal. */
|
|
60
88
|
async function openSubmodulesModal(): Promise<unknown> {
|
|
61
89
|
const git = createGitCommand();
|
|
62
|
-
|
|
90
|
+
|
|
91
|
+
let entries: SubmoduleEntry[];
|
|
92
|
+
try {
|
|
93
|
+
entries = git.listSubmodules();
|
|
94
|
+
} catch (error) {
|
|
95
|
+
return handleSubmoduleModalError('failed to list submodules', error);
|
|
96
|
+
}
|
|
97
|
+
|
|
63
98
|
console.log('Git submodules: building modal', { count: entries.length });
|
|
64
99
|
|
|
65
|
-
const modal =
|
|
100
|
+
const modal = buildSubmodulesModal(entries);
|
|
101
|
+
try {
|
|
102
|
+
return await modal.open();
|
|
103
|
+
} catch (error) {
|
|
104
|
+
return handleSubmoduleModalError('failed to open modal', error);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Builds the submodule manager modal with given entries. */
|
|
109
|
+
function buildSubmodulesModal(entries: SubmoduleEntry[]): ModalBuilder {
|
|
110
|
+
return new ModalBuilder('Manage Submodules')
|
|
66
111
|
.text('Review, add, update, sync, and remove submodules without leaving Git.')
|
|
112
|
+
.text('Use Update Remote to follow each submodule branch configured in .gitmodules.')
|
|
67
113
|
.separator()
|
|
68
|
-
.
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
114
|
+
.verticalBox(
|
|
115
|
+
[
|
|
116
|
+
{
|
|
117
|
+
type: 'input' as const,
|
|
118
|
+
id: 'url',
|
|
119
|
+
label: 'Submodule URL',
|
|
120
|
+
kind: 'url' as const,
|
|
121
|
+
placeholder: 'https://example.com/repo.git',
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
type: 'grid' as const,
|
|
125
|
+
columns: 'minmax(0, 1fr) minmax(0, 1fr)',
|
|
126
|
+
gap: '.75rem',
|
|
127
|
+
content: [
|
|
128
|
+
{
|
|
129
|
+
type: 'input' as const,
|
|
130
|
+
id: 'path',
|
|
131
|
+
label: 'Submodule Path',
|
|
132
|
+
placeholder: 'libs/example',
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
type: 'input' as const,
|
|
136
|
+
id: 'name',
|
|
137
|
+
label: 'Submodule Name',
|
|
138
|
+
placeholder: 'example',
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
type: 'input' as const,
|
|
144
|
+
id: 'branch',
|
|
145
|
+
label: 'Branch (optional)',
|
|
146
|
+
placeholder: 'main',
|
|
147
|
+
},
|
|
148
|
+
],
|
|
149
|
+
{ gap: '1rem' },
|
|
150
|
+
)
|
|
151
|
+
.horizontalBox(
|
|
152
|
+
[
|
|
153
|
+
{ type: 'button' as const, id: 'submodules-add', content: 'Add Submodule', variant: 'primary' as const },
|
|
154
|
+
{ type: 'button' as const, id: 'repo-submodules', content: 'Refresh' },
|
|
155
|
+
],
|
|
156
|
+
{ gap: '.5rem', align: 'centered', wrap: true },
|
|
157
|
+
)
|
|
158
|
+
.horizontalBox(
|
|
159
|
+
[
|
|
160
|
+
{ type: 'button' as const, id: 'submodules-update-all', content: 'Update All (Recursive)' },
|
|
161
|
+
{ type: 'button' as const, id: 'submodules-update-all-remote', content: 'Update All From Branches' },
|
|
162
|
+
{ type: 'button' as const, id: 'submodules-sync-all', content: 'Sync All' },
|
|
163
|
+
],
|
|
164
|
+
{ gap: '.5rem', align: 'centered', wrap: true },
|
|
165
|
+
)
|
|
94
166
|
.separator()
|
|
95
167
|
.list('submodules', {
|
|
96
168
|
label: 'Submodules',
|
|
97
169
|
emptyText: 'This repository has no submodules yet.',
|
|
98
170
|
items: entries.map((entry) => buildSubmoduleRow(entry)),
|
|
99
171
|
});
|
|
100
|
-
|
|
101
|
-
return modal.open();
|
|
102
172
|
}
|
|
103
173
|
|
|
104
174
|
/** Builds and opens the remove confirmation modal for one submodule. */
|
|
105
175
|
async function openRemoveConfirmationModal(path: string, name?: string): Promise<unknown> {
|
|
106
176
|
const submodulePath = String(path || '').trim();
|
|
107
177
|
if (!submodulePath) return;
|
|
178
|
+
|
|
108
179
|
console.log('Git submodules: building remove confirmation', { path: submodulePath, name });
|
|
109
180
|
|
|
110
181
|
const modal = new ModalBuilder('Confirm Submodule Removal')
|
|
111
182
|
.text(`Remove the submodule at ${submodulePath}? This will deinitialize the submodule, remove it from the index, and delete its working tree entry.`)
|
|
112
|
-
.
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
183
|
+
.horizontalBox(
|
|
184
|
+
[
|
|
185
|
+
{ type: 'button' as const, id: 'repo-submodules', content: 'Back' },
|
|
186
|
+
{
|
|
187
|
+
type: 'button' as const,
|
|
188
|
+
id: 'submodules-remove-confirm',
|
|
189
|
+
content: 'Remove Submodule',
|
|
190
|
+
variant: 'danger' as const,
|
|
191
|
+
payload: { path: submodulePath, name: String(name || '').trim() || undefined },
|
|
192
|
+
},
|
|
193
|
+
],
|
|
194
|
+
{ gap: '.5rem', align: 'centered', wrap: true },
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
try {
|
|
198
|
+
return await modal.open();
|
|
199
|
+
} catch (error) {
|
|
200
|
+
return handleSubmoduleModalError('failed to open remove confirmation modal', error);
|
|
201
|
+
}
|
|
122
202
|
}
|
|
123
203
|
|
|
124
204
|
/** Removes one submodule and refreshes the toolkit modal. */
|
|
@@ -155,6 +235,15 @@ async function updateSubmodule(payload: ModalActionPayload): Promise<void> {
|
|
|
155
235
|
await openSubmodulesModal();
|
|
156
236
|
}
|
|
157
237
|
|
|
238
|
+
/** Updates one submodule from its configured branch and refreshes the toolkit modal. */
|
|
239
|
+
async function updateSubmoduleRemote(payload: ModalActionPayload): Promise<void> {
|
|
240
|
+
const path = payloadString(payload, 'path');
|
|
241
|
+
if (!path) return;
|
|
242
|
+
const git = createGitCommand();
|
|
243
|
+
git.updateSubmoduleRemote(path);
|
|
244
|
+
await openSubmodulesModal();
|
|
245
|
+
}
|
|
246
|
+
|
|
158
247
|
/** Syncs one submodule and refreshes the toolkit modal. */
|
|
159
248
|
async function syncSubmodule(payload: ModalActionPayload): Promise<void> {
|
|
160
249
|
const path = payloadString(payload, 'path');
|
|
@@ -171,6 +260,13 @@ async function updateAllSubmodules(): Promise<void> {
|
|
|
171
260
|
await openSubmodulesModal();
|
|
172
261
|
}
|
|
173
262
|
|
|
263
|
+
/** Updates all submodules from their configured branches and refreshes the toolkit modal. */
|
|
264
|
+
async function updateAllSubmodulesRemote(): Promise<void> {
|
|
265
|
+
const git = createGitCommand();
|
|
266
|
+
git.updateAllSubmodulesRemote();
|
|
267
|
+
await openSubmodulesModal();
|
|
268
|
+
}
|
|
269
|
+
|
|
174
270
|
/** Syncs all submodules recursively and refreshes the toolkit modal. */
|
|
175
271
|
async function syncAllSubmodules(): Promise<void> {
|
|
176
272
|
const git = createGitCommand();
|
|
@@ -180,7 +276,7 @@ async function syncAllSubmodules(): Promise<void> {
|
|
|
180
276
|
|
|
181
277
|
/** Registers the Git submodule toolkit menu and action handlers. */
|
|
182
278
|
export function registerSubmoduleToolkit(): void {
|
|
183
|
-
const repoMenu = getOrCreateMenu('repository', 'Repository');
|
|
279
|
+
const repoMenu = getOrCreateMenu('repository', 'Repository', { surface: 'menubar' });
|
|
184
280
|
repoMenu?.addItem({ label: 'Submodules', action: 'repo-submodules' });
|
|
185
281
|
|
|
186
282
|
registerAction('repo-submodules', async () => {
|
|
@@ -196,6 +292,10 @@ export function registerSubmoduleToolkit(): void {
|
|
|
196
292
|
return updateAllSubmodules();
|
|
197
293
|
});
|
|
198
294
|
|
|
295
|
+
registerAction('submodules-update-all-remote', async () => {
|
|
296
|
+
return updateAllSubmodulesRemote();
|
|
297
|
+
});
|
|
298
|
+
|
|
199
299
|
registerAction('submodules-sync-all', async () => {
|
|
200
300
|
return syncAllSubmodules();
|
|
201
301
|
});
|
|
@@ -204,6 +304,10 @@ export function registerSubmoduleToolkit(): void {
|
|
|
204
304
|
return updateSubmodule(asPayload(payload));
|
|
205
305
|
});
|
|
206
306
|
|
|
307
|
+
registerAction('submodules-update-remote', async (payload?: unknown) => {
|
|
308
|
+
return updateSubmoduleRemote(asPayload(payload));
|
|
309
|
+
});
|
|
310
|
+
|
|
207
311
|
registerAction('submodules-sync', async (payload?: unknown) => {
|
|
208
312
|
return syncSubmodule(asPayload(payload));
|
|
209
313
|
});
|