@openvcs/git-plugin 0.1.0-edge.20260422.23 → 0.1.0-edge.20260423.25
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/plugin-request-handler.js +24 -6
- package/bin/submodules.js +41 -5
- package/package.json +2 -2
- package/src/plugin-request-handler.ts +32 -12
- package/src/submodules.ts +48 -6
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
import { VcsDelegateBase, pluginError, } from '@openvcs/sdk/runtime';
|
|
4
4
|
import { asNumber, asRecord, asString, asStringArray, asTrimmedString, buildCloneArgs, parseStatusOutput, } from './plugin-helpers.js';
|
|
5
5
|
import { GitCommand } from './git.js';
|
|
6
|
+
/** Returns an optional boolean only when the input is already a boolean. */
|
|
7
|
+
function asOptionalBoolean(value) {
|
|
8
|
+
return typeof value === 'boolean' ? value : undefined;
|
|
9
|
+
}
|
|
6
10
|
/** Reduces a file status string to the primary status code needed for discard routing. */
|
|
7
11
|
function getPrimaryDiscardStatus(status) {
|
|
8
12
|
const normalized = asTrimmedString(status);
|
|
@@ -237,8 +241,8 @@ export class GitVcsDelegates extends VcsDelegateBase {
|
|
|
237
241
|
branch: asTrimmedString(query.rev) || undefined,
|
|
238
242
|
skip: asNumber(query.skip, 0) || undefined,
|
|
239
243
|
limit: asNumber(query.limit, 0),
|
|
240
|
-
topo_order: query.topo_order
|
|
241
|
-
include_merges: query.include_merges
|
|
244
|
+
topo_order: asOptionalBoolean(query.topo_order),
|
|
245
|
+
include_merges: asOptionalBoolean(query.include_merges),
|
|
242
246
|
author_contains: asTrimmedString(query.author_contains) || undefined,
|
|
243
247
|
since_utc: asTrimmedString(query.since_utc) || undefined,
|
|
244
248
|
until_utc: asTrimmedString(query.until_utc) || undefined,
|
|
@@ -291,15 +295,29 @@ export class GitVcsDelegates extends VcsDelegateBase {
|
|
|
291
295
|
}
|
|
292
296
|
const status = git.runChecked(['status', '--porcelain=1', '-z', '-uall', '--', ...paths], 'git-discard-paths-failed');
|
|
293
297
|
const discardPlan = planDiscardPaths(status.stdout);
|
|
298
|
+
let failure;
|
|
294
299
|
if (discardPlan.unstageThenRemove.length > 0) {
|
|
295
|
-
|
|
300
|
+
try {
|
|
301
|
+
git.runChecked(['rm', '-f', '--cached', '--', ...discardPlan.unstageThenRemove], 'git-discard-paths-failed');
|
|
302
|
+
}
|
|
303
|
+
catch (error) {
|
|
304
|
+
failure = error;
|
|
305
|
+
}
|
|
296
306
|
}
|
|
297
|
-
if (discardPlan.restore.length > 0) {
|
|
298
|
-
|
|
307
|
+
if (!failure && discardPlan.restore.length > 0) {
|
|
308
|
+
try {
|
|
309
|
+
git.runChecked(['restore', '--source=HEAD', '--staged', '--worktree', '--', ...discardPlan.restore], 'git-discard-paths-failed');
|
|
310
|
+
}
|
|
311
|
+
catch (error) {
|
|
312
|
+
failure = error;
|
|
313
|
+
}
|
|
299
314
|
}
|
|
300
|
-
if (discardPlan.clean.length > 0) {
|
|
315
|
+
if (!failure && discardPlan.clean.length > 0) {
|
|
301
316
|
git.runChecked(['clean', '-f', '--', ...discardPlan.clean], 'git-discard-paths-failed');
|
|
302
317
|
}
|
|
318
|
+
if (failure) {
|
|
319
|
+
throw failure;
|
|
320
|
+
}
|
|
303
321
|
return null;
|
|
304
322
|
}
|
|
305
323
|
applyReversePatch(params, _context) {
|
package/bin/submodules.js
CHANGED
|
@@ -18,7 +18,7 @@ function payloadString(payload, key) {
|
|
|
18
18
|
return String(payload[key] ?? '').trim();
|
|
19
19
|
}
|
|
20
20
|
/** Builds one modal row for a submodule entry. */
|
|
21
|
-
function buildSubmoduleRow(entry) {
|
|
21
|
+
export function buildSubmoduleRow(entry) {
|
|
22
22
|
const metaBits = [entry.commit ? `commit ${entry.commit}` : '', entry.branch ? `branch ${entry.branch}` : '']
|
|
23
23
|
.map((part) => String(part || '').trim())
|
|
24
24
|
.filter(Boolean);
|
|
@@ -51,12 +51,44 @@ function buildSubmoduleRow(entry) {
|
|
|
51
51
|
],
|
|
52
52
|
};
|
|
53
53
|
}
|
|
54
|
+
/** Builds an error fallback modal with a descriptive message. */
|
|
55
|
+
function buildErrorModal(message) {
|
|
56
|
+
return new ModalBuilder('Error').text(message).text('Please try again or check the Git repository state.');
|
|
57
|
+
}
|
|
58
|
+
/** Opens a fallback modal for a submodule UI failure and preserves the original error on fallback failure. */
|
|
59
|
+
export async function handleSubmoduleModalError(prefix, error, openFallback = (message) => buildErrorModal(message).open()) {
|
|
60
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
61
|
+
const fullMessage = `${prefix}: ${message}`;
|
|
62
|
+
console.error(`Git submodules: ${fullMessage}`);
|
|
63
|
+
try {
|
|
64
|
+
return await openFallback(fullMessage);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
54
70
|
/** Builds and opens the submodule manager modal. */
|
|
55
71
|
async function openSubmodulesModal() {
|
|
56
72
|
const git = createGitCommand();
|
|
57
|
-
|
|
73
|
+
let entries;
|
|
74
|
+
try {
|
|
75
|
+
entries = git.listSubmodules();
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
return handleSubmoduleModalError('failed to list submodules', error);
|
|
79
|
+
}
|
|
58
80
|
console.log('Git submodules: building modal', { count: entries.length });
|
|
59
|
-
const modal =
|
|
81
|
+
const modal = buildSubmodulesModal(entries);
|
|
82
|
+
try {
|
|
83
|
+
return await modal.open();
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
return handleSubmoduleModalError('failed to open modal', error);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/** Builds the submodule manager modal with given entries. */
|
|
90
|
+
function buildSubmodulesModal(entries) {
|
|
91
|
+
return new ModalBuilder('Manage Submodules')
|
|
60
92
|
.text('Review, add, update, sync, and remove submodules without leaving Git.')
|
|
61
93
|
.text('Use Update Remote to follow each submodule branch configured in .gitmodules.')
|
|
62
94
|
.separator()
|
|
@@ -109,7 +141,6 @@ async function openSubmodulesModal() {
|
|
|
109
141
|
emptyText: 'This repository has no submodules yet.',
|
|
110
142
|
items: entries.map((entry) => buildSubmoduleRow(entry)),
|
|
111
143
|
});
|
|
112
|
-
return modal.open();
|
|
113
144
|
}
|
|
114
145
|
/** Builds and opens the remove confirmation modal for one submodule. */
|
|
115
146
|
async function openRemoveConfirmationModal(path, name) {
|
|
@@ -129,7 +160,12 @@ async function openRemoveConfirmationModal(path, name) {
|
|
|
129
160
|
payload: { path: submodulePath, name: String(name || '').trim() || undefined },
|
|
130
161
|
},
|
|
131
162
|
], { gap: '.5rem', align: 'centered', wrap: true });
|
|
132
|
-
|
|
163
|
+
try {
|
|
164
|
+
return await modal.open();
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
return handleSubmoduleModalError('failed to open remove confirmation modal', error);
|
|
168
|
+
}
|
|
133
169
|
}
|
|
134
170
|
/** Removes one submodule and refreshes the toolkit modal. */
|
|
135
171
|
async function removeSubmodule(payload) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openvcs/git-plugin",
|
|
3
|
-
"version": "0.1.0-edge.
|
|
3
|
+
"version": "0.1.0-edge.20260423.25",
|
|
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",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"openvcs": {
|
|
19
19
|
"id": "openvcs.git",
|
|
20
20
|
"name": "Git",
|
|
21
|
-
"version": "0.1.0-edge.
|
|
21
|
+
"version": "0.1.0-edge.20260423.25",
|
|
22
22
|
"author": "OpenVCS Contributors",
|
|
23
23
|
"description": "Git VCS backend plugin for OpenVCS",
|
|
24
24
|
"default_enabled": true,
|
|
@@ -30,6 +30,11 @@ export interface DiscardPathPlan {
|
|
|
30
30
|
clean: string[];
|
|
31
31
|
}
|
|
32
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
|
+
|
|
33
38
|
/** Reduces a file status string to the primary status code needed for discard routing. */
|
|
34
39
|
function getPrimaryDiscardStatus(status: string): string {
|
|
35
40
|
const normalized = asTrimmedString(status);
|
|
@@ -396,8 +401,8 @@ export class GitVcsDelegates extends VcsDelegateBase<GitRuntimeDependencies> {
|
|
|
396
401
|
branch: asTrimmedString(query.rev) || undefined,
|
|
397
402
|
skip: asNumber(query.skip, 0) || undefined,
|
|
398
403
|
limit: asNumber(query.limit, 0),
|
|
399
|
-
topo_order: query.topo_order
|
|
400
|
-
include_merges: query.include_merges
|
|
404
|
+
topo_order: asOptionalBoolean(query.topo_order),
|
|
405
|
+
include_merges: asOptionalBoolean(query.include_merges),
|
|
401
406
|
author_contains: asTrimmedString(query.author_contains) || undefined,
|
|
402
407
|
since_utc: asTrimmedString(query.since_utc) || undefined,
|
|
403
408
|
until_utc: asTrimmedString(query.until_utc) || undefined,
|
|
@@ -490,23 +495,38 @@ export class GitVcsDelegates extends VcsDelegateBase<GitRuntimeDependencies> {
|
|
|
490
495
|
);
|
|
491
496
|
const discardPlan = planDiscardPaths(status.stdout);
|
|
492
497
|
|
|
498
|
+
let failure: unknown;
|
|
499
|
+
|
|
493
500
|
if (discardPlan.unstageThenRemove.length > 0) {
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
501
|
+
try {
|
|
502
|
+
git.runChecked(
|
|
503
|
+
['rm', '-f', '--cached', '--', ...discardPlan.unstageThenRemove],
|
|
504
|
+
'git-discard-paths-failed',
|
|
505
|
+
);
|
|
506
|
+
} catch (error) {
|
|
507
|
+
failure = error;
|
|
508
|
+
}
|
|
498
509
|
}
|
|
499
510
|
|
|
500
|
-
if (discardPlan.restore.length > 0) {
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
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
|
+
}
|
|
505
520
|
}
|
|
506
521
|
|
|
507
|
-
if (discardPlan.clean.length > 0) {
|
|
522
|
+
if (!failure && discardPlan.clean.length > 0) {
|
|
508
523
|
git.runChecked(['clean', '-f', '--', ...discardPlan.clean], 'git-discard-paths-failed');
|
|
509
524
|
}
|
|
525
|
+
|
|
526
|
+
if (failure) {
|
|
527
|
+
throw failure;
|
|
528
|
+
}
|
|
529
|
+
|
|
510
530
|
return null;
|
|
511
531
|
}
|
|
512
532
|
|
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);
|
|
@@ -62,13 +62,52 @@ function buildSubmoduleRow(entry: SubmoduleEntry) {
|
|
|
62
62
|
};
|
|
63
63
|
}
|
|
64
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
|
+
|
|
65
87
|
/** Builds and opens the submodule manager modal. */
|
|
66
88
|
async function openSubmodulesModal(): Promise<unknown> {
|
|
67
89
|
const git = createGitCommand();
|
|
68
|
-
|
|
90
|
+
|
|
91
|
+
let entries: SubmoduleEntry[];
|
|
92
|
+
try {
|
|
93
|
+
entries = git.listSubmodules();
|
|
94
|
+
} catch (error) {
|
|
95
|
+
return handleSubmoduleModalError('failed to list submodules', error);
|
|
96
|
+
}
|
|
97
|
+
|
|
69
98
|
console.log('Git submodules: building modal', { count: entries.length });
|
|
70
99
|
|
|
71
|
-
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')
|
|
72
111
|
.text('Review, add, update, sync, and remove submodules without leaving Git.')
|
|
73
112
|
.text('Use Update Remote to follow each submodule branch configured in .gitmodules.')
|
|
74
113
|
.separator()
|
|
@@ -130,14 +169,13 @@ async function openSubmodulesModal(): Promise<unknown> {
|
|
|
130
169
|
emptyText: 'This repository has no submodules yet.',
|
|
131
170
|
items: entries.map((entry) => buildSubmoduleRow(entry)),
|
|
132
171
|
});
|
|
133
|
-
|
|
134
|
-
return modal.open();
|
|
135
172
|
}
|
|
136
173
|
|
|
137
174
|
/** Builds and opens the remove confirmation modal for one submodule. */
|
|
138
175
|
async function openRemoveConfirmationModal(path: string, name?: string): Promise<unknown> {
|
|
139
176
|
const submodulePath = String(path || '').trim();
|
|
140
177
|
if (!submodulePath) return;
|
|
178
|
+
|
|
141
179
|
console.log('Git submodules: building remove confirmation', { path: submodulePath, name });
|
|
142
180
|
|
|
143
181
|
const modal = new ModalBuilder('Confirm Submodule Removal')
|
|
@@ -156,7 +194,11 @@ async function openRemoveConfirmationModal(path: string, name?: string): Promise
|
|
|
156
194
|
{ gap: '.5rem', align: 'centered', wrap: true },
|
|
157
195
|
);
|
|
158
196
|
|
|
159
|
-
|
|
197
|
+
try {
|
|
198
|
+
return await modal.open();
|
|
199
|
+
} catch (error) {
|
|
200
|
+
return handleSubmoduleModalError('failed to open remove confirmation modal', error);
|
|
201
|
+
}
|
|
160
202
|
}
|
|
161
203
|
|
|
162
204
|
/** Removes one submodule and refreshes the toolkit modal. */
|