@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
package/ARCHITECTURE.md
CHANGED
|
@@ -23,12 +23,20 @@ through `@openvcs/sdk/runtime` delegates and exposes a single VCS backend id:
|
|
|
23
23
|
exact `vcs.*` JSON-RPC method names consumed by the runtime.
|
|
24
24
|
- Status reads use `git status --porcelain=1 --branch -z -uall` so file paths are
|
|
25
25
|
NUL-delimited and not C-quoted.
|
|
26
|
+
- After porcelain parsing, the plugin cross-references `.gitmodules` paths so
|
|
27
|
+
tracked submodule entries are surfaced with a dedicated submodule status marker
|
|
28
|
+
for the client UI.
|
|
26
29
|
- For rename and copy records, the porcelain format includes two NUL-terminated
|
|
27
30
|
paths: the original/source path first, then the new/destination path. The
|
|
28
31
|
plugin assigns `path` to the new path and `old_path` to the original path.
|
|
29
32
|
- Network commands (`fetch`, `push`, `pull`) omit optional arguments (remote,
|
|
30
33
|
refspec, branch) when not provided, allowing Git to use its defaults instead
|
|
31
34
|
of receiving empty string arguments.
|
|
35
|
+
- Clone uses `git clone --recurse-submodules` so repositories arrive with
|
|
36
|
+
submodules initialized by default.
|
|
37
|
+
- The Repository menu submodule toolkit keeps pinned `git submodule update`
|
|
38
|
+
behavior separate from explicit `--remote` updates that follow the configured
|
|
39
|
+
branch in `.gitmodules`.
|
|
32
40
|
|
|
33
41
|
## State
|
|
34
42
|
|
package/README.md
CHANGED
|
@@ -9,6 +9,9 @@ This directory contains the System Git VCS backend plugin used by OpenVCS.
|
|
|
9
9
|
- The plugin can add top-level app menus and items through `@openvcs/sdk/runtime` helpers.
|
|
10
10
|
- The plugin can open generic plugin-owned modals with the SDK `ModalBuilder` helper.
|
|
11
11
|
- The Repository menu includes Git-only submodule management tooling.
|
|
12
|
+
- In the desktop client, the `Submodules` entry appears in `Repository` after a Git repository is open and the Git plugin runtime is active.
|
|
13
|
+
- Repository clone operations recurse into submodules by default.
|
|
14
|
+
- Repository status views tag tracked submodule paths distinctly so the client can render them as submodules.
|
|
12
15
|
- Git operations are executed through the local `git` CLI.
|
|
13
16
|
- The runtime uses a trust model (no per-capability permission prompts).
|
|
14
17
|
|
|
@@ -18,7 +21,7 @@ This directory contains the System Git VCS backend plugin used by OpenVCS.
|
|
|
18
21
|
npm install
|
|
19
22
|
```
|
|
20
23
|
|
|
21
|
-
- The SDK dependency
|
|
24
|
+
- The SDK dependency tracks the `edge` tag so it always follows the latest SDK commit.
|
|
22
25
|
|
|
23
26
|
## Validate
|
|
24
27
|
|
|
@@ -41,6 +44,12 @@ npm run build
|
|
|
41
44
|
npm test
|
|
42
45
|
```
|
|
43
46
|
|
|
47
|
+
Submodule workflow highlights:
|
|
48
|
+
|
|
49
|
+
- `clone_repo` uses `git clone --recurse-submodules`.
|
|
50
|
+
- Repository > Submodules supports add, sync, remove, pinned updates, and explicit remote-tracking updates.
|
|
51
|
+
- `Update Remote` follows the branch configured for each submodule in `.gitmodules`.
|
|
52
|
+
|
|
44
53
|
## Pack For Config Use
|
|
45
54
|
|
|
46
55
|
```bash
|
|
@@ -56,11 +65,13 @@ The npm package can be consumed from prerelease channels published by CI:
|
|
|
56
65
|
|
|
57
66
|
- `latest`: stable releases
|
|
58
67
|
- `beta`: builds from the `Beta` branch
|
|
68
|
+
- `edge`: working builds from `Dev` push commits
|
|
59
69
|
- `nightly`: scheduled builds from `Dev` when there are changes since the last nightly
|
|
60
70
|
|
|
61
71
|
Examples:
|
|
62
72
|
|
|
63
73
|
```bash
|
|
74
|
+
npm install @openvcs/git-plugin@edge
|
|
64
75
|
npm install @openvcs/git-plugin@beta
|
|
65
76
|
npm install @openvcs/git-plugin@nightly
|
|
66
77
|
```
|
package/bin/git.js
CHANGED
|
@@ -4,7 +4,7 @@ import { spawnSync } from 'node:child_process';
|
|
|
4
4
|
import { rmSync } from 'node:fs';
|
|
5
5
|
import { join } from 'node:path';
|
|
6
6
|
import { pluginError } from '@openvcs/sdk/runtime';
|
|
7
|
-
import { asString, buildFetchArgs, buildPullFfOnlyArgs, buildPushArgs, parseCommits, parseStatusOutput, } from './plugin-helpers.js';
|
|
7
|
+
import { applySubmoduleStatusHints, asString, buildFetchArgs, buildPullFfOnlyArgs, buildPushArgs, buildSubmoduleUpdateArgs, parseCommits, parseStatusOutput, } from './plugin-helpers.js';
|
|
8
8
|
export class GitCommand {
|
|
9
9
|
cwd;
|
|
10
10
|
constructor(cwd) {
|
|
@@ -60,7 +60,7 @@ export class GitCommand {
|
|
|
60
60
|
}
|
|
61
61
|
status() {
|
|
62
62
|
const result = this.run(['status', '--porcelain=1', '--branch', '-z', '-uall']);
|
|
63
|
-
const parsed = parseStatusOutput(result.stdout);
|
|
63
|
+
const parsed = applySubmoduleStatusHints(parseStatusOutput(result.stdout), this.listSubmodulePaths());
|
|
64
64
|
return { ...parsed, exitCode: result.status };
|
|
65
65
|
}
|
|
66
66
|
currentBranch() {
|
|
@@ -163,26 +163,41 @@ export class GitCommand {
|
|
|
163
163
|
const args = buildPullFfOnlyArgs(options);
|
|
164
164
|
return this.runChecked(args, 'git-pull-failed');
|
|
165
165
|
}
|
|
166
|
-
commit
|
|
167
|
-
|
|
166
|
+
/** Returns the current HEAD commit id. */
|
|
167
|
+
currentHead() {
|
|
168
|
+
return this.runChecked(['rev-parse', 'HEAD'], 'git-head-failed').stdout.trim();
|
|
168
169
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
170
|
+
/** Creates a commit, optionally limited to the provided paths. */
|
|
171
|
+
commit(message, name, email, paths) {
|
|
172
|
+
const execArgs = [
|
|
173
|
+
...(name ? ['-c', `user.name=${name}`] : []),
|
|
174
|
+
...(email ? ['-c', `user.email=${email}`] : []),
|
|
175
|
+
'commit',
|
|
176
|
+
'-m',
|
|
177
|
+
message,
|
|
178
|
+
...(paths && paths.length > 0 ? ['--', ...paths] : []),
|
|
179
|
+
];
|
|
180
|
+
return this.runChecked(execArgs, 'git-commit-failed');
|
|
181
|
+
}
|
|
182
|
+
/** Creates a commit from the current index only. */
|
|
183
|
+
commitIndex(message, name, email) {
|
|
177
184
|
const commitMessage = message || 'Stage changes';
|
|
178
185
|
const execArgs = [
|
|
179
186
|
...(name ? ['-c', `user.name=${name}`] : []),
|
|
180
187
|
...(email ? ['-c', `user.email=${email}`] : []),
|
|
181
|
-
|
|
182
|
-
'-m',
|
|
188
|
+
'commit',
|
|
189
|
+
'-m',
|
|
190
|
+
commitMessage,
|
|
183
191
|
];
|
|
184
192
|
return this.runChecked(execArgs, 'git-commit-failed');
|
|
185
193
|
}
|
|
194
|
+
/** Stages the provided repository-relative paths into the index. */
|
|
195
|
+
stagePaths(paths) {
|
|
196
|
+
if (paths.length === 0) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
this.runChecked(['add', '-A', '--', ...paths], 'git-stage-paths-failed');
|
|
200
|
+
}
|
|
186
201
|
listCommits(options = {}) {
|
|
187
202
|
const args = ['log', '--all'];
|
|
188
203
|
if (options.topo_order) {
|
|
@@ -206,7 +221,7 @@ export class GitCommand {
|
|
|
206
221
|
if (options.until_utc) {
|
|
207
222
|
args.push(`--until=${options.until_utc}`);
|
|
208
223
|
}
|
|
209
|
-
args.push('--pretty=format:%H%
|
|
224
|
+
args.push('--pretty=format:%H%x00%s%x00%aN%x00%aI%x00%P%x1e');
|
|
210
225
|
if (options.branch) {
|
|
211
226
|
args.push(options.branch);
|
|
212
227
|
}
|
|
@@ -217,9 +232,10 @@ export class GitCommand {
|
|
|
217
232
|
const commits = parseCommits(result.stdout);
|
|
218
233
|
return { commits, exitCode: result.status };
|
|
219
234
|
}
|
|
220
|
-
|
|
235
|
+
/** Reads `.gitmodules` entries indexed by submodule name and path. */
|
|
236
|
+
readSubmoduleConfig() {
|
|
221
237
|
const configResult = this.run(['config', '-f', '.gitmodules', '--null', '--list']);
|
|
222
|
-
const
|
|
238
|
+
const byName = new Map();
|
|
223
239
|
if (configResult.status === 0) {
|
|
224
240
|
for (const entry of configResult.stdout.split('\0')) {
|
|
225
241
|
const trimmed = entry.trim();
|
|
@@ -234,22 +250,30 @@ export class GitCommand {
|
|
|
234
250
|
if (!match)
|
|
235
251
|
continue;
|
|
236
252
|
const [, name, field] = match;
|
|
237
|
-
const target =
|
|
253
|
+
const target = byName.get(name) || { name };
|
|
238
254
|
if (field === 'path')
|
|
239
255
|
target.path = value;
|
|
240
256
|
if (field === 'url')
|
|
241
257
|
target.url = value;
|
|
242
258
|
if (field === 'branch')
|
|
243
259
|
target.branch = value;
|
|
244
|
-
|
|
260
|
+
byName.set(name, target);
|
|
245
261
|
}
|
|
246
262
|
}
|
|
247
|
-
const
|
|
248
|
-
for (const entry of
|
|
263
|
+
const byPath = new Map();
|
|
264
|
+
for (const entry of byName.values()) {
|
|
249
265
|
if (entry.path) {
|
|
250
|
-
|
|
266
|
+
byPath.set(entry.path, entry);
|
|
251
267
|
}
|
|
252
268
|
}
|
|
269
|
+
return { byName, byPath };
|
|
270
|
+
}
|
|
271
|
+
/** Returns known submodule paths from `.gitmodules`. */
|
|
272
|
+
listSubmodulePaths() {
|
|
273
|
+
return new Set(this.readSubmoduleConfig().byPath.keys());
|
|
274
|
+
}
|
|
275
|
+
listSubmodules() {
|
|
276
|
+
const { byPath: configByPath } = this.readSubmoduleConfig();
|
|
253
277
|
const statusResult = this.run(['submodule', 'status', '--recursive']);
|
|
254
278
|
if (statusResult.status !== 0) {
|
|
255
279
|
return [];
|
|
@@ -295,10 +319,18 @@ export class GitCommand {
|
|
|
295
319
|
return this.runChecked(args, 'git-submodule-add-failed');
|
|
296
320
|
}
|
|
297
321
|
updateSubmodule(path) {
|
|
298
|
-
return this.runChecked(
|
|
322
|
+
return this.runChecked(buildSubmoduleUpdateArgs({ path }), 'git-submodule-update-failed');
|
|
299
323
|
}
|
|
300
324
|
updateAllSubmodules() {
|
|
301
|
-
return this.runChecked(
|
|
325
|
+
return this.runChecked(buildSubmoduleUpdateArgs({}), 'git-submodule-update-failed');
|
|
326
|
+
}
|
|
327
|
+
/** Updates one submodule from its configured branch recursively. */
|
|
328
|
+
updateSubmoduleRemote(path) {
|
|
329
|
+
return this.runChecked(buildSubmoduleUpdateArgs({ path, remote: true }), 'git-submodule-update-remote-failed');
|
|
330
|
+
}
|
|
331
|
+
/** Updates all submodules from their configured branches recursively. */
|
|
332
|
+
updateAllSubmodulesRemote() {
|
|
333
|
+
return this.runChecked(buildSubmoduleUpdateArgs({ remote: true }), 'git-submodule-update-remote-failed');
|
|
302
334
|
}
|
|
303
335
|
syncSubmodule(path) {
|
|
304
336
|
return this.runChecked(['submodule', 'sync', '--recursive', '--', path], 'git-submodule-sync-failed');
|
|
@@ -354,8 +386,11 @@ export class GitCommand {
|
|
|
354
386
|
hashObject(content) {
|
|
355
387
|
return this.run(['hash-object', '-w', '--stdin'], { stdin: content }).stdout.trim();
|
|
356
388
|
}
|
|
389
|
+
/** Stages a textual patch into the index without requiring worktree/index parity. */
|
|
357
390
|
stagePatch(patch) {
|
|
358
|
-
this.runChecked(['apply', '--
|
|
391
|
+
this.runChecked(['apply', '--cached', '--unidiff-zero'], 'git-stage-patch-failed', {
|
|
392
|
+
stdin: patch,
|
|
393
|
+
});
|
|
359
394
|
}
|
|
360
395
|
applyReversePatch(patch) {
|
|
361
396
|
this.runChecked(['apply', '-R', patch], 'git-apply-reverse-failed');
|
|
@@ -366,9 +401,10 @@ export class GitCommand {
|
|
|
366
401
|
resetSoftTo(ref) {
|
|
367
402
|
this.runChecked(['reset', '--soft', ref], 'git-reset-soft-failed');
|
|
368
403
|
}
|
|
404
|
+
/** Reads the effective Git commit identity from config. */
|
|
369
405
|
getIdentity() {
|
|
370
|
-
const nameResult = this.run(['config', '--
|
|
371
|
-
const emailResult = this.run(['config', '--
|
|
406
|
+
const nameResult = this.run(['config', '--get', 'user.name']);
|
|
407
|
+
const emailResult = this.run(['config', '--get', 'user.email']);
|
|
372
408
|
if (nameResult.status !== 0 || emailResult.status !== 0) {
|
|
373
409
|
return null;
|
|
374
410
|
}
|
|
@@ -377,6 +413,7 @@ export class GitCommand {
|
|
|
377
413
|
email: emailResult.stdout.trim(),
|
|
378
414
|
};
|
|
379
415
|
}
|
|
416
|
+
/** Stores repository-local commit identity in Git config. */
|
|
380
417
|
setIdentityLocal(name, email) {
|
|
381
418
|
this.runChecked(['config', '--local', 'user.name', name], 'git-identity-set-failed');
|
|
382
419
|
this.runChecked(['config', '--local', 'user.email', email], 'git-identity-set-failed');
|
package/bin/plugin-helpers.js
CHANGED
|
@@ -52,6 +52,13 @@ export function buildPushArgs(params) {
|
|
|
52
52
|
pushOptionalArg(args, params.refspec);
|
|
53
53
|
return args;
|
|
54
54
|
}
|
|
55
|
+
/** Builds `git clone` arguments with recursive submodule initialization enabled. */
|
|
56
|
+
export function buildCloneArgs(params) {
|
|
57
|
+
const args = ['clone', '--recurse-submodules'];
|
|
58
|
+
pushOptionalArg(args, params.url);
|
|
59
|
+
pushOptionalArg(args, params.dest);
|
|
60
|
+
return args;
|
|
61
|
+
}
|
|
55
62
|
/** Builds `git pull --ff-only` arguments while omitting empty optional values. */
|
|
56
63
|
export function buildPullFfOnlyArgs(params) {
|
|
57
64
|
const args = ['pull', '--ff-only'];
|
|
@@ -59,6 +66,18 @@ export function buildPullFfOnlyArgs(params) {
|
|
|
59
66
|
pushOptionalArg(args, params.branch);
|
|
60
67
|
return args;
|
|
61
68
|
}
|
|
69
|
+
/** Builds `git submodule update` arguments for pinned or remote-tracking updates. */
|
|
70
|
+
export function buildSubmoduleUpdateArgs(params) {
|
|
71
|
+
const args = ['submodule', 'update', '--init', '--recursive'];
|
|
72
|
+
if (params.remote === true) {
|
|
73
|
+
args.push('--remote');
|
|
74
|
+
}
|
|
75
|
+
const path = asTrimmedString(params.path);
|
|
76
|
+
if (path) {
|
|
77
|
+
args.push('--', path);
|
|
78
|
+
}
|
|
79
|
+
return args;
|
|
80
|
+
}
|
|
62
81
|
/** Parses `git status --porcelain=1 --branch -z -uall` output into OpenVCS payloads. */
|
|
63
82
|
export function parseStatusOutput(output) {
|
|
64
83
|
const records = output.split('\0').filter(Boolean);
|
|
@@ -130,6 +149,31 @@ export function parseStatusOutput(output) {
|
|
|
130
149
|
},
|
|
131
150
|
};
|
|
132
151
|
}
|
|
152
|
+
/** Applies submodule-specific status labels to known submodule paths. */
|
|
153
|
+
export function applySubmoduleStatusHints(parsed, submodulePaths) {
|
|
154
|
+
const knownPaths = new Set(Array.from(submodulePaths)
|
|
155
|
+
.map((entry) => asTrimmedString(entry))
|
|
156
|
+
.filter(Boolean));
|
|
157
|
+
if (knownPaths.size === 0) {
|
|
158
|
+
return parsed;
|
|
159
|
+
}
|
|
160
|
+
return {
|
|
161
|
+
...parsed,
|
|
162
|
+
payload: {
|
|
163
|
+
...parsed.payload,
|
|
164
|
+
files: parsed.payload.files.map((file) => {
|
|
165
|
+
const path = asTrimmedString(file.path);
|
|
166
|
+
if (!knownPaths.has(path)) {
|
|
167
|
+
return file;
|
|
168
|
+
}
|
|
169
|
+
return {
|
|
170
|
+
...file,
|
|
171
|
+
status: 'S',
|
|
172
|
+
};
|
|
173
|
+
}),
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
}
|
|
133
177
|
/** Parses `git log` output into commit entries expected by the host. */
|
|
134
178
|
export function parseCommits(raw) {
|
|
135
179
|
const records = raw
|
|
@@ -1,8 +1,67 @@
|
|
|
1
1
|
// Copyright © 2025-2026 OpenVCS Contributors
|
|
2
2
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
3
3
|
import { VcsDelegateBase, pluginError, } from '@openvcs/sdk/runtime';
|
|
4
|
-
import { asNumber, asRecord, asString, asStringArray, asTrimmedString, } from './plugin-helpers.js';
|
|
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
|
+
}
|
|
10
|
+
/** Reduces a file status string to the primary status code needed for discard routing. */
|
|
11
|
+
function getPrimaryDiscardStatus(status) {
|
|
12
|
+
const normalized = asTrimmedString(status);
|
|
13
|
+
if (!normalized) {
|
|
14
|
+
return 'M';
|
|
15
|
+
}
|
|
16
|
+
for (const candidate of ['?', 'R', 'C', 'A', 'D', 'U', 'T', 'S', 'M']) {
|
|
17
|
+
if (normalized.includes(candidate)) {
|
|
18
|
+
return candidate;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return normalized[0] ?? 'M';
|
|
22
|
+
}
|
|
23
|
+
/** Builds a discard plan that handles tracked, added, copied, and renamed paths. */
|
|
24
|
+
export function planDiscardPaths(statusOutput) {
|
|
25
|
+
const restore = new Set();
|
|
26
|
+
const unstageThenRemove = new Set();
|
|
27
|
+
const clean = new Set();
|
|
28
|
+
const parsed = parseStatusOutput(statusOutput);
|
|
29
|
+
for (const file of parsed.payload.files) {
|
|
30
|
+
const path = asTrimmedString(file.path);
|
|
31
|
+
const oldPath = asTrimmedString(file.old_path);
|
|
32
|
+
const primaryStatus = getPrimaryDiscardStatus(file.status);
|
|
33
|
+
if (!path) {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (primaryStatus === '?') {
|
|
37
|
+
clean.add(path);
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (primaryStatus === 'R') {
|
|
41
|
+
if (oldPath) {
|
|
42
|
+
restore.add(oldPath);
|
|
43
|
+
}
|
|
44
|
+
if (file.staged) {
|
|
45
|
+
unstageThenRemove.add(path);
|
|
46
|
+
}
|
|
47
|
+
clean.add(path);
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (primaryStatus === 'C' || primaryStatus === 'A') {
|
|
51
|
+
if (file.staged) {
|
|
52
|
+
unstageThenRemove.add(path);
|
|
53
|
+
}
|
|
54
|
+
clean.add(path);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
restore.add(path);
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
restore: Array.from(restore),
|
|
61
|
+
unstageThenRemove: Array.from(unstageThenRemove),
|
|
62
|
+
clean: Array.from(clean),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
6
65
|
/** Implements the Git-backed `vcs.*` delegate surface for the SDK runtime. */
|
|
7
66
|
export class GitVcsDelegates extends VcsDelegateBase {
|
|
8
67
|
/** Returns the required repository worktree path for a session id. */
|
|
@@ -45,7 +104,7 @@ export class GitVcsDelegates extends VcsDelegateBase {
|
|
|
45
104
|
throw pluginError('vcs-clone-invalid-args', 'url and dest are required');
|
|
46
105
|
}
|
|
47
106
|
const git = this.deps.createGitCommand(process.cwd());
|
|
48
|
-
const output = git.runChecked(
|
|
107
|
+
const output = git.runChecked(buildCloneArgs({ url, dest: destination }), 'vcs-clone-failed');
|
|
49
108
|
const lines = `${output.stdout}\n${output.stderr}`
|
|
50
109
|
.split(/\r?\n/g)
|
|
51
110
|
.map((line) => line.trim())
|
|
@@ -159,15 +218,13 @@ export class GitVcsDelegates extends VcsDelegateBase {
|
|
|
159
218
|
}
|
|
160
219
|
commit(params, _context) {
|
|
161
220
|
const git = this.requireGit(params.session_id);
|
|
162
|
-
|
|
163
|
-
git.
|
|
164
|
-
return result.stdout.trim();
|
|
221
|
+
git.commit(asTrimmedString(params.message), asTrimmedString(params.name), asTrimmedString(params.email), asStringArray(params.paths));
|
|
222
|
+
return git.currentHead();
|
|
165
223
|
}
|
|
166
224
|
commitIndex(params, _context) {
|
|
167
225
|
const git = this.requireGit(params.session_id);
|
|
168
|
-
|
|
169
|
-
git.
|
|
170
|
-
return result.stdout.trim();
|
|
226
|
+
git.commitIndex(asTrimmedString(params.message), asTrimmedString(params.name), asTrimmedString(params.email));
|
|
227
|
+
return git.currentHead();
|
|
171
228
|
}
|
|
172
229
|
getStatusSummary(params, _context) {
|
|
173
230
|
const git = this.requireGit(params.session_id);
|
|
@@ -184,8 +241,8 @@ export class GitVcsDelegates extends VcsDelegateBase {
|
|
|
184
241
|
branch: asTrimmedString(query.rev) || undefined,
|
|
185
242
|
skip: asNumber(query.skip, 0) || undefined,
|
|
186
243
|
limit: asNumber(query.limit, 0),
|
|
187
|
-
topo_order: query.topo_order
|
|
188
|
-
include_merges: query.include_merges
|
|
244
|
+
topo_order: asOptionalBoolean(query.topo_order),
|
|
245
|
+
include_merges: asOptionalBoolean(query.include_merges),
|
|
189
246
|
author_contains: asTrimmedString(query.author_contains) || undefined,
|
|
190
247
|
since_utc: asTrimmedString(query.since_utc) || undefined,
|
|
191
248
|
until_utc: asTrimmedString(query.until_utc) || undefined,
|
|
@@ -225,13 +282,42 @@ export class GitVcsDelegates extends VcsDelegateBase {
|
|
|
225
282
|
git.stagePatch(asString(params.patch));
|
|
226
283
|
return null;
|
|
227
284
|
}
|
|
285
|
+
stagePaths(params, _context) {
|
|
286
|
+
const git = this.requireGit(params.session_id);
|
|
287
|
+
git.stagePaths(asStringArray(params.paths));
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
228
290
|
discardPaths(params, _context) {
|
|
229
291
|
const git = this.requireGit(params.session_id);
|
|
230
292
|
const paths = asStringArray(params.paths);
|
|
231
293
|
if (paths.length === 0) {
|
|
232
294
|
return null;
|
|
233
295
|
}
|
|
234
|
-
git.runChecked(['
|
|
296
|
+
const status = git.runChecked(['status', '--porcelain=1', '-z', '-uall', '--', ...paths], 'git-discard-paths-failed');
|
|
297
|
+
const discardPlan = planDiscardPaths(status.stdout);
|
|
298
|
+
let failure;
|
|
299
|
+
if (discardPlan.unstageThenRemove.length > 0) {
|
|
300
|
+
try {
|
|
301
|
+
git.runChecked(['rm', '-f', '--cached', '--', ...discardPlan.unstageThenRemove], 'git-discard-paths-failed');
|
|
302
|
+
}
|
|
303
|
+
catch (error) {
|
|
304
|
+
failure = error;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
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
|
+
}
|
|
314
|
+
}
|
|
315
|
+
if (!failure && discardPlan.clean.length > 0) {
|
|
316
|
+
git.runChecked(['clean', '-f', '--', ...discardPlan.clean], 'git-discard-paths-failed');
|
|
317
|
+
}
|
|
318
|
+
if (failure) {
|
|
319
|
+
throw failure;
|
|
320
|
+
}
|
|
235
321
|
return null;
|
|
236
322
|
}
|
|
237
323
|
applyReversePatch(params, _context) {
|
package/bin/plugin.js
CHANGED
|
@@ -38,7 +38,7 @@ export function OnPluginStart() {
|
|
|
38
38
|
}
|
|
39
39
|
const delegates = new GitVcsDelegates(createGitRuntimeDependencies());
|
|
40
40
|
PluginDefinition.vcs = delegates.toDelegates();
|
|
41
|
-
const repoMenu = getOrCreateMenu('repository', 'Repository');
|
|
41
|
+
const repoMenu = getOrCreateMenu('repository', 'Repository', { surface: 'menubar' });
|
|
42
42
|
if (repoMenu) {
|
|
43
43
|
repoMenu.addItem({ label: 'Edit .gitignore', action: 'repo-edit-gitignore' });
|
|
44
44
|
repoMenu.addItem({ label: 'Edit .gitattributes', action: 'repo-edit-gitattributes' });
|