@gitkraken/core-gitlens 0.9.7 → 0.9.8

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/CHANGELOG.md CHANGED
@@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.9.8] - 2026-10-03
10
+
11
+ ### Changed
12
+
13
+ - Speeds up reading status for a worktree with tens of thousands of changed or untracked files, which could block the event loop for long stretches: parsing no longer builds a URI for every entry or splits every line into fields. `GitStatusFile.uri` is now built on first read, and the `uri` constructor argument and `parseGitStatus`'s `getUri` parameter are optional (git, git-cli)
14
+
9
15
  ## [0.9.7] - 2026-10-03
10
16
 
11
17
  ### Added
@@ -527,7 +533,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
527
533
 
528
534
  - Initial release. Bundles `@gitlens/utils`, `@gitlens/git`, `@gitlens/git-cli`, `@gitlens/ai`, and `@gitlens/git-github` into a single core npm package with subpath exports.
529
535
 
530
- [unreleased]: https://github.com/gitkraken/vscode-gitlens/compare/releases/core/v0.9.7...HEAD
536
+ [unreleased]: https://github.com/gitkraken/vscode-gitlens/compare/releases/core/v0.9.8...HEAD
537
+ [0.9.8]: https://github.com/gitkraken/vscode-gitlens/compare/releases/core/v0.9.7...gitkraken:releases/core/v0.9.8
531
538
  [0.9.7]: https://github.com/gitkraken/vscode-gitlens/compare/releases/core/v0.9.6...gitkraken:releases/core/v0.9.7
532
539
  [0.9.6]: https://github.com/gitkraken/vscode-gitlens/compare/releases/core/v0.9.5...gitkraken:releases/core/v0.9.6
533
540
  [0.9.5]: https://github.com/gitkraken/vscode-gitlens/compare/releases/core/v0.9.4...gitkraken:releases/core/v0.9.5
@@ -13,8 +13,8 @@ export declare class GitStatusFile implements GitFile {
13
13
  readonly conflictStatus: GitFileConflictStatus | undefined;
14
14
  readonly indexStatus: GitFileIndexStatus | undefined;
15
15
  readonly workingTreeStatus: GitFileWorkingTreeStatus | undefined;
16
- private readonly _uri;
17
- constructor(repoPath: string, x: string | undefined, y: string | undefined, path: string, uri: Uri, originalPath?: string | undefined, submodule?: {
16
+ private _uri;
17
+ constructor(repoPath: string, x: string | undefined, y: string | undefined, path: string, uri?: Uri, originalPath?: string | undefined, submodule?: {
18
18
  readonly oid: string;
19
19
  readonly previousOid?: string;
20
20
  } | undefined);
@@ -30,6 +30,7 @@ export declare class GitStatusFile implements GitFile {
30
30
  get mixed(): boolean;
31
31
  get staged(): boolean;
32
32
  get status(): GitFileStatus;
33
+ /** Built on first read unless passed in, since a status can hold tens of thousands of files whose uri is never read */
33
34
  get uri(): Uri;
34
35
  get wip(): boolean;
35
36
  static is(file: unknown): file is GitStatusFile;
@@ -1,4 +1,6 @@
1
1
  import { loggable } from "../../utils/decorators/log.js";
2
+ import { fileUri, joinUriPath } from "../../utils/uri.js";
3
+ import { normalizePath } from "../../utils/path.js";
2
4
  import __decorate from "../../_virtual/@oxc-project+runtime/helpers/esm/decorate.js";
3
5
  import { GitFileConflictStatus, GitFileIndexStatus, GitFileWorkingTreeStatus } from "./fileStatus.js";
4
6
  //#region ../git/src/models/statusFile.ts
@@ -98,8 +100,9 @@ let GitStatusFile = _GitStatusFile = class GitStatusFile {
98
100
  get status() {
99
101
  return this.conflictStatus ?? this.indexStatus ?? this.workingTreeStatus;
100
102
  }
103
+ /** Built on first read unless passed in, since a status can hold tens of thousands of files whose uri is never read */
101
104
  get uri() {
102
- return this._uri;
105
+ return this._uri ??= joinUriPath(fileUri(normalizePath(this.repoPath)), normalizePath(this.path));
103
106
  }
104
107
  get wip() {
105
108
  return this.workingTreeStatus != null;
@@ -1 +1 @@
1
- {"version":3,"file":"statusFile.js","names":[],"sources":["../../../../git/src/models/statusFile.ts"],"sourcesContent":["import { loggable } from '@gitlens/utils/decorators/log.js';\nimport type { Uri } from '@gitlens/utils/uri.js';\nimport type { GitFile } from './file.js';\nimport type { GitFileStatus } from './fileStatus.js';\nimport { GitFileConflictStatus, GitFileIndexStatus, GitFileWorkingTreeStatus } from './fileStatus.js';\n\n@loggable(i => i.path)\nexport class GitStatusFile implements GitFile {\n\tpublic readonly conflictStatus: GitFileConflictStatus | undefined;\n\tpublic readonly indexStatus: GitFileIndexStatus | undefined;\n\tpublic readonly workingTreeStatus: GitFileWorkingTreeStatus | undefined;\n\tprivate readonly _uri: Uri;\n\n\tconstructor(\n\t\tpublic readonly repoPath: string,\n\t\tx: string | undefined,\n\t\ty: string | undefined,\n\t\tpublic readonly path: string,\n\t\turi: Uri,\n\t\tpublic readonly originalPath?: string,\n\t\tpublic readonly submodule?: { readonly oid: string; readonly previousOid?: string } | undefined,\n\t) {\n\t\tthis._uri = uri;\n\t\tif (x != null && y != null) {\n\t\t\tswitch (x + y) {\n\t\t\t\tcase '??':\n\t\t\t\t\tthis.workingTreeStatus = GitFileWorkingTreeStatus.Untracked;\n\t\t\t\t\tbreak;\n\t\t\t\tcase '!!':\n\t\t\t\t\tthis.workingTreeStatus = GitFileWorkingTreeStatus.Ignored;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'AA':\n\t\t\t\t\tthis.conflictStatus = GitFileConflictStatus.AddedByBoth;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'AU':\n\t\t\t\t\tthis.conflictStatus = GitFileConflictStatus.AddedByUs;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'UA':\n\t\t\t\t\tthis.conflictStatus = GitFileConflictStatus.AddedByThem;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'DD':\n\t\t\t\t\tthis.conflictStatus = GitFileConflictStatus.DeletedByBoth;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'DU':\n\t\t\t\t\tthis.conflictStatus = GitFileConflictStatus.DeletedByUs;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'UD':\n\t\t\t\t\tthis.conflictStatus = GitFileConflictStatus.DeletedByThem;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'UU':\n\t\t\t\t\tthis.conflictStatus = GitFileConflictStatus.ModifiedByBoth;\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (this.conflictStatus == null) {\n\t\t\tswitch (x) {\n\t\t\t\tcase 'A':\n\t\t\t\t\tthis.indexStatus = GitFileIndexStatus.Added;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'D':\n\t\t\t\t\tthis.indexStatus = GitFileIndexStatus.Deleted;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'M':\n\t\t\t\t\tthis.indexStatus = GitFileIndexStatus.Modified;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'T':\n\t\t\t\t\tthis.indexStatus = GitFileIndexStatus.TypeChanged;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'R':\n\t\t\t\t\tthis.indexStatus = GitFileIndexStatus.Renamed;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'C':\n\t\t\t\t\tthis.indexStatus = GitFileIndexStatus.Copied;\n\t\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tswitch (y) {\n\t\t\t\tcase 'A':\n\t\t\t\t\t// case '?':\n\t\t\t\t\tthis.workingTreeStatus = GitFileWorkingTreeStatus.Added;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'D':\n\t\t\t\t\tthis.workingTreeStatus = GitFileWorkingTreeStatus.Deleted;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'M':\n\t\t\t\t\tthis.workingTreeStatus = GitFileWorkingTreeStatus.Modified;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'T':\n\t\t\t\t\tthis.workingTreeStatus = GitFileWorkingTreeStatus.TypeChanged;\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tget conflicted(): boolean {\n\t\treturn this.conflictStatus != null;\n\t}\n\n\t/**\n\t * True when the file has both staged (index) and unstaged (working-tree) changes and is not\n\t * conflicted. Used by the host-side discard flows to apply partial-discard semantics — drop the\n\t * working-tree delta while preserving the staged portion. Centralized here so those host paths\n\t * agree on what \"mixed\" means. (The webview button predicate can't consume this getter — it\n\t * runs against the wire `GitFileChangeShape` and infers the same state from the duplicate\n\t * staged/unstaged rows the WIP feed emits per mixed path.)\n\t */\n\tget mixed(): boolean {\n\t\treturn this.indexStatus != null && this.workingTreeStatus != null && this.conflictStatus == null;\n\t}\n\n\tget staged(): boolean {\n\t\treturn this.indexStatus != null;\n\t}\n\n\tget status(): GitFileStatus {\n\t\treturn (this.conflictStatus ?? this.indexStatus ?? this.workingTreeStatus)!;\n\t}\n\n\tget uri(): Uri {\n\t\treturn this._uri;\n\t}\n\n\tget wip(): boolean {\n\t\treturn this.workingTreeStatus != null;\n\t}\n\n\tstatic is(file: unknown): file is GitStatusFile {\n\t\treturn file instanceof GitStatusFile;\n\t}\n}\n"],"mappings":";;;;;AAOO,IAAM,gBAAA,iBAAN,MAAM,cAAiC;CAO5B;CAGA;CAEA;CACA;CAZjB;CACA;CACA;CACA;CAEA,YACC,UACA,GACA,GACA,MACA,KACA,cACA,WACC;EAPe,KAAA,WAAA;EAGA,KAAA,OAAA;EAEA,KAAA,eAAA;EACA,KAAA,YAAA;EAEhB,KAAK,OAAO;EACZ,IAAI,KAAK,QAAQ,KAAK,MACrB,QAAQ,IAAI,GAAZ;GACC,KAAK;IACJ,KAAK,oBAAoB,yBAAyB;IAClD;GACD,KAAK;IACJ,KAAK,oBAAoB,yBAAyB;IAClD;GACD,KAAK;IACJ,KAAK,iBAAiB,sBAAsB;IAC5C;GACD,KAAK;IACJ,KAAK,iBAAiB,sBAAsB;IAC5C;GACD,KAAK;IACJ,KAAK,iBAAiB,sBAAsB;IAC5C;GACD,KAAK;IACJ,KAAK,iBAAiB,sBAAsB;IAC5C;GACD,KAAK;IACJ,KAAK,iBAAiB,sBAAsB;IAC5C;GACD,KAAK;IACJ,KAAK,iBAAiB,sBAAsB;IAC5C;GACD,KAAK,MACJ,KAAK,iBAAiB,sBAAsB;EAE9C;EAGD,IAAI,KAAK,kBAAkB,MAAM;GAChC,QAAQ,GAAR;IACC,KAAK;KACJ,KAAK,cAAc,mBAAmB;KACtC;IACD,KAAK;KACJ,KAAK,cAAc,mBAAmB;KACtC;IACD,KAAK;KACJ,KAAK,cAAc,mBAAmB;KACtC;IACD,KAAK;KACJ,KAAK,cAAc,mBAAmB;KACtC;IACD,KAAK;KACJ,KAAK,cAAc,mBAAmB;KACtC;IACD,KAAK,KACJ,KAAK,cAAc,mBAAmB;GAExC;GAEA,QAAQ,GAAR;IACC,KAAK;KAEJ,KAAK,oBAAoB,yBAAyB;KAClD;IACD,KAAK;KACJ,KAAK,oBAAoB,yBAAyB;KAClD;IACD,KAAK;KACJ,KAAK,oBAAoB,yBAAyB;KAClD;IACD,KAAK,KACJ,KAAK,oBAAoB,yBAAyB;GAEpD;EACD;CACD;CAEA,IAAI,aAAsB;EACzB,OAAO,KAAK,kBAAkB;CAC/B;;;;;;;;;CAUA,IAAI,QAAiB;EACpB,OAAO,KAAK,eAAe,QAAQ,KAAK,qBAAqB,QAAQ,KAAK,kBAAkB;CAC7F;CAEA,IAAI,SAAkB;EACrB,OAAO,KAAK,eAAe;CAC5B;CAEA,IAAI,SAAwB;EAC3B,OAAQ,KAAK,kBAAkB,KAAK,eAAe,KAAK;CACzD;CAEA,IAAI,MAAW;EACd,OAAO,KAAK;CACb;CAEA,IAAI,MAAe;EAClB,OAAO,KAAK,qBAAqB;CAClC;CAEA,OAAO,GAAG,MAAsC;EAC/C,OAAO,gBAAA;CACR;AACD;AA5HC,gBAAA,iBAAA,WAAA,CAAA,UAAS,MAAK,EAAE,IAAI,CAAA,GAAA,aAAA"}
1
+ {"version":3,"file":"statusFile.js","names":[],"sources":["../../../../git/src/models/statusFile.ts"],"sourcesContent":["import { loggable } from '@gitlens/utils/decorators/log.js';\nimport { normalizePath } from '@gitlens/utils/path.js';\nimport type { Uri } from '@gitlens/utils/uri.js';\nimport { fileUri, joinUriPath } from '@gitlens/utils/uri.js';\nimport type { GitFile } from './file.js';\nimport type { GitFileStatus } from './fileStatus.js';\nimport { GitFileConflictStatus, GitFileIndexStatus, GitFileWorkingTreeStatus } from './fileStatus.js';\n\n@loggable(i => i.path)\nexport class GitStatusFile implements GitFile {\n\tpublic readonly conflictStatus: GitFileConflictStatus | undefined;\n\tpublic readonly indexStatus: GitFileIndexStatus | undefined;\n\tpublic readonly workingTreeStatus: GitFileWorkingTreeStatus | undefined;\n\tprivate _uri: Uri | undefined;\n\n\tconstructor(\n\t\tpublic readonly repoPath: string,\n\t\tx: string | undefined,\n\t\ty: string | undefined,\n\t\tpublic readonly path: string,\n\t\turi?: Uri,\n\t\tpublic readonly originalPath?: string,\n\t\tpublic readonly submodule?: { readonly oid: string; readonly previousOid?: string } | undefined,\n\t) {\n\t\tthis._uri = uri;\n\t\tif (x != null && y != null) {\n\t\t\tswitch (x + y) {\n\t\t\t\tcase '??':\n\t\t\t\t\tthis.workingTreeStatus = GitFileWorkingTreeStatus.Untracked;\n\t\t\t\t\tbreak;\n\t\t\t\tcase '!!':\n\t\t\t\t\tthis.workingTreeStatus = GitFileWorkingTreeStatus.Ignored;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'AA':\n\t\t\t\t\tthis.conflictStatus = GitFileConflictStatus.AddedByBoth;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'AU':\n\t\t\t\t\tthis.conflictStatus = GitFileConflictStatus.AddedByUs;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'UA':\n\t\t\t\t\tthis.conflictStatus = GitFileConflictStatus.AddedByThem;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'DD':\n\t\t\t\t\tthis.conflictStatus = GitFileConflictStatus.DeletedByBoth;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'DU':\n\t\t\t\t\tthis.conflictStatus = GitFileConflictStatus.DeletedByUs;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'UD':\n\t\t\t\t\tthis.conflictStatus = GitFileConflictStatus.DeletedByThem;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'UU':\n\t\t\t\t\tthis.conflictStatus = GitFileConflictStatus.ModifiedByBoth;\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (this.conflictStatus == null) {\n\t\t\tswitch (x) {\n\t\t\t\tcase 'A':\n\t\t\t\t\tthis.indexStatus = GitFileIndexStatus.Added;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'D':\n\t\t\t\t\tthis.indexStatus = GitFileIndexStatus.Deleted;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'M':\n\t\t\t\t\tthis.indexStatus = GitFileIndexStatus.Modified;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'T':\n\t\t\t\t\tthis.indexStatus = GitFileIndexStatus.TypeChanged;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'R':\n\t\t\t\t\tthis.indexStatus = GitFileIndexStatus.Renamed;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'C':\n\t\t\t\t\tthis.indexStatus = GitFileIndexStatus.Copied;\n\t\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tswitch (y) {\n\t\t\t\tcase 'A':\n\t\t\t\t\t// case '?':\n\t\t\t\t\tthis.workingTreeStatus = GitFileWorkingTreeStatus.Added;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'D':\n\t\t\t\t\tthis.workingTreeStatus = GitFileWorkingTreeStatus.Deleted;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'M':\n\t\t\t\t\tthis.workingTreeStatus = GitFileWorkingTreeStatus.Modified;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'T':\n\t\t\t\t\tthis.workingTreeStatus = GitFileWorkingTreeStatus.TypeChanged;\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tget conflicted(): boolean {\n\t\treturn this.conflictStatus != null;\n\t}\n\n\t/**\n\t * True when the file has both staged (index) and unstaged (working-tree) changes and is not\n\t * conflicted. Used by the host-side discard flows to apply partial-discard semantics — drop the\n\t * working-tree delta while preserving the staged portion. Centralized here so those host paths\n\t * agree on what \"mixed\" means. (The webview button predicate can't consume this getter — it\n\t * runs against the wire `GitFileChangeShape` and infers the same state from the duplicate\n\t * staged/unstaged rows the WIP feed emits per mixed path.)\n\t */\n\tget mixed(): boolean {\n\t\treturn this.indexStatus != null && this.workingTreeStatus != null && this.conflictStatus == null;\n\t}\n\n\tget staged(): boolean {\n\t\treturn this.indexStatus != null;\n\t}\n\n\tget status(): GitFileStatus {\n\t\treturn (this.conflictStatus ?? this.indexStatus ?? this.workingTreeStatus)!;\n\t}\n\n\t/** Built on first read unless passed in, since a status can hold tens of thousands of files whose uri is never read */\n\tget uri(): Uri {\n\t\treturn (this._uri ??= joinUriPath(fileUri(normalizePath(this.repoPath)), normalizePath(this.path)));\n\t}\n\n\tget wip(): boolean {\n\t\treturn this.workingTreeStatus != null;\n\t}\n\n\tstatic is(file: unknown): file is GitStatusFile {\n\t\treturn file instanceof GitStatusFile;\n\t}\n}\n"],"mappings":";;;;;;;AASO,IAAM,gBAAA,iBAAN,MAAM,cAAiC;CAO5B;CAGA;CAEA;CACA;CAZjB;CACA;CACA;CACA;CAEA,YACC,UACA,GACA,GACA,MACA,KACA,cACA,WACC;EAPe,KAAA,WAAA;EAGA,KAAA,OAAA;EAEA,KAAA,eAAA;EACA,KAAA,YAAA;EAEhB,KAAK,OAAO;EACZ,IAAI,KAAK,QAAQ,KAAK,MACrB,QAAQ,IAAI,GAAZ;GACC,KAAK;IACJ,KAAK,oBAAoB,yBAAyB;IAClD;GACD,KAAK;IACJ,KAAK,oBAAoB,yBAAyB;IAClD;GACD,KAAK;IACJ,KAAK,iBAAiB,sBAAsB;IAC5C;GACD,KAAK;IACJ,KAAK,iBAAiB,sBAAsB;IAC5C;GACD,KAAK;IACJ,KAAK,iBAAiB,sBAAsB;IAC5C;GACD,KAAK;IACJ,KAAK,iBAAiB,sBAAsB;IAC5C;GACD,KAAK;IACJ,KAAK,iBAAiB,sBAAsB;IAC5C;GACD,KAAK;IACJ,KAAK,iBAAiB,sBAAsB;IAC5C;GACD,KAAK,MACJ,KAAK,iBAAiB,sBAAsB;EAE9C;EAGD,IAAI,KAAK,kBAAkB,MAAM;GAChC,QAAQ,GAAR;IACC,KAAK;KACJ,KAAK,cAAc,mBAAmB;KACtC;IACD,KAAK;KACJ,KAAK,cAAc,mBAAmB;KACtC;IACD,KAAK;KACJ,KAAK,cAAc,mBAAmB;KACtC;IACD,KAAK;KACJ,KAAK,cAAc,mBAAmB;KACtC;IACD,KAAK;KACJ,KAAK,cAAc,mBAAmB;KACtC;IACD,KAAK,KACJ,KAAK,cAAc,mBAAmB;GAExC;GAEA,QAAQ,GAAR;IACC,KAAK;KAEJ,KAAK,oBAAoB,yBAAyB;KAClD;IACD,KAAK;KACJ,KAAK,oBAAoB,yBAAyB;KAClD;IACD,KAAK;KACJ,KAAK,oBAAoB,yBAAyB;KAClD;IACD,KAAK,KACJ,KAAK,oBAAoB,yBAAyB;GAEpD;EACD;CACD;CAEA,IAAI,aAAsB;EACzB,OAAO,KAAK,kBAAkB;CAC/B;;;;;;;;;CAUA,IAAI,QAAiB;EACpB,OAAO,KAAK,eAAe,QAAQ,KAAK,qBAAqB,QAAQ,KAAK,kBAAkB;CAC7F;CAEA,IAAI,SAAkB;EACrB,OAAO,KAAK,eAAe;CAC5B;CAEA,IAAI,SAAwB;EAC3B,OAAQ,KAAK,kBAAkB,KAAK,eAAe,KAAK;CACzD;;CAGA,IAAI,MAAW;EACd,OAAQ,KAAK,SAAS,YAAY,QAAQ,cAAc,KAAK,QAAQ,CAAC,GAAG,cAAc,KAAK,IAAI,CAAC;CAClG;CAEA,IAAI,MAAe;EAClB,OAAO,KAAK,qBAAqB;CAClC;CAEA,OAAO,GAAG,MAAsC;EAC/C,OAAO,gBAAA;CACR;AACD;AA7HC,gBAAA,iBAAA,WAAA,CAAA,UAAS,MAAK,EAAE,IAAI,CAAA,GAAA,aAAA"}
@@ -1,6 +1,7 @@
1
1
  import { Uri } from "../../utils/uri.js";
2
2
  import { GitStatus } from "../../git/models/status.js";
3
3
  //#region ../git-cli/src/parsers/statusParser.d.ts
4
- export declare function parseGitStatus(data: string, repoPath: string, porcelainVersion: number, getUri: (path: string) => Uri): GitStatus | undefined;
4
+ /** Without `getUri`, each file builds its uri on first read rather than during the parse */
5
+ export declare function parseGitStatus(data: string, repoPath: string, porcelainVersion: number, getUri?: (path: string) => Uri): GitStatus | undefined;
5
6
  //#endregion
6
7
  //# sourceMappingURL=statusParser.d.ts.map
@@ -7,6 +7,17 @@ import { GitStatusFile } from "../../git/models/statusFile.js";
7
7
  const aheadStatusV1Regex = /(?:ahead ([0-9]+))/;
8
8
  const behindStatusV1Regex = /(?:behind ([0-9]+))/;
9
9
  const quoteRegex = /"/g;
10
+ const submoduleCharCode = "S".charCodeAt(0);
11
+ /** Returns the index just after the `n`th space, or -1 if the line has fewer than `n` spaces. */
12
+ function indexAfterNthSpace(line, n) {
13
+ let index = -1;
14
+ for (let i = 0; i < n; i++) {
15
+ index = line.indexOf(" ", index + 1);
16
+ if (index === -1) return -1;
17
+ }
18
+ return index + 1;
19
+ }
20
+ /** Without `getUri`, each file builds its uri on first read rather than during the parse */
10
21
  function parseGitStatus(data, repoPath, porcelainVersion, getUri) {
11
22
  try {
12
23
  var _usingCtx$1 = _usingCtx();
@@ -105,36 +116,51 @@ function parseStatusV2(lines, repoPath, getUri) {
105
116
  state.ahead = Number(lineParts[2].substring(1));
106
117
  state.behind = Number(lineParts[3].substring(1));
107
118
  }
108
- } else {
109
- const lineParts = line.split(" ");
110
- switch (lineParts[0][0]) {
111
- case "1": {
112
- const submodule = lineParts[2]?.startsWith("S") ? {
113
- oid: lineParts[7],
114
- previousOid: lineParts[6]
115
- } : void 0;
116
- files.push(parseStatusFile(repoPath, lineParts[1], lineParts.slice(8).join(" "), getUri, void 0, submodule));
117
- break;
119
+ } else switch (line[0]) {
120
+ case "1": {
121
+ const pathStart = indexAfterNthSpace(line, 8);
122
+ if (pathStart === -1) break;
123
+ let submodule;
124
+ if (line.charCodeAt(5) === submoduleCharCode) {
125
+ const parts = line.split(" ");
126
+ submodule = {
127
+ oid: parts[7],
128
+ previousOid: parts[6]
129
+ };
118
130
  }
119
- case "2": {
120
- const submodule = lineParts[2]?.startsWith("S") ? {
121
- oid: lineParts[7],
122
- previousOid: lineParts[6]
123
- } : void 0;
124
- const file = lineParts.slice(9).join(" ").split(" ");
125
- files.push(parseStatusFile(repoPath, lineParts[1], file[0], getUri, file[1], submodule));
126
- break;
131
+ files.push(parseStatusFile(repoPath, line.substring(2, 4), line.substring(pathStart), getUri, void 0, submodule));
132
+ break;
133
+ }
134
+ case "2": {
135
+ const pathStart = indexAfterNthSpace(line, 9);
136
+ if (pathStart === -1) break;
137
+ let submodule;
138
+ if (line.charCodeAt(5) === submoduleCharCode) {
139
+ const parts = line.split(" ");
140
+ submodule = {
141
+ oid: parts[7],
142
+ previousOid: parts[6]
143
+ };
127
144
  }
128
- case "u": {
129
- const submodule = lineParts[2]?.startsWith("S") ? {
130
- oid: lineParts[9],
131
- previousOid: lineParts[7]
132
- } : void 0;
133
- files.push(parseStatusFile(repoPath, lineParts[1], lineParts.slice(10).join(" "), getUri, void 0, submodule));
134
- break;
145
+ const tab = line.indexOf(" ", pathStart);
146
+ files.push(parseStatusFile(repoPath, line.substring(2, 4), tab === -1 ? line.substring(pathStart) : line.substring(pathStart, tab), getUri, tab === -1 ? void 0 : line.substring(tab + 1), submodule));
147
+ break;
148
+ }
149
+ case "u": {
150
+ const pathStart = indexAfterNthSpace(line, 10);
151
+ if (pathStart === -1) break;
152
+ let submodule;
153
+ if (line.charCodeAt(5) === submoduleCharCode) {
154
+ const parts = line.split(" ");
155
+ submodule = {
156
+ oid: parts[9],
157
+ previousOid: parts[7]
158
+ };
135
159
  }
136
- case "?": files.push(parseStatusFile(repoPath, "??", lineParts.slice(1).join(" "), getUri));
160
+ files.push(parseStatusFile(repoPath, line.substring(2, 4), line.substring(pathStart), getUri, void 0, submodule));
161
+ break;
137
162
  }
163
+ case "?": files.push(parseStatusFile(repoPath, "??", line.substring(2), getUri));
138
164
  }
139
165
  }
140
166
  return new GitStatus(normalizePath(repoPath), branch ?? "", sha ?? "", files, upstream ? {
@@ -151,7 +177,7 @@ function parseStatusFile(repoPath, rawStatus, fileName, getUri, originalFileName
151
177
  y = rawStatus[1] !== "." ? rawStatus[1].trim() : void 0;
152
178
  if (y == null || y.length === 0) y = void 0;
153
179
  }
154
- return new GitStatusFile(repoPath, x, y, fileName, getUri(fileName), originalFileName, submodule);
180
+ return new GitStatusFile(repoPath, x, y, fileName, getUri?.(fileName), originalFileName, submodule);
155
181
  }
156
182
  //#endregion
157
183
  export { parseGitStatus };
@@ -1 +1 @@
1
- {"version":3,"file":"statusParser.js","names":[],"sources":["../../../../git-cli/src/parsers/statusParser.ts"],"sourcesContent":["import { GitStatus } from '@gitlens/git/models/status.js';\nimport { GitStatusFile } from '@gitlens/git/models/statusFile.js';\nimport { normalizePath } from '@gitlens/utils/path.js';\nimport { maybeStopWatch } from '@gitlens/utils/stopwatch.js';\nimport type { Uri } from '@gitlens/utils/uri.js';\n\nconst aheadStatusV1Regex = /(?:ahead ([0-9]+))/;\nconst behindStatusV1Regex = /(?:behind ([0-9]+))/;\nconst quoteRegex = /\"/g;\n\nexport function parseGitStatus(\n\tdata: string,\n\trepoPath: string,\n\tporcelainVersion: number,\n\tgetUri: (path: string) => Uri,\n): GitStatus | undefined {\n\tusing sw = maybeStopWatch(`Git.parseStatus(${repoPath}, v=${porcelainVersion})`, {\n\t\tlog: { onlyExit: true, level: 'debug' },\n\t});\n\tif (!data) {\n\t\tsw?.stop({ suffix: ` no data` });\n\t\treturn undefined;\n\t}\n\n\tconst lines = data.split('\\n').filter(<T>(i?: T): i is T => Boolean(i));\n\tif (lines.length === 0) {\n\t\tsw?.stop({ suffix: ` parsed no files` });\n\t\treturn undefined;\n\t}\n\n\tconst status =\n\t\tporcelainVersion < 2 ? parseStatusV1(lines, repoPath, getUri) : parseStatusV2(lines, repoPath, getUri);\n\n\tsw?.stop({ suffix: ` parsed ${status.files.length} files` });\n\n\treturn status;\n}\n\nfunction parseStatusV1(lines: string[], repoPath: string, getUri: (path: string) => Uri): GitStatus {\n\tlet branch: string | undefined;\n\tconst files = [];\n\tconst state = {\n\t\tahead: 0,\n\t\tbehind: 0,\n\t};\n\tlet upstream;\n\tlet missing = false;\n\n\tlet position = -1;\n\twhile (++position < lines.length) {\n\t\tconst line = lines[position];\n\t\t// Header\n\t\tif (line.startsWith('##')) {\n\t\t\tconst lineParts = line.split(' ');\n\t\t\t[branch, upstream] = lineParts[1].split('...');\n\t\t\tif (lineParts.length > 2) {\n\t\t\t\tconst upstreamStatus = lineParts.slice(2).join(' ');\n\t\t\t\tif (upstreamStatus === '[gone]') {\n\t\t\t\t\tmissing = true;\n\t\t\t\t\tstate.ahead = 0;\n\t\t\t\t\tstate.behind = 0;\n\t\t\t\t} else {\n\t\t\t\t\tconst aheadStatus = aheadStatusV1Regex.exec(upstreamStatus);\n\t\t\t\t\tstate.ahead = aheadStatus == null ? 0 : Number(aheadStatus[1]) || 0;\n\n\t\t\t\t\tconst behindStatus = behindStatusV1Regex.exec(upstreamStatus);\n\t\t\t\t\tstate.behind = behindStatus == null ? 0 : Number(behindStatus[1]) || 0;\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tconst rawStatus = line.substring(0, 2);\n\t\t\tconst fileName = line.substring(3);\n\t\t\tif (rawStatus.startsWith('R') || rawStatus.startsWith('C')) {\n\t\t\t\tconst [file1, file2] = fileName.replace(quoteRegex, '').split(' -> ');\n\t\t\t\tfiles.push(parseStatusFile(repoPath, rawStatus, file2.trim(), getUri, file1.trim()));\n\t\t\t} else {\n\t\t\t\tfiles.push(parseStatusFile(repoPath, rawStatus, fileName, getUri));\n\t\t\t}\n\t\t}\n\t}\n\n\treturn new GitStatus(\n\t\tnormalizePath(repoPath),\n\t\tbranch ?? '',\n\t\t'',\n\t\tfiles,\n\t\tupstream ? { name: upstream, missing: missing, state: state } : undefined,\n\t);\n}\n\nfunction parseStatusV2(lines: string[], repoPath: string, getUri: (path: string) => Uri): GitStatus {\n\tlet branch: string | undefined;\n\tconst files = [];\n\tlet sha: string | undefined;\n\tconst state = {\n\t\tahead: 0,\n\t\tbehind: 0,\n\t};\n\tlet missing = true;\n\tlet upstream;\n\n\tlet position = -1;\n\twhile (++position < lines.length) {\n\t\tconst line = lines[position];\n\t\t// Headers\n\t\tif (line.startsWith('#')) {\n\t\t\tconst lineParts = line.split(' ');\n\t\t\tswitch (lineParts[1]) {\n\t\t\t\tcase 'branch.oid':\n\t\t\t\t\tsha = lineParts[2];\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'branch.head':\n\t\t\t\t\tbranch = lineParts[2];\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'branch.upstream':\n\t\t\t\t\tupstream = lineParts[2];\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'branch.ab':\n\t\t\t\t\tmissing = false;\n\t\t\t\t\tstate.ahead = Number(lineParts[2].substring(1));\n\t\t\t\t\tstate.behind = Number(lineParts[3].substring(1));\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t} else {\n\t\t\tconst lineParts = line.split(' ');\n\t\t\tswitch (lineParts[0][0]) {\n\t\t\t\tcase '1': {\n\t\t\t\t\t// normal: 1 <XY> <sub> <mH> <mI> <mW> <hH> <hI> <path>\n\t\t\t\t\t// <sub> starts with 'S' if submodule, 'N' if not\n\t\t\t\t\tconst submodule = lineParts[2]?.startsWith('S')\n\t\t\t\t\t\t? { oid: lineParts[7], previousOid: lineParts[6] }\n\t\t\t\t\t\t: undefined;\n\t\t\t\t\tfiles.push(\n\t\t\t\t\t\tparseStatusFile(\n\t\t\t\t\t\t\trepoPath,\n\t\t\t\t\t\t\tlineParts[1],\n\t\t\t\t\t\t\tlineParts.slice(8).join(' '),\n\t\t\t\t\t\t\tgetUri,\n\t\t\t\t\t\t\tundefined,\n\t\t\t\t\t\t\tsubmodule,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase '2': {\n\t\t\t\t\t// rename: 2 <XY> <sub> <mH> <mI> <mW> <hH> <hI> <X><score> <path>\\t<origPath>\n\t\t\t\t\tconst submodule = lineParts[2]?.startsWith('S')\n\t\t\t\t\t\t? { oid: lineParts[7], previousOid: lineParts[6] }\n\t\t\t\t\t\t: undefined;\n\t\t\t\t\tconst file = lineParts.slice(9).join(' ').split('\\t');\n\t\t\t\t\tfiles.push(parseStatusFile(repoPath, lineParts[1], file[0], getUri, file[1], submodule));\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase 'u': {\n\t\t\t\t\t// unmerged: u <XY> <sub> <m1> <m2> <m3> <mW> <h1> <h2> <h3> <path>\n\t\t\t\t\tconst submodule = lineParts[2]?.startsWith('S')\n\t\t\t\t\t\t? { oid: lineParts[9], previousOid: lineParts[7] }\n\t\t\t\t\t\t: undefined;\n\t\t\t\t\tfiles.push(\n\t\t\t\t\t\tparseStatusFile(\n\t\t\t\t\t\t\trepoPath,\n\t\t\t\t\t\t\tlineParts[1],\n\t\t\t\t\t\t\tlineParts.slice(10).join(' '),\n\t\t\t\t\t\t\tgetUri,\n\t\t\t\t\t\t\tundefined,\n\t\t\t\t\t\t\tsubmodule,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase '?': // untracked\n\t\t\t\t\tfiles.push(parseStatusFile(repoPath, '??', lineParts.slice(1).join(' '), getUri));\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn new GitStatus(\n\t\tnormalizePath(repoPath),\n\t\tbranch ?? '',\n\t\tsha ?? '',\n\t\tfiles,\n\t\tupstream ? { name: upstream, missing: missing, state: state } : undefined,\n\t);\n}\n\nfunction parseStatusFile(\n\trepoPath: string,\n\trawStatus: string,\n\tfileName: string,\n\tgetUri: (path: string) => Uri,\n\toriginalFileName?: string,\n\tsubmodule?: { readonly oid: string; readonly previousOid?: string },\n): GitStatusFile {\n\tlet x = !rawStatus.startsWith('.') ? rawStatus[0].trim() : undefined;\n\tif (x == null || x.length === 0) {\n\t\tx = undefined;\n\t}\n\n\tlet y = undefined;\n\tif (rawStatus.length > 1) {\n\t\ty = rawStatus[1] !== '.' ? rawStatus[1].trim() : undefined;\n\t\tif (y == null || y.length === 0) {\n\t\t\ty = undefined;\n\t\t}\n\t}\n\n\treturn new GitStatusFile(repoPath, x, y, fileName, getUri(fileName), originalFileName, submodule);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"statusParser.js","names":[],"sources":["../../../../git-cli/src/parsers/statusParser.ts"],"sourcesContent":["import { GitStatus } from '@gitlens/git/models/status.js';\nimport { GitStatusFile } from '@gitlens/git/models/statusFile.js';\nimport { normalizePath } from '@gitlens/utils/path.js';\nimport { maybeStopWatch } from '@gitlens/utils/stopwatch.js';\nimport type { Uri } from '@gitlens/utils/uri.js';\n\nconst aheadStatusV1Regex = /(?:ahead ([0-9]+))/;\nconst behindStatusV1Regex = /(?:behind ([0-9]+))/;\nconst quoteRegex = /\"/g;\nconst submoduleCharCode = 'S'.charCodeAt(0);\n\n/** Returns the index just after the `n`th space, or -1 if the line has fewer than `n` spaces. */\nfunction indexAfterNthSpace(line: string, n: number): number {\n\tlet index = -1;\n\tfor (let i = 0; i < n; i++) {\n\t\tindex = line.indexOf(' ', index + 1);\n\t\tif (index === -1) return -1;\n\t}\n\n\treturn index + 1;\n}\n\n/** Without `getUri`, each file builds its uri on first read rather than during the parse */\nexport function parseGitStatus(\n\tdata: string,\n\trepoPath: string,\n\tporcelainVersion: number,\n\tgetUri?: (path: string) => Uri,\n): GitStatus | undefined {\n\tusing sw = maybeStopWatch(`Git.parseStatus(${repoPath}, v=${porcelainVersion})`, {\n\t\tlog: { onlyExit: true, level: 'debug' },\n\t});\n\tif (!data) {\n\t\tsw?.stop({ suffix: ` no data` });\n\t\treturn undefined;\n\t}\n\n\tconst lines = data.split('\\n').filter(<T>(i?: T): i is T => Boolean(i));\n\tif (lines.length === 0) {\n\t\tsw?.stop({ suffix: ` parsed no files` });\n\t\treturn undefined;\n\t}\n\n\tconst status =\n\t\tporcelainVersion < 2 ? parseStatusV1(lines, repoPath, getUri) : parseStatusV2(lines, repoPath, getUri);\n\n\tsw?.stop({ suffix: ` parsed ${status.files.length} files` });\n\n\treturn status;\n}\n\nfunction parseStatusV1(lines: string[], repoPath: string, getUri: ((path: string) => Uri) | undefined): GitStatus {\n\tlet branch: string | undefined;\n\tconst files = [];\n\tconst state = {\n\t\tahead: 0,\n\t\tbehind: 0,\n\t};\n\tlet upstream;\n\tlet missing = false;\n\n\tlet position = -1;\n\twhile (++position < lines.length) {\n\t\tconst line = lines[position];\n\t\t// Header\n\t\tif (line.startsWith('##')) {\n\t\t\tconst lineParts = line.split(' ');\n\t\t\t[branch, upstream] = lineParts[1].split('...');\n\t\t\tif (lineParts.length > 2) {\n\t\t\t\tconst upstreamStatus = lineParts.slice(2).join(' ');\n\t\t\t\tif (upstreamStatus === '[gone]') {\n\t\t\t\t\tmissing = true;\n\t\t\t\t\tstate.ahead = 0;\n\t\t\t\t\tstate.behind = 0;\n\t\t\t\t} else {\n\t\t\t\t\tconst aheadStatus = aheadStatusV1Regex.exec(upstreamStatus);\n\t\t\t\t\tstate.ahead = aheadStatus == null ? 0 : Number(aheadStatus[1]) || 0;\n\n\t\t\t\t\tconst behindStatus = behindStatusV1Regex.exec(upstreamStatus);\n\t\t\t\t\tstate.behind = behindStatus == null ? 0 : Number(behindStatus[1]) || 0;\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tconst rawStatus = line.substring(0, 2);\n\t\t\tconst fileName = line.substring(3);\n\t\t\tif (rawStatus.startsWith('R') || rawStatus.startsWith('C')) {\n\t\t\t\tconst [file1, file2] = fileName.replace(quoteRegex, '').split(' -> ');\n\t\t\t\tfiles.push(parseStatusFile(repoPath, rawStatus, file2.trim(), getUri, file1.trim()));\n\t\t\t} else {\n\t\t\t\tfiles.push(parseStatusFile(repoPath, rawStatus, fileName, getUri));\n\t\t\t}\n\t\t}\n\t}\n\n\treturn new GitStatus(\n\t\tnormalizePath(repoPath),\n\t\tbranch ?? '',\n\t\t'',\n\t\tfiles,\n\t\tupstream ? { name: upstream, missing: missing, state: state } : undefined,\n\t);\n}\n\nfunction parseStatusV2(lines: string[], repoPath: string, getUri: ((path: string) => Uri) | undefined): GitStatus {\n\tlet branch: string | undefined;\n\tconst files = [];\n\tlet sha: string | undefined;\n\tconst state = {\n\t\tahead: 0,\n\t\tbehind: 0,\n\t};\n\tlet missing = true;\n\tlet upstream;\n\n\tlet position = -1;\n\twhile (++position < lines.length) {\n\t\tconst line = lines[position];\n\t\t// Headers\n\t\tif (line.startsWith('#')) {\n\t\t\tconst lineParts = line.split(' ');\n\t\t\tswitch (lineParts[1]) {\n\t\t\t\tcase 'branch.oid':\n\t\t\t\t\tsha = lineParts[2];\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'branch.head':\n\t\t\t\t\tbranch = lineParts[2];\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'branch.upstream':\n\t\t\t\t\tupstream = lineParts[2];\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'branch.ab':\n\t\t\t\t\tmissing = false;\n\t\t\t\t\tstate.ahead = Number(lineParts[2].substring(1));\n\t\t\t\t\tstate.behind = Number(lineParts[3].substring(1));\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t} else {\n\t\t\tswitch (line[0]) {\n\t\t\t\tcase '1': {\n\t\t\t\t\t// normal: 1 <XY> <sub> <mH> <mI> <mW> <hH> <hI> <path>\n\t\t\t\t\tconst pathStart = indexAfterNthSpace(line, 8);\n\t\t\t\t\tif (pathStart === -1) break;\n\n\t\t\t\t\t// <sub> starts with 'S' if submodule, 'N' if not\n\t\t\t\t\tlet submodule;\n\t\t\t\t\tif (line.charCodeAt(5) === submoduleCharCode) {\n\t\t\t\t\t\tconst parts = line.split(' ');\n\t\t\t\t\t\tsubmodule = { oid: parts[7], previousOid: parts[6] };\n\t\t\t\t\t}\n\n\t\t\t\t\tfiles.push(\n\t\t\t\t\t\tparseStatusFile(\n\t\t\t\t\t\t\trepoPath,\n\t\t\t\t\t\t\tline.substring(2, 4),\n\t\t\t\t\t\t\tline.substring(pathStart),\n\t\t\t\t\t\t\tgetUri,\n\t\t\t\t\t\t\tundefined,\n\t\t\t\t\t\t\tsubmodule,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase '2': {\n\t\t\t\t\t// rename: 2 <XY> <sub> <mH> <mI> <mW> <hH> <hI> <X><score> <path>\\t<origPath>\n\t\t\t\t\tconst pathStart = indexAfterNthSpace(line, 9);\n\t\t\t\t\tif (pathStart === -1) break;\n\n\t\t\t\t\tlet submodule;\n\t\t\t\t\tif (line.charCodeAt(5) === submoduleCharCode) {\n\t\t\t\t\t\tconst parts = line.split(' ');\n\t\t\t\t\t\tsubmodule = { oid: parts[7], previousOid: parts[6] };\n\t\t\t\t\t}\n\n\t\t\t\t\tconst tab = line.indexOf('\\t', pathStart);\n\t\t\t\t\tfiles.push(\n\t\t\t\t\t\tparseStatusFile(\n\t\t\t\t\t\t\trepoPath,\n\t\t\t\t\t\t\tline.substring(2, 4),\n\t\t\t\t\t\t\ttab === -1 ? line.substring(pathStart) : line.substring(pathStart, tab),\n\t\t\t\t\t\t\tgetUri,\n\t\t\t\t\t\t\ttab === -1 ? undefined : line.substring(tab + 1),\n\t\t\t\t\t\t\tsubmodule,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase 'u': {\n\t\t\t\t\t// unmerged: u <XY> <sub> <m1> <m2> <m3> <mW> <h1> <h2> <h3> <path>\n\t\t\t\t\tconst pathStart = indexAfterNthSpace(line, 10);\n\t\t\t\t\tif (pathStart === -1) break;\n\n\t\t\t\t\tlet submodule;\n\t\t\t\t\tif (line.charCodeAt(5) === submoduleCharCode) {\n\t\t\t\t\t\tconst parts = line.split(' ');\n\t\t\t\t\t\tsubmodule = { oid: parts[9], previousOid: parts[7] };\n\t\t\t\t\t}\n\n\t\t\t\t\tfiles.push(\n\t\t\t\t\t\tparseStatusFile(\n\t\t\t\t\t\t\trepoPath,\n\t\t\t\t\t\t\tline.substring(2, 4),\n\t\t\t\t\t\t\tline.substring(pathStart),\n\t\t\t\t\t\t\tgetUri,\n\t\t\t\t\t\t\tundefined,\n\t\t\t\t\t\t\tsubmodule,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase '?': // untracked\n\t\t\t\t\tfiles.push(parseStatusFile(repoPath, '??', line.substring(2), getUri));\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn new GitStatus(\n\t\tnormalizePath(repoPath),\n\t\tbranch ?? '',\n\t\tsha ?? '',\n\t\tfiles,\n\t\tupstream ? { name: upstream, missing: missing, state: state } : undefined,\n\t);\n}\n\nfunction parseStatusFile(\n\trepoPath: string,\n\trawStatus: string,\n\tfileName: string,\n\tgetUri: ((path: string) => Uri) | undefined,\n\toriginalFileName?: string,\n\tsubmodule?: { readonly oid: string; readonly previousOid?: string },\n): GitStatusFile {\n\tlet x = !rawStatus.startsWith('.') ? rawStatus[0].trim() : undefined;\n\tif (x == null || x.length === 0) {\n\t\tx = undefined;\n\t}\n\n\tlet y = undefined;\n\tif (rawStatus.length > 1) {\n\t\ty = rawStatus[1] !== '.' ? rawStatus[1].trim() : undefined;\n\t\tif (y == null || y.length === 0) {\n\t\t\ty = undefined;\n\t\t}\n\t}\n\n\treturn new GitStatusFile(repoPath, x, y, fileName, getUri?.(fileName), originalFileName, submodule);\n}\n"],"mappings":"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"}
@@ -3,7 +3,7 @@ import { iterateByDelimiter } from "../../utils/string.js";
3
3
  import { getScopedLogger } from "../../utils/logger.scoped.js";
4
4
  import { getSettledValue } from "../../utils/promise.js";
5
5
  import { debug } from "../../utils/decorators/log.js";
6
- import { fileUri, joinUriPath, toFsPath } from "../../utils/uri.js";
6
+ import { toFsPath } from "../../utils/uri.js";
7
7
  import { normalizePath, splitPath, stripFolderGlob } from "../../utils/path.js";
8
8
  import { PromiseMap } from "../../utils/promiseCache.js";
9
9
  import __decorate from "../../_virtual/@oxc-project+runtime/helpers/esm/decorate.js";
@@ -100,8 +100,7 @@ var StatusGitSubProvider = class {
100
100
  untracked: options?.untracked,
101
101
  branch: options?.branch
102
102
  }, cancellation);
103
- const repoUri = fileUri(normalizePath(repoPath));
104
- const status = parseGitStatus(result.stdout, repoPath, porcelainVersion, (p) => joinUriPath(repoUri, normalizePath(p)));
103
+ const status = parseGitStatus(result.stdout, repoPath, porcelainVersion);
105
104
  if (options?.branch === false) {
106
105
  if (status == null && result.completion.status === "exited" && result.exitCode === 0) return new GitStatus(normalizePath(repoPath), "", "", []);
107
106
  return status;
@@ -143,8 +142,7 @@ var StatusGitSubProvider = class {
143
142
  similarityThreshold: this.context.config?.commits.similarityThreshold,
144
143
  correlationKey
145
144
  }, cancellation, relativePath);
146
- const repoUri = fileUri(normalizePath(repoPath));
147
- return parseGitStatus(result.stdout, repoPath, porcelainVersion, (p) => joinUriPath(repoUri, normalizePath(p)))?.files;
145
+ return parseGitStatus(result.stdout, repoPath, porcelainVersion)?.files;
148
146
  }
149
147
  async statusCore(repoPath, porcelainVersion = 1, options, cancellation, ...pathspecs) {
150
148
  const params = ["status", porcelainVersion >= 2 ? `--porcelain=v${porcelainVersion}` : "--porcelain"];
@@ -1 +1 @@
1
- {"version":3,"file":"status.js","names":[],"sources":["../../../../git-cli/src/providers/status.ts"],"sourcesContent":["import type { Cache } from '@gitlens/git/cache.js';\nimport type { GitServiceContext } from '@gitlens/git/context.js';\nimport type { GitFile } from '@gitlens/git/models/file.js';\nimport { GitFileWorkingTreeStatus } from '@gitlens/git/models/fileStatus.js';\nimport type { GitConflictFile } from '@gitlens/git/models/staging.js';\nimport { GitStatus } from '@gitlens/git/models/status.js';\nimport type { GitStatusFile } from '@gitlens/git/models/statusFile.js';\nimport type { GitStatusSubProvider, GitWorkingChangesState } from '@gitlens/git/providers/status.js';\nimport type { GitCommandPriority } from '@gitlens/git/run.types.js';\nimport { isCancellationError, raceWithTimeout } from '@gitlens/utils/cancellation.js';\nimport { debug } from '@gitlens/utils/decorators/log.js';\nimport { createDisposable } from '@gitlens/utils/disposable.js';\nimport { getScopedLogger } from '@gitlens/utils/logger.scoped.js';\nimport { normalizePath, splitPath, stripFolderGlob } from '@gitlens/utils/path.js';\nimport { getSettledValue } from '@gitlens/utils/promise.js';\nimport { PromiseMap } from '@gitlens/utils/promiseCache.js';\nimport { iterateByDelimiter } from '@gitlens/utils/string.js';\nimport type { Uri } from '@gitlens/utils/uri.js';\nimport { fileUri, joinUriPath, toFsPath } from '@gitlens/utils/uri.js';\nimport type { CliGitProviderInternal } from '../cliGitProvider.js';\nimport type { GitResult } from '../exec/exec.types.js';\nimport type { Git } from '../exec/git.js';\nimport { gitConfigsStatus } from '../exec/git.js';\nimport { parseGitConflictFiles } from '../parsers/indexParser.js';\nimport { parseGitStatus } from '../parsers/statusParser.js';\n\n/** Backstop when `advanced.git.timeout` is disabled (0). Independent of the command timeout so disabling it can't\n * also disable deadlock recovery — mirrors `@gate`'s always-on 5-min force-clear. */\nconst disabledTimeoutBackstopMs = 1000 * 60 * 5;\n\n/**\n * Deadlock-backstop duration for a status read (see `dedupeByStatusGeneration`), exported for testing.\n * Always returns a value so a wedged read recovers even with `advanced.git.timeout` disabled (which bounds git\n * *commands*, not deadlock recovery). With a timeout set, `gitTimeout * 2` sits above the per-command timeout so\n * `git.run` reads reject at their own timeout first; the backstop's real job is bounding the timeout-less\n * `git.stream` reads (`hasUntrackedFiles`/`hasConflictingFiles`).\n */\nexport function computeDeadlockBackstopMs(gitTimeout: number | undefined): number {\n\tconst timeout = gitTimeout ?? 60000;\n\treturn timeout > 0 ? timeout * 2 : disabledTimeoutBackstopMs;\n}\n\n/**\n * A non-default `untracked`/`branch` gets its own key, so a full-status caller never joins a narrower run; the\n * default stays `'getStatus'`, which `getStatusForPathCore`'s rename path joins.\n */\nfunction getStatusReadKey(options?: { untracked?: 'no' | 'normal' | 'all'; branch?: false }): string {\n\tif (options?.untracked == null && options?.branch !== false) return 'getStatus';\n\n\tconst parts: string[] = [];\n\tif (options?.untracked != null) {\n\t\tparts.push(`untracked=${options.untracked}`);\n\t}\n\tif (options?.branch === false) {\n\t\tparts.push('branch=false');\n\t}\n\treturn `getStatus:${parts.join(':')}`;\n}\n\nexport class StatusGitSubProvider implements GitStatusSubProvider {\n\tconstructor(\n\t\tprivate readonly context: GitServiceContext,\n\t\tprivate readonly git: Git,\n\t\tprivate readonly cache: Cache,\n\t\tprivate readonly provider: CliGitProviderInternal,\n\t) {}\n\n\t/** In-flight reads, keyed by `<read>\\0<repoPath>\\0<generation>`. `PromiseMap` (not a bare Map) so joiners\n\t * get per-caller cancellation: the shared run aborts only when ALL current callers do. */\n\tprivate readonly _pendingReads = new PromiseMap<string, unknown>();\n\n\t/**\n\t * Single-flight for point-in-time reads of mutable working-tree state — replaces `@gate`, which dedups on\n\t * repoPath with no notion of *when* a run started and so joins a pre-change read to a post-change caller\n\t * (see {@link Cache.getStatusGeneration}). Same-generation callers share one run (via a `PromiseMap`, so a\n\t * joiner cancelling can't reject the others); a caller in a newer generation gets a different key and a\n\t * fresh run; a `force` read advances the clock first, so it fences every later reader too, not just itself.\n\t * The exec-layer command dedup (`Git.pendingCommands`) is fenced automatically: each run carries this entry's\n\t * aggregate signal, whose id is part of the exec cache key, so a newer-generation run never joins an older\n\t * one's process; the generation-derived `correlationKey` is a readable second guard on that key. A `raceWithTimeout` backstop (scaled to `advanced.git.timeout`, see\n\t * {@link computeDeadlockBackstopMs}) replaces `@gate`'s 5-min force-clear so a never-settling read (a hung\n\t * `git.stream`, which has no per-command timeout) can't wedge same-generation callers — including blocking\n\t * awaiters like the commit flow — indefinitely.\n\t */\n\tprivate dedupeByStatusGeneration<T>(\n\t\trepoPath: string,\n\t\tread: string,\n\t\trun: (correlationKey: string, signal: AbortSignal | undefined) => Promise<T>,\n\t\tcancellation?: AbortSignal,\n\t\tforce?: boolean,\n\t): Promise<T> {\n\t\t// A `force` (user refresh) read is the user asserting the working tree may have changed without the watcher\n\t\t// observing it — the same class of event as a watcher tick, so it ADVANCES the clock rather than carving out\n\t\t// a private key for itself. A one-shot nonce would fence only this caller: the pre-assertion run stays\n\t\t// joinable, so the very next ordinary reader still picks up pre-change content — and, stamped with a newer\n\t\t// `Wip.revision` (assigned at producer start, not read start), applies it right back over the refreshed\n\t\t// result. Advancing is also CHEAPER than a nonce: concurrent readers join the one fresh read instead of each\n\t\t// spawning a private one.\n\t\tif (force) {\n\t\t\tthis.cache.incrementStatusGeneration(repoPath);\n\t\t}\n\t\tconst generation = this.cache.getStatusGeneration(repoPath);\n\t\t// `\\0` separators: `read`/`repoPath` are free-form (paths can contain `:`), so a printable delimiter could\n\t\t// collide (`getStatusForPath:/a:x` vs `getStatusForPath:/a` + `:x`).\n\t\tconst key = `${read}\\0${repoPath}\\0${generation}`;\n\t\tconst correlationKey = `status:${generation}`;\n\t\tconst backstopMs = computeDeadlockBackstopMs(this.git.options.gitTimeout);\n\n\t\treturn this._pendingReads.getOrCreate(\n\t\t\tkey,\n\t\t\t(cacheable, signal) => {\n\t\t\t\t// Point-in-time read: never memoize a settled value. `invalidate()` at the start makes the entry\n\t\t\t\t// self-evict on settle, so a later caller in this same generation re-reads (a newer generation\n\t\t\t\t// already gets a different key). `signal` is the aggregate — the run aborts only if every caller\n\t\t\t\t// cancels, so one caller's abort can't reject the others. The backstop rejects a wedged read so\n\t\t\t\t// waiters unblock and the entry self-evicts; it ALSO aborts the underlying git op (via `backstop`\n\t\t\t\t// linked into the run's signal) so a wedged read releases its GitQueue slot/process rather than\n\t\t\t\t// running orphaned — best-effort, since a truly-stuck process may ignore the abort.\n\t\t\t\tcacheable.invalidate();\n\t\t\t\tconst backstop = new AbortController();\n\t\t\t\tconst runSignal = signal != null ? AbortSignal.any([signal, backstop.signal]) : backstop.signal;\n\t\t\t\treturn raceWithTimeout(run(correlationKey, runSignal), backstopMs, backstop);\n\t\t\t},\n\t\t\tcancellation,\n\t\t) as Promise<T>;\n\t}\n\n\t@debug()\n\tgetStatus(\n\t\trepoPath: string | undefined,\n\t\toptions?: {\n\t\t\tpriority?: GitCommandPriority;\n\t\t\tforce?: boolean;\n\t\t\tuntracked?: 'no' | 'normal' | 'all';\n\t\t\tbranch?: false;\n\t\t},\n\t\tcancellation?: AbortSignal,\n\t): Promise<GitStatus | undefined> {\n\t\tif (repoPath == null) return Promise.resolve(undefined);\n\n\t\t// `-u` already means `-uall`, so an explicit `'all'` shares the default run\n\t\tconst opts = options?.untracked === 'all' ? { ...options, untracked: undefined } : options;\n\n\t\treturn this.dedupeByStatusGeneration(\n\t\t\trepoPath,\n\t\t\tgetStatusReadKey(opts),\n\t\t\t(correlationKey, signal) =>\n\t\t\t\tthis.getStatusCore(repoPath, { ...opts, correlationKey: correlationKey }, signal),\n\t\t\tcancellation,\n\t\t\topts?.force,\n\t\t);\n\t}\n\n\tprivate async getStatusCore(\n\t\trepoPath: string,\n\t\toptions?: {\n\t\t\tpriority?: GitCommandPriority;\n\t\t\tcorrelationKey?: string;\n\t\t\tuntracked?: 'no' | 'normal' | 'all';\n\t\t\tbranch?: false;\n\t\t},\n\t\tcancellation?: AbortSignal,\n\t): Promise<GitStatus | undefined> {\n\t\tconst porcelainVersion = (await this.git.supports('git:status:porcelain-v2')) ? 2 : 1;\n\n\t\tconst result = await this.statusCore(\n\t\t\trepoPath,\n\t\t\tporcelainVersion,\n\t\t\t{\n\t\t\t\tsimilarityThreshold: this.context.config?.commits.similarityThreshold,\n\t\t\t\tpriority: options?.priority,\n\t\t\t\tcorrelationKey: options?.correlationKey,\n\t\t\t\tuntracked: options?.untracked,\n\t\t\t\tbranch: options?.branch,\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\t\tconst repoUri = fileUri(normalizePath(repoPath));\n\t\tconst status = parseGitStatus(result.stdout, repoPath, porcelainVersion, p =>\n\t\t\tjoinUriPath(repoUri, normalizePath(p)),\n\t\t);\n\n\t\tif (options?.branch === false) {\n\t\t\t// Without `--branch` a clean tree prints nothing at all, which the parser reads as no status\n\t\t\tif (status == null && result.completion.status === 'exited' && result.exitCode === 0) {\n\t\t\t\treturn new GitStatus(normalizePath(repoPath), '', '', []);\n\t\t\t}\n\t\t\t// No branch name either, which `GitStatus` reads as detached, so there is no paused rebase to look up\n\t\t\treturn status;\n\t\t}\n\n\t\tif (status?.detached) {\n\t\t\tconst pausedOpStatus = await this.provider.pausedOps?.getPausedOperationStatus?.(\n\t\t\t\trepoPath,\n\t\t\t\tundefined,\n\t\t\t\tcancellation,\n\t\t\t);\n\t\t\tif (pausedOpStatus?.type === 'rebase') {\n\t\t\t\treturn new GitStatus(\n\t\t\t\t\trepoPath,\n\t\t\t\t\tpausedOpStatus.incoming.name,\n\t\t\t\t\tstatus.sha,\n\t\t\t\t\tstatus.files,\n\t\t\t\t\tstatus.upstream,\n\t\t\t\t\ttrue,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\treturn status;\n\t}\n\n\t@debug()\n\tasync getStatusForFile(\n\t\trepoPath: string,\n\t\tpathOrUri: string | Uri,\n\t\toptions?: { renames?: boolean },\n\t\tcancellation?: AbortSignal,\n\t): Promise<GitStatusFile | undefined> {\n\t\tconst files = await this.getStatusForPathCore(\n\t\t\trepoPath,\n\t\t\ttoFsPath(pathOrUri),\n\t\t\t{ ...options, exact: true },\n\t\t\tcancellation,\n\t\t);\n\t\treturn files?.[0];\n\t}\n\n\t@debug()\n\tasync getStatusForPath(\n\t\trepoPath: string,\n\t\tpathOrUri: string | Uri,\n\t\toptions?: { renames?: boolean },\n\t\tcancellation?: AbortSignal,\n\t): Promise<GitStatusFile[] | undefined> {\n\t\treturn this.getStatusForPathCore(repoPath, toFsPath(pathOrUri), { ...options, exact: false }, cancellation);\n\t}\n\n\tprivate async getStatusForPathCore(\n\t\trepoPath: string,\n\t\tpathOrUri: string,\n\t\toptions: { exact: boolean; renames?: boolean },\n\t\tcancellation?: AbortSignal,\n\t): Promise<GitStatusFile[] | undefined> {\n\t\tconst relativePath = stripFolderGlob(splitPath(pathOrUri, repoPath)[0]);\n\n\t\t// Rename-aware queries can't scope by pathspec (that disables Git's rename detection), so they run the\n\t\t// SAME full `git status` as `getStatus`. Delegate to it — sharing one process/dedup entry — and filter,\n\t\t// rather than spawning a second identical `git status`.\n\t\tif (options.renames !== false) {\n\t\t\tconst status = await this.getStatus(repoPath, undefined, cancellation);\n\t\t\tif (status == null) return undefined;\n\n\t\t\tif (options.exact) {\n\t\t\t\tconst file = status.files.find(f => f.path === relativePath);\n\t\t\t\treturn file ? [file] : undefined;\n\t\t\t}\n\t\t\treturn status.files.filter(f => f.path.startsWith(relativePath));\n\t\t}\n\n\t\t// Non-rename query: pathspec-scoped `git status` (a distinct command), deduped on its own key. `exact`\n\t\t// is NOT in the key — the pathspec-scoped command is identical for exact/non-exact, so both share one run.\n\t\treturn this.dedupeByStatusGeneration(\n\t\t\trepoPath,\n\t\t\t// Free-form `relativePath` (may contain `:`) goes LAST so the prefix can't be shifted into it.\n\t\t\t`getStatusForPath:${relativePath}`,\n\t\t\t(correlationKey, signal) => this.getStatusForPathScoped(repoPath, relativePath, correlationKey, signal),\n\t\t\tcancellation,\n\t\t);\n\t}\n\n\tprivate async getStatusForPathScoped(\n\t\trepoPath: string,\n\t\trelativePath: string,\n\t\tcorrelationKey: string,\n\t\tcancellation?: AbortSignal,\n\t): Promise<GitStatusFile[] | undefined> {\n\t\tconst porcelainVersion = (await this.git.supports('git:status:porcelain-v2')) ? 2 : 1;\n\t\tconst result = await this.statusCore(\n\t\t\trepoPath,\n\t\t\tporcelainVersion,\n\t\t\t{ similarityThreshold: this.context.config?.commits.similarityThreshold, correlationKey: correlationKey },\n\t\t\tcancellation,\n\t\t\trelativePath,\n\t\t);\n\n\t\tconst repoUri = fileUri(normalizePath(repoPath));\n\t\tconst status = parseGitStatus(result.stdout, repoPath, porcelainVersion, p =>\n\t\t\tjoinUriPath(repoUri, normalizePath(p)),\n\t\t);\n\t\treturn status?.files;\n\t}\n\n\tprivate async statusCore(\n\t\trepoPath: string,\n\t\tporcelainVersion: number = 1,\n\t\toptions?: {\n\t\t\tsimilarityThreshold?: number | null;\n\t\t\tpriority?: GitCommandPriority;\n\t\t\tcorrelationKey?: string;\n\t\t\tuntracked?: 'no' | 'normal' | 'all';\n\t\t\tbranch?: false;\n\t\t},\n\t\tcancellation?: AbortSignal,\n\t\t...pathspecs: string[]\n\t): Promise<GitResult> {\n\t\tconst params = ['status', porcelainVersion >= 2 ? `--porcelain=v${porcelainVersion}` : '--porcelain'];\n\t\tif (options?.branch !== false) {\n\t\t\tparams.push('--branch');\n\t\t}\n\t\tparams.push(options?.untracked != null ? `-u${options.untracked}` : '-u');\n\t\tif (await this.git.supports('git:status:find-renames')) {\n\t\t\tparams.push(\n\t\t\t\t`--find-renames${options?.similarityThreshold == null ? '' : `=${options.similarityThreshold}%`}`,\n\t\t\t);\n\t\t}\n\n\t\treturn this.git.run(\n\t\t\t{\n\t\t\t\tcwd: repoPath,\n\t\t\t\tcancellation: cancellation,\n\t\t\t\tconfigs: gitConfigsStatus,\n\t\t\t\tenv: { GIT_OPTIONAL_LOCKS: '0' },\n\t\t\t\tcorrelationKey: options?.correlationKey,\n\t\t\t\t...(options?.priority != null ? { priority: options.priority } : undefined),\n\t\t\t},\n\t\t\t...params,\n\t\t\t'--',\n\t\t\t...pathspecs,\n\t\t);\n\t}\n\n\t@debug()\n\thasWorkingChanges(\n\t\trepoPath: string,\n\t\toptions?: {\n\t\t\tstaged?: boolean;\n\t\t\tunstaged?: boolean;\n\t\t\tuntracked?: boolean;\n\t\t\tthrowOnError?: boolean;\n\t\t\tpriority?: GitCommandPriority;\n\t\t},\n\t\tcancellation?: AbortSignal,\n\t): Promise<boolean> {\n\t\tconst scope = getScopedLogger();\n\n\t\tconst staged = options?.staged ?? true;\n\t\tconst unstaged = options?.unstaged ?? true;\n\t\tconst untracked = options?.untracked ?? true;\n\t\t// `throwOnError` is in the key because it changes the RESULT on git failure (throw vs `false`) — a\n\t\t// throwing caller must not join, and be silently satisfied by, a non-throwing caller's graceful run.\n\t\t// `priority` is in the key for a different reason: it doesn't change the answer, but a joiner INHERITS\n\t\t// the in-flight run's scheduling. `GitQueue` refuses to start background work while anything is\n\t\t// waiting at normal/interactive, so letting a foreground caller (the overview's dirty pill, the\n\t\t// Worktrees view) join a `background` probe would strand it for the whole graph load. Splitting the\n\t\t// two lanes costs at most one extra spawn; joining costs unbounded foreground latency.\n\t\tconst throwOnError = options?.throwOnError ?? false;\n\t\tconst priority = options?.priority ?? 'default';\n\n\t\treturn this.dedupeByStatusGeneration(\n\t\t\trepoPath,\n\t\t\t`hasWorkingChanges:${staged}:${unstaged}:${untracked}:${throwOnError}:${priority}`,\n\t\t\tasync (correlationKey, signal) => {\n\t\t\t\ttry {\n\t\t\t\t\tif (staged || unstaged) {\n\t\t\t\t\t\tconst result = await this.git.run(\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tcwd: repoPath,\n\t\t\t\t\t\t\t\tcancellation: signal,\n\t\t\t\t\t\t\t\terrors: 'ignore',\n\t\t\t\t\t\t\t\tcorrelationKey: correlationKey,\n\t\t\t\t\t\t\t\t...(options?.priority != null ? { priority: options.priority } : undefined),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t'diff',\n\t\t\t\t\t\t\t'--quiet',\n\t\t\t\t\t\t\tstaged && unstaged ? 'HEAD' : staged ? '--staged' : undefined,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tif (result.exitCode === 1) {\n\t\t\t\t\t\t\tif (staged && unstaged) {\n\t\t\t\t\t\t\t\tscope?.addExitInfo('has staged and unstaged changes');\n\t\t\t\t\t\t\t} else if (staged) {\n\t\t\t\t\t\t\t\tscope?.addExitInfo('has staged changes');\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tscope?.addExitInfo('has unstaged changes');\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Check for untracked files. NOTE: this runs through `git.stream`, which spawns directly\n\t\t\t\t\t// and never enters the queue — so `priority` above doesn't reach it. Only clean worktrees\n\t\t\t\t\t// get this far (a dirty one already returned above), and callers fanning out across many\n\t\t\t\t\t// worktrees are expected to bound their own concurrency.\n\t\t\t\t\tif (untracked) {\n\t\t\t\t\t\tconst hasUntracked = await this.hasUntrackedFiles(repoPath, signal);\n\t\t\t\t\t\tif (hasUntracked) {\n\t\t\t\t\t\t\tscope?.addExitInfo('has untracked files');\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tscope?.addExitInfo('no working changes');\n\t\t\t\t\treturn false;\n\t\t\t\t} catch (ex) {\n\t\t\t\t\t// Re-throw cancellation errors\n\t\t\t\t\tif (isCancellationError(ex)) throw ex;\n\n\t\t\t\t\t// Log other errors and return false for graceful degradation\n\t\t\t\t\tscope?.error(ex);\n\t\t\t\t\tscope?.addExitInfo('error checking for changes');\n\t\t\t\t\tif (throwOnError) throw ex;\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\t}\n\n\t@debug()\n\tgetWorkingChangesState(repoPath: string, cancellation?: AbortSignal): Promise<GitWorkingChangesState> {\n\t\tconst scope = getScopedLogger();\n\n\t\treturn this.dedupeByStatusGeneration(\n\t\t\trepoPath,\n\t\t\t'getWorkingChangesState',\n\t\t\tasync (correlationKey, signal) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst [stagedResult, unstagedResult, untrackedResult] = await Promise.allSettled([\n\t\t\t\t\t\t// Check for staged changes\n\t\t\t\t\t\tthis.git.run(\n\t\t\t\t\t\t\t{ cwd: repoPath, cancellation: signal, errors: 'ignore', correlationKey: correlationKey },\n\t\t\t\t\t\t\t'diff',\n\t\t\t\t\t\t\t'--quiet',\n\t\t\t\t\t\t\t'--staged',\n\t\t\t\t\t\t),\n\t\t\t\t\t\t// Check for unstaged changes\n\t\t\t\t\t\tthis.git.run(\n\t\t\t\t\t\t\t{ cwd: repoPath, cancellation: signal, errors: 'ignore', correlationKey: correlationKey },\n\t\t\t\t\t\t\t'diff',\n\t\t\t\t\t\t\t'--quiet',\n\t\t\t\t\t\t),\n\t\t\t\t\t\t// Check for untracked files\n\t\t\t\t\t\tthis.hasUntrackedFiles(repoPath, signal),\n\t\t\t\t\t]);\n\n\t\t\t\t\tconst result = {\n\t\t\t\t\t\tstaged: getSettledValue(stagedResult)?.exitCode === 1,\n\t\t\t\t\t\tunstaged: getSettledValue(unstagedResult)?.exitCode === 1,\n\t\t\t\t\t\tuntracked: getSettledValue(untrackedResult) === true,\n\t\t\t\t\t};\n\n\t\t\t\t\tscope?.addExitInfo(\n\t\t\t\t\t\tresult.staged || result.unstaged || result.untracked\n\t\t\t\t\t\t\t? `has ${result.staged ? 'staged' : ''}${result.unstaged ? (result.staged ? ', unstaged' : 'unstaged ') : ''}${\n\t\t\t\t\t\t\t\t\tresult.untracked\n\t\t\t\t\t\t\t\t\t\t? result.staged || result.unstaged\n\t\t\t\t\t\t\t\t\t\t\t? ', untracked'\n\t\t\t\t\t\t\t\t\t\t\t: 'untracked'\n\t\t\t\t\t\t\t\t\t\t: ''\n\t\t\t\t\t\t\t\t} changes`\n\t\t\t\t\t\t\t: 'no working changes',\n\t\t\t\t\t);\n\n\t\t\t\t\treturn result;\n\t\t\t\t} catch (ex) {\n\t\t\t\t\tif (isCancellationError(ex)) throw ex;\n\n\t\t\t\t\tscope?.error(ex);\n\t\t\t\t\tscope?.addExitInfo('error checking for changes');\n\t\t\t\t\t// Return all false on error for graceful degradation\n\t\t\t\t\treturn { staged: false, unstaged: false, untracked: false };\n\t\t\t\t}\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\t}\n\n\thasConflictingFiles(repoPath: string, cancellation?: AbortSignal): Promise<boolean> {\n\t\t// Route through `dedupeByStatusGeneration` (like every other point-in-time read here) for the join-fence\n\t\t// and the `raceWithTimeout` backstop — a bare `git.stream` has no per-command timeout and could wedge.\n\t\treturn this.dedupeByStatusGeneration(\n\t\t\trepoPath,\n\t\t\t'hasConflictingFiles',\n\t\t\tasync (_correlationKey, signal) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst stream = this.git.stream({ cwd: repoPath, cancellation: signal }, 'ls-files', '--unmerged');\n\t\t\t\t\tusing _streamDisposer = createDisposable(() => void stream.return?.(undefined));\n\n\t\t\t\t\t// Early exit on first chunk - breaking causes SIGPIPE, killing git process\n\t\t\t\t\tfor await (const _chunk of stream) {\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\n\t\t\t\t\treturn false;\n\t\t\t\t} catch (ex) {\n\t\t\t\t\t// Re-throw cancellation errors\n\t\t\t\t\tif (isCancellationError(ex)) throw ex;\n\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\t}\n\n\t@debug()\n\tgetConflictingFiles(repoPath: string, cancellation?: AbortSignal): Promise<GitConflictFile[]> {\n\t\tconst scope = getScopedLogger();\n\n\t\treturn this.dedupeByStatusGeneration(\n\t\t\trepoPath,\n\t\t\t'getConflictingFiles',\n\t\t\tasync (correlationKey, signal) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await this.git.run(\n\t\t\t\t\t\t{ cwd: repoPath, cancellation: signal, errors: 'ignore', correlationKey: correlationKey },\n\t\t\t\t\t\t'ls-files',\n\t\t\t\t\t\t'-z',\n\t\t\t\t\t\t'--unmerged',\n\t\t\t\t\t);\n\n\t\t\t\t\tif (!result.stdout) {\n\t\t\t\t\t\tscope?.addExitInfo('no conflicting files');\n\t\t\t\t\t\treturn [];\n\t\t\t\t\t}\n\n\t\t\t\t\tconst files = parseGitConflictFiles(result.stdout, repoPath);\n\t\t\t\t\tscope?.addExitInfo(`${String(files.length)} conflicting file(s)`);\n\t\t\t\t\treturn files;\n\t\t\t\t} catch (ex) {\n\t\t\t\t\t// Re-throw cancellation errors\n\t\t\t\t\tif (isCancellationError(ex)) throw ex;\n\n\t\t\t\t\t// Log other errors and return empty array for graceful degradation\n\t\t\t\t\tscope?.error(ex);\n\t\t\t\t\tscope?.addExitInfo('error getting conflicting files');\n\t\t\t\t\treturn [];\n\t\t\t\t}\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\t}\n\n\tprivate async hasUntrackedFiles(repoPath: string, cancellation?: AbortSignal): Promise<boolean> {\n\t\ttry {\n\t\t\tconst stream = this.git.stream(\n\t\t\t\t{ cwd: repoPath, cancellation: cancellation },\n\t\t\t\t'ls-files',\n\t\t\t\t// '-z', // Unneeded since we are only looking for presence\n\t\t\t\t'--others',\n\t\t\t\t'--exclude-standard',\n\t\t\t);\n\t\t\tusing _streamDisposer = createDisposable(() => void stream.return?.(undefined));\n\n\t\t\t// Early exit on first chunk - breaking causes SIGPIPE, killing git process\n\t\t\tfor await (const _chunk of stream) {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\treturn false;\n\t\t} catch (ex) {\n\t\t\t// Re-throw cancellation errors\n\t\t\tif (isCancellationError(ex)) throw ex;\n\n\t\t\t// Treat other errors as \"no untracked files\" for graceful degradation\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t@debug()\n\tgetUntrackedFiles(repoPath: string, cancellation?: AbortSignal): Promise<GitFile[]> {\n\t\tconst scope = getScopedLogger();\n\n\t\treturn this.dedupeByStatusGeneration(\n\t\t\trepoPath,\n\t\t\t'getUntrackedFiles',\n\t\t\tasync (correlationKey, signal) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await this.git.run(\n\t\t\t\t\t\t{ cwd: repoPath, cancellation: signal, errors: 'ignore', correlationKey: correlationKey },\n\t\t\t\t\t\t'ls-files',\n\t\t\t\t\t\t'-z',\n\t\t\t\t\t\t'--others',\n\t\t\t\t\t\t'--exclude-standard',\n\t\t\t\t\t);\n\n\t\t\t\t\tif (!result.stdout) {\n\t\t\t\t\t\tscope?.addExitInfo('no untracked files');\n\t\t\t\t\t\treturn [];\n\t\t\t\t\t}\n\n\t\t\t\t\tconst files: GitFile[] = [];\n\n\t\t\t\t\tfor (const line of iterateByDelimiter(result.stdout, '\\0')) {\n\t\t\t\t\t\tif (!line.length) continue;\n\n\t\t\t\t\t\tfiles.push({ path: line, repoPath: repoPath, status: GitFileWorkingTreeStatus.Untracked });\n\t\t\t\t\t}\n\n\t\t\t\t\tscope?.addExitInfo(`${String(files.length)} untracked file(s)`);\n\t\t\t\t\treturn files;\n\t\t\t\t} catch (ex) {\n\t\t\t\t\t// Re-throw cancellation errors\n\t\t\t\t\tif (isCancellationError(ex)) throw ex;\n\n\t\t\t\t\t// Log other errors and return empty array for graceful degradation\n\t\t\t\t\tscope?.error(ex);\n\t\t\t\t\tscope?.addExitInfo('error getting untracked files');\n\t\t\t\t\treturn [];\n\t\t\t\t}\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"status.js","names":[],"sources":["../../../../git-cli/src/providers/status.ts"],"sourcesContent":["import type { Cache } from '@gitlens/git/cache.js';\nimport type { GitServiceContext } from '@gitlens/git/context.js';\nimport type { GitFile } from '@gitlens/git/models/file.js';\nimport { GitFileWorkingTreeStatus } from '@gitlens/git/models/fileStatus.js';\nimport type { GitConflictFile } from '@gitlens/git/models/staging.js';\nimport { GitStatus } from '@gitlens/git/models/status.js';\nimport type { GitStatusFile } from '@gitlens/git/models/statusFile.js';\nimport type { GitStatusSubProvider, GitWorkingChangesState } from '@gitlens/git/providers/status.js';\nimport type { GitCommandPriority } from '@gitlens/git/run.types.js';\nimport { isCancellationError, raceWithTimeout } from '@gitlens/utils/cancellation.js';\nimport { debug } from '@gitlens/utils/decorators/log.js';\nimport { createDisposable } from '@gitlens/utils/disposable.js';\nimport { getScopedLogger } from '@gitlens/utils/logger.scoped.js';\nimport { normalizePath, splitPath, stripFolderGlob } from '@gitlens/utils/path.js';\nimport { getSettledValue } from '@gitlens/utils/promise.js';\nimport { PromiseMap } from '@gitlens/utils/promiseCache.js';\nimport { iterateByDelimiter } from '@gitlens/utils/string.js';\nimport type { Uri } from '@gitlens/utils/uri.js';\nimport { toFsPath } from '@gitlens/utils/uri.js';\nimport type { CliGitProviderInternal } from '../cliGitProvider.js';\nimport type { GitResult } from '../exec/exec.types.js';\nimport type { Git } from '../exec/git.js';\nimport { gitConfigsStatus } from '../exec/git.js';\nimport { parseGitConflictFiles } from '../parsers/indexParser.js';\nimport { parseGitStatus } from '../parsers/statusParser.js';\n\n/** Backstop when `advanced.git.timeout` is disabled (0). Independent of the command timeout so disabling it can't\n * also disable deadlock recovery — mirrors `@gate`'s always-on 5-min force-clear. */\nconst disabledTimeoutBackstopMs = 1000 * 60 * 5;\n\n/**\n * Deadlock-backstop duration for a status read (see `dedupeByStatusGeneration`), exported for testing.\n * Always returns a value so a wedged read recovers even with `advanced.git.timeout` disabled (which bounds git\n * *commands*, not deadlock recovery). With a timeout set, `gitTimeout * 2` sits above the per-command timeout so\n * `git.run` reads reject at their own timeout first; the backstop's real job is bounding the timeout-less\n * `git.stream` reads (`hasUntrackedFiles`/`hasConflictingFiles`).\n */\nexport function computeDeadlockBackstopMs(gitTimeout: number | undefined): number {\n\tconst timeout = gitTimeout ?? 60000;\n\treturn timeout > 0 ? timeout * 2 : disabledTimeoutBackstopMs;\n}\n\n/**\n * A non-default `untracked`/`branch` gets its own key, so a full-status caller never joins a narrower run; the\n * default stays `'getStatus'`, which `getStatusForPathCore`'s rename path joins.\n */\nfunction getStatusReadKey(options?: { untracked?: 'no' | 'normal' | 'all'; branch?: false }): string {\n\tif (options?.untracked == null && options?.branch !== false) return 'getStatus';\n\n\tconst parts: string[] = [];\n\tif (options?.untracked != null) {\n\t\tparts.push(`untracked=${options.untracked}`);\n\t}\n\tif (options?.branch === false) {\n\t\tparts.push('branch=false');\n\t}\n\treturn `getStatus:${parts.join(':')}`;\n}\n\nexport class StatusGitSubProvider implements GitStatusSubProvider {\n\tconstructor(\n\t\tprivate readonly context: GitServiceContext,\n\t\tprivate readonly git: Git,\n\t\tprivate readonly cache: Cache,\n\t\tprivate readonly provider: CliGitProviderInternal,\n\t) {}\n\n\t/** In-flight reads, keyed by `<read>\\0<repoPath>\\0<generation>`. `PromiseMap` (not a bare Map) so joiners\n\t * get per-caller cancellation: the shared run aborts only when ALL current callers do. */\n\tprivate readonly _pendingReads = new PromiseMap<string, unknown>();\n\n\t/**\n\t * Single-flight for point-in-time reads of mutable working-tree state — replaces `@gate`, which dedups on\n\t * repoPath with no notion of *when* a run started and so joins a pre-change read to a post-change caller\n\t * (see {@link Cache.getStatusGeneration}). Same-generation callers share one run (via a `PromiseMap`, so a\n\t * joiner cancelling can't reject the others); a caller in a newer generation gets a different key and a\n\t * fresh run; a `force` read advances the clock first, so it fences every later reader too, not just itself.\n\t * The exec-layer command dedup (`Git.pendingCommands`) is fenced automatically: each run carries this entry's\n\t * aggregate signal, whose id is part of the exec cache key, so a newer-generation run never joins an older\n\t * one's process; the generation-derived `correlationKey` is a readable second guard on that key. A `raceWithTimeout` backstop (scaled to `advanced.git.timeout`, see\n\t * {@link computeDeadlockBackstopMs}) replaces `@gate`'s 5-min force-clear so a never-settling read (a hung\n\t * `git.stream`, which has no per-command timeout) can't wedge same-generation callers — including blocking\n\t * awaiters like the commit flow — indefinitely.\n\t */\n\tprivate dedupeByStatusGeneration<T>(\n\t\trepoPath: string,\n\t\tread: string,\n\t\trun: (correlationKey: string, signal: AbortSignal | undefined) => Promise<T>,\n\t\tcancellation?: AbortSignal,\n\t\tforce?: boolean,\n\t): Promise<T> {\n\t\t// A `force` (user refresh) read is the user asserting the working tree may have changed without the watcher\n\t\t// observing it — the same class of event as a watcher tick, so it ADVANCES the clock rather than carving out\n\t\t// a private key for itself. A one-shot nonce would fence only this caller: the pre-assertion run stays\n\t\t// joinable, so the very next ordinary reader still picks up pre-change content — and, stamped with a newer\n\t\t// `Wip.revision` (assigned at producer start, not read start), applies it right back over the refreshed\n\t\t// result. Advancing is also CHEAPER than a nonce: concurrent readers join the one fresh read instead of each\n\t\t// spawning a private one.\n\t\tif (force) {\n\t\t\tthis.cache.incrementStatusGeneration(repoPath);\n\t\t}\n\t\tconst generation = this.cache.getStatusGeneration(repoPath);\n\t\t// `\\0` separators: `read`/`repoPath` are free-form (paths can contain `:`), so a printable delimiter could\n\t\t// collide (`getStatusForPath:/a:x` vs `getStatusForPath:/a` + `:x`).\n\t\tconst key = `${read}\\0${repoPath}\\0${generation}`;\n\t\tconst correlationKey = `status:${generation}`;\n\t\tconst backstopMs = computeDeadlockBackstopMs(this.git.options.gitTimeout);\n\n\t\treturn this._pendingReads.getOrCreate(\n\t\t\tkey,\n\t\t\t(cacheable, signal) => {\n\t\t\t\t// Point-in-time read: never memoize a settled value. `invalidate()` at the start makes the entry\n\t\t\t\t// self-evict on settle, so a later caller in this same generation re-reads (a newer generation\n\t\t\t\t// already gets a different key). `signal` is the aggregate — the run aborts only if every caller\n\t\t\t\t// cancels, so one caller's abort can't reject the others. The backstop rejects a wedged read so\n\t\t\t\t// waiters unblock and the entry self-evicts; it ALSO aborts the underlying git op (via `backstop`\n\t\t\t\t// linked into the run's signal) so a wedged read releases its GitQueue slot/process rather than\n\t\t\t\t// running orphaned — best-effort, since a truly-stuck process may ignore the abort.\n\t\t\t\tcacheable.invalidate();\n\t\t\t\tconst backstop = new AbortController();\n\t\t\t\tconst runSignal = signal != null ? AbortSignal.any([signal, backstop.signal]) : backstop.signal;\n\t\t\t\treturn raceWithTimeout(run(correlationKey, runSignal), backstopMs, backstop);\n\t\t\t},\n\t\t\tcancellation,\n\t\t) as Promise<T>;\n\t}\n\n\t@debug()\n\tgetStatus(\n\t\trepoPath: string | undefined,\n\t\toptions?: {\n\t\t\tpriority?: GitCommandPriority;\n\t\t\tforce?: boolean;\n\t\t\tuntracked?: 'no' | 'normal' | 'all';\n\t\t\tbranch?: false;\n\t\t},\n\t\tcancellation?: AbortSignal,\n\t): Promise<GitStatus | undefined> {\n\t\tif (repoPath == null) return Promise.resolve(undefined);\n\n\t\t// `-u` already means `-uall`, so an explicit `'all'` shares the default run\n\t\tconst opts = options?.untracked === 'all' ? { ...options, untracked: undefined } : options;\n\n\t\treturn this.dedupeByStatusGeneration(\n\t\t\trepoPath,\n\t\t\tgetStatusReadKey(opts),\n\t\t\t(correlationKey, signal) =>\n\t\t\t\tthis.getStatusCore(repoPath, { ...opts, correlationKey: correlationKey }, signal),\n\t\t\tcancellation,\n\t\t\topts?.force,\n\t\t);\n\t}\n\n\tprivate async getStatusCore(\n\t\trepoPath: string,\n\t\toptions?: {\n\t\t\tpriority?: GitCommandPriority;\n\t\t\tcorrelationKey?: string;\n\t\t\tuntracked?: 'no' | 'normal' | 'all';\n\t\t\tbranch?: false;\n\t\t},\n\t\tcancellation?: AbortSignal,\n\t): Promise<GitStatus | undefined> {\n\t\tconst porcelainVersion = (await this.git.supports('git:status:porcelain-v2')) ? 2 : 1;\n\n\t\tconst result = await this.statusCore(\n\t\t\trepoPath,\n\t\t\tporcelainVersion,\n\t\t\t{\n\t\t\t\tsimilarityThreshold: this.context.config?.commits.similarityThreshold,\n\t\t\t\tpriority: options?.priority,\n\t\t\t\tcorrelationKey: options?.correlationKey,\n\t\t\t\tuntracked: options?.untracked,\n\t\t\t\tbranch: options?.branch,\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\t\tconst status = parseGitStatus(result.stdout, repoPath, porcelainVersion);\n\n\t\tif (options?.branch === false) {\n\t\t\t// Without `--branch` a clean tree prints nothing at all, which the parser reads as no status\n\t\t\tif (status == null && result.completion.status === 'exited' && result.exitCode === 0) {\n\t\t\t\treturn new GitStatus(normalizePath(repoPath), '', '', []);\n\t\t\t}\n\t\t\t// No branch name either, which `GitStatus` reads as detached, so there is no paused rebase to look up\n\t\t\treturn status;\n\t\t}\n\n\t\tif (status?.detached) {\n\t\t\tconst pausedOpStatus = await this.provider.pausedOps?.getPausedOperationStatus?.(\n\t\t\t\trepoPath,\n\t\t\t\tundefined,\n\t\t\t\tcancellation,\n\t\t\t);\n\t\t\tif (pausedOpStatus?.type === 'rebase') {\n\t\t\t\treturn new GitStatus(\n\t\t\t\t\trepoPath,\n\t\t\t\t\tpausedOpStatus.incoming.name,\n\t\t\t\t\tstatus.sha,\n\t\t\t\t\tstatus.files,\n\t\t\t\t\tstatus.upstream,\n\t\t\t\t\ttrue,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\treturn status;\n\t}\n\n\t@debug()\n\tasync getStatusForFile(\n\t\trepoPath: string,\n\t\tpathOrUri: string | Uri,\n\t\toptions?: { renames?: boolean },\n\t\tcancellation?: AbortSignal,\n\t): Promise<GitStatusFile | undefined> {\n\t\tconst files = await this.getStatusForPathCore(\n\t\t\trepoPath,\n\t\t\ttoFsPath(pathOrUri),\n\t\t\t{ ...options, exact: true },\n\t\t\tcancellation,\n\t\t);\n\t\treturn files?.[0];\n\t}\n\n\t@debug()\n\tasync getStatusForPath(\n\t\trepoPath: string,\n\t\tpathOrUri: string | Uri,\n\t\toptions?: { renames?: boolean },\n\t\tcancellation?: AbortSignal,\n\t): Promise<GitStatusFile[] | undefined> {\n\t\treturn this.getStatusForPathCore(repoPath, toFsPath(pathOrUri), { ...options, exact: false }, cancellation);\n\t}\n\n\tprivate async getStatusForPathCore(\n\t\trepoPath: string,\n\t\tpathOrUri: string,\n\t\toptions: { exact: boolean; renames?: boolean },\n\t\tcancellation?: AbortSignal,\n\t): Promise<GitStatusFile[] | undefined> {\n\t\tconst relativePath = stripFolderGlob(splitPath(pathOrUri, repoPath)[0]);\n\n\t\t// Rename-aware queries can't scope by pathspec (that disables Git's rename detection), so they run the\n\t\t// SAME full `git status` as `getStatus`. Delegate to it — sharing one process/dedup entry — and filter,\n\t\t// rather than spawning a second identical `git status`.\n\t\tif (options.renames !== false) {\n\t\t\tconst status = await this.getStatus(repoPath, undefined, cancellation);\n\t\t\tif (status == null) return undefined;\n\n\t\t\tif (options.exact) {\n\t\t\t\tconst file = status.files.find(f => f.path === relativePath);\n\t\t\t\treturn file ? [file] : undefined;\n\t\t\t}\n\t\t\treturn status.files.filter(f => f.path.startsWith(relativePath));\n\t\t}\n\n\t\t// Non-rename query: pathspec-scoped `git status` (a distinct command), deduped on its own key. `exact`\n\t\t// is NOT in the key — the pathspec-scoped command is identical for exact/non-exact, so both share one run.\n\t\treturn this.dedupeByStatusGeneration(\n\t\t\trepoPath,\n\t\t\t// Free-form `relativePath` (may contain `:`) goes LAST so the prefix can't be shifted into it.\n\t\t\t`getStatusForPath:${relativePath}`,\n\t\t\t(correlationKey, signal) => this.getStatusForPathScoped(repoPath, relativePath, correlationKey, signal),\n\t\t\tcancellation,\n\t\t);\n\t}\n\n\tprivate async getStatusForPathScoped(\n\t\trepoPath: string,\n\t\trelativePath: string,\n\t\tcorrelationKey: string,\n\t\tcancellation?: AbortSignal,\n\t): Promise<GitStatusFile[] | undefined> {\n\t\tconst porcelainVersion = (await this.git.supports('git:status:porcelain-v2')) ? 2 : 1;\n\t\tconst result = await this.statusCore(\n\t\t\trepoPath,\n\t\t\tporcelainVersion,\n\t\t\t{ similarityThreshold: this.context.config?.commits.similarityThreshold, correlationKey: correlationKey },\n\t\t\tcancellation,\n\t\t\trelativePath,\n\t\t);\n\n\t\tconst status = parseGitStatus(result.stdout, repoPath, porcelainVersion);\n\t\treturn status?.files;\n\t}\n\n\tprivate async statusCore(\n\t\trepoPath: string,\n\t\tporcelainVersion: number = 1,\n\t\toptions?: {\n\t\t\tsimilarityThreshold?: number | null;\n\t\t\tpriority?: GitCommandPriority;\n\t\t\tcorrelationKey?: string;\n\t\t\tuntracked?: 'no' | 'normal' | 'all';\n\t\t\tbranch?: false;\n\t\t},\n\t\tcancellation?: AbortSignal,\n\t\t...pathspecs: string[]\n\t): Promise<GitResult> {\n\t\tconst params = ['status', porcelainVersion >= 2 ? `--porcelain=v${porcelainVersion}` : '--porcelain'];\n\t\tif (options?.branch !== false) {\n\t\t\tparams.push('--branch');\n\t\t}\n\t\tparams.push(options?.untracked != null ? `-u${options.untracked}` : '-u');\n\t\tif (await this.git.supports('git:status:find-renames')) {\n\t\t\tparams.push(\n\t\t\t\t`--find-renames${options?.similarityThreshold == null ? '' : `=${options.similarityThreshold}%`}`,\n\t\t\t);\n\t\t}\n\n\t\treturn this.git.run(\n\t\t\t{\n\t\t\t\tcwd: repoPath,\n\t\t\t\tcancellation: cancellation,\n\t\t\t\tconfigs: gitConfigsStatus,\n\t\t\t\tenv: { GIT_OPTIONAL_LOCKS: '0' },\n\t\t\t\tcorrelationKey: options?.correlationKey,\n\t\t\t\t...(options?.priority != null ? { priority: options.priority } : undefined),\n\t\t\t},\n\t\t\t...params,\n\t\t\t'--',\n\t\t\t...pathspecs,\n\t\t);\n\t}\n\n\t@debug()\n\thasWorkingChanges(\n\t\trepoPath: string,\n\t\toptions?: {\n\t\t\tstaged?: boolean;\n\t\t\tunstaged?: boolean;\n\t\t\tuntracked?: boolean;\n\t\t\tthrowOnError?: boolean;\n\t\t\tpriority?: GitCommandPriority;\n\t\t},\n\t\tcancellation?: AbortSignal,\n\t): Promise<boolean> {\n\t\tconst scope = getScopedLogger();\n\n\t\tconst staged = options?.staged ?? true;\n\t\tconst unstaged = options?.unstaged ?? true;\n\t\tconst untracked = options?.untracked ?? true;\n\t\t// `throwOnError` is in the key because it changes the RESULT on git failure (throw vs `false`) — a\n\t\t// throwing caller must not join, and be silently satisfied by, a non-throwing caller's graceful run.\n\t\t// `priority` is in the key for a different reason: it doesn't change the answer, but a joiner INHERITS\n\t\t// the in-flight run's scheduling. `GitQueue` refuses to start background work while anything is\n\t\t// waiting at normal/interactive, so letting a foreground caller (the overview's dirty pill, the\n\t\t// Worktrees view) join a `background` probe would strand it for the whole graph load. Splitting the\n\t\t// two lanes costs at most one extra spawn; joining costs unbounded foreground latency.\n\t\tconst throwOnError = options?.throwOnError ?? false;\n\t\tconst priority = options?.priority ?? 'default';\n\n\t\treturn this.dedupeByStatusGeneration(\n\t\t\trepoPath,\n\t\t\t`hasWorkingChanges:${staged}:${unstaged}:${untracked}:${throwOnError}:${priority}`,\n\t\t\tasync (correlationKey, signal) => {\n\t\t\t\ttry {\n\t\t\t\t\tif (staged || unstaged) {\n\t\t\t\t\t\tconst result = await this.git.run(\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tcwd: repoPath,\n\t\t\t\t\t\t\t\tcancellation: signal,\n\t\t\t\t\t\t\t\terrors: 'ignore',\n\t\t\t\t\t\t\t\tcorrelationKey: correlationKey,\n\t\t\t\t\t\t\t\t...(options?.priority != null ? { priority: options.priority } : undefined),\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t'diff',\n\t\t\t\t\t\t\t'--quiet',\n\t\t\t\t\t\t\tstaged && unstaged ? 'HEAD' : staged ? '--staged' : undefined,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tif (result.exitCode === 1) {\n\t\t\t\t\t\t\tif (staged && unstaged) {\n\t\t\t\t\t\t\t\tscope?.addExitInfo('has staged and unstaged changes');\n\t\t\t\t\t\t\t} else if (staged) {\n\t\t\t\t\t\t\t\tscope?.addExitInfo('has staged changes');\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tscope?.addExitInfo('has unstaged changes');\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Check for untracked files. NOTE: this runs through `git.stream`, which spawns directly\n\t\t\t\t\t// and never enters the queue — so `priority` above doesn't reach it. Only clean worktrees\n\t\t\t\t\t// get this far (a dirty one already returned above), and callers fanning out across many\n\t\t\t\t\t// worktrees are expected to bound their own concurrency.\n\t\t\t\t\tif (untracked) {\n\t\t\t\t\t\tconst hasUntracked = await this.hasUntrackedFiles(repoPath, signal);\n\t\t\t\t\t\tif (hasUntracked) {\n\t\t\t\t\t\t\tscope?.addExitInfo('has untracked files');\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tscope?.addExitInfo('no working changes');\n\t\t\t\t\treturn false;\n\t\t\t\t} catch (ex) {\n\t\t\t\t\t// Re-throw cancellation errors\n\t\t\t\t\tif (isCancellationError(ex)) throw ex;\n\n\t\t\t\t\t// Log other errors and return false for graceful degradation\n\t\t\t\t\tscope?.error(ex);\n\t\t\t\t\tscope?.addExitInfo('error checking for changes');\n\t\t\t\t\tif (throwOnError) throw ex;\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\t}\n\n\t@debug()\n\tgetWorkingChangesState(repoPath: string, cancellation?: AbortSignal): Promise<GitWorkingChangesState> {\n\t\tconst scope = getScopedLogger();\n\n\t\treturn this.dedupeByStatusGeneration(\n\t\t\trepoPath,\n\t\t\t'getWorkingChangesState',\n\t\t\tasync (correlationKey, signal) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst [stagedResult, unstagedResult, untrackedResult] = await Promise.allSettled([\n\t\t\t\t\t\t// Check for staged changes\n\t\t\t\t\t\tthis.git.run(\n\t\t\t\t\t\t\t{ cwd: repoPath, cancellation: signal, errors: 'ignore', correlationKey: correlationKey },\n\t\t\t\t\t\t\t'diff',\n\t\t\t\t\t\t\t'--quiet',\n\t\t\t\t\t\t\t'--staged',\n\t\t\t\t\t\t),\n\t\t\t\t\t\t// Check for unstaged changes\n\t\t\t\t\t\tthis.git.run(\n\t\t\t\t\t\t\t{ cwd: repoPath, cancellation: signal, errors: 'ignore', correlationKey: correlationKey },\n\t\t\t\t\t\t\t'diff',\n\t\t\t\t\t\t\t'--quiet',\n\t\t\t\t\t\t),\n\t\t\t\t\t\t// Check for untracked files\n\t\t\t\t\t\tthis.hasUntrackedFiles(repoPath, signal),\n\t\t\t\t\t]);\n\n\t\t\t\t\tconst result = {\n\t\t\t\t\t\tstaged: getSettledValue(stagedResult)?.exitCode === 1,\n\t\t\t\t\t\tunstaged: getSettledValue(unstagedResult)?.exitCode === 1,\n\t\t\t\t\t\tuntracked: getSettledValue(untrackedResult) === true,\n\t\t\t\t\t};\n\n\t\t\t\t\tscope?.addExitInfo(\n\t\t\t\t\t\tresult.staged || result.unstaged || result.untracked\n\t\t\t\t\t\t\t? `has ${result.staged ? 'staged' : ''}${result.unstaged ? (result.staged ? ', unstaged' : 'unstaged ') : ''}${\n\t\t\t\t\t\t\t\t\tresult.untracked\n\t\t\t\t\t\t\t\t\t\t? result.staged || result.unstaged\n\t\t\t\t\t\t\t\t\t\t\t? ', untracked'\n\t\t\t\t\t\t\t\t\t\t\t: 'untracked'\n\t\t\t\t\t\t\t\t\t\t: ''\n\t\t\t\t\t\t\t\t} changes`\n\t\t\t\t\t\t\t: 'no working changes',\n\t\t\t\t\t);\n\n\t\t\t\t\treturn result;\n\t\t\t\t} catch (ex) {\n\t\t\t\t\tif (isCancellationError(ex)) throw ex;\n\n\t\t\t\t\tscope?.error(ex);\n\t\t\t\t\tscope?.addExitInfo('error checking for changes');\n\t\t\t\t\t// Return all false on error for graceful degradation\n\t\t\t\t\treturn { staged: false, unstaged: false, untracked: false };\n\t\t\t\t}\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\t}\n\n\thasConflictingFiles(repoPath: string, cancellation?: AbortSignal): Promise<boolean> {\n\t\t// Route through `dedupeByStatusGeneration` (like every other point-in-time read here) for the join-fence\n\t\t// and the `raceWithTimeout` backstop — a bare `git.stream` has no per-command timeout and could wedge.\n\t\treturn this.dedupeByStatusGeneration(\n\t\t\trepoPath,\n\t\t\t'hasConflictingFiles',\n\t\t\tasync (_correlationKey, signal) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst stream = this.git.stream({ cwd: repoPath, cancellation: signal }, 'ls-files', '--unmerged');\n\t\t\t\t\tusing _streamDisposer = createDisposable(() => void stream.return?.(undefined));\n\n\t\t\t\t\t// Early exit on first chunk - breaking causes SIGPIPE, killing git process\n\t\t\t\t\tfor await (const _chunk of stream) {\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\n\t\t\t\t\treturn false;\n\t\t\t\t} catch (ex) {\n\t\t\t\t\t// Re-throw cancellation errors\n\t\t\t\t\tif (isCancellationError(ex)) throw ex;\n\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\t}\n\n\t@debug()\n\tgetConflictingFiles(repoPath: string, cancellation?: AbortSignal): Promise<GitConflictFile[]> {\n\t\tconst scope = getScopedLogger();\n\n\t\treturn this.dedupeByStatusGeneration(\n\t\t\trepoPath,\n\t\t\t'getConflictingFiles',\n\t\t\tasync (correlationKey, signal) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await this.git.run(\n\t\t\t\t\t\t{ cwd: repoPath, cancellation: signal, errors: 'ignore', correlationKey: correlationKey },\n\t\t\t\t\t\t'ls-files',\n\t\t\t\t\t\t'-z',\n\t\t\t\t\t\t'--unmerged',\n\t\t\t\t\t);\n\n\t\t\t\t\tif (!result.stdout) {\n\t\t\t\t\t\tscope?.addExitInfo('no conflicting files');\n\t\t\t\t\t\treturn [];\n\t\t\t\t\t}\n\n\t\t\t\t\tconst files = parseGitConflictFiles(result.stdout, repoPath);\n\t\t\t\t\tscope?.addExitInfo(`${String(files.length)} conflicting file(s)`);\n\t\t\t\t\treturn files;\n\t\t\t\t} catch (ex) {\n\t\t\t\t\t// Re-throw cancellation errors\n\t\t\t\t\tif (isCancellationError(ex)) throw ex;\n\n\t\t\t\t\t// Log other errors and return empty array for graceful degradation\n\t\t\t\t\tscope?.error(ex);\n\t\t\t\t\tscope?.addExitInfo('error getting conflicting files');\n\t\t\t\t\treturn [];\n\t\t\t\t}\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\t}\n\n\tprivate async hasUntrackedFiles(repoPath: string, cancellation?: AbortSignal): Promise<boolean> {\n\t\ttry {\n\t\t\tconst stream = this.git.stream(\n\t\t\t\t{ cwd: repoPath, cancellation: cancellation },\n\t\t\t\t'ls-files',\n\t\t\t\t// '-z', // Unneeded since we are only looking for presence\n\t\t\t\t'--others',\n\t\t\t\t'--exclude-standard',\n\t\t\t);\n\t\t\tusing _streamDisposer = createDisposable(() => void stream.return?.(undefined));\n\n\t\t\t// Early exit on first chunk - breaking causes SIGPIPE, killing git process\n\t\t\tfor await (const _chunk of stream) {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\treturn false;\n\t\t} catch (ex) {\n\t\t\t// Re-throw cancellation errors\n\t\t\tif (isCancellationError(ex)) throw ex;\n\n\t\t\t// Treat other errors as \"no untracked files\" for graceful degradation\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t@debug()\n\tgetUntrackedFiles(repoPath: string, cancellation?: AbortSignal): Promise<GitFile[]> {\n\t\tconst scope = getScopedLogger();\n\n\t\treturn this.dedupeByStatusGeneration(\n\t\t\trepoPath,\n\t\t\t'getUntrackedFiles',\n\t\t\tasync (correlationKey, signal) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst result = await this.git.run(\n\t\t\t\t\t\t{ cwd: repoPath, cancellation: signal, errors: 'ignore', correlationKey: correlationKey },\n\t\t\t\t\t\t'ls-files',\n\t\t\t\t\t\t'-z',\n\t\t\t\t\t\t'--others',\n\t\t\t\t\t\t'--exclude-standard',\n\t\t\t\t\t);\n\n\t\t\t\t\tif (!result.stdout) {\n\t\t\t\t\t\tscope?.addExitInfo('no untracked files');\n\t\t\t\t\t\treturn [];\n\t\t\t\t\t}\n\n\t\t\t\t\tconst files: GitFile[] = [];\n\n\t\t\t\t\tfor (const line of iterateByDelimiter(result.stdout, '\\0')) {\n\t\t\t\t\t\tif (!line.length) continue;\n\n\t\t\t\t\t\tfiles.push({ path: line, repoPath: repoPath, status: GitFileWorkingTreeStatus.Untracked });\n\t\t\t\t\t}\n\n\t\t\t\t\tscope?.addExitInfo(`${String(files.length)} untracked file(s)`);\n\t\t\t\t\treturn files;\n\t\t\t\t} catch (ex) {\n\t\t\t\t\t// Re-throw cancellation errors\n\t\t\t\t\tif (isCancellationError(ex)) throw ex;\n\n\t\t\t\t\t// Log other errors and return empty array for graceful degradation\n\t\t\t\t\tscope?.error(ex);\n\t\t\t\t\tscope?.addExitInfo('error getting untracked files');\n\t\t\t\t\treturn [];\n\t\t\t\t}\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA4BA,MAAM,4BAA4B;;;;;;;;AASlC,SAAgB,0BAA0B,YAAwC;CACjF,MAAM,UAAU,cAAc;CAC9B,OAAO,UAAU,IAAI,UAAU,IAAI;AACpC;;;;;AAMA,SAAS,iBAAiB,SAA2E;CACpG,IAAI,SAAS,aAAa,QAAQ,SAAS,WAAW,OAAO,OAAO;CAEpE,MAAM,QAAkB,CAAC;CACzB,IAAI,SAAS,aAAa,MACzB,MAAM,KAAK,aAAa,QAAQ,WAAW;CAE5C,IAAI,SAAS,WAAW,OACvB,MAAM,KAAK,cAAc;CAE1B,OAAO,aAAa,MAAM,KAAK,GAAG;AACnC;AAEA,IAAa,uBAAb,MAAkE;CAE/C;CACA;CACA;CACA;CAJlB,YACC,SACA,KACA,OACA,UACC;EAJgB,KAAA,UAAA;EACA,KAAA,MAAA;EACA,KAAA,QAAA;EACA,KAAA,WAAA;CACf;;;CAIH,gBAAiC,IAAI,WAA4B;;;;;;;;;;;;;;CAejE,yBACC,UACA,MACA,KACA,cACA,OACa;EAQb,IAAI,OACH,KAAK,MAAM,0BAA0B,QAAQ;EAE9C,MAAM,aAAa,KAAK,MAAM,oBAAoB,QAAQ;EAG1D,MAAM,MAAM,GAAG,KAAK,IAAI,SAAS,IAAI;EACrC,MAAM,iBAAiB,UAAU;EACjC,MAAM,aAAa,0BAA0B,KAAK,IAAI,QAAQ,UAAU;EAExE,OAAO,KAAK,cAAc,YACzB,MACC,WAAW,WAAW;GAQtB,UAAU,WAAW;GACrB,MAAM,WAAW,IAAI,gBAAgB;GACrC,MAAM,YAAY,UAAU,OAAO,YAAY,IAAI,CAAC,QAAQ,SAAS,MAAM,CAAC,IAAI,SAAS;GACzF,OAAO,gBAAgB,IAAI,gBAAgB,SAAS,GAAG,YAAY,QAAQ;EAC5E,GACA,YACD;CACD;CAEA,UAEC,UACA,SAMA,cACiC;EACjC,IAAI,YAAY,MAAM,OAAO,QAAQ,QAAQ,KAAA,CAAS;EAGtD,MAAM,OAAO,SAAS,cAAc,QAAQ;GAAE,GAAG;GAAS,WAAW,KAAA;EAAU,IAAI;EAEnF,OAAO,KAAK,yBACX,UACA,iBAAiB,IAAI,IACpB,gBAAgB,WAChB,KAAK,cAAc,UAAU;GAAE,GAAG;GAAsB;EAAe,GAAG,MAAM,GACjF,cACA,MAAM,KACP;CACD;CAEA,MAAc,cACb,UACA,SAMA,cACiC;EACjC,MAAM,mBAAoB,MAAM,KAAK,IAAI,SAAS,yBAAyB,IAAK,IAAI;EAEpF,MAAM,SAAS,MAAM,KAAK,WACzB,UACA,kBACA;GACC,qBAAqB,KAAK,QAAQ,QAAQ,QAAQ;GAClD,UAAU,SAAS;GACnB,gBAAgB,SAAS;GACzB,WAAW,SAAS;GACpB,QAAQ,SAAS;EAClB,GACA,YACD;EACA,MAAM,SAAS,eAAe,OAAO,QAAQ,UAAU,gBAAgB;EAEvE,IAAI,SAAS,WAAW,OAAO;GAE9B,IAAI,UAAU,QAAQ,OAAO,WAAW,WAAW,YAAY,OAAO,aAAa,GAClF,OAAO,IAAI,UAAU,cAAc,QAAQ,GAAG,IAAI,IAAI,CAAC,CAAC;GAGzD,OAAO;EACR;EAEA,IAAI,QAAQ,UAAU;GACrB,MAAM,iBAAiB,MAAM,KAAK,SAAS,WAAW,2BACrD,UACA,KAAA,GACA,YACD;GACA,IAAI,gBAAgB,SAAS,UAC5B,OAAO,IAAI,UACV,UACA,eAAe,SAAS,MACxB,OAAO,KACP,OAAO,OACP,OAAO,UACP,IACD;EAEF;EACA,OAAO;CACR;CAEA,MACM,iBACL,UACA,WACA,SACA,cACqC;EAOrC,QAAO,MANa,KAAK,qBACxB,UACA,SAAS,SAAS,GAClB;GAAE,GAAG;GAAS,OAAO;EAAK,GAC1B,YACD,EAAA,GACe;CAChB;CAEA,MACM,iBACL,UACA,WACA,SACA,cACuC;EACvC,OAAO,KAAK,qBAAqB,UAAU,SAAS,SAAS,GAAG;GAAE,GAAG;GAAS,OAAO;EAAM,GAAG,YAAY;CAC3G;CAEA,MAAc,qBACb,UACA,WACA,SACA,cACuC;EACvC,MAAM,eAAe,gBAAgB,UAAU,WAAW,QAAQ,CAAC,CAAC,EAAE;EAKtE,IAAI,QAAQ,YAAY,OAAO;GAC9B,MAAM,SAAS,MAAM,KAAK,UAAU,UAAU,KAAA,GAAW,YAAY;GACrE,IAAI,UAAU,MAAM,OAAO,KAAA;GAE3B,IAAI,QAAQ,OAAO;IAClB,MAAM,OAAO,OAAO,MAAM,MAAK,MAAK,EAAE,SAAS,YAAY;IAC3D,OAAO,OAAO,CAAC,IAAI,IAAI,KAAA;GACxB;GACA,OAAO,OAAO,MAAM,QAAO,MAAK,EAAE,KAAK,WAAW,YAAY,CAAC;EAChE;EAIA,OAAO,KAAK,yBACX,UAEA,oBAAoB,iBACnB,gBAAgB,WAAW,KAAK,uBAAuB,UAAU,cAAc,gBAAgB,MAAM,GACtG,YACD;CACD;CAEA,MAAc,uBACb,UACA,cACA,gBACA,cACuC;EACvC,MAAM,mBAAoB,MAAM,KAAK,IAAI,SAAS,yBAAyB,IAAK,IAAI;EACpF,MAAM,SAAS,MAAM,KAAK,WACzB,UACA,kBACA;GAAE,qBAAqB,KAAK,QAAQ,QAAQ,QAAQ;GAAqC;EAAe,GACxG,cACA,YACD;EAGA,OADe,eAAe,OAAO,QAAQ,UAAU,gBAC3C,CAAC,EAAE;CAChB;CAEA,MAAc,WACb,UACA,mBAA2B,GAC3B,SAOA,cACA,GAAG,WACkB;EACrB,MAAM,SAAS,CAAC,UAAU,oBAAoB,IAAI,gBAAgB,qBAAqB,aAAa;EACpG,IAAI,SAAS,WAAW,OACvB,OAAO,KAAK,UAAU;EAEvB,OAAO,KAAK,SAAS,aAAa,OAAO,KAAK,QAAQ,cAAc,IAAI;EACxE,IAAI,MAAM,KAAK,IAAI,SAAS,yBAAyB,GACpD,OAAO,KACN,iBAAiB,SAAS,uBAAuB,OAAO,KAAK,IAAI,QAAQ,oBAAoB,IAC9F;EAGD,OAAO,KAAK,IAAI,IACf;GACC,KAAK;GACS;GACd,SAAS;GACT,KAAK,EAAE,oBAAoB,IAAI;GAC/B,gBAAgB,SAAS;GACzB,GAAI,SAAS,YAAY,OAAO,EAAE,UAAU,QAAQ,SAAS,IAAI,KAAA;EAClE,GACA,GAAG,QACH,MACA,GAAG,SACJ;CACD;CAEA,kBAEC,UACA,SAOA,cACmB;EACnB,MAAM,QAAQ,gBAAgB;EAE9B,MAAM,SAAS,SAAS,UAAU;EAClC,MAAM,WAAW,SAAS,YAAY;EACtC,MAAM,YAAY,SAAS,aAAa;EAQxC,MAAM,eAAe,SAAS,gBAAgB;EAC9C,MAAM,WAAW,SAAS,YAAY;EAEtC,OAAO,KAAK,yBACX,UACA,qBAAqB,OAAO,GAAG,SAAS,GAAG,UAAU,GAAG,aAAa,GAAG,YACxE,OAAO,gBAAgB,WAAW;GACjC,IAAI;IACH,IAAI,UAAU,UAaT;UAAA,MAZiB,KAAK,IAAI,IAC7B;MACC,KAAK;MACL,cAAc;MACd,QAAQ;MACQ;MAChB,GAAI,SAAS,YAAY,OAAO,EAAE,UAAU,QAAQ,SAAS,IAAI,KAAA;KAClE,GACA,QACA,WACA,UAAU,WAAW,SAAS,SAAS,aAAa,KAAA,CACrD,EAAA,CACW,aAAa,GAAG;MAC1B,IAAI,UAAU,UACb,OAAO,YAAY,iCAAiC;WAC9C,IAAI,QACV,OAAO,YAAY,oBAAoB;WAEvC,OAAO,YAAY,sBAAsB;MAE1C,OAAO;KACR;;IAOD,IAAI,WAEC;SAAA,MADuB,KAAK,kBAAkB,UAAU,MAAM,GAChD;MACjB,OAAO,YAAY,qBAAqB;MACxC,OAAO;KACR;;IAGD,OAAO,YAAY,oBAAoB;IACvC,OAAO;GACR,SAAS,IAAI;IAEZ,IAAI,oBAAoB,EAAE,GAAG,MAAM;IAGnC,OAAO,MAAM,EAAE;IACf,OAAO,YAAY,4BAA4B;IAC/C,IAAI,cAAc,MAAM;IACxB,OAAO;GACR;EACD,GACA,YACD;CACD;CAEA,uBACuB,UAAkB,cAA6D;EACrG,MAAM,QAAQ,gBAAgB;EAE9B,OAAO,KAAK,yBACX,UACA,0BACA,OAAO,gBAAgB,WAAW;GACjC,IAAI;IACH,MAAM,CAAC,cAAc,gBAAgB,mBAAmB,MAAM,QAAQ,WAAW;KAEhF,KAAK,IAAI,IACR;MAAE,KAAK;MAAU,cAAc;MAAQ,QAAQ;MAA0B;KAAe,GACxF,QACA,WACA,UACD;KAEA,KAAK,IAAI,IACR;MAAE,KAAK;MAAU,cAAc;MAAQ,QAAQ;MAA0B;KAAe,GACxF,QACA,SACD;KAEA,KAAK,kBAAkB,UAAU,MAAM;IACxC,CAAC;IAED,MAAM,SAAS;KACd,QAAQ,gBAAgB,YAAY,CAAC,EAAE,aAAa;KACpD,UAAU,gBAAgB,cAAc,CAAC,EAAE,aAAa;KACxD,WAAW,gBAAgB,eAAe,MAAM;IACjD;IAEA,OAAO,YACN,OAAO,UAAU,OAAO,YAAY,OAAO,YACxC,OAAO,OAAO,SAAS,WAAW,KAAK,OAAO,WAAY,OAAO,SAAS,eAAe,cAAe,KACxG,OAAO,YACJ,OAAO,UAAU,OAAO,WACvB,gBACA,cACD,GACH,YACA,oBACJ;IAEA,OAAO;GACR,SAAS,IAAI;IACZ,IAAI,oBAAoB,EAAE,GAAG,MAAM;IAEnC,OAAO,MAAM,EAAE;IACf,OAAO,YAAY,4BAA4B;IAE/C,OAAO;KAAE,QAAQ;KAAO,UAAU;KAAO,WAAW;IAAM;GAC3D;EACD,GACA,YACD;CACD;CAEA,oBAAoB,UAAkB,cAA8C;EAGnF,OAAO,KAAK,yBACX,UACA,uBACA,OAAO,iBAAiB,WAAW;GAClC,IAAI;;;KACH,MAAM,SAAS,KAAK,IAAI,OAAO;MAAE,KAAK;MAAU,cAAc;KAAO,GAAG,YAAY,YAAY;KACxE,YAAA,EAAA,uBAAuB,KAAK,OAAO,SAAS,KAAA,CAAS,CAAC,CAAA;KAG9E,WAAW,MAAM,UAAU,QAC1B,OAAO;KAGR,OAAO;;;;;;GACR,SAAS,IAAI;IAEZ,IAAI,oBAAoB,EAAE,GAAG,MAAM;IAEnC,OAAO;GACR;EACD,GACA,YACD;CACD;CAEA,oBACoB,UAAkB,cAAwD;EAC7F,MAAM,QAAQ,gBAAgB;EAE9B,OAAO,KAAK,yBACX,UACA,uBACA,OAAO,gBAAgB,WAAW;GACjC,IAAI;IACH,MAAM,SAAS,MAAM,KAAK,IAAI,IAC7B;KAAE,KAAK;KAAU,cAAc;KAAQ,QAAQ;KAA0B;IAAe,GACxF,YACA,MACA,YACD;IAEA,IAAI,CAAC,OAAO,QAAQ;KACnB,OAAO,YAAY,sBAAsB;KACzC,OAAO,CAAC;IACT;IAEA,MAAM,QAAQ,sBAAsB,OAAO,QAAQ,QAAQ;IAC3D,OAAO,YAAY,GAAG,OAAO,MAAM,MAAM,EAAE,qBAAqB;IAChE,OAAO;GACR,SAAS,IAAI;IAEZ,IAAI,oBAAoB,EAAE,GAAG,MAAM;IAGnC,OAAO,MAAM,EAAE;IACf,OAAO,YAAY,iCAAiC;IACpD,OAAO,CAAC;GACT;EACD,GACA,YACD;CACD;CAEA,MAAc,kBAAkB,UAAkB,cAA8C;EAC/F,IAAI;;;IACH,MAAM,SAAS,KAAK,IAAI,OACvB;KAAE,KAAK;KAAwB;IAAa,GAC5C,YAEA,YACA,oBACD;IACwB,WAAA,EAAA,uBAAuB,KAAK,OAAO,SAAS,KAAA,CAAS,CAAC,CAAA;IAG9E,WAAW,MAAM,UAAU,QAC1B,OAAO;IAGR,OAAO;;;;;;EACR,SAAS,IAAI;GAEZ,IAAI,oBAAoB,EAAE,GAAG,MAAM;GAGnC,OAAO;EACR;CACD;CAEA,kBACkB,UAAkB,cAAgD;EACnF,MAAM,QAAQ,gBAAgB;EAE9B,OAAO,KAAK,yBACX,UACA,qBACA,OAAO,gBAAgB,WAAW;GACjC,IAAI;IACH,MAAM,SAAS,MAAM,KAAK,IAAI,IAC7B;KAAE,KAAK;KAAU,cAAc;KAAQ,QAAQ;KAA0B;IAAe,GACxF,YACA,MACA,YACA,oBACD;IAEA,IAAI,CAAC,OAAO,QAAQ;KACnB,OAAO,YAAY,oBAAoB;KACvC,OAAO,CAAC;IACT;IAEA,MAAM,QAAmB,CAAC;IAE1B,KAAK,MAAM,QAAQ,mBAAmB,OAAO,QAAQ,IAAI,GAAG;KAC3D,IAAI,CAAC,KAAK,QAAQ;KAElB,MAAM,KAAK;MAAE,MAAM;MAAgB;MAAU,QAAQ,yBAAyB;KAAU,CAAC;IAC1F;IAEA,OAAO,YAAY,GAAG,OAAO,MAAM,MAAM,EAAE,mBAAmB;IAC9D,OAAO;GACR,SAAS,IAAI;IAEZ,IAAI,oBAAoB,EAAE,GAAG,MAAM;IAGnC,OAAO,MAAM,EAAE;IACf,OAAO,YAAY,+BAA+B;IAClD,OAAO,CAAC;GACT;EACD,GACA,YACD;CACD;AACD;AAheE,WAAA,CAAA,MAAM,CAAA,GAAA,qBAAA,WAAA,aAAA,IAAA;AAiFN,WAAA,CAAA,MAAM,CAAA,GAAA,qBAAA,WAAA,oBAAA,IAAA;AAgBN,WAAA,CAAA,MAAM,CAAA,GAAA,qBAAA,WAAA,oBAAA,IAAA;AAqGN,WAAA,CAAA,MAAM,CAAA,GAAA,qBAAA,WAAA,qBAAA,IAAA;AAsFN,WAAA,CAAA,MAAM,CAAA,GAAA,qBAAA,WAAA,0BAAA,IAAA;AAuFN,WAAA,CAAA,MAAM,CAAA,GAAA,qBAAA,WAAA,uBAAA,IAAA;AAgEN,WAAA,CAAA,MAAM,CAAA,GAAA,qBAAA,WAAA,qBAAA,IAAA"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gitkraken/core-gitlens",
3
3
  "description": "GitLens core — shared Git / AI / GitHub primitives for internal GitKraken consumption",
4
- "version": "0.9.7",
4
+ "version": "0.9.8",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "author": {
7
7
  "name": "GitKraken",