@gitkraken/core-gitlens 0.7.2 → 0.9.0

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.
Files changed (192) hide show
  1. package/CHANGELOG.md +72 -1
  2. package/dist/git/cache.d.ts +29 -5
  3. package/dist/git/cache.js +52 -11
  4. package/dist/git/cache.js.map +1 -1
  5. package/dist/git/context.d.ts +6 -1
  6. package/dist/git/context.js.map +1 -1
  7. package/dist/git/errors.d.ts +28 -1
  8. package/dist/git/errors.js +45 -1
  9. package/dist/git/errors.js.map +1 -1
  10. package/dist/git/features.d.ts +1 -1
  11. package/dist/git/features.js +1 -0
  12. package/dist/git/features.js.map +1 -1
  13. package/dist/git/models/reflog.d.ts +6 -0
  14. package/dist/git/models/reflog.js.map +1 -1
  15. package/dist/git/models/revision.d.ts +0 -1
  16. package/dist/git/models/revision.js +1 -2
  17. package/dist/git/models/revision.js.map +1 -1
  18. package/dist/git/providers/branches.d.ts +5 -1
  19. package/dist/git/providers/commits.d.ts +44 -2
  20. package/dist/git/providers/config.d.ts +7 -1
  21. package/dist/git/providers/operations.d.ts +19 -0
  22. package/dist/git/providers/provider.d.ts +29 -2
  23. package/dist/git/providers/refs.d.ts +48 -3
  24. package/dist/git/providers/revision.d.ts +7 -1
  25. package/dist/git/providers/staging.d.ts +13 -0
  26. package/dist/git/providers/stash.d.ts +3 -2
  27. package/dist/git/providers/status.d.ts +4 -0
  28. package/dist/git/providers/worktrees.d.ts +23 -3
  29. package/dist/git/run.types.d.ts +22 -0
  30. package/dist/git-cli/cliGitProvider.d.ts +39 -2
  31. package/dist/git-cli/cliGitProvider.js +109 -11
  32. package/dist/git-cli/cliGitProvider.js.map +1 -1
  33. package/dist/git-cli/exec/commandClassifier.d.ts +31 -0
  34. package/dist/git-cli/exec/commandClassifier.js +391 -0
  35. package/dist/git-cli/exec/commandClassifier.js.map +1 -0
  36. package/dist/git-cli/exec/git.d.ts +36 -5
  37. package/dist/git-cli/exec/git.js +163 -28
  38. package/dist/git-cli/exec/git.js.map +1 -1
  39. package/dist/git-cli/exec/gitQueue.d.ts +4 -1
  40. package/dist/git-cli/exec/gitQueue.js +1 -1
  41. package/dist/git-cli/exec/gitQueue.js.map +1 -1
  42. package/dist/git-cli/parsers/reflogParser.d.ts +7 -0
  43. package/dist/git-cli/parsers/reflogParser.js +11 -1
  44. package/dist/git-cli/parsers/reflogParser.js.map +1 -1
  45. package/dist/git-cli/providers/branches.d.ts +10 -1
  46. package/dist/git-cli/providers/branches.js +31 -22
  47. package/dist/git-cli/providers/branches.js.map +1 -1
  48. package/dist/git-cli/providers/commits.d.ts +27 -3
  49. package/dist/git-cli/providers/commits.js +112 -12
  50. package/dist/git-cli/providers/commits.js.map +1 -1
  51. package/dist/git-cli/providers/config.js +2 -1
  52. package/dist/git-cli/providers/config.js.map +1 -1
  53. package/dist/git-cli/providers/diff.js +4 -4
  54. package/dist/git-cli/providers/diff.js.map +1 -1
  55. package/dist/git-cli/providers/operations.d.ts +13 -0
  56. package/dist/git-cli/providers/operations.js +64 -5
  57. package/dist/git-cli/providers/operations.js.map +1 -1
  58. package/dist/git-cli/providers/patch.js +21 -45
  59. package/dist/git-cli/providers/patch.js.map +1 -1
  60. package/dist/git-cli/providers/refs.d.ts +20 -3
  61. package/dist/git-cli/providers/refs.js +140 -10
  62. package/dist/git-cli/providers/refs.js.map +1 -1
  63. package/dist/git-cli/providers/remotes.js +3 -2
  64. package/dist/git-cli/providers/remotes.js.map +1 -1
  65. package/dist/git-cli/providers/revision.d.ts +5 -1
  66. package/dist/git-cli/providers/revision.js +20 -5
  67. package/dist/git-cli/providers/revision.js.map +1 -1
  68. package/dist/git-cli/providers/staging.d.ts +9 -0
  69. package/dist/git-cli/providers/staging.js +63 -6
  70. package/dist/git-cli/providers/staging.js.map +1 -1
  71. package/dist/git-cli/providers/stash.d.ts +3 -2
  72. package/dist/git-cli/providers/stash.js +34 -22
  73. package/dist/git-cli/providers/stash.js.map +1 -1
  74. package/dist/git-cli/providers/status.d.ts +2 -0
  75. package/dist/git-cli/providers/status.js +27 -9
  76. package/dist/git-cli/providers/status.js.map +1 -1
  77. package/dist/git-cli/providers/worktrees.d.ts +31 -2
  78. package/dist/git-cli/providers/worktrees.js +75 -7
  79. package/dist/git-cli/providers/worktrees.js.map +1 -1
  80. package/dist/plus/git-github/api/github.d.ts +64 -3
  81. package/dist/plus/git-github/api/github.js +237 -24
  82. package/dist/plus/git-github/api/github.js.map +1 -1
  83. package/dist/plus/git-github/providers/github/branches.d.ts +3 -1
  84. package/dist/plus/git-github/providers/github/branches.js +2 -1
  85. package/dist/plus/git-github/providers/github/branches.js.map +1 -1
  86. package/dist/plus/git-github/providers/github/commits.d.ts +4 -2
  87. package/dist/plus/git-github/providers/github/commits.js +3 -1
  88. package/dist/plus/git-github/providers/github/commits.js.map +1 -1
  89. package/dist/plus/git-github/providers/github/refs.d.ts +10 -3
  90. package/dist/plus/git-github/providers/github/refs.js +4 -4
  91. package/dist/plus/git-github/providers/github/refs.js.map +1 -1
  92. package/dist/plus/git-github/providers/github/revision.d.ts +4 -1
  93. package/dist/plus/git-github/providers/github/revision.js +6 -1
  94. package/dist/plus/git-github/providers/github/revision.js.map +1 -1
  95. package/dist/plus/integrations/index.d.ts +5 -2
  96. package/dist/plus/integrations/index.js.map +1 -1
  97. package/dist/plus/integrations/integrationService.d.ts +35 -9
  98. package/dist/plus/integrations/integrationService.js +16 -3
  99. package/dist/plus/integrations/integrationService.js.map +1 -1
  100. package/dist/plus/integrations/manager.d.ts +107 -37
  101. package/dist/plus/integrations/models/gitHostIntegration.d.ts +128 -13
  102. package/dist/plus/integrations/models/gitHostIntegration.js +130 -17
  103. package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
  104. package/dist/plus/integrations/models/integration.d.ts +51 -3
  105. package/dist/plus/integrations/models/integration.js +125 -18
  106. package/dist/plus/integrations/models/integration.js.map +1 -1
  107. package/dist/plus/integrations/models/issuesIntegration.d.ts +18 -5
  108. package/dist/plus/integrations/models/issuesIntegration.js +40 -10
  109. package/dist/plus/integrations/models/issuesIntegration.js.map +1 -1
  110. package/dist/plus/integrations/providers/azure/azure.d.ts +1 -1
  111. package/dist/plus/integrations/providers/azure/azure.js +13 -17
  112. package/dist/plus/integrations/providers/azure/azure.js.map +1 -1
  113. package/dist/plus/integrations/providers/azure/models.d.ts +29 -0
  114. package/dist/plus/integrations/providers/azure/models.js +112 -1
  115. package/dist/plus/integrations/providers/azure/models.js.map +1 -1
  116. package/dist/plus/integrations/providers/azureDevOps.d.ts +54 -2
  117. package/dist/plus/integrations/providers/azureDevOps.js +80 -2
  118. package/dist/plus/integrations/providers/azureDevOps.js.map +1 -1
  119. package/dist/plus/integrations/providers/azureDevOpsServer.js +2 -1
  120. package/dist/plus/integrations/providers/azureDevOpsServer.js.map +1 -1
  121. package/dist/plus/integrations/providers/bitbucket/bitbucket.d.ts +61 -0
  122. package/dist/plus/integrations/providers/bitbucket/bitbucket.js +165 -23
  123. package/dist/plus/integrations/providers/bitbucket/bitbucket.js.map +1 -1
  124. package/dist/plus/integrations/providers/bitbucket/models.d.ts +6 -1
  125. package/dist/plus/integrations/providers/bitbucket/models.js +4 -3
  126. package/dist/plus/integrations/providers/bitbucket/models.js.map +1 -1
  127. package/dist/plus/integrations/providers/bitbucket-server/pullRequestSearch.js +1 -1
  128. package/dist/plus/integrations/providers/bitbucket-server/pullRequestSearch.js.map +1 -1
  129. package/dist/plus/integrations/providers/bitbucket-server.d.ts +38 -1
  130. package/dist/plus/integrations/providers/bitbucket-server.js +33 -1
  131. package/dist/plus/integrations/providers/bitbucket-server.js.map +1 -1
  132. package/dist/plus/integrations/providers/bitbucket.d.ts +36 -1
  133. package/dist/plus/integrations/providers/bitbucket.js +29 -1
  134. package/dist/plus/integrations/providers/bitbucket.js.map +1 -1
  135. package/dist/plus/integrations/providers/github.d.ts +48 -4
  136. package/dist/plus/integrations/providers/github.js +101 -6
  137. package/dist/plus/integrations/providers/github.js.map +1 -1
  138. package/dist/plus/integrations/providers/gitlab/gitlab.d.ts +35 -0
  139. package/dist/plus/integrations/providers/gitlab/gitlab.js +132 -15
  140. package/dist/plus/integrations/providers/gitlab/gitlab.js.map +1 -1
  141. package/dist/plus/integrations/providers/gitlab/models.d.ts +1 -0
  142. package/dist/plus/integrations/providers/gitlab/models.js +1 -1
  143. package/dist/plus/integrations/providers/gitlab/models.js.map +1 -1
  144. package/dist/plus/integrations/providers/gitlab.d.ts +48 -1
  145. package/dist/plus/integrations/providers/gitlab.js +79 -3
  146. package/dist/plus/integrations/providers/gitlab.js.map +1 -1
  147. package/dist/plus/integrations/providers/jira-server.d.ts +3 -3
  148. package/dist/plus/integrations/providers/jira-server.js +3 -3
  149. package/dist/plus/integrations/providers/jira-server.js.map +1 -1
  150. package/dist/plus/integrations/providers/models.d.ts +12 -1
  151. package/dist/plus/integrations/providers/models.js +11 -1
  152. package/dist/plus/integrations/providers/models.js.map +1 -1
  153. package/dist/plus/integrations/providers/providerErrors.d.ts +9 -1
  154. package/dist/plus/integrations/providers/providerErrors.js +10 -1
  155. package/dist/plus/integrations/providers/providerErrors.js.map +1 -1
  156. package/dist/plus/integrations/providers/providersApi.d.ts +59 -0
  157. package/dist/plus/integrations/providers/providersApi.js +136 -1
  158. package/dist/plus/integrations/providers/providersApi.js.map +1 -1
  159. package/dist/plus/integrations/providers/utils/providerPaging.d.ts +42 -0
  160. package/dist/plus/integrations/providers/utils/providerPaging.js +58 -1
  161. package/dist/plus/integrations/providers/utils/providerPaging.js.map +1 -1
  162. package/dist/plus/integrations/providers/utils.d.ts +1 -1
  163. package/dist/plus/integrations/reads/currentAccount.d.ts +35 -0
  164. package/dist/plus/integrations/reads/currentAccount.js +46 -0
  165. package/dist/plus/integrations/reads/currentAccount.js.map +1 -0
  166. package/dist/plus/integrations/reads/drains.d.ts +4 -1
  167. package/dist/plus/integrations/reads/drains.js +7 -3
  168. package/dist/plus/integrations/reads/drains.js.map +1 -1
  169. package/dist/plus/integrations/reads/issueBatch.d.ts +72 -12
  170. package/dist/plus/integrations/reads/issueBatch.js +193 -48
  171. package/dist/plus/integrations/reads/issueBatch.js.map +1 -1
  172. package/dist/plus/integrations/reads/pullRequestBatch.d.ts +57 -0
  173. package/dist/plus/integrations/reads/pullRequestBatch.js +80 -0
  174. package/dist/plus/integrations/reads/pullRequestBatch.js.map +1 -0
  175. package/dist/plus/integrations/reads/pullRequestBranches.d.ts +58 -0
  176. package/dist/plus/integrations/reads/pullRequestBranches.js +111 -0
  177. package/dist/plus/integrations/reads/pullRequestBranches.js.map +1 -0
  178. package/dist/plus/integrations/reads/pullRequests.js +3 -3
  179. package/dist/plus/integrations/reads/pullRequests.js.map +1 -1
  180. package/dist/plus/integrations/reads/sweeps.js +3 -3
  181. package/dist/plus/integrations/reads/sweeps.js.map +1 -1
  182. package/dist/plus/integrations/results.d.ts +5 -2
  183. package/dist/plus/integrations/results.js.map +1 -1
  184. package/dist/utils/env/node/exec.js +1 -1
  185. package/dist/utils/env/node/exec.js.map +1 -1
  186. package/docs/integrations.md +90 -36
  187. package/docs/kepler-git-write-api-parity.md +365 -0
  188. package/docs/kepler-read-api-parity.md +75 -28
  189. package/package.json +1 -1
  190. package/dist/plus/integrations/reads/trackerIssue.d.ts +0 -33
  191. package/dist/plus/integrations/reads/trackerIssue.js +0 -60
  192. package/dist/plus/integrations/reads/trackerIssue.js.map +0 -1
@@ -44,6 +44,8 @@ export declare class StatusGitSubProvider implements GitStatusSubProvider {
44
44
  getStatus(repoPath: string | undefined, options?: {
45
45
  priority?: GitCommandPriority;
46
46
  force?: boolean;
47
+ untracked?: 'no' | 'normal' | 'all';
48
+ branch?: false;
47
49
  }, cancellation?: AbortSignal): Promise<GitStatus | undefined>;
48
50
  private getStatusCore;
49
51
  getStatusForFile(repoPath: string, pathOrUri: string | Uri, options?: {
@@ -29,6 +29,17 @@ function computeDeadlockBackstopMs(gitTimeout) {
29
29
  const timeout = gitTimeout ?? 6e4;
30
30
  return timeout > 0 ? timeout * 2 : disabledTimeoutBackstopMs;
31
31
  }
32
+ /**
33
+ * A non-default `untracked`/`branch` gets its own key, so a full-status caller never joins a narrower run; the
34
+ * default stays `'getStatus'`, which `getStatusForPathCore`'s rename path joins.
35
+ */
36
+ function getStatusReadKey(options) {
37
+ if (options?.untracked == null && options?.branch !== false) return "getStatus";
38
+ const parts = [];
39
+ if (options?.untracked != null) parts.push(`untracked=${options.untracked}`);
40
+ if (options?.branch === false) parts.push("branch=false");
41
+ return `getStatus:${parts.join(":")}`;
42
+ }
32
43
  var StatusGitSubProvider = class {
33
44
  context;
34
45
  git;
@@ -71,20 +82,30 @@ var StatusGitSubProvider = class {
71
82
  }
72
83
  getStatus(repoPath, options, cancellation) {
73
84
  if (repoPath == null) return Promise.resolve(void 0);
74
- return this.dedupeByStatusGeneration(repoPath, "getStatus", (correlationKey, signal) => this.getStatusCore(repoPath, {
85
+ const opts = options?.untracked === "all" ? {
75
86
  ...options,
87
+ untracked: void 0
88
+ } : options;
89
+ return this.dedupeByStatusGeneration(repoPath, getStatusReadKey(opts), (correlationKey, signal) => this.getStatusCore(repoPath, {
90
+ ...opts,
76
91
  correlationKey
77
- }, signal), cancellation, options?.force);
92
+ }, signal), cancellation, opts?.force);
78
93
  }
79
94
  async getStatusCore(repoPath, options, cancellation) {
80
95
  const porcelainVersion = await this.git.supports("git:status:porcelain-v2") ? 2 : 1;
81
96
  const result = await this.statusCore(repoPath, porcelainVersion, {
82
97
  similarityThreshold: this.context.config?.commits.similarityThreshold,
83
98
  priority: options?.priority,
84
- correlationKey: options?.correlationKey
99
+ correlationKey: options?.correlationKey,
100
+ untracked: options?.untracked,
101
+ branch: options?.branch
85
102
  }, cancellation);
86
103
  const repoUri = fileUri(normalizePath(repoPath));
87
104
  const status = parseGitStatus(result.stdout, repoPath, porcelainVersion, (p) => joinUriPath(repoUri, normalizePath(p)));
105
+ if (options?.branch === false) {
106
+ if (status == null && result.completion.status === "exited" && result.exitCode === 0) return new GitStatus(normalizePath(repoPath), "", "", []);
107
+ return status;
108
+ }
88
109
  if (status?.detached) {
89
110
  const pausedOpStatus = await this.provider.pausedOps?.getPausedOperationStatus?.(repoPath, void 0, cancellation);
90
111
  if (pausedOpStatus?.type === "rebase") return new GitStatus(repoPath, pausedOpStatus.incoming.name, status.sha, status.files, status.upstream, true);
@@ -126,12 +147,9 @@ var StatusGitSubProvider = class {
126
147
  return parseGitStatus(result.stdout, repoPath, porcelainVersion, (p) => joinUriPath(repoUri, normalizePath(p)))?.files;
127
148
  }
128
149
  async statusCore(repoPath, porcelainVersion = 1, options, cancellation, ...pathspecs) {
129
- const params = [
130
- "status",
131
- porcelainVersion >= 2 ? `--porcelain=v${porcelainVersion}` : "--porcelain",
132
- "--branch",
133
- "-u"
134
- ];
150
+ const params = ["status", porcelainVersion >= 2 ? `--porcelain=v${porcelainVersion}` : "--porcelain"];
151
+ if (options?.branch !== false) params.push("--branch");
152
+ params.push(options?.untracked != null ? `-u${options.untracked}` : "-u");
135
153
  if (await this.git.supports("git:status:find-renames")) params.push(`--find-renames${options?.similarityThreshold == null ? "" : `=${options.similarityThreshold}%`}`);
136
154
  return this.git.run({
137
155
  cwd: repoPath,
@@ -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\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?: { priority?: GitCommandPriority; force?: boolean },\n\t\tcancellation?: AbortSignal,\n\t): Promise<GitStatus | undefined> {\n\t\tif (repoPath == null) return Promise.resolve(undefined);\n\n\t\treturn this.dedupeByStatusGeneration(\n\t\t\trepoPath,\n\t\t\t'getStatus',\n\t\t\t(correlationKey, signal) =>\n\t\t\t\tthis.getStatusCore(repoPath, { ...options, correlationKey: correlationKey }, signal),\n\t\t\tcancellation,\n\t\t\toptions?.force,\n\t\t);\n\t}\n\n\tprivate async getStatusCore(\n\t\trepoPath: string,\n\t\toptions?: { priority?: GitCommandPriority; correlationKey?: string },\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},\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 (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?: { similarityThreshold?: number | null; priority?: GitCommandPriority; correlationKey?: string },\n\t\tcancellation?: AbortSignal,\n\t\t...pathspecs: string[]\n\t): Promise<GitResult> {\n\t\tconst params = [\n\t\t\t'status',\n\t\t\tporcelainVersion >= 2 ? `--porcelain=v${porcelainVersion}` : '--porcelain',\n\t\t\t'--branch',\n\t\t\t'-u',\n\t\t];\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 { 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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"}
@@ -2,6 +2,7 @@ import { Uri } from "../../utils/uri.js";
2
2
  import { GitServiceContext } from "../../git/context.js";
3
3
  import { GitWorktree } from "../../git/models/worktree.js";
4
4
  import { Cache } from "../../git/cache.js";
5
+ import { GitOperationRunOptions } from "../../git/providers/operations.js";
5
6
  import { GitWorktreesSubProvider } from "../../git/providers/worktrees.js";
6
7
  import { Git } from "../exec/git.js";
7
8
  import { CliGitProviderInternal } from "../cliGitProvider.js";
@@ -12,25 +13,53 @@ export declare class WorktreesGitSubProvider implements GitWorktreesSubProvider
12
13
  private readonly cache;
13
14
  private readonly provider;
14
15
  constructor(context: GitServiceContext, git: Git, cache: Cache, provider: CliGitProviderInternal);
16
+ /**
17
+ * Creates a worktree (`git worktree add`). `runOptions` are spread into the underlying `git.run` call, so a
18
+ * large checkout can run without the default command timeout (`{ timeout: 0 }`) and still be cancelled.
19
+ */
15
20
  createWorktree(repoPath: string, path: string, options?: {
16
21
  commitish?: string;
17
22
  createBranch?: string;
18
23
  detach?: boolean;
19
24
  force?: boolean;
20
25
  noTracking?: boolean;
21
- }): Promise<void>;
26
+ }, runOptions?: GitOperationRunOptions): Promise<void>;
22
27
  createWorktreeWithResult(repoPath: string, path: string, options?: {
23
28
  commitish?: string;
24
29
  createBranch?: string;
25
30
  detach?: boolean;
26
31
  force?: boolean;
27
32
  noTracking?: boolean;
28
- }): Promise<GitWorktree | undefined>;
33
+ }, runOptions?: GitOperationRunOptions): Promise<GitWorktree | undefined>;
29
34
  getWorktree(repoPath: string, predicate: (w: GitWorktree) => boolean, cancellation?: AbortSignal): Promise<GitWorktree | undefined>;
30
35
  getWorktrees(repoPath: string, cancellation?: AbortSignal): Promise<GitWorktree[]>;
31
36
  getWorktreesDefaultUri(repoPath: string): Uri | undefined;
37
+ /**
38
+ * Deletes a worktree (`git worktree remove`). Pass `force: 'locked'` to also override a locked worktree.
39
+ *
40
+ * `runOptions` are spread into the underlying `git.run` call (e.g. `{ timeout: 0 }` for a large worktree
41
+ * whose removal may exceed the default command timeout).
42
+ *
43
+ * On failure, the thrown {@link WorktreeDeleteError}'s `original` property carries the raw
44
+ * {@link GitError} (stderr/stdout/exit code) untouched — a caller that needs to classify a
45
+ * platform-specific failure (e.g. a Windows file-lock retry) can read it directly rather than parsing
46
+ * the typed error's message.
47
+ */
32
48
  deleteWorktree(repoPath: string, path: string | Uri, options?: {
33
49
  force?: boolean | 'locked';
50
+ }, runOptions?: GitOperationRunOptions): Promise<void>;
51
+ pruneWorktrees(repoPath: string, options?: {
52
+ expire?: string;
53
+ }): Promise<void>;
54
+ /**
55
+ * Raw worktree paths currently known to git (`worktree <path>` lines from `--porcelain`), normalized the
56
+ * same way {@link deleteWorktree} does. A failed listing (`errors: 'ignore'`) yields an empty set rather
57
+ * than throwing — used by `pruneWorktrees` to diff before/after, where a transient read failure must not
58
+ * be mistaken for every worktree having vanished.
59
+ */
60
+ private listWorktreePaths;
61
+ lockWorktree(repoPath: string, path: string | Uri, options?: {
62
+ reason?: string;
34
63
  }): Promise<void>;
35
64
  unlockWorktree(repoPath: string, path: string | Uri): Promise<void>;
36
65
  }
@@ -1,3 +1,4 @@
1
+ import { isCancellationError } from "../../utils/cancellation.js";
1
2
  import { getScopedLogger } from "../../utils/logger.scoped.js";
2
3
  import { getSettledValue } from "../../utils/promise.js";
3
4
  import { debug } from "../../utils/decorators/log.js";
@@ -27,7 +28,11 @@ var WorktreesGitSubProvider = class {
27
28
  this.cache = cache;
28
29
  this.provider = provider;
29
30
  }
30
- async createWorktree(repoPath, path, options) {
31
+ /**
32
+ * Creates a worktree (`git worktree add`). `runOptions` are spread into the underlying `git.run` call, so a
33
+ * large checkout can run without the default command timeout (`{ timeout: 0 }`) and still be cancelled.
34
+ */
35
+ async createWorktree(repoPath, path, options, runOptions) {
31
36
  const scope = getScopedLogger();
32
37
  const args = ["worktree", "add"];
33
38
  if (options?.force) args.push("--force");
@@ -37,12 +42,16 @@ var WorktreesGitSubProvider = class {
37
42
  args.push(path);
38
43
  if (options?.commitish) args.push(options.commitish);
39
44
  try {
40
- await this.git.run({ cwd: repoPath }, ...args);
45
+ await this.git.run({
46
+ cwd: repoPath,
47
+ ...runOptions
48
+ }, ...args);
41
49
  if (options?.createBranch) this.cache.deleteBaseBranchName(repoPath, options.createBranch);
42
50
  this.context.hooks?.cache?.onReset?.(repoPath, ...options?.createBranch ? ["branches", "worktrees"] : ["worktrees"]);
43
51
  this.context.hooks?.repository?.onChanged?.(repoPath, options?.createBranch ? ["worktrees", "heads"] : ["worktrees"]);
44
52
  } catch (ex) {
45
53
  scope?.error(ex);
54
+ if (isCancellationError(ex)) throw ex;
46
55
  throw getGitCommandError("worktree-create", ex, (reason) => new WorktreeCreateError({
47
56
  reason,
48
57
  gitCommand: {
@@ -52,10 +61,10 @@ var WorktreesGitSubProvider = class {
52
61
  }, ex));
53
62
  }
54
63
  }
55
- async createWorktreeWithResult(repoPath, path, options) {
56
- await this.createWorktree(repoPath, path, options);
64
+ async createWorktreeWithResult(repoPath, path, options, runOptions) {
65
+ await this.createWorktree(repoPath, path, options, runOptions);
57
66
  const normalized = normalizePath(path);
58
- return this.getWorktree(repoPath, (w) => normalizePath(w.path) === normalized);
67
+ return this.getWorktree(repoPath, (w) => normalizePath(w.path) === normalized, runOptions?.cancellation);
59
68
  }
60
69
  async getWorktree(repoPath, predicate, cancellation) {
61
70
  return (await this.getWorktrees(repoPath, cancellation)).find(predicate);
@@ -83,7 +92,18 @@ var WorktreesGitSubProvider = class {
83
92
  getWorktreesDefaultUri(repoPath) {
84
93
  return this.context.workspace?.getWorktreeDefaultUri?.(repoPath);
85
94
  }
86
- async deleteWorktree(repoPath, path, options) {
95
+ /**
96
+ * Deletes a worktree (`git worktree remove`). Pass `force: 'locked'` to also override a locked worktree.
97
+ *
98
+ * `runOptions` are spread into the underlying `git.run` call (e.g. `{ timeout: 0 }` for a large worktree
99
+ * whose removal may exceed the default command timeout).
100
+ *
101
+ * On failure, the thrown {@link WorktreeDeleteError}'s `original` property carries the raw
102
+ * {@link GitError} (stderr/stdout/exit code) untouched — a caller that needs to classify a
103
+ * platform-specific failure (e.g. a Windows file-lock retry) can read it directly rather than parsing
104
+ * the typed error's message.
105
+ */
106
+ async deleteWorktree(repoPath, path, options, runOptions) {
87
107
  const scope = getScopedLogger();
88
108
  await this.git.ensureSupports("git:worktrees", (requiredVersion, installedVersion) => l10n.t("Deleting worktrees requires a newer version of Git (>= {0}) than is currently installed ({1}). Please install a more recent version of Git and try again.", requiredVersion, installedVersion));
89
109
  const args = ["worktree", "remove"];
@@ -97,11 +117,13 @@ var WorktreesGitSubProvider = class {
97
117
  try {
98
118
  await this.git.run({
99
119
  cwd: repoPath,
100
- errors: "throw"
120
+ errors: "throw",
121
+ ...runOptions
101
122
  }, ...args);
102
123
  deleted = true;
103
124
  } catch (ex) {
104
125
  scope?.error(ex);
126
+ if (isCancellationError(ex)) throw ex;
105
127
  const gitError = getGitCommandError("worktree-delete", ex, (reason) => new WorktreeDeleteError({
106
128
  reason,
107
129
  lockReason: reason === "locked" ? getLockReason(ex) : void 0,
@@ -160,6 +182,50 @@ var WorktreesGitSubProvider = class {
160
182
  }
161
183
  }
162
184
  }
185
+ async pruneWorktrees(repoPath, options) {
186
+ await this.git.ensureSupports("git:worktrees", (requiredVersion, installedVersion) => l10n.t("Pruning worktrees requires a newer version of Git (>= {0}) than is currently installed ({1}). Please install a more recent version of Git and try again.", requiredVersion, installedVersion));
187
+ const before = await this.listWorktreePaths(repoPath);
188
+ const args = ["worktree", "prune"];
189
+ if (options?.expire) args.push("--expire", options.expire);
190
+ await this.git.run({
191
+ cwd: repoPath,
192
+ errors: "throw"
193
+ }, ...args);
194
+ const after = await this.listWorktreePaths(repoPath);
195
+ if (before != null && after != null) {
196
+ for (const path of before) if (!after.has(path)) this.cache.unregisterRepoPath(path);
197
+ }
198
+ this.context.hooks?.cache?.onReset?.(repoPath, "worktrees");
199
+ this.context.hooks?.repository?.onChanged?.(repoPath, ["worktrees"]);
200
+ }
201
+ /**
202
+ * Raw worktree paths currently known to git (`worktree <path>` lines from `--porcelain`), normalized the
203
+ * same way {@link deleteWorktree} does. A failed listing (`errors: 'ignore'`) yields an empty set rather
204
+ * than throwing — used by `pruneWorktrees` to diff before/after, where a transient read failure must not
205
+ * be mistaken for every worktree having vanished.
206
+ */
207
+ async listWorktreePaths(repoPath) {
208
+ const result = await this.git.run({
209
+ cwd: repoPath,
210
+ errors: "ignore"
211
+ }, "worktree", "list", "--porcelain");
212
+ if (result.completion.status !== "exited" || result.exitCode !== 0) return void 0;
213
+ const paths = /* @__PURE__ */ new Set();
214
+ for (const line of result.stdout.split("\n")) if (line.startsWith("worktree ")) paths.add(normalizePath(toFsPath(line.slice(9).trim())));
215
+ return paths;
216
+ }
217
+ async lockWorktree(repoPath, path, options) {
218
+ await this.git.ensureSupports("git:worktrees", (requiredVersion, installedVersion) => l10n.t("Locking worktrees requires a newer version of Git (>= {0}) than is currently installed ({1}). Please install a more recent version of Git and try again.", requiredVersion, installedVersion));
219
+ const args = ["worktree", "lock"];
220
+ if (options?.reason) args.push("--reason", options.reason);
221
+ args.push(normalizePath(toFsPath(path)));
222
+ await this.git.run({
223
+ cwd: repoPath,
224
+ errors: "throw"
225
+ }, ...args);
226
+ this.context.hooks?.cache?.onReset?.(repoPath, "worktrees");
227
+ this.context.hooks?.repository?.onChanged?.(repoPath, ["worktrees"]);
228
+ }
163
229
  async unlockWorktree(repoPath, path) {
164
230
  await this.git.ensureSupports("git:worktrees", (requiredVersion, installedVersion) => l10n.t("Unlocking worktrees requires a newer version of Git (>= {0}) than is currently installed ({1}). Please install a more recent version of Git and try again.", requiredVersion, installedVersion));
165
231
  await this.git.run({
@@ -175,6 +241,8 @@ __decorate([debug()], WorktreesGitSubProvider.prototype, "getWorktree", null);
175
241
  __decorate([debug()], WorktreesGitSubProvider.prototype, "getWorktrees", null);
176
242
  __decorate([debug()], WorktreesGitSubProvider.prototype, "getWorktreesDefaultUri", null);
177
243
  __decorate([debug()], WorktreesGitSubProvider.prototype, "deleteWorktree", null);
244
+ __decorate([debug()], WorktreesGitSubProvider.prototype, "pruneWorktrees", null);
245
+ __decorate([debug()], WorktreesGitSubProvider.prototype, "lockWorktree", null);
178
246
  __decorate([debug()], WorktreesGitSubProvider.prototype, "unlockWorktree", null);
179
247
  //#endregion
180
248
  export { WorktreesGitSubProvider };
@@ -1 +1 @@
1
- {"version":3,"file":"worktrees.js","names":["fs"],"sources":["../../../../git-cli/src/providers/worktrees.ts"],"sourcesContent":["import { exec } from 'child_process';\nimport { promises as fs } from 'fs';\nimport * as l10n from '@vscode/l10n';\nimport type { Cache } from '@gitlens/git/cache.js';\nimport type { GitServiceContext } from '@gitlens/git/context.js';\nimport { WorktreeCreateError, WorktreeDeleteError } from '@gitlens/git/errors.js';\nimport type { GitWorktree, WorkspaceFolderResolver } from '@gitlens/git/models/worktree.js';\nimport type { GitWorktreesSubProvider } from '@gitlens/git/providers/worktrees.js';\nimport { debug } from '@gitlens/utils/decorators/log.js';\nimport { isWindows } from '@gitlens/utils/env/node/platform.js';\nimport { getScopedLogger } from '@gitlens/utils/logger.scoped.js';\nimport { basename, normalizePath } from '@gitlens/utils/path.js';\nimport { getSettledValue } from '@gitlens/utils/promise.js';\nimport type { Uri } from '@gitlens/utils/uri.js';\nimport { fileUri, toFsPath } from '@gitlens/utils/uri.js';\nimport type { CliGitProviderInternal } from '../cliGitProvider.js';\nimport type { Git, GitError } from '../exec/git.js';\nimport { getGitCommandError, GitErrors } from '../exec/git.js';\nimport { parseGitWorktrees } from '../parsers/worktreeParser.js';\n\n/** Extracts the lock reason from Git's `cannot remove a locked working tree` output, when it provides one */\nfunction getLockReason(ex: GitError): string | undefined {\n\treturn GitErrors.worktreeLockedReason.exec(ex.stderr || ex.stdout || ex.message || '')?.[1].trim() || undefined;\n}\n\nexport class WorktreesGitSubProvider implements GitWorktreesSubProvider {\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@debug()\n\tasync createWorktree(\n\t\trepoPath: string,\n\t\tpath: string,\n\t\toptions?: {\n\t\t\tcommitish?: string;\n\t\t\tcreateBranch?: string;\n\t\t\tdetach?: boolean;\n\t\t\tforce?: boolean;\n\t\t\tnoTracking?: boolean;\n\t\t},\n\t): Promise<void> {\n\t\tconst scope = getScopedLogger();\n\n\t\tconst args = ['worktree', 'add'];\n\t\tif (options?.force) {\n\t\t\targs.push('--force');\n\t\t}\n\t\tif (options?.createBranch) {\n\t\t\targs.push('-b', options.createBranch);\n\t\t}\n\t\tif (options?.createBranch && options.noTracking) {\n\t\t\targs.push('--no-track');\n\t\t}\n\t\tif (options?.detach) {\n\t\t\targs.push('--detach');\n\t\t}\n\t\targs.push(path);\n\t\tif (options?.commitish) {\n\t\t\targs.push(options.commitish);\n\t\t}\n\n\t\ttry {\n\t\t\tawait this.git.run({ cwd: repoPath }, ...args);\n\n\t\t\tif (options?.createBranch) {\n\t\t\t\t// Evict the cached base only — see `checkout -b` for why the persisted keys are left alone.\n\t\t\t\tthis.cache.deleteBaseBranchName(repoPath, options.createBranch);\n\t\t\t}\n\n\t\t\tthis.context.hooks?.cache?.onReset?.(\n\t\t\t\trepoPath,\n\t\t\t\t...(options?.createBranch ? (['branches', 'worktrees'] as const) : (['worktrees'] as const)),\n\t\t\t);\n\t\t\tthis.context.hooks?.repository?.onChanged?.(\n\t\t\t\trepoPath,\n\t\t\t\toptions?.createBranch ? ['worktrees', 'heads'] : ['worktrees'],\n\t\t\t);\n\t\t} catch (ex) {\n\t\t\tscope?.error(ex);\n\t\t\tthrow getGitCommandError(\n\t\t\t\t'worktree-create',\n\t\t\t\tex as GitError,\n\t\t\t\treason =>\n\t\t\t\t\tnew WorktreeCreateError(\n\t\t\t\t\t\t{ reason: reason, gitCommand: { repoPath: repoPath, args: args } },\n\t\t\t\t\t\tex as GitError,\n\t\t\t\t\t),\n\t\t\t);\n\t\t}\n\t}\n\n\tasync createWorktreeWithResult(\n\t\trepoPath: string,\n\t\tpath: string,\n\t\toptions?: {\n\t\t\tcommitish?: string;\n\t\t\tcreateBranch?: string;\n\t\t\tdetach?: boolean;\n\t\t\tforce?: boolean;\n\t\t\tnoTracking?: boolean;\n\t\t},\n\t): Promise<GitWorktree | undefined> {\n\t\tawait this.createWorktree(repoPath, path, options);\n\t\tconst normalized = normalizePath(path);\n\t\treturn this.getWorktree(repoPath, w => normalizePath(w.path) === normalized);\n\t}\n\n\t@debug()\n\tasync getWorktree(\n\t\trepoPath: string,\n\t\tpredicate: (w: GitWorktree) => boolean,\n\t\tcancellation?: AbortSignal,\n\t): Promise<GitWorktree | undefined> {\n\t\treturn (await this.getWorktrees(repoPath, cancellation)).find(predicate);\n\t}\n\n\t@debug()\n\tasync getWorktrees(repoPath: string, cancellation?: AbortSignal): Promise<GitWorktree[]> {\n\t\tawait this.git.ensureSupports('git:worktrees', (requiredVersion, installedVersion) =>\n\t\t\tl10n.t(\n\t\t\t\t'Displaying worktrees requires a newer version of Git (>= {0}) than is currently installed ({1}). Please install a more recent version of Git and try again.',\n\t\t\t\trequiredVersion,\n\t\t\t\tinstalledVersion,\n\t\t\t),\n\t\t);\n\n\t\treturn this.cache.getWorktrees(\n\t\t\trepoPath,\n\t\t\tasync (commonPath, cacheable, signal) => {\n\t\t\t\t// Prefer the aggregate signal from the cache; fall back to the caller's cancellation.\n\t\t\t\tsignal ??= cancellation;\n\t\t\t\tconst [dataResult, branchesResult] = await Promise.allSettled([\n\t\t\t\t\tthis.git.run({ cwd: commonPath, cancellation: signal }, 'worktree', 'list', '--porcelain'),\n\t\t\t\t\tthis.provider.branches.getBranches(commonPath, undefined, signal),\n\t\t\t\t]);\n\n\t\t\t\tif (dataResult.status === 'rejected') {\n\t\t\t\t\t// Don't cache a bogus empty result for a transient command failure — retry on next access.\n\t\t\t\t\tcacheable.invalidate();\n\t\t\t\t}\n\n\t\t\t\tconst getWorkspaceFolder: WorkspaceFolderResolver | undefined = this.context.workspace\n\t\t\t\t\t? uri => {\n\t\t\t\t\t\t\tconst folder = this.context.workspace!.getFolder(uri.fsPath);\n\t\t\t\t\t\t\tif (folder == null) return undefined;\n\t\t\t\t\t\t\treturn { uri: fileUri(folder.path), name: basename(folder.path) };\n\t\t\t\t\t\t}\n\t\t\t\t\t: undefined;\n\n\t\t\t\treturn parseGitWorktrees(\n\t\t\t\t\tgetSettledValue(dataResult)?.stdout,\n\t\t\t\t\tcommonPath,\n\t\t\t\t\tgetSettledValue(branchesResult)?.values ?? [],\n\t\t\t\t\tgetWorkspaceFolder,\n\t\t\t\t);\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\t}\n\n\t@debug()\n\tgetWorktreesDefaultUri(repoPath: string): Uri | undefined {\n\t\treturn this.context.workspace?.getWorktreeDefaultUri?.(repoPath);\n\t}\n\n\t@debug()\n\tasync deleteWorktree(\n\t\trepoPath: string,\n\t\tpath: string | Uri,\n\t\toptions?: { force?: boolean | 'locked' },\n\t): Promise<void> {\n\t\tconst scope = getScopedLogger();\n\n\t\tawait this.git.ensureSupports('git:worktrees', (requiredVersion, installedVersion) =>\n\t\t\tl10n.t(\n\t\t\t\t'Deleting worktrees requires a newer version of Git (>= {0}) than is currently installed ({1}). Please install a more recent version of Git and try again.',\n\t\t\t\trequiredVersion,\n\t\t\t\tinstalledVersion,\n\t\t\t),\n\t\t);\n\n\t\tconst args = ['worktree', 'remove'];\n\t\tif (options?.force) {\n\t\t\targs.push('--force');\n\t\t\t// Git requires a second `--force` to remove a locked worktree\n\t\t\tif (options.force === 'locked') {\n\t\t\t\targs.push('--force');\n\t\t\t}\n\t\t}\n\n\t\tconst pathStr = normalizePath(toFsPath(path));\n\t\targs.push(pathStr);\n\n\t\tlet deleted = false;\n\t\ttry {\n\t\t\tawait this.git.run({ cwd: repoPath, errors: 'throw' }, ...args);\n\t\t\tdeleted = true;\n\t\t} catch (ex) {\n\t\t\tscope?.error(ex);\n\t\t\tconst gitError = getGitCommandError(\n\t\t\t\t'worktree-delete',\n\t\t\t\tex as GitError,\n\t\t\t\treason =>\n\t\t\t\t\tnew WorktreeDeleteError(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\treason: reason,\n\t\t\t\t\t\t\tlockReason: reason === 'locked' ? getLockReason(ex as GitError) : undefined,\n\t\t\t\t\t\t\tgitCommand: { repoPath: repoPath, args: args },\n\t\t\t\t\t\t},\n\t\t\t\t\t\tex as GitError,\n\t\t\t\t\t),\n\t\t\t);\n\n\t\t\tif (gitError.details.reason === 'directoryNotEmpty') {\n\t\t\t\tscope?.warn(\n\t\t\t\t\t`Failed to fully delete worktree '${pathStr}' because it is not empty. Attempting to delete it manually.`,\n\t\t\t\t);\n\t\t\t\ttry {\n\t\t\t\t\tawait fs.rm(pathStr, { force: true, recursive: true });\n\t\t\t\t\tdeleted = true;\n\t\t\t\t\treturn;\n\t\t\t\t} catch (ex) {\n\t\t\t\t\tif (isWindows) {\n\t\t\t\t\t\tconst match = /EPERM: operation not permitted, unlink '(.*?)'/i.exec((ex as GitError).message);\n\t\t\t\t\t\tif (match != null) {\n\t\t\t\t\t\t\tscope?.warn(\n\t\t\t\t\t\t\t\t`Failed to manually delete '${pathStr}' because it is in use. Attempting to forcefully delete it.`,\n\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\t// Windows-specific: `del` via cmd.exe to force-remove a locked symlink.\n\t\t\t\t\t\t\t// Node's fs.rm/unlink fails with EPERM when the symlink target is in use.\n\t\t\t\t\t\t\tfunction deleteInUseSymlink(symlink: string) {\n\t\t\t\t\t\t\t\treturn new Promise((resolve, reject) => {\n\t\t\t\t\t\t\t\t\texec(`del \"${symlink}\"`, (ex, stdout, stderr) => {\n\t\t\t\t\t\t\t\t\t\tif (ex) {\n\t\t\t\t\t\t\t\t\t\t\treject(ex instanceof Error ? ex : new Error(String(ex)));\n\t\t\t\t\t\t\t\t\t\t} else if (stderr) {\n\t\t\t\t\t\t\t\t\t\t\treject(new Error(stderr));\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\tresolve(stdout);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tconst [, file] = match;\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tawait deleteInUseSymlink(file);\n\t\t\t\t\t\t\t\tawait fs.rm(pathStr, { force: true, recursive: true, maxRetries: 1, retryDelay: 1 });\n\t\t\t\t\t\t\t\tdeleted = true;\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t} catch (ex) {\n\t\t\t\t\t\t\t\tscope?.error(ex, `Failed to forcefully delete '${pathStr}' because it is in use.`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthrow gitError;\n\t\t} finally {\n\t\t\tif (deleted) {\n\t\t\t\t// Clean up Cache registry + per-worktree cache entries for the deleted worktree.\n\t\t\t\t// Order matters: unregister before emitting the repo-level change so any listener\n\t\t\t\t// that re-queries worktree-aware state sees the registry already updated.\n\t\t\t\tthis.cache.unregisterRepoPath(pathStr);\n\t\t\t\tthis.context.hooks?.cache?.onReset?.(repoPath, 'worktrees');\n\t\t\t\tthis.context.hooks?.repository?.onChanged?.(repoPath, ['worktrees']);\n\t\t\t}\n\t\t}\n\t}\n\n\t@debug()\n\tasync unlockWorktree(repoPath: string, path: string | Uri): Promise<void> {\n\t\tawait this.git.ensureSupports('git:worktrees', (requiredVersion, installedVersion) =>\n\t\t\tl10n.t(\n\t\t\t\t'Unlocking worktrees requires a newer version of Git (>= {0}) than is currently installed ({1}). Please install a more recent version of Git and try again.',\n\t\t\t\trequiredVersion,\n\t\t\t\tinstalledVersion,\n\t\t\t),\n\t\t);\n\n\t\tawait this.git.run({ cwd: repoPath, errors: 'throw' }, 'worktree', 'unlock', normalizePath(toFsPath(path)));\n\n\t\tthis.context.hooks?.cache?.onReset?.(repoPath, 'worktrees');\n\t\tthis.context.hooks?.repository?.onChanged?.(repoPath, ['worktrees']);\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"worktrees.js","names":["fs"],"sources":["../../../../git-cli/src/providers/worktrees.ts"],"sourcesContent":["import { exec } from 'child_process';\nimport { promises as fs } from 'fs';\nimport * as l10n from '@vscode/l10n';\nimport type { Cache } from '@gitlens/git/cache.js';\nimport type { GitServiceContext } from '@gitlens/git/context.js';\nimport { WorktreeCreateError, WorktreeDeleteError } from '@gitlens/git/errors.js';\nimport type { GitWorktree, WorkspaceFolderResolver } from '@gitlens/git/models/worktree.js';\nimport type { GitOperationRunOptions } from '@gitlens/git/providers/operations.js';\nimport type { GitWorktreesSubProvider } from '@gitlens/git/providers/worktrees.js';\nimport { isCancellationError } from '@gitlens/utils/cancellation.js';\nimport { debug } from '@gitlens/utils/decorators/log.js';\nimport { isWindows } from '@gitlens/utils/env/node/platform.js';\nimport { getScopedLogger } from '@gitlens/utils/logger.scoped.js';\nimport { basename, normalizePath } from '@gitlens/utils/path.js';\nimport { getSettledValue } from '@gitlens/utils/promise.js';\nimport type { Uri } from '@gitlens/utils/uri.js';\nimport { fileUri, toFsPath } from '@gitlens/utils/uri.js';\nimport type { CliGitProviderInternal } from '../cliGitProvider.js';\nimport type { Git, GitError } from '../exec/git.js';\nimport { getGitCommandError, GitErrors } from '../exec/git.js';\nimport { parseGitWorktrees } from '../parsers/worktreeParser.js';\n\n/** Extracts the lock reason from Git's `cannot remove a locked working tree` output, when it provides one */\nfunction getLockReason(ex: GitError): string | undefined {\n\treturn GitErrors.worktreeLockedReason.exec(ex.stderr || ex.stdout || ex.message || '')?.[1].trim() || undefined;\n}\n\nexport class WorktreesGitSubProvider implements GitWorktreesSubProvider {\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/**\n\t * Creates a worktree (`git worktree add`). `runOptions` are spread into the underlying `git.run` call, so a\n\t * large checkout can run without the default command timeout (`{ timeout: 0 }`) and still be cancelled.\n\t */\n\t@debug()\n\tasync createWorktree(\n\t\trepoPath: string,\n\t\tpath: string,\n\t\toptions?: {\n\t\t\tcommitish?: string;\n\t\t\tcreateBranch?: string;\n\t\t\tdetach?: boolean;\n\t\t\tforce?: boolean;\n\t\t\tnoTracking?: boolean;\n\t\t},\n\t\trunOptions?: GitOperationRunOptions,\n\t): Promise<void> {\n\t\tconst scope = getScopedLogger();\n\n\t\tconst args = ['worktree', 'add'];\n\t\tif (options?.force) {\n\t\t\targs.push('--force');\n\t\t}\n\t\tif (options?.createBranch) {\n\t\t\targs.push('-b', options.createBranch);\n\t\t}\n\t\tif (options?.createBranch && options.noTracking) {\n\t\t\targs.push('--no-track');\n\t\t}\n\t\tif (options?.detach) {\n\t\t\targs.push('--detach');\n\t\t}\n\t\targs.push(path);\n\t\tif (options?.commitish) {\n\t\t\targs.push(options.commitish);\n\t\t}\n\n\t\ttry {\n\t\t\tawait this.git.run({ cwd: repoPath, ...runOptions }, ...args);\n\n\t\t\tif (options?.createBranch) {\n\t\t\t\t// Evict the cached base only — see `checkout -b` for why the persisted keys are left alone.\n\t\t\t\tthis.cache.deleteBaseBranchName(repoPath, options.createBranch);\n\t\t\t}\n\n\t\t\tthis.context.hooks?.cache?.onReset?.(\n\t\t\t\trepoPath,\n\t\t\t\t...(options?.createBranch ? (['branches', 'worktrees'] as const) : (['worktrees'] as const)),\n\t\t\t);\n\t\t\tthis.context.hooks?.repository?.onChanged?.(\n\t\t\t\trepoPath,\n\t\t\t\toptions?.createBranch ? ['worktrees', 'heads'] : ['worktrees'],\n\t\t\t);\n\t\t} catch (ex) {\n\t\t\tscope?.error(ex);\n\t\t\t// A cancelled or timed-out checkout is not git refusing the worktree\n\t\t\tif (isCancellationError(ex)) throw ex;\n\n\t\t\tthrow getGitCommandError(\n\t\t\t\t'worktree-create',\n\t\t\t\tex as GitError,\n\t\t\t\treason =>\n\t\t\t\t\tnew WorktreeCreateError(\n\t\t\t\t\t\t{ reason: reason, gitCommand: { repoPath: repoPath, args: args } },\n\t\t\t\t\t\tex as GitError,\n\t\t\t\t\t),\n\t\t\t);\n\t\t}\n\t}\n\n\tasync createWorktreeWithResult(\n\t\trepoPath: string,\n\t\tpath: string,\n\t\toptions?: {\n\t\t\tcommitish?: string;\n\t\t\tcreateBranch?: string;\n\t\t\tdetach?: boolean;\n\t\t\tforce?: boolean;\n\t\t\tnoTracking?: boolean;\n\t\t},\n\t\trunOptions?: GitOperationRunOptions,\n\t): Promise<GitWorktree | undefined> {\n\t\tawait this.createWorktree(repoPath, path, options, runOptions);\n\t\tconst normalized = normalizePath(path);\n\t\treturn this.getWorktree(repoPath, w => normalizePath(w.path) === normalized, runOptions?.cancellation);\n\t}\n\n\t@debug()\n\tasync getWorktree(\n\t\trepoPath: string,\n\t\tpredicate: (w: GitWorktree) => boolean,\n\t\tcancellation?: AbortSignal,\n\t): Promise<GitWorktree | undefined> {\n\t\treturn (await this.getWorktrees(repoPath, cancellation)).find(predicate);\n\t}\n\n\t@debug()\n\tasync getWorktrees(repoPath: string, cancellation?: AbortSignal): Promise<GitWorktree[]> {\n\t\tawait this.git.ensureSupports('git:worktrees', (requiredVersion, installedVersion) =>\n\t\t\tl10n.t(\n\t\t\t\t'Displaying worktrees requires a newer version of Git (>= {0}) than is currently installed ({1}). Please install a more recent version of Git and try again.',\n\t\t\t\trequiredVersion,\n\t\t\t\tinstalledVersion,\n\t\t\t),\n\t\t);\n\n\t\treturn this.cache.getWorktrees(\n\t\t\trepoPath,\n\t\t\tasync (commonPath, cacheable, signal) => {\n\t\t\t\t// Prefer the aggregate signal from the cache; fall back to the caller's cancellation.\n\t\t\t\tsignal ??= cancellation;\n\t\t\t\tconst [dataResult, branchesResult] = await Promise.allSettled([\n\t\t\t\t\tthis.git.run({ cwd: commonPath, cancellation: signal }, 'worktree', 'list', '--porcelain'),\n\t\t\t\t\tthis.provider.branches.getBranches(commonPath, undefined, signal),\n\t\t\t\t]);\n\n\t\t\t\tif (dataResult.status === 'rejected') {\n\t\t\t\t\t// Don't cache a bogus empty result for a transient command failure — retry on next access.\n\t\t\t\t\tcacheable.invalidate();\n\t\t\t\t}\n\n\t\t\t\tconst getWorkspaceFolder: WorkspaceFolderResolver | undefined = this.context.workspace\n\t\t\t\t\t? uri => {\n\t\t\t\t\t\t\tconst folder = this.context.workspace!.getFolder(uri.fsPath);\n\t\t\t\t\t\t\tif (folder == null) return undefined;\n\t\t\t\t\t\t\treturn { uri: fileUri(folder.path), name: basename(folder.path) };\n\t\t\t\t\t\t}\n\t\t\t\t\t: undefined;\n\n\t\t\t\treturn parseGitWorktrees(\n\t\t\t\t\tgetSettledValue(dataResult)?.stdout,\n\t\t\t\t\tcommonPath,\n\t\t\t\t\tgetSettledValue(branchesResult)?.values ?? [],\n\t\t\t\t\tgetWorkspaceFolder,\n\t\t\t\t);\n\t\t\t},\n\t\t\tcancellation,\n\t\t);\n\t}\n\n\t@debug()\n\tgetWorktreesDefaultUri(repoPath: string): Uri | undefined {\n\t\treturn this.context.workspace?.getWorktreeDefaultUri?.(repoPath);\n\t}\n\n\t/**\n\t * Deletes a worktree (`git worktree remove`). Pass `force: 'locked'` to also override a locked worktree.\n\t *\n\t * `runOptions` are spread into the underlying `git.run` call (e.g. `{ timeout: 0 }` for a large worktree\n\t * whose removal may exceed the default command timeout).\n\t *\n\t * On failure, the thrown {@link WorktreeDeleteError}'s `original` property carries the raw\n\t * {@link GitError} (stderr/stdout/exit code) untouched — a caller that needs to classify a\n\t * platform-specific failure (e.g. a Windows file-lock retry) can read it directly rather than parsing\n\t * the typed error's message.\n\t */\n\t@debug()\n\tasync deleteWorktree(\n\t\trepoPath: string,\n\t\tpath: string | Uri,\n\t\toptions?: { force?: boolean | 'locked' },\n\t\trunOptions?: GitOperationRunOptions,\n\t): Promise<void> {\n\t\tconst scope = getScopedLogger();\n\n\t\tawait this.git.ensureSupports('git:worktrees', (requiredVersion, installedVersion) =>\n\t\t\tl10n.t(\n\t\t\t\t'Deleting worktrees requires a newer version of Git (>= {0}) than is currently installed ({1}). Please install a more recent version of Git and try again.',\n\t\t\t\trequiredVersion,\n\t\t\t\tinstalledVersion,\n\t\t\t),\n\t\t);\n\n\t\tconst args = ['worktree', 'remove'];\n\t\tif (options?.force) {\n\t\t\targs.push('--force');\n\t\t\t// Git requires a second `--force` to remove a locked worktree\n\t\t\tif (options.force === 'locked') {\n\t\t\t\targs.push('--force');\n\t\t\t}\n\t\t}\n\n\t\tconst pathStr = normalizePath(toFsPath(path));\n\t\targs.push(pathStr);\n\n\t\tlet deleted = false;\n\t\ttry {\n\t\t\tawait this.git.run({ cwd: repoPath, errors: 'throw', ...runOptions }, ...args);\n\t\t\tdeleted = true;\n\t\t} catch (ex) {\n\t\t\tscope?.error(ex);\n\t\t\t// A cancelled or timed-out removal (`runOptions.cancellation`/`timeout`) is not git refusing it, and\n\t\t\t// wrapping it would hand the caller a `WorktreeDeleteError` whose `original` is no `GitError` at all\n\t\t\tif (isCancellationError(ex)) throw ex;\n\n\t\t\tconst gitError = getGitCommandError(\n\t\t\t\t'worktree-delete',\n\t\t\t\tex as GitError,\n\t\t\t\treason =>\n\t\t\t\t\tnew WorktreeDeleteError(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\treason: reason,\n\t\t\t\t\t\t\tlockReason: reason === 'locked' ? getLockReason(ex as GitError) : undefined,\n\t\t\t\t\t\t\tgitCommand: { repoPath: repoPath, args: args },\n\t\t\t\t\t\t},\n\t\t\t\t\t\tex as GitError,\n\t\t\t\t\t),\n\t\t\t);\n\n\t\t\tif (gitError.details.reason === 'directoryNotEmpty') {\n\t\t\t\tscope?.warn(\n\t\t\t\t\t`Failed to fully delete worktree '${pathStr}' because it is not empty. Attempting to delete it manually.`,\n\t\t\t\t);\n\t\t\t\ttry {\n\t\t\t\t\tawait fs.rm(pathStr, { force: true, recursive: true });\n\t\t\t\t\tdeleted = true;\n\t\t\t\t\treturn;\n\t\t\t\t} catch (ex) {\n\t\t\t\t\tif (isWindows) {\n\t\t\t\t\t\tconst match = /EPERM: operation not permitted, unlink '(.*?)'/i.exec((ex as GitError).message);\n\t\t\t\t\t\tif (match != null) {\n\t\t\t\t\t\t\tscope?.warn(\n\t\t\t\t\t\t\t\t`Failed to manually delete '${pathStr}' because it is in use. Attempting to forcefully delete it.`,\n\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\t// Windows-specific: `del` via cmd.exe to force-remove a locked symlink.\n\t\t\t\t\t\t\t// Node's fs.rm/unlink fails with EPERM when the symlink target is in use.\n\t\t\t\t\t\t\tfunction deleteInUseSymlink(symlink: string) {\n\t\t\t\t\t\t\t\treturn new Promise((resolve, reject) => {\n\t\t\t\t\t\t\t\t\texec(`del \"${symlink}\"`, (ex, stdout, stderr) => {\n\t\t\t\t\t\t\t\t\t\tif (ex) {\n\t\t\t\t\t\t\t\t\t\t\treject(ex instanceof Error ? ex : new Error(String(ex)));\n\t\t\t\t\t\t\t\t\t\t} else if (stderr) {\n\t\t\t\t\t\t\t\t\t\t\treject(new Error(stderr));\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\tresolve(stdout);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tconst [, file] = match;\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tawait deleteInUseSymlink(file);\n\t\t\t\t\t\t\t\tawait fs.rm(pathStr, { force: true, recursive: true, maxRetries: 1, retryDelay: 1 });\n\t\t\t\t\t\t\t\tdeleted = true;\n\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t} catch (ex) {\n\t\t\t\t\t\t\t\tscope?.error(ex, `Failed to forcefully delete '${pathStr}' because it is in use.`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tthrow gitError;\n\t\t} finally {\n\t\t\tif (deleted) {\n\t\t\t\t// Clean up Cache registry + per-worktree cache entries for the deleted worktree.\n\t\t\t\t// Order matters: unregister before emitting the repo-level change so any listener\n\t\t\t\t// that re-queries worktree-aware state sees the registry already updated.\n\t\t\t\tthis.cache.unregisterRepoPath(pathStr);\n\t\t\t\tthis.context.hooks?.cache?.onReset?.(repoPath, 'worktrees');\n\t\t\t\tthis.context.hooks?.repository?.onChanged?.(repoPath, ['worktrees']);\n\t\t\t}\n\t\t}\n\t}\n\n\t@debug()\n\tasync pruneWorktrees(repoPath: string, options?: { expire?: string }): Promise<void> {\n\t\tawait this.git.ensureSupports('git:worktrees', (requiredVersion, installedVersion) =>\n\t\t\tl10n.t(\n\t\t\t\t'Pruning worktrees requires a newer version of Git (>= {0}) than is currently installed ({1}). Please install a more recent version of Git and try again.',\n\t\t\t\trequiredVersion,\n\t\t\t\tinstalledVersion,\n\t\t\t),\n\t\t);\n\n\t\tconst before = await this.listWorktreePaths(repoPath);\n\n\t\tconst args = ['worktree', 'prune'];\n\t\tif (options?.expire) {\n\t\t\targs.push('--expire', options.expire);\n\t\t}\n\n\t\tawait this.git.run({ cwd: repoPath, errors: 'throw' }, ...args);\n\n\t\tconst after = await this.listWorktreePaths(repoPath);\n\n\t\t// Order matters (see `deleteWorktree`): unregister before emitting the repo-level change so any\n\t\t// listener that re-queries worktree-aware state sees the registry already updated.\n\t\tif (before != null && after != null) {\n\t\t\tfor (const path of before) {\n\t\t\t\tif (!after.has(path)) {\n\t\t\t\t\tthis.cache.unregisterRepoPath(path);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthis.context.hooks?.cache?.onReset?.(repoPath, 'worktrees');\n\t\tthis.context.hooks?.repository?.onChanged?.(repoPath, ['worktrees']);\n\t}\n\n\t/**\n\t * Raw worktree paths currently known to git (`worktree <path>` lines from `--porcelain`), normalized the\n\t * same way {@link deleteWorktree} does. A failed listing (`errors: 'ignore'`) yields an empty set rather\n\t * than throwing — used by `pruneWorktrees` to diff before/after, where a transient read failure must not\n\t * be mistaken for every worktree having vanished.\n\t */\n\tprivate async listWorktreePaths(repoPath: string): Promise<Set<string> | undefined> {\n\t\tconst result = await this.git.run({ cwd: repoPath, errors: 'ignore' }, 'worktree', 'list', '--porcelain');\n\t\t// A failed listing is not an empty one: read as empty, the after-prune one would unregister everything\n\t\tif (result.completion.status !== 'exited' || result.exitCode !== 0) return undefined;\n\n\t\tconst paths = new Set<string>();\n\t\tfor (const line of result.stdout.split('\\n')) {\n\t\t\tif (line.startsWith('worktree ')) {\n\t\t\t\tpaths.add(normalizePath(toFsPath(line.slice('worktree '.length).trim())));\n\t\t\t}\n\t\t}\n\t\treturn paths;\n\t}\n\n\t@debug()\n\tasync lockWorktree(repoPath: string, path: string | Uri, options?: { reason?: string }): Promise<void> {\n\t\tawait this.git.ensureSupports('git:worktrees', (requiredVersion, installedVersion) =>\n\t\t\tl10n.t(\n\t\t\t\t'Locking worktrees requires a newer version of Git (>= {0}) than is currently installed ({1}). Please install a more recent version of Git and try again.',\n\t\t\t\trequiredVersion,\n\t\t\t\tinstalledVersion,\n\t\t\t),\n\t\t);\n\n\t\tconst args = ['worktree', 'lock'];\n\t\tif (options?.reason) {\n\t\t\targs.push('--reason', options.reason);\n\t\t}\n\t\targs.push(normalizePath(toFsPath(path)));\n\n\t\tawait this.git.run({ cwd: repoPath, errors: 'throw' }, ...args);\n\n\t\tthis.context.hooks?.cache?.onReset?.(repoPath, 'worktrees');\n\t\tthis.context.hooks?.repository?.onChanged?.(repoPath, ['worktrees']);\n\t}\n\n\t@debug()\n\tasync unlockWorktree(repoPath: string, path: string | Uri): Promise<void> {\n\t\tawait this.git.ensureSupports('git:worktrees', (requiredVersion, installedVersion) =>\n\t\t\tl10n.t(\n\t\t\t\t'Unlocking worktrees requires a newer version of Git (>= {0}) than is currently installed ({1}). Please install a more recent version of Git and try again.',\n\t\t\t\trequiredVersion,\n\t\t\t\tinstalledVersion,\n\t\t\t),\n\t\t);\n\n\t\tawait this.git.run({ cwd: repoPath, errors: 'throw' }, 'worktree', 'unlock', normalizePath(toFsPath(path)));\n\n\t\tthis.context.hooks?.cache?.onReset?.(repoPath, 'worktrees');\n\t\tthis.context.hooks?.repository?.onChanged?.(repoPath, ['worktrees']);\n\t}\n}\n"],"mappings":"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"}
@@ -395,8 +395,10 @@ export declare class GitHubApi {
395
395
  *
396
396
  * Returns POSITIONALLY — one slot per input coordinate, in order — for the same reason {@link countIssues}
397
397
  * does: a caller's key is arbitrary text and would break the GraphQL document, so the aliases are generated
398
- * and the caller maps back by index. `undefined` in a slot means the issue does not exist (or is not visible
399
- * to this token), never that the read failed; a failure throws.
398
+ * and the caller maps back by index. A `fulfilled` slot with `undefined` means the issue does not exist (or
399
+ * is not visible to this token) — a PROVEN ABSENCE. A `rejected` slot means only THAT target failed — e.g. an
400
+ * org enforcing SAML SSO the token isn't authorized for — which is not proof the issue is gone, so it must
401
+ * not be cached as absent. A whole-request failure (bad credentials, rate limit, no data at all) still throws.
400
402
  */
401
403
  getIssuesBatch(provider: Provider, token: GitHubTokenInfo, coordinates: readonly {
402
404
  owner: string;
@@ -406,7 +408,66 @@ export declare class GitHubApi {
406
408
  baseUrl?: string;
407
409
  avatarSize?: number;
408
410
  includeBody?: boolean;
409
- }, cancellation?: AbortSignal): Promise<(IssueShape | undefined)[]>;
411
+ }, cancellation?: AbortSignal): Promise<PromiseSettledResult<IssueShape | undefined>[]>;
412
+ /**
413
+ * Resolves several pull requests, in any state (open/closed/merged), BY COORDINATE — `(owner, repo, number)`
414
+ * — in one request, via aliased `repository` fields rather than aliased searches. The pull-request twin of
415
+ * {@link getIssuesBatch}; see that method for why aliasing the point read (exact number, no result ceiling,
416
+ * a proven absence) is the design.
417
+ *
418
+ * Selects the FULL {@link gqlPullRequestFragment} — not the lite shape {@link getPullRequest} uses — so a
419
+ * row from this read carries the same fields (reviews, review requests, checks, diff stats, assignees,
420
+ * mergeable state) as {@link searchMyPullRequestsPage}'s account-wide rows.
421
+ *
422
+ * Returns POSITIONALLY — one slot per input coordinate, in order — for the same reason {@link getIssuesBatch}
423
+ * does: a caller's key is arbitrary text and would break the GraphQL document, so the aliases are generated
424
+ * and the caller maps back by index. A `fulfilled` slot with `undefined` means the pull request (or its
425
+ * repository) does not exist or is not visible to this token — a PROVEN ABSENCE. A `rejected` slot means only
426
+ * THAT target failed — e.g. an org enforcing SAML SSO the token isn't authorized for — which is not proof the
427
+ * pull request is gone, so it must not be cached as absent. A whole-request failure still throws.
428
+ */
429
+ getPullRequestsBatch(provider: Provider, token: GitHubTokenInfo, coordinates: readonly {
430
+ owner: string;
431
+ repo: string;
432
+ number: number;
433
+ }[], options?: {
434
+ baseUrl?: string;
435
+ avatarSize?: number;
436
+ }, cancellation?: AbortSignal): Promise<PromiseSettledResult<PullRequest | undefined>[]>;
437
+ /**
438
+ * Finds, for several branches in one request, every pull request whose head is that branch — in any state,
439
+ * most recently updated first, up to `limit` per branch. Each target names the BASE repository the pull
440
+ * requests are opened against, the head branch's short name and, for a branch that lives in a fork,
441
+ * `headOwner`, the fork's owner.
442
+ *
443
+ * Keyed on the head ref NAME (`repository.pullRequests(headRefName:)`) rather than on the ref itself
444
+ * (`repository.ref(qualifiedName:).associatedPullRequests`, which {@link getPullRequestForBranch} uses): a
445
+ * merged pull request's branch is usually deleted afterwards, and the ref lookup then answers "none" for a
446
+ * pull request that exists.
447
+ *
448
+ * `headRefName` matches every fork's branch of that name too, so rows are kept only when their head
449
+ * repository is the base repository itself (`headOwner` omitted) or a fork owned by `headOwner` — otherwise
450
+ * a `main` in the base repository would claim every fork's `main`. That filter runs after the server's
451
+ * `limit`, so `truncated` is set whenever the server matched more rows than it returned, since any of the
452
+ * unfetched ones might have passed it.
453
+ *
454
+ * Returns POSITIONALLY, with the same per-alias rules as {@link getPullRequestsBatch}: a missing base
455
+ * repository (NOT_FOUND on a null repository) is a PROVEN "none" — an empty list — while any other error on
456
+ * a target's alias rejects only that target, and a whole-request failure still throws.
457
+ */
458
+ getPullRequestsForBranches(provider: Provider, token: GitHubTokenInfo, targets: readonly {
459
+ owner: string;
460
+ repo: string;
461
+ branch: string;
462
+ headOwner?: string;
463
+ }[], options: {
464
+ baseUrl?: string;
465
+ avatarSize?: number;
466
+ limit: number;
467
+ }, cancellation?: AbortSignal): Promise<PromiseSettledResult<{
468
+ pullRequests: PullRequest[];
469
+ truncated: boolean;
470
+ }>[]>;
410
471
  /**
411
472
  * The PR twin of {@link countIssues}: counts pull requests for several scopes in ONE request via aliased
412
473
  * `search` fields selecting only `issueCount` with `first: 0`. Same positional/undefined contract as the issue