@openvcs/git-plugin 0.1.0-nightly.20260412.10 → 0.1.0-nightly.20260421.19

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.
@@ -1,8 +1,63 @@
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, buildCloneArgs, } 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
+ /** Reduces a file status string to the primary status code needed for discard routing. */
7
+ function getPrimaryDiscardStatus(status) {
8
+ const normalized = asTrimmedString(status);
9
+ if (!normalized) {
10
+ return 'M';
11
+ }
12
+ for (const candidate of ['?', 'R', 'C', 'A', 'D', 'U', 'T', 'S', 'M']) {
13
+ if (normalized.includes(candidate)) {
14
+ return candidate;
15
+ }
16
+ }
17
+ return normalized[0] ?? 'M';
18
+ }
19
+ /** Builds a discard plan that handles tracked, added, copied, and renamed paths. */
20
+ export function planDiscardPaths(statusOutput) {
21
+ const restore = new Set();
22
+ const unstageThenRemove = new Set();
23
+ const clean = new Set();
24
+ const parsed = parseStatusOutput(statusOutput);
25
+ for (const file of parsed.payload.files) {
26
+ const path = asTrimmedString(file.path);
27
+ const oldPath = asTrimmedString(file.old_path);
28
+ const primaryStatus = getPrimaryDiscardStatus(file.status);
29
+ if (!path) {
30
+ continue;
31
+ }
32
+ if (primaryStatus === '?') {
33
+ clean.add(path);
34
+ continue;
35
+ }
36
+ if (primaryStatus === 'R') {
37
+ if (oldPath) {
38
+ restore.add(oldPath);
39
+ }
40
+ if (file.staged) {
41
+ unstageThenRemove.add(path);
42
+ }
43
+ clean.add(path);
44
+ continue;
45
+ }
46
+ if (primaryStatus === 'C' || primaryStatus === 'A') {
47
+ if (file.staged) {
48
+ unstageThenRemove.add(path);
49
+ }
50
+ clean.add(path);
51
+ continue;
52
+ }
53
+ restore.add(path);
54
+ }
55
+ return {
56
+ restore: Array.from(restore),
57
+ unstageThenRemove: Array.from(unstageThenRemove),
58
+ clean: Array.from(clean),
59
+ };
60
+ }
6
61
  /** Implements the Git-backed `vcs.*` delegate surface for the SDK runtime. */
7
62
  export class GitVcsDelegates extends VcsDelegateBase {
8
63
  /** Returns the required repository worktree path for a session id. */
@@ -231,7 +286,17 @@ export class GitVcsDelegates extends VcsDelegateBase {
231
286
  if (paths.length === 0) {
232
287
  return null;
233
288
  }
234
- git.runChecked(['checkout', '--', ...paths], 'git-discard-paths-failed');
289
+ const status = git.runChecked(['status', '--porcelain=1', '-z', '-uall', '--', ...paths], 'git-discard-paths-failed');
290
+ const discardPlan = planDiscardPaths(status.stdout);
291
+ if (discardPlan.unstageThenRemove.length > 0) {
292
+ git.runChecked(['rm', '-f', '--cached', '--', ...discardPlan.unstageThenRemove], 'git-discard-paths-failed');
293
+ }
294
+ if (discardPlan.restore.length > 0) {
295
+ git.runChecked(['restore', '--source=HEAD', '--staged', '--worktree', '--', ...discardPlan.restore], 'git-discard-paths-failed');
296
+ }
297
+ if (discardPlan.clean.length > 0) {
298
+ git.runChecked(['clean', '-f', '--', ...discardPlan.clean], 'git-discard-paths-failed');
299
+ }
235
300
  return null;
236
301
  }
237
302
  applyReversePatch(params, _context) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openvcs/git-plugin",
3
- "version": "0.1.0-nightly.20260412.10",
3
+ "version": "0.1.0-nightly.20260421.19",
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-nightly.20260412.10",
21
+ "version": "0.1.0-nightly.20260421.19",
22
22
  "author": "OpenVCS Contributors",
23
23
  "description": "Git VCS backend plugin for OpenVCS",
24
24
  "default_enabled": true,
@@ -37,7 +37,7 @@
37
37
  "build": "node ./node_modules/@openvcs/sdk/bin/openvcs.js build"
38
38
  },
39
39
  "dependencies": {
40
- "@openvcs/sdk": "^0.2.18-edge.20260411.9"
40
+ "@openvcs/sdk": "edge"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/node": "^25.5.0",
@@ -15,10 +15,87 @@ import {
15
15
  asStringArray,
16
16
  asTrimmedString,
17
17
  buildCloneArgs,
18
+ parseStatusOutput,
18
19
  } from './plugin-helpers.js';
19
20
  import { GitCommand } from './git.js';
20
21
  import type { GitSession } from './plugin-types.js';
21
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
+ /** Reduces a file status string to the primary status code needed for discard routing. */
34
+ function getPrimaryDiscardStatus(status: string): string {
35
+ const normalized = asTrimmedString(status);
36
+ if (!normalized) {
37
+ return 'M';
38
+ }
39
+
40
+ for (const candidate of ['?', 'R', 'C', 'A', 'D', 'U', 'T', 'S', 'M']) {
41
+ if (normalized.includes(candidate)) {
42
+ return candidate;
43
+ }
44
+ }
45
+
46
+ return normalized[0] ?? 'M';
47
+ }
48
+
49
+ /** Builds a discard plan that handles tracked, added, copied, and renamed paths. */
50
+ export function planDiscardPaths(statusOutput: string): DiscardPathPlan {
51
+ const restore = new Set<string>();
52
+ const unstageThenRemove = new Set<string>();
53
+ const clean = new Set<string>();
54
+ const parsed = parseStatusOutput(statusOutput);
55
+
56
+ for (const file of parsed.payload.files) {
57
+ const path = asTrimmedString(file.path);
58
+ const oldPath = asTrimmedString(file.old_path);
59
+ const primaryStatus = getPrimaryDiscardStatus(file.status);
60
+
61
+ if (!path) {
62
+ continue;
63
+ }
64
+
65
+ if (primaryStatus === '?') {
66
+ clean.add(path);
67
+ continue;
68
+ }
69
+
70
+ if (primaryStatus === 'R') {
71
+ if (oldPath) {
72
+ restore.add(oldPath);
73
+ }
74
+ if (file.staged) {
75
+ unstageThenRemove.add(path);
76
+ }
77
+ clean.add(path);
78
+ continue;
79
+ }
80
+
81
+ if (primaryStatus === 'C' || primaryStatus === 'A') {
82
+ if (file.staged) {
83
+ unstageThenRemove.add(path);
84
+ }
85
+ clean.add(path);
86
+ continue;
87
+ }
88
+
89
+ restore.add(path);
90
+ }
91
+
92
+ return {
93
+ restore: Array.from(restore),
94
+ unstageThenRemove: Array.from(unstageThenRemove),
95
+ clean: Array.from(clean),
96
+ };
97
+ }
98
+
22
99
  /** Describes the Git runtime services consumed by the VCS delegates. */
23
100
  export interface GitRuntimeDependencies {
24
101
  /** Allocates a new repository session and returns its id. */
@@ -396,7 +473,29 @@ export class GitVcsDelegates extends VcsDelegateBase<GitRuntimeDependencies> {
396
473
  return null;
397
474
  }
398
475
 
399
- git.runChecked(['checkout', '--', ...paths], 'git-discard-paths-failed');
476
+ const status = git.runChecked(
477
+ ['status', '--porcelain=1', '-z', '-uall', '--', ...paths],
478
+ 'git-discard-paths-failed',
479
+ );
480
+ const discardPlan = planDiscardPaths(status.stdout);
481
+
482
+ if (discardPlan.unstageThenRemove.length > 0) {
483
+ git.runChecked(
484
+ ['rm', '-f', '--cached', '--', ...discardPlan.unstageThenRemove],
485
+ 'git-discard-paths-failed',
486
+ );
487
+ }
488
+
489
+ if (discardPlan.restore.length > 0) {
490
+ git.runChecked(
491
+ ['restore', '--source=HEAD', '--staged', '--worktree', '--', ...discardPlan.restore],
492
+ 'git-discard-paths-failed',
493
+ );
494
+ }
495
+
496
+ if (discardPlan.clean.length > 0) {
497
+ git.runChecked(['clean', '-f', '--', ...discardPlan.clean], 'git-discard-paths-failed');
498
+ }
400
499
  return null;
401
500
  }
402
501