@gitkraken/core-gitlens 0.11.1 → 0.13.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 (201) hide show
  1. package/CHANGELOG.md +84 -1
  2. package/dist/git/cache.d.ts +84 -29
  3. package/dist/git/cache.js +188 -54
  4. package/dist/git/cache.js.map +1 -1
  5. package/dist/git/features.d.ts +1 -1
  6. package/dist/git/features.js +4 -0
  7. package/dist/git/features.js.map +1 -1
  8. package/dist/git/models/branch.d.ts +15 -2
  9. package/dist/git/models/branch.js +27 -5
  10. package/dist/git/models/branch.js.map +1 -1
  11. package/dist/git/models/issue.d.ts +3 -1
  12. package/dist/git/models/issue.js.map +1 -1
  13. package/dist/git/models/status.js +1 -1
  14. package/dist/git/models/worktree.d.ts +4 -1
  15. package/dist/git/models/worktree.js +4 -2
  16. package/dist/git/models/worktree.js.map +1 -1
  17. package/dist/git/providers/branches.d.ts +73 -8
  18. package/dist/git/providers/pausedOperations.d.ts +6 -0
  19. package/dist/git/providers/provider.d.ts +34 -2
  20. package/dist/git/providers/status.d.ts +8 -2
  21. package/dist/git/remotes/azure-devops.js +61 -53
  22. package/dist/git/remotes/azure-devops.js.map +1 -1
  23. package/dist/git/service.d.ts +8 -5
  24. package/dist/git/service.js +16 -8
  25. package/dist/git/service.js.map +1 -1
  26. package/dist/git/utils/branch.utils.d.ts +8 -1
  27. package/dist/git/utils/branch.utils.js +30 -1
  28. package/dist/git/utils/branch.utils.js.map +1 -1
  29. package/dist/git/utils/issue.utils.d.ts +3 -1
  30. package/dist/git/utils/issue.utils.js +5 -1
  31. package/dist/git/utils/issue.utils.js.map +1 -1
  32. package/dist/git/watching/changeEvent.d.ts +13 -2
  33. package/dist/git/watching/changeEvent.js +17 -3
  34. package/dist/git/watching/changeEvent.js.map +1 -1
  35. package/dist/git/watching/gitDirPoller.d.ts +111 -0
  36. package/dist/git/watching/gitDirPoller.js +266 -0
  37. package/dist/git/watching/gitDirPoller.js.map +1 -0
  38. package/dist/git/watching/provider.d.ts +16 -0
  39. package/dist/git/watching/watchGroup.d.ts +40 -4
  40. package/dist/git/watching/watchGroup.js +143 -17
  41. package/dist/git/watching/watchGroup.js.map +1 -1
  42. package/dist/git/watching/watchService.d.ts +70 -1
  43. package/dist/git/watching/watchService.js +158 -13
  44. package/dist/git/watching/watchService.js.map +1 -1
  45. package/dist/git/watching/watchSession.d.ts +44 -0
  46. package/dist/git/watching/watchSession.js +91 -6
  47. package/dist/git/watching/watchSession.js.map +1 -1
  48. package/dist/git/watching/watcherPatterns.d.ts +21 -0
  49. package/dist/git/watching/watcherPatterns.js +67 -1
  50. package/dist/git/watching/watcherPatterns.js.map +1 -1
  51. package/dist/git/watching/worktreeSummary.d.ts +34 -7
  52. package/dist/git/watching/worktreeSummary.js +39 -12
  53. package/dist/git/watching/worktreeSummary.js.map +1 -1
  54. package/dist/git-cli/cliGitProvider.d.ts +34 -2
  55. package/dist/git-cli/cliGitProvider.js +119 -17
  56. package/dist/git-cli/cliGitProvider.js.map +1 -1
  57. package/dist/git-cli/exec/commandClassifier.d.ts +15 -0
  58. package/dist/git-cli/exec/commandClassifier.js +48 -2
  59. package/dist/git-cli/exec/commandClassifier.js.map +1 -1
  60. package/dist/git-cli/exec/git.d.ts +15 -3
  61. package/dist/git-cli/exec/git.js +38 -17
  62. package/dist/git-cli/exec/git.js.map +1 -1
  63. package/dist/git-cli/exec/gitQueue.d.ts +2 -0
  64. package/dist/git-cli/exec/gitQueue.js +2 -1
  65. package/dist/git-cli/exec/gitQueue.js.map +1 -1
  66. package/dist/git-cli/exec/indexLock.d.ts +38 -0
  67. package/dist/git-cli/exec/indexLock.js +150 -0
  68. package/dist/git-cli/exec/indexLock.js.map +1 -0
  69. package/dist/git-cli/parsers/worktreeParser.js +42 -2
  70. package/dist/git-cli/parsers/worktreeParser.js.map +1 -1
  71. package/dist/git-cli/providers/branches.d.ts +109 -10
  72. package/dist/git-cli/providers/branches.js +624 -131
  73. package/dist/git-cli/providers/branches.js.map +1 -1
  74. package/dist/git-cli/providers/config.d.ts +9 -5
  75. package/dist/git-cli/providers/config.js +60 -27
  76. package/dist/git-cli/providers/config.js.map +1 -1
  77. package/dist/git-cli/providers/operations.d.ts +17 -0
  78. package/dist/git-cli/providers/operations.js +153 -96
  79. package/dist/git-cli/providers/operations.js.map +1 -1
  80. package/dist/git-cli/providers/pausedOperations.d.ts +8 -0
  81. package/dist/git-cli/providers/pausedOperations.js +60 -40
  82. package/dist/git-cli/providers/pausedOperations.js.map +1 -1
  83. package/dist/git-cli/providers/refs.js +11 -8
  84. package/dist/git-cli/providers/refs.js.map +1 -1
  85. package/dist/git-cli/providers/remotes.js +19 -10
  86. package/dist/git-cli/providers/remotes.js.map +1 -1
  87. package/dist/git-cli/providers/staging.js +40 -15
  88. package/dist/git-cli/providers/staging.js.map +1 -1
  89. package/dist/git-cli/providers/stash.d.ts +2 -0
  90. package/dist/git-cli/providers/stash.js +33 -18
  91. package/dist/git-cli/providers/stash.js.map +1 -1
  92. package/dist/git-cli/providers/status.d.ts +3 -1
  93. package/dist/git-cli/providers/status.js +38 -20
  94. package/dist/git-cli/providers/status.js.map +1 -1
  95. package/dist/git-cli/providers/tags.js +6 -4
  96. package/dist/git-cli/providers/tags.js.map +1 -1
  97. package/dist/git-cli/providers/worktrees.d.ts +1 -0
  98. package/dist/git-cli/providers/worktrees.js +60 -31
  99. package/dist/git-cli/providers/worktrees.js.map +1 -1
  100. package/dist/git-cli/service.d.ts +6 -0
  101. package/dist/git-cli/service.js +5 -2
  102. package/dist/git-cli/service.js.map +1 -1
  103. package/dist/ipc/discovery.js +1 -1
  104. package/dist/plus/agents/providers/claudeCodeTranscript.js +6 -6
  105. package/dist/plus/agents/providers/claudeCodeTranscript.js.map +1 -1
  106. package/dist/plus/ai/providers/openAICompatibleProviderBase.js +1 -1
  107. package/dist/plus/git-github/providers/github/status.d.ts +3 -1
  108. package/dist/plus/git-github/providers/github/status.js +1 -1
  109. package/dist/plus/git-github/providers/github/status.js.map +1 -1
  110. package/dist/plus/integrations/collectionMetadata.d.ts +13 -2
  111. package/dist/plus/integrations/collectionMetadata.js +14 -2
  112. package/dist/plus/integrations/collectionMetadata.js.map +1 -1
  113. package/dist/plus/integrations/index.d.ts +2 -2
  114. package/dist/plus/integrations/index.js.map +1 -1
  115. package/dist/plus/integrations/integrationService.d.ts +1 -0
  116. package/dist/plus/integrations/integrationService.js +1 -1
  117. package/dist/plus/integrations/integrationService.js.map +1 -1
  118. package/dist/plus/integrations/manager.d.ts +1 -0
  119. package/dist/plus/integrations/models/gitHostIntegration.d.ts +41 -5
  120. package/dist/plus/integrations/models/gitHostIntegration.js +109 -30
  121. package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
  122. package/dist/plus/integrations/models/integration.js +1 -1
  123. package/dist/plus/integrations/models/issueReads.d.ts +17 -4
  124. package/dist/plus/integrations/providers/apiClients.d.ts +2 -0
  125. package/dist/plus/integrations/providers/apiClients.js +7 -1
  126. package/dist/plus/integrations/providers/apiClients.js.map +1 -1
  127. package/dist/plus/integrations/providers/azure/azure.d.ts +5 -4
  128. package/dist/plus/integrations/providers/azure/azure.js +38 -77
  129. package/dist/plus/integrations/providers/azure/azure.js.map +1 -1
  130. package/dist/plus/integrations/providers/azure/azure.utils.js +20 -17
  131. package/dist/plus/integrations/providers/azure/azure.utils.js.map +1 -1
  132. package/dist/plus/integrations/providers/azure/models.d.ts +27 -10
  133. package/dist/plus/integrations/providers/azure/models.js +40 -20
  134. package/dist/plus/integrations/providers/azure/models.js.map +1 -1
  135. package/dist/plus/integrations/providers/azure/workItemStateCategories.d.ts +23 -0
  136. package/dist/plus/integrations/providers/azure/workItemStateCategories.js +84 -0
  137. package/dist/plus/integrations/providers/azure/workItemStateCategories.js.map +1 -0
  138. package/dist/plus/integrations/providers/azureDevOps.d.ts +44 -0
  139. package/dist/plus/integrations/providers/azureDevOps.js +113 -22
  140. package/dist/plus/integrations/providers/azureDevOps.js.map +1 -1
  141. package/dist/plus/integrations/providers/azureDevOpsServer.d.ts +45 -1
  142. package/dist/plus/integrations/providers/azureDevOpsServer.js +121 -6
  143. package/dist/plus/integrations/providers/azureDevOpsServer.js.map +1 -1
  144. package/dist/plus/integrations/providers/issueSorts.d.ts +0 -1
  145. package/dist/plus/integrations/providers/jira-server.js +29 -19
  146. package/dist/plus/integrations/providers/jira-server.js.map +1 -1
  147. package/dist/plus/integrations/providers/jira.d.ts +5 -2
  148. package/dist/plus/integrations/providers/jira.js +63 -44
  149. package/dist/plus/integrations/providers/jira.js.map +1 -1
  150. package/dist/plus/integrations/providers/jiraIssueByKey.d.ts +3 -4
  151. package/dist/plus/integrations/providers/jiraIssueByKey.js +3 -4
  152. package/dist/plus/integrations/providers/jiraIssueByKey.js.map +1 -1
  153. package/dist/plus/integrations/providers/linearIssuesEtag.d.ts +5 -5
  154. package/dist/plus/integrations/providers/linearIssuesEtag.js +6 -6
  155. package/dist/plus/integrations/providers/linearIssuesEtag.js.map +1 -1
  156. package/dist/plus/integrations/providers/models.d.ts +12 -3
  157. package/dist/plus/integrations/providers/models.js +16 -9
  158. package/dist/plus/integrations/providers/models.js.map +1 -1
  159. package/dist/plus/integrations/providers/providersApi.d.ts +21 -33
  160. package/dist/plus/integrations/providers/providersApi.js +159 -49
  161. package/dist/plus/integrations/providers/providersApi.js.map +1 -1
  162. package/dist/plus/integrations/providers/utils/jiraIssueDrains.d.ts +49 -15
  163. package/dist/plus/integrations/providers/utils/jiraIssueDrains.js +132 -10
  164. package/dist/plus/integrations/providers/utils/jiraIssueDrains.js.map +1 -1
  165. package/dist/plus/integrations/providers/utils/providerPaging.js +8 -2
  166. package/dist/plus/integrations/providers/utils/providerPaging.js.map +1 -1
  167. package/dist/plus/integrations/reads/cursors.js +1 -1
  168. package/dist/plus/integrations/reads/drains.d.ts +0 -1
  169. package/dist/plus/integrations/reads/filters.d.ts +3 -3
  170. package/dist/plus/integrations/reads/filters.js.map +1 -1
  171. package/dist/plus/integrations/reads/hierarchy.js +2 -2
  172. package/dist/plus/integrations/reads/hierarchy.js.map +1 -1
  173. package/dist/plus/integrations/reads/hierarchy.utils.d.ts +13 -0
  174. package/dist/plus/integrations/reads/hierarchy.utils.js +26 -1
  175. package/dist/plus/integrations/reads/hierarchy.utils.js.map +1 -1
  176. package/dist/plus/integrations/reads/issueBatch.d.ts +2 -1
  177. package/dist/plus/integrations/reads/issueBatch.js +1 -1
  178. package/dist/plus/integrations/reads/issueBatch.js.map +1 -1
  179. package/dist/plus/integrations/reads/issueTracker.js +27 -18
  180. package/dist/plus/integrations/reads/issueTracker.js.map +1 -1
  181. package/dist/plus/integrations/reads/issues.d.ts +2 -1
  182. package/dist/plus/integrations/reads/issues.js +19 -4
  183. package/dist/plus/integrations/reads/issues.js.map +1 -1
  184. package/dist/plus/integrations/reads/pullRequestBatch.js +1 -1
  185. package/dist/plus/integrations/reads/resolveRepository.js +12 -3
  186. package/dist/plus/integrations/reads/resolveRepository.js.map +1 -1
  187. package/dist/plus/integrations/reads/warnings.d.ts +11 -4
  188. package/dist/plus/integrations/reads/warnings.js +15 -5
  189. package/dist/plus/integrations/reads/warnings.js.map +1 -1
  190. package/dist/plus/integrations/results.d.ts +48 -4
  191. package/dist/plus/integrations/results.js +33 -13
  192. package/dist/plus/integrations/results.js.map +1 -1
  193. package/dist/plus/integrations/utils/integration.utils.d.ts +6 -1
  194. package/dist/plus/integrations/utils/integration.utils.js +8 -1
  195. package/dist/plus/integrations/utils/integration.utils.js.map +1 -1
  196. package/dist/utils/env/node/exec.js +2 -2
  197. package/dist/utils/promiseCache.d.ts +10 -0
  198. package/dist/utils/promiseCache.js +17 -2
  199. package/dist/utils/promiseCache.js.map +1 -1
  200. package/docs/integrations.md +93 -31
  201. package/package.json +2 -2
@@ -13,6 +13,11 @@ const baseKeysField = 8;
13
13
  /** Every group a full read covers — a base read refreshes the {@link BaseKeys} snapshot itself */
14
14
  const allFields = 7;
15
15
  const emptyRefs = Object.freeze([]);
16
+ const noBaseKeys = [
17
+ void 0,
18
+ void 0,
19
+ void 0
20
+ ];
16
21
  var SummarySubscription = class {
17
22
  entry;
18
23
  options;
@@ -72,10 +77,11 @@ var SummarySubscription = class {
72
77
  * HEAD's commit, upstream, ahead/behind) and `base` (ahead of base, the base's tip, and the refs it depends on) — and
73
78
  * an event recomputes only the groups it can affect: a working-tree edit, an index or ignore-rule change reads only
74
79
  * `dirty`; the upstream moving reads only `branch`; a ref the base depends on reads only `base`; a `gk` config change
75
- * (mostly bookkeeping writes to the shared `.git/gk/config`) re-reads the branch's stored merge targets and recorded
76
- * base, and reads `base` only when one of them changed; the checked-out branch moving, or a HEAD change, reads all of
77
- * them. Stash, unrelated tags and branches, and the like read nothing. One run is in flight per worktree at a time;
78
- * events during a run are merged into a single follow-up run.
80
+ * (mostly bookkeeping writes to the shared `.git/gk/config`), or core's own write of the branch's merge target or
81
+ * base, re-reads the branch's stored merge targets and recorded base, and reads `base` only when one of them changed;
82
+ * the checked-out branch moving, or a HEAD change, reads all of them. Stash, unrelated tags and branches, and the like
83
+ * read nothing. One run is in flight per worktree at a time; events during a run are merged into a single follow-up
84
+ * run.
79
85
  *
80
86
  * **Value-change-only notification.** {@link WorktreeSummarySubscription.onDidChange} and {@link onDidChangeSummary}
81
87
  * fire only when some field's value differs from the previous summary — a recompute that reads the same answer is
@@ -91,10 +97,14 @@ var WorktreeSummaryService = class {
91
97
  entries = /* @__PURE__ */ new Map();
92
98
  watchService;
93
99
  getRepository;
100
+ getCommonPath;
101
+ mergeTargetsListener;
94
102
  _disposed = false;
95
103
  constructor(options) {
96
104
  this.watchService = options.watchService;
97
105
  this.getRepository = options.getRepository;
106
+ this.getCommonPath = options.getCommonPath;
107
+ this.mergeTargetsListener = options.onDidChangeMergeTargets?.((e) => this.onMergeTargetsChanged(e));
98
108
  }
99
109
  [Symbol.dispose]() {
100
110
  this.dispose();
@@ -102,6 +112,7 @@ var WorktreeSummaryService = class {
102
112
  dispose() {
103
113
  if (this._disposed) return;
104
114
  this._disposed = true;
115
+ this.mergeTargetsListener?.dispose();
105
116
  for (const entry of [...this.entries.values()]) for (const subscription of [...entry.subscribers]) subscription.dispose();
106
117
  this.entries.clear();
107
118
  this._onDidChangeSummary.dispose();
@@ -208,6 +219,20 @@ var WorktreeSummaryService = class {
208
219
  }
209
220
  this.queue(entry, fields);
210
221
  }
222
+ /**
223
+ * Queued synchronously, so a store awaited before `getSummary` finds the run in flight. The watcher's later
224
+ * `gkConfig` event for the same write then finds the keys unchanged and leaves the base alone.
225
+ */
226
+ onMergeTargetsChanged(e) {
227
+ for (const entry of this.entries.values()) {
228
+ if (entry.handle == null) continue;
229
+ const value = entry.value;
230
+ if (value != null && (value.branch == null || e.refs != null && !e.refs.has(value.branch))) continue;
231
+ const commonPath = this.getCommonPath?.(entry.key);
232
+ if (commonPath != null && commonPath !== e.repoPath) continue;
233
+ this.queue(entry, baseKeysField);
234
+ }
235
+ }
211
236
  queue(entry, fields) {
212
237
  if (entry.disposed || fields === 0) return;
213
238
  entry.pending |= entry.value == null || entry.value.stale ? allFields : fields;
@@ -330,21 +355,21 @@ async function readSummary(repo, key, fields, previous, previousBaseKeys, signal
330
355
  let baseKeysRead = false;
331
356
  let baseFailed = false;
332
357
  let baseError;
333
- const baseBranch = !detached && repo.branches.getAheadOfBase != null ? branch : void 0;
334
- if (fields & baseKeysField && !(fields & baseField) && baseBranch != null) {
335
- baseKeys = await readBaseKeys(repo, baseBranch);
358
+ const baseReadable = repo.branches.getAheadOfBase != null && (detached || branch != null);
359
+ if (fields & baseKeysField && !(fields & baseField) && baseReadable) {
360
+ baseKeys = await readBaseKeys(repo, branch);
336
361
  baseKeysRead = true;
337
362
  if (baseKeys == null || previousBaseKeys == null || !baseKeysEqual(baseKeys, previousBaseKeys)) fields |= baseField;
338
363
  }
339
364
  if (fields & baseField) {
340
- if (baseBranch == null) {
365
+ if (!baseReadable) {
341
366
  base = void 0;
342
367
  dependsOn = emptyRefs;
343
368
  baseKeys = void 0;
344
369
  } else {
345
- if (!baseKeysRead) baseKeys = await readBaseKeys(repo, baseBranch);
370
+ if (!baseKeysRead) baseKeys = await readBaseKeys(repo, branch);
346
371
  try {
347
- const aheadOfBase = await repo.branches.getAheadOfBase?.(baseBranch, signal);
372
+ const aheadOfBase = await repo.branches.getAheadOfBase?.(branch, void 0, signal);
348
373
  base = aheadOfBase?.base;
349
374
  dependsOn = aheadOfBase?.dependsOn ?? emptyRefs;
350
375
  } catch (ex) {
@@ -379,9 +404,11 @@ async function readSummary(repo, key, fields, previous, previousBaseKeys, signal
379
404
  }
380
405
  /**
381
406
  * Reads {@link BaseKeys} for `branch`, normalized the way `getAheadOfBase` reads them. `undefined` when any read
382
- * failed — an unknown snapshot, so the next `gk` config change reads the base rather than trusting it.
407
+ * failed — an unknown snapshot, so the next `gk` config change reads the base rather than trusting it. A detached
408
+ * HEAD (`branch` undefined) has no stored values to read.
383
409
  */
384
410
  async function readBaseKeys(repo, branch) {
411
+ if (branch == null) return noBaseKeys;
385
412
  try {
386
413
  const [user, detected, recorded] = await Promise.all([
387
414
  repo.branches.getStoredUserMergeTargetBranchName?.(branch),
@@ -421,7 +448,7 @@ async function toSummaryUpstream(repo, upstream, signal) {
421
448
  };
422
449
  }
423
450
  function summariesEqual(a, b) {
424
- return a.repoPath === b.repoPath && a.branch === b.branch && a.detached === b.detached && a.sha === b.sha && a.ahead === b.ahead && a.behind === b.behind && a.dirty === b.dirty && a.stale === b.stale && areEqual(a.baseDependsOn, b.baseDependsOn) && (a.upstream === b.upstream || a.upstream != null && b.upstream != null && a.upstream.name === b.upstream.name && a.upstream.remote === b.upstream.remote && a.upstream.branch === b.upstream.branch && a.upstream.missing === b.upstream.missing) && (a.base === b.base || a.base != null && b.base != null && a.base.ref === b.base.ref && a.base.ahead === b.base.ahead && a.base.sha === b.base.sha);
451
+ return a.repoPath === b.repoPath && a.branch === b.branch && a.detached === b.detached && a.sha === b.sha && a.ahead === b.ahead && a.behind === b.behind && a.dirty === b.dirty && a.stale === b.stale && areEqual(a.baseDependsOn, b.baseDependsOn) && (a.upstream === b.upstream || a.upstream != null && b.upstream != null && a.upstream.name === b.upstream.name && a.upstream.remote === b.upstream.remote && a.upstream.branch === b.upstream.branch && a.upstream.missing === b.upstream.missing) && (a.base === b.base || a.base != null && b.base != null && a.base.ref === b.base.ref && a.base.ahead === b.base.ahead && a.base.sha === b.base.sha && a.base.forkPoint === b.base.forkPoint && a.base.tier === b.base.tier);
425
452
  }
426
453
  //#endregion
427
454
  export { WorktreeSummaryService };
@@ -1 +1 @@
1
- {"version":3,"file":"worktreeSummary.js","names":[],"sources":["../../../../git/src/watching/worktreeSummary.ts"],"sourcesContent":["import { areEqual } from '@gitlens/utils/array.js';\nimport { CancellationError, isCancellationError, raceWithSignal } from '@gitlens/utils/cancellation.js';\nimport type { UnifiedDisposable } from '@gitlens/utils/disposable.js';\nimport type { Event } from '@gitlens/utils/event.js';\nimport { Emitter } from '@gitlens/utils/event.js';\nimport { Logger } from '@gitlens/utils/logger.js';\nimport type { ResourceUsage } from '@gitlens/utils/resourceUsage.js';\nimport { getRepositoryKey } from '@gitlens/utils/uri.js';\nimport type { GitTrackingUpstream } from '../models/branch.js';\nimport type { GitDir } from '../models/repository.js';\nimport type { RepositoryService } from '../repositoryService.js';\nimport type { WatcherRepoChangeEvent } from './changeEvent.js';\nimport type { RepositoryWatchService, WatchHandle } from './watchService.js';\nimport type { RepositoryWatchSession } from './watchSession.js';\n\nexport interface WorktreeSummaryUpstream {\n\t/** The upstream's short name (`origin/main`) */\n\treadonly name: string;\n\t/**\n\t * The remote it tracks: the longest of the repository's remote names that {@link name} starts with, followed by\n\t * `/` (a remote name can hold a slash). `undefined`, with {@link branch}, when it names no known remote (a branch\n\t * tracking another local branch, or a remote since removed)\n\t */\n\treadonly remote: string | undefined;\n\t/** The branch on {@link remote}; `undefined` whenever {@link remote} is */\n\treadonly branch: string | undefined;\n\treadonly missing: boolean;\n}\n\nexport interface WorktreeSummaryBase {\n\t/** The ref the count was taken against (see `AheadOfBase.base`) */\n\treadonly ref: string;\n\treadonly ahead: number;\n\t/** The commit {@link ref} pointed at when the count was read; `undefined` when it couldn't be resolved */\n\treadonly sha: string | undefined;\n}\n\nexport interface WorktreeSummary {\n\t/** The repository path in {@link getRepositoryKey} form */\n\treadonly repoPath: string;\n\t/** The checked-out branch; `undefined` when detached (or unborn with no name) */\n\treadonly branch: string | undefined;\n\treadonly detached: boolean;\n\t/** HEAD's commit; `undefined` when HEAD has no commit yet */\n\treadonly sha: string | undefined;\n\treadonly upstream: WorktreeSummaryUpstream | undefined;\n\t/** Commits ahead of the upstream; 0 without one */\n\treadonly ahead: number;\n\t/** Commits behind the upstream; 0 without one */\n\treadonly behind: number;\n\t/** Whether there is any staged, unstaged or untracked (non-ignored) change */\n\treadonly dirty: boolean;\n\t/** How far the branch is past its base; `undefined` when detached, unsupported, or there is no base */\n\treadonly base: WorktreeSummaryBase | undefined;\n\t/**\n\t * Full ref names whose movement can change {@link base}, as `getAheadOfBase` reports them (see\n\t * `AheadOfBase.dependsOn`) — given with or without a base, so a watcher also learns when one appears. Empty when\n\t * the base isn't read (detached, or a provider without `getAheadOfBase`), and when the base read failed with no\n\t * earlier list for this branch (the first read, or the one after a checkout).\n\t */\n\treadonly baseDependsOn: readonly string[];\n\t/**\n\t * The latest recompute failed (worktree gone, lock contention); the other fields hold the last values read. Also set\n\t * when only the base read failed: the other fields are fresh, and `base` and `baseDependsOn` are their last values,\n\t * or `undefined` and empty if the branch changed.\n\t */\n\treadonly stale: boolean;\n}\n\nexport interface WorktreeSummarySubscribeOptions {\n\t/** Debounce for repo (`.git`) changes this subscriber asks of the watch session; the shortest across subscribers wins */\n\treadonly delayMs?: number;\n\t/** Cap on how long continuous repo activity can defer a recompute; `Infinity` means no cap */\n\treadonly maxWaitMs?: number;\n\t/** Debounce for working-tree changes this subscriber asks of the watch session */\n\treadonly workingTreeDelayMs?: number;\n\t/** Cap on how long continuous working-tree activity can defer a recompute; `Infinity` means no cap */\n\treadonly workingTreeMaxWaitMs?: number;\n}\n\nexport interface WorktreeSummarySubscription extends UnifiedDisposable {\n\t/** The latest summary; `undefined` until the first read succeeds, or after disposal */\n\treadonly current: WorktreeSummary | undefined;\n\t/** Fires when the summary's value changes */\n\treadonly onDidChange: Event<WorktreeSummary>;\n}\n\nexport interface WorktreeSummaryServiceOptions {\n\treadonly watchService: RepositoryWatchService;\n\treadonly getRepository: (repoPath: string) => RepositoryService | undefined;\n}\n\n/** Read groups, as bit flags so a pending set is a number rather than an allocation per event */\nconst dirtyField = 1;\nconst branchField = 2;\nconst baseField = 4;\n/** Re-reads only the stored values the base read consults, and the base itself only when they changed */\nconst baseKeysField = 8;\n/** Every group a full read covers — a base read refreshes the {@link BaseKeys} snapshot itself */\nconst allFields = dirtyField | branchField | baseField;\n\nconst emptyRefs: readonly string[] = Object.freeze([]);\n\n/** The current branch's stored merge targets (user, detected) and recorded base, as `getAheadOfBase` consults them */\ntype BaseKeys = readonly [string | undefined, string | undefined, string | undefined];\n\ninterface ReadResult {\n\treadonly summary: WorktreeSummary;\n\treadonly baseKeys: BaseKeys | undefined;\n\t/** Why the base read failed when the rest of the summary was read; the summary is then stale */\n\treadonly baseError: unknown;\n}\n\ninterface SummaryEntry {\n\treadonly key: string;\n\t/** Aborted when the last subscriber leaves, cancelling any in-flight read */\n\treadonly abort: AbortController;\n\treadonly subscribers: Set<SummarySubscription>;\n\thandle: WatchHandle | undefined;\n\t/** The timing-neutral listening subscriptions and their event listeners */\n\tlisteners: UnifiedDisposable[] | undefined;\n\tvalue: WorktreeSummary | undefined;\n\t/** The stored base values as of the last base read; a `gk` config change re-reads the base only when they differ */\n\tbaseKeys: BaseKeys | undefined;\n\t/** Why the latest read failed while there was no value to keep */\n\terror: Error | undefined;\n\t/** Read groups due on the next run */\n\tpending: number;\n\t/** The active run (or the start-up, which ends in one); cleared in the same turn the loop sees nothing pending */\n\trunning: Promise<void> | undefined;\n\tdisposed: boolean;\n}\n\nclass SummarySubscription implements WorktreeSummarySubscription {\n\tprivate readonly emitter = new Emitter<WorktreeSummary>();\n\t/** Listener-less session subscriptions that carry this subscriber's timing into the session */\n\tprivate timing: UnifiedDisposable[] | undefined;\n\tprivate disposed = false;\n\n\tconstructor(\n\t\tprivate readonly entry: SummaryEntry,\n\t\tprivate readonly options: WorktreeSummarySubscribeOptions | undefined,\n\t\tprivate readonly onDispose: (subscription: SummarySubscription) => void,\n\t) {}\n\n\tget current(): WorktreeSummary | undefined {\n\t\treturn this.disposed ? undefined : this.entry.value;\n\t}\n\n\tget onDidChange(): Event<WorktreeSummary> {\n\t\treturn this.emitter.event;\n\t}\n\n\t[Symbol.dispose](): void {\n\t\tthis.dispose();\n\t}\n\n\tdispose(): void {\n\t\tif (this.disposed) return;\n\n\t\tthis.disposed = true;\n\t\tif (this.timing != null) {\n\t\t\tfor (const d of this.timing) {\n\t\t\t\td.dispose();\n\t\t\t}\n\t\t\tthis.timing = undefined;\n\t\t}\n\t\tthis.emitter.dispose();\n\t\tthis.onDispose(this);\n\t}\n\n\tattach(session: RepositoryWatchSession): void {\n\t\tif (this.disposed || this.timing != null) return;\n\n\t\tthis.timing = [\n\t\t\tsession.subscribe({ delayMs: this.options?.delayMs, maxWaitMs: this.options?.maxWaitMs }),\n\t\t\tsession.subscribeToWorkingTree({\n\t\t\t\tdelayMs: this.options?.workingTreeDelayMs,\n\t\t\t\tmaxWaitMs: this.options?.workingTreeMaxWaitMs,\n\t\t\t}),\n\t\t];\n\t}\n\n\tfire(summary: WorktreeSummary): void {\n\t\tthis.emitter.fire(summary);\n\t}\n}\n\n/**\n * A cached, change-notifying summary of each subscribed worktree — its branch, upstream and ahead/behind, whether it\n * has working changes, and how far it is past its base — kept current by the repository watch layer.\n *\n * **Lifecycle.** Entries are per repository key and ref-counted across {@link subscribe} calls. The first subscriber\n * starts watching the worktree and reads everything; the last one to leave aborts any in-flight read, releases the\n * watch and drops the value. Each subscriber's timing options join the watch session's cadence (the shortest wins);\n * the service's own listening subscriptions never constrain it.\n *\n * **Field-scoped recompute.** The summary is read in three groups — `dirty` (working changes), `branch` (branch,\n * HEAD's commit, upstream, ahead/behind) and `base` (ahead of base, the base's tip, and the refs it depends on) — and\n * an event recomputes only the groups it can affect: a working-tree edit, an index or ignore-rule change reads only\n * `dirty`; the upstream moving reads only `branch`; a ref the base depends on reads only `base`; a `gk` config change\n * (mostly bookkeeping writes to the shared `.git/gk/config`) re-reads the branch's stored merge targets and recorded\n * base, and reads `base` only when one of them changed; the checked-out branch moving, or a HEAD change, reads all of\n * them. Stash, unrelated tags and branches, and the like read nothing. One run is in flight per worktree at a time;\n * events during a run are merged into a single follow-up run.\n *\n * **Value-change-only notification.** {@link WorktreeSummarySubscription.onDidChange} and {@link onDidChangeSummary}\n * fire only when some field's value differs from the previous summary — a recompute that reads the same answer is\n * silent, so a consumer can re-render on every event without diffing. A failed recompute keeps the last values and\n * sets `stale` (firing once); the next successful one clears it.\n */\nexport class WorktreeSummaryService implements UnifiedDisposable {\n\tprivate readonly _onDidChangeSummary = new Emitter<WorktreeSummary>();\n\t/** Fires when the summary of any subscribed worktree changes value */\n\tget onDidChangeSummary(): Event<WorktreeSummary> {\n\t\treturn this._onDidChangeSummary.event;\n\t}\n\n\tprivate readonly entries = new Map<string, SummaryEntry>();\n\tprivate readonly watchService: RepositoryWatchService;\n\tprivate readonly getRepository: (repoPath: string) => RepositoryService | undefined;\n\tprivate _disposed = false;\n\n\tconstructor(options: WorktreeSummaryServiceOptions) {\n\t\tthis.watchService = options.watchService;\n\t\tthis.getRepository = options.getRepository;\n\t}\n\n\t[Symbol.dispose](): void {\n\t\tthis.dispose();\n\t}\n\n\tdispose(): void {\n\t\tif (this._disposed) return;\n\n\t\tthis._disposed = true;\n\t\tfor (const entry of [...this.entries.values()]) {\n\t\t\tfor (const subscription of [...entry.subscribers]) {\n\t\t\t\tsubscription.dispose();\n\t\t\t}\n\t\t}\n\t\tthis.entries.clear();\n\t\tthis._onDidChangeSummary.dispose();\n\t}\n\n\t/** Subscribes to a worktree's summary, starting to watch and read it if this is its first subscriber */\n\tsubscribe(repoPath: string, options?: WorktreeSummarySubscribeOptions): WorktreeSummarySubscription {\n\t\tconst key = getRepositoryKey(repoPath);\n\n\t\tlet entry = this._disposed ? undefined : this.entries.get(key);\n\t\tconst isNew = entry == null;\n\t\tif (entry == null) {\n\t\t\tentry = createEntry(key);\n\t\t\tif (this._disposed) {\n\t\t\t\tentry.disposed = true;\n\t\t\t} else {\n\t\t\t\tthis.entries.set(key, entry);\n\t\t\t}\n\t\t}\n\n\t\tconst target = entry;\n\t\tconst subscription = new SummarySubscription(target, options, s => this.release(target, s));\n\t\ttarget.subscribers.add(subscription);\n\n\t\tif (isNew) {\n\t\t\tif (!target.disposed) {\n\t\t\t\ttarget.pending = allFields;\n\t\t\t\ttarget.running = this.start(target);\n\t\t\t}\n\t\t} else if (target.handle != null) {\n\t\t\tsubscription.attach(target.handle.session);\n\t\t} else if (target.running == null && !target.disposed) {\n\t\t\t// The start failed (no repository or git directory yet); a new subscriber retries it\n\t\t\ttarget.pending = allFields;\n\t\t\ttarget.running = this.start(target);\n\t\t}\n\n\t\treturn subscription;\n\t}\n\n\t/**\n\t * Returns a worktree's summary. For a subscribed worktree, the cached value — after waiting for any run in flight\n\t * or pending; it rejects with the read's error when there is still no value. Otherwise a one-off full read that\n\t * is neither cached nor watched.\n\t */\n\tasync getSummary(repoPath: string, cancellation?: AbortSignal): Promise<WorktreeSummary> {\n\t\tconst key = getRepositoryKey(repoPath);\n\t\tconst entry = this.entries.get(key);\n\t\tif (entry == null) {\n\t\t\tconst repo = this.getRepository(key);\n\t\t\tif (repo == null) throw new Error(`No repository found for '${key}'`);\n\n\t\t\tconst result = await readSummary(repo, key, allFields, undefined, undefined, cancellation);\n\t\t\treturn result.summary;\n\t\t}\n\n\t\tif (entry.running != null) {\n\t\t\tawait (cancellation != null ? raceWithSignal(entry.running, cancellation) : entry.running);\n\t\t}\n\t\tif (entry.disposed) throw new CancellationError();\n\t\tif (entry.value != null) return entry.value;\n\n\t\tthrow entry.error ?? new Error(`No summary available for '${key}'`);\n\t}\n\n\tgetResourceUsage(): ResourceUsage {\n\t\treturn { 'worktreeSummary.entries.count': this.entries.size };\n\t}\n\n\tprivate async start(entry: SummaryEntry): Promise<void> {\n\t\tlet gitDir: GitDir | undefined;\n\t\ttry {\n\t\t\tgitDir = await this.getRepository(entry.key)?.config.getGitDir?.();\n\t\t} catch (ex) {\n\t\t\tif (entry.disposed) return;\n\n\t\t\tentry.running = undefined;\n\t\t\tif (!isCancellationError(ex)) {\n\t\t\t\tthis.fail(entry, ex);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tif (entry.disposed) return;\n\n\t\tconst handle = gitDir != null ? this.watchService.watch(entry.key, gitDir) : undefined;\n\t\tif (handle == null) {\n\t\t\tentry.running = undefined;\n\t\t\tthis.fail(entry, new Error(`Unable to watch '${entry.key}': no repository or git directory`));\n\t\t\treturn;\n\t\t}\n\n\t\tentry.handle = handle;\n\t\tconst session = handle.session;\n\t\t// Timing-neutral: the cadence comes from the subscribers' own session subscriptions (see `attach`)\n\t\tconst repoSub = session.subscribe({ delayMs: Infinity, maxWaitMs: Infinity });\n\t\tconst workingTreeSub = session.subscribeToWorkingTree({ delayMs: Infinity, maxWaitMs: Infinity });\n\t\tentry.listeners = [\n\t\t\trepoSub.onDidChange(e => this.onRepoChanged(entry, e)),\n\t\t\tworkingTreeSub.onDidChangeWorkingTree(() => this.queue(entry, dirtyField)),\n\t\t\trepoSub,\n\t\t\tworkingTreeSub,\n\t\t];\n\t\tfor (const subscription of entry.subscribers) {\n\t\t\tsubscription.attach(session);\n\t\t}\n\n\t\tawait this.drain(entry);\n\t}\n\n\tprivate onRepoChanged(entry: SummaryEntry, e: WatcherRepoChangeEvent): void {\n\t\tconst value = entry.value;\n\t\tif (value == null || e.changed('unknown', 'head')) {\n\t\t\tthis.queue(entry, allFields);\n\t\t\treturn;\n\t\t}\n\n\t\tlet fields = 0;\n\t\tif (e.changed('index', 'ignores')) {\n\t\t\tfields |= dirtyField;\n\t\t}\n\t\tif (e.changed('config')) {\n\t\t\tfields |= branchField | baseField;\n\t\t}\n\t\tif (e.changed('gkConfig')) {\n\t\t\tfields |= baseKeysField;\n\t\t}\n\t\tif (value.branch != null && e.mayHaveChangedRef(`refs/heads/${value.branch}`)) {\n\t\t\tfields |= allFields;\n\t\t}\n\t\t// The upstream's short name doesn't say whether it's remote or local (`--track main`), so test both forms\n\t\tif (\n\t\t\tvalue.upstream != null &&\n\t\t\t(e.mayHaveChangedRef(`refs/remotes/${value.upstream.name}`) ||\n\t\t\t\te.mayHaveChangedRef(`refs/heads/${value.upstream.name}`))\n\t\t) {\n\t\t\tfields |= branchField;\n\t\t}\n\t\tif ((fields & baseField) === 0) {\n\t\t\tfor (const ref of value.baseDependsOn) {\n\t\t\t\tif (e.mayHaveChangedRef(ref)) {\n\t\t\t\t\tfields |= baseField;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthis.queue(entry, fields);\n\t}\n\n\tprivate queue(entry: SummaryEntry, fields: number): void {\n\t\tif (entry.disposed || fields === 0) return;\n\n\t\t// Without a value a partial read has nothing to merge into, and a stale value's fields can't be trusted: a\n\t\t// partial read would clear `stale` while vouching for groups it never re-read\n\t\tentry.pending |= entry.value == null || entry.value.stale ? allFields : fields;\n\t\tentry.running ??= this.drain(entry);\n\t}\n\n\t/** Runs until nothing is pending. Never rejects — every failure becomes entry state. */\n\tprivate async drain(entry: SummaryEntry): Promise<void> {\n\t\twhile (entry.pending !== 0 && !entry.disposed) {\n\t\t\tconst fields = entry.pending;\n\t\t\tentry.pending = 0;\n\t\t\tawait this.run(entry, fields);\n\t\t}\n\t\t// Same turn as the loop's last check, so an event can't queue fields that no run will pick up\n\t\tentry.running = undefined;\n\t}\n\n\tprivate async run(entry: SummaryEntry, fields: number): Promise<void> {\n\t\tconst previous = entry.value;\n\n\t\tlet result: ReadResult;\n\t\ttry {\n\t\t\tconst repo = this.getRepository(entry.key);\n\t\t\tif (repo == null) throw new Error(`No repository found for '${entry.key}'`);\n\n\t\t\tresult = await readSummary(repo, entry.key, fields, previous, entry.baseKeys, entry.abort.signal);\n\t\t} catch (ex) {\n\t\t\tif (entry.disposed || isCancellationError(ex)) return;\n\n\t\t\tthis.fail(entry, ex);\n\t\t\treturn;\n\t\t}\n\t\tif (entry.disposed) return;\n\n\t\tif (result.summary.stale && (previous == null || !previous.stale)) {\n\t\t\tLogger.warn(\n\t\t\t\t`WorktreeSummaryService: reading the base of '${entry.key}' failed: ${String(result.baseError)}`,\n\t\t\t);\n\t\t}\n\n\t\tentry.error = undefined;\n\t\tentry.baseKeys = result.baseKeys;\n\t\tif (previous != null && summariesEqual(previous, result.summary)) return;\n\n\t\tentry.value = result.summary;\n\t\tthis.fireChange(entry, result.summary);\n\t}\n\n\tprivate fail(entry: SummaryEntry, ex: unknown): void {\n\t\tconst previous = entry.value;\n\t\tif (previous == null) {\n\t\t\tif (entry.error == null) {\n\t\t\t\tLogger.warn(`WorktreeSummaryService: reading '${entry.key}' failed: ${String(ex)}`);\n\t\t\t}\n\t\t\tentry.error = ex instanceof Error ? ex : new Error(String(ex));\n\t\t\treturn;\n\t\t}\n\t\tif (previous.stale) return;\n\n\t\tLogger.warn(`WorktreeSummaryService: refreshing '${entry.key}' failed: ${String(ex)}`);\n\t\tconst summary: WorktreeSummary = { ...previous, stale: true };\n\t\tentry.value = summary;\n\t\tthis.fireChange(entry, summary);\n\t}\n\n\tprivate fireChange(entry: SummaryEntry, summary: WorktreeSummary): void {\n\t\tfor (const subscription of entry.subscribers) {\n\t\t\tsubscription.fire(summary);\n\t\t}\n\t\tthis._onDidChangeSummary.fire(summary);\n\t}\n\n\tprivate release(entry: SummaryEntry, subscription: SummarySubscription): void {\n\t\tentry.subscribers.delete(subscription);\n\t\tif (entry.subscribers.size !== 0) return;\n\n\t\tentry.disposed = true;\n\t\tentry.abort.abort();\n\t\tif (entry.listeners != null) {\n\t\t\tfor (const d of entry.listeners) {\n\t\t\t\td.dispose();\n\t\t\t}\n\t\t\tentry.listeners = undefined;\n\t\t}\n\t\tentry.handle?.dispose();\n\t\tentry.handle = undefined;\n\t\tentry.value = undefined;\n\t\tentry.baseKeys = undefined;\n\t\tentry.pending = 0;\n\t\tentry.running = undefined;\n\n\t\tif (this.entries.get(entry.key) === entry) {\n\t\t\tthis.entries.delete(entry.key);\n\t\t}\n\t}\n}\n\nfunction createEntry(key: string): SummaryEntry {\n\treturn {\n\t\tkey: key,\n\t\tabort: new AbortController(),\n\t\tsubscribers: new Set(),\n\t\thandle: undefined,\n\t\tlisteners: undefined,\n\t\tvalue: undefined,\n\t\tbaseKeys: undefined,\n\t\terror: undefined,\n\t\tpending: 0,\n\t\trunning: undefined,\n\t\tdisposed: false,\n\t};\n}\n\n/**\n * Reads the due groups and fills the rest from `previous` (every group when there is none). The branch read comes\n * before the base read, which needs its name; the dirty read runs alongside both. A branch read that finds a\n * different branch (or detached state) also reads the base and dirty groups, and stored base values that differ\n * from `previousBaseKeys` read the base group. A failed base read doesn't fail the rest: the summary comes back stale,\n * keeping the previous base and `baseDependsOn` only while the branch is the same.\n */\nasync function readSummary(\n\trepo: RepositoryService,\n\tkey: string,\n\tfields: number,\n\tprevious: WorktreeSummary | undefined,\n\tpreviousBaseKeys: BaseKeys | undefined,\n\tsignal: AbortSignal | undefined,\n): Promise<ReadResult> {\n\tif (previous == null) {\n\t\tfields = allFields;\n\t}\n\n\tlet dirtyRead = fields & dirtyField ? readDirty(repo, signal) : undefined;\n\n\tlet branch = previous?.branch;\n\tlet detached = previous?.detached ?? false;\n\tlet sha = previous?.sha;\n\tlet upstream = previous?.upstream;\n\tlet ahead = previous?.ahead ?? 0;\n\tlet behind = previous?.behind ?? 0;\n\tif (fields & branchField) {\n\t\tconst current = await repo.branches.getBranch(undefined, undefined, signal);\n\t\tdetached = current?.detached ?? false;\n\t\tbranch = detached ? undefined : current?.name || undefined;\n\t\tsha = current?.sha;\n\t\tupstream = current?.upstream != null ? await toSummaryUpstream(repo, current.upstream, signal) : undefined;\n\t\tahead = current?.upstream?.state.ahead ?? 0;\n\t\tbehind = current?.upstream?.state.behind ?? 0;\n\n\t\tif (previous != null && (branch !== previous.branch || detached !== previous.detached)) {\n\t\t\tfields |= baseField;\n\t\t\tdirtyRead ??= readDirty(repo, signal);\n\t\t}\n\t}\n\n\tlet base = previous?.base;\n\tlet dependsOn = previous?.baseDependsOn ?? emptyRefs;\n\tlet baseKeys = previousBaseKeys;\n\tlet baseKeysRead = false;\n\tlet baseFailed = false;\n\tlet baseError: unknown;\n\tconst baseBranch = !detached && repo.branches.getAheadOfBase != null ? branch : undefined;\n\tif (fields & baseKeysField && !(fields & baseField) && baseBranch != null) {\n\t\tbaseKeys = await readBaseKeys(repo, baseBranch);\n\t\tbaseKeysRead = true;\n\t\tif (baseKeys == null || previousBaseKeys == null || !baseKeysEqual(baseKeys, previousBaseKeys)) {\n\t\t\tfields |= baseField;\n\t\t}\n\t}\n\tif (fields & baseField) {\n\t\tif (baseBranch == null) {\n\t\t\tbase = undefined;\n\t\t\tdependsOn = emptyRefs;\n\t\t\tbaseKeys = undefined;\n\t\t} else {\n\t\t\t// Snapshot before the base read, so a write it misses is one the next `gk` config event sees as a change\n\t\t\tif (!baseKeysRead) {\n\t\t\t\tbaseKeys = await readBaseKeys(repo, baseBranch);\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst aheadOfBase = await repo.branches.getAheadOfBase?.(baseBranch, signal);\n\t\t\t\tbase = aheadOfBase?.base;\n\t\t\t\tdependsOn = aheadOfBase?.dependsOn ?? emptyRefs;\n\t\t\t} catch (ex) {\n\t\t\t\tif (isCancellationError(ex)) throw ex;\n\n\t\t\t\tbaseFailed = true;\n\t\t\t\tbaseError = ex;\n\t\t\t\t// Keep `baseDependsOn` for the same branch: a fetch moving one of its refs is what retries the base\n\t\t\t\tconst sameBranch = previous != null && branch === previous.branch && detached === previous.detached;\n\t\t\t\tbase = sameBranch ? previous.base : undefined;\n\t\t\t\tdependsOn = sameBranch ? previous.baseDependsOn : emptyRefs;\n\t\t\t\tbaseKeys = undefined;\n\t\t\t}\n\t\t}\n\t}\n\n\tconst dirty = dirtyRead != null ? await dirtyRead : (previous?.dirty ?? false);\n\n\treturn {\n\t\tsummary: {\n\t\t\trepoPath: key,\n\t\t\tbranch: branch,\n\t\t\tdetached: detached,\n\t\t\tsha: sha,\n\t\t\tupstream: upstream,\n\t\t\tahead: ahead,\n\t\t\tbehind: behind,\n\t\t\tdirty: dirty,\n\t\t\tbase: base,\n\t\t\tbaseDependsOn: dependsOn,\n\t\t\tstale: baseFailed,\n\t\t},\n\t\tbaseKeys: baseKeys,\n\t\tbaseError: baseError,\n\t};\n}\n\n/**\n * Reads {@link BaseKeys} for `branch`, normalized the way `getAheadOfBase` reads them. `undefined` when any read\n * failed — an unknown snapshot, so the next `gk` config change reads the base rather than trusting it.\n */\nasync function readBaseKeys(repo: RepositoryService, branch: string): Promise<BaseKeys | undefined> {\n\ttry {\n\t\tconst [user, detected, recorded] = await Promise.all([\n\t\t\trepo.branches.getStoredUserMergeTargetBranchName?.(branch),\n\t\t\trepo.branches.getStoredDetectedMergeTargetBranchName?.(branch),\n\t\t\trepo.config.getGkConfig?.(`branch.${branch}.gk-merge-base`),\n\t\t]);\n\t\treturn [user?.trim() || undefined, detected?.trim() || undefined, recorded?.trim() || undefined];\n\t} catch (ex) {\n\t\tif (isCancellationError(ex)) throw ex;\n\n\t\treturn undefined;\n\t}\n}\n\nfunction baseKeysEqual(a: BaseKeys, b: BaseKeys): boolean {\n\treturn a[0] === b[0] && a[1] === b[1] && a[2] === b[2];\n}\n\nfunction readDirty(repo: RepositoryService, signal: AbortSignal | undefined): Promise<boolean> {\n\tconst read = repo.status.hasWorkingChanges({ untracked: true }, signal);\n\t// Awaited only after the branch and base reads; if one of those throws first, this rejection has no other handler\n\tread.catch(noop);\n\treturn read;\n}\n\nfunction noop(): void {}\n\n/** Splits the upstream at the longest remote name it starts with; no split when none matches or nothing follows it */\nasync function toSummaryUpstream(\n\trepo: RepositoryService,\n\tupstream: GitTrackingUpstream,\n\tsignal: AbortSignal | undefined,\n): Promise<WorktreeSummaryUpstream> {\n\tconst name = upstream.name;\n\n\tlet remote: string | undefined;\n\tfor (const r of await repo.remotes.getRemotes(undefined, signal)) {\n\t\tif (r.name && name.startsWith(`${r.name}/`) && (remote == null || r.name.length > remote.length)) {\n\t\t\tremote = r.name;\n\t\t}\n\t}\n\n\tconst branch = remote != null ? name.substring(remote.length + 1) : undefined;\n\treturn {\n\t\tname: name,\n\t\tremote: branch ? remote : undefined,\n\t\tbranch: branch || undefined,\n\t\tmissing: upstream.missing,\n\t};\n}\n\nfunction summariesEqual(a: WorktreeSummary, b: WorktreeSummary): boolean {\n\treturn (\n\t\ta.repoPath === b.repoPath &&\n\t\ta.branch === b.branch &&\n\t\ta.detached === b.detached &&\n\t\ta.sha === b.sha &&\n\t\ta.ahead === b.ahead &&\n\t\ta.behind === b.behind &&\n\t\ta.dirty === b.dirty &&\n\t\ta.stale === b.stale &&\n\t\tareEqual(a.baseDependsOn, b.baseDependsOn) &&\n\t\t(a.upstream === b.upstream ||\n\t\t\t(a.upstream != null &&\n\t\t\t\tb.upstream != null &&\n\t\t\t\ta.upstream.name === b.upstream.name &&\n\t\t\t\ta.upstream.remote === b.upstream.remote &&\n\t\t\t\ta.upstream.branch === b.upstream.branch &&\n\t\t\t\ta.upstream.missing === b.upstream.missing)) &&\n\t\t(a.base === b.base ||\n\t\t\t(a.base != null &&\n\t\t\t\tb.base != null &&\n\t\t\t\ta.base.ref === b.base.ref &&\n\t\t\t\ta.base.ahead === b.base.ahead &&\n\t\t\t\ta.base.sha === b.base.sha))\n\t);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"worktreeSummary.js","names":[],"sources":["../../../../git/src/watching/worktreeSummary.ts"],"sourcesContent":["import { areEqual } from '@gitlens/utils/array.js';\nimport { CancellationError, isCancellationError, raceWithSignal } from '@gitlens/utils/cancellation.js';\nimport type { UnifiedDisposable } from '@gitlens/utils/disposable.js';\nimport type { Event } from '@gitlens/utils/event.js';\nimport { Emitter } from '@gitlens/utils/event.js';\nimport { Logger } from '@gitlens/utils/logger.js';\nimport type { ResourceUsage } from '@gitlens/utils/resourceUsage.js';\nimport { getRepositoryKey } from '@gitlens/utils/uri.js';\nimport type { MergeTargetsChangeEvent } from '../cache.js';\nimport type { GitTrackingUpstream } from '../models/branch.js';\nimport type { GitDir } from '../models/repository.js';\nimport type { AheadOfBaseTier } from '../providers/branches.js';\nimport type { RepositoryService } from '../repositoryService.js';\nimport type { WatcherRepoChangeEvent } from './changeEvent.js';\nimport type { RepositoryWatchService, WatchHandle } from './watchService.js';\nimport type { RepositoryWatchSession } from './watchSession.js';\n\nexport interface WorktreeSummaryUpstream {\n\t/** The upstream's short name (`origin/main`) */\n\treadonly name: string;\n\t/**\n\t * The remote it tracks: the longest of the repository's remote names that {@link name} starts with, followed by\n\t * `/` (a remote name can hold a slash). `undefined`, with {@link branch}, when it names no known remote (a branch\n\t * tracking another local branch, or a remote since removed)\n\t */\n\treadonly remote: string | undefined;\n\t/** The branch on {@link remote}; `undefined` whenever {@link remote} is */\n\treadonly branch: string | undefined;\n\treadonly missing: boolean;\n}\n\nexport interface WorktreeSummaryBase {\n\t/** The ref the count was taken against (see `AheadOfBase.base`) */\n\treadonly ref: string;\n\treadonly ahead: number;\n\t/** The commit {@link ref} pointed at when the count was read; `undefined` when it couldn't be resolved */\n\treadonly sha: string | undefined;\n\t/** Where HEAD forked from {@link ref} (see `AheadOfBase.base`) */\n\treadonly forkPoint: string | undefined;\n\t/** The source the base came from (see `AheadOfBase.base`) */\n\treadonly tier: AheadOfBaseTier;\n}\n\nexport interface WorktreeSummary {\n\t/** The repository path in {@link getRepositoryKey} form */\n\treadonly repoPath: string;\n\t/** The checked-out branch; `undefined` when detached (or unborn with no name) */\n\treadonly branch: string | undefined;\n\treadonly detached: boolean;\n\t/** HEAD's commit; `undefined` when HEAD has no commit yet */\n\treadonly sha: string | undefined;\n\treadonly upstream: WorktreeSummaryUpstream | undefined;\n\t/** Commits ahead of the upstream; 0 without one */\n\treadonly ahead: number;\n\t/** Commits behind the upstream; 0 without one */\n\treadonly behind: number;\n\t/** Whether there is any staged, unstaged or untracked (non-ignored) change */\n\treadonly dirty: boolean;\n\t/**\n\t * How far the branch — or a detached HEAD, measured against the default branch — is past its base; `undefined` when\n\t * unsupported or there is no base\n\t */\n\treadonly base: WorktreeSummaryBase | undefined;\n\t/**\n\t * Full ref names whose movement can change {@link base}, as `getAheadOfBase` reports them (see\n\t * `AheadOfBase.dependsOn`) — given with or without a base, so a watcher also learns when one appears. Empty when\n\t * the base isn't read (an unborn HEAD with no name, or a provider without `getAheadOfBase`), and when the base read\n\t * failed with no earlier list for this HEAD (the first read, or the one after a checkout).\n\t */\n\treadonly baseDependsOn: readonly string[];\n\t/**\n\t * The latest recompute failed (worktree gone, lock contention); the other fields hold the last values read. Also set\n\t * when only the base read failed: the other fields are fresh, and `base` and `baseDependsOn` are their last values,\n\t * or `undefined` and empty if the branch changed.\n\t */\n\treadonly stale: boolean;\n}\n\nexport interface WorktreeSummarySubscribeOptions {\n\t/** Debounce for repo (`.git`) changes this subscriber asks of the watch session; the shortest across subscribers wins */\n\treadonly delayMs?: number;\n\t/** Cap on how long continuous repo activity can defer a recompute; `Infinity` means no cap */\n\treadonly maxWaitMs?: number;\n\t/** Debounce for working-tree changes this subscriber asks of the watch session */\n\treadonly workingTreeDelayMs?: number;\n\t/** Cap on how long continuous working-tree activity can defer a recompute; `Infinity` means no cap */\n\treadonly workingTreeMaxWaitMs?: number;\n}\n\nexport interface WorktreeSummarySubscription extends UnifiedDisposable {\n\t/** The latest summary; `undefined` until the first read succeeds, or after disposal */\n\treadonly current: WorktreeSummary | undefined;\n\t/** Fires when the summary's value changes */\n\treadonly onDidChange: Event<WorktreeSummary>;\n}\n\nexport interface WorktreeSummaryServiceOptions {\n\treadonly watchService: RepositoryWatchService;\n\treadonly getRepository: (repoPath: string) => RepositoryService | undefined;\n\t/**\n\t * Fires as core writes a branch's merge target or base, before the store's promise resolves; the summary re-reads\n\t * that branch's stored values then, so a `getSummary` right after an awaited store sees the new base\n\t */\n\treadonly onDidChangeMergeTargets?: Event<MergeTargetsChangeEvent>;\n\t/**\n\t * Maps a worktree path to the repository common path that {@link onDidChangeMergeTargets} events carry; only that\n\t * repository's summaries react. Without it, or when it returns `undefined`, every summary reacts.\n\t */\n\treadonly getCommonPath?: (repoPath: string) => string | undefined;\n}\n\n/** Read groups, as bit flags so a pending set is a number rather than an allocation per event */\nconst dirtyField = 1;\nconst branchField = 2;\nconst baseField = 4;\n/** Re-reads only the stored values the base read consults, and the base itself only when they changed */\nconst baseKeysField = 8;\n/** Every group a full read covers — a base read refreshes the {@link BaseKeys} snapshot itself */\nconst allFields = dirtyField | branchField | baseField;\n\nconst emptyRefs: readonly string[] = Object.freeze([]);\n\n/**\n * The current branch's stored merge targets (user, detected) and recorded base, as `getAheadOfBase` consults them.\n * A detached HEAD has no stored values, so it holds {@link noBaseKeys}.\n */\ntype BaseKeys = readonly [string | undefined, string | undefined, string | undefined];\n\nconst noBaseKeys: BaseKeys = [undefined, undefined, undefined];\n\ninterface ReadResult {\n\treadonly summary: WorktreeSummary;\n\treadonly baseKeys: BaseKeys | undefined;\n\t/** Why the base read failed when the rest of the summary was read; the summary is then stale */\n\treadonly baseError: unknown;\n}\n\ninterface SummaryEntry {\n\treadonly key: string;\n\t/** Aborted when the last subscriber leaves, cancelling any in-flight read */\n\treadonly abort: AbortController;\n\treadonly subscribers: Set<SummarySubscription>;\n\thandle: WatchHandle | undefined;\n\t/** The timing-neutral listening subscriptions and their event listeners */\n\tlisteners: UnifiedDisposable[] | undefined;\n\tvalue: WorktreeSummary | undefined;\n\t/** The stored base values as of the last base read; a `gk` config change re-reads the base only when they differ */\n\tbaseKeys: BaseKeys | undefined;\n\t/** Why the latest read failed while there was no value to keep */\n\terror: Error | undefined;\n\t/** Read groups due on the next run */\n\tpending: number;\n\t/** The active run (or the start-up, which ends in one); cleared in the same turn the loop sees nothing pending */\n\trunning: Promise<void> | undefined;\n\tdisposed: boolean;\n}\n\nclass SummarySubscription implements WorktreeSummarySubscription {\n\tprivate readonly emitter = new Emitter<WorktreeSummary>();\n\t/** Listener-less session subscriptions that carry this subscriber's timing into the session */\n\tprivate timing: UnifiedDisposable[] | undefined;\n\tprivate disposed = false;\n\n\tconstructor(\n\t\tprivate readonly entry: SummaryEntry,\n\t\tprivate readonly options: WorktreeSummarySubscribeOptions | undefined,\n\t\tprivate readonly onDispose: (subscription: SummarySubscription) => void,\n\t) {}\n\n\tget current(): WorktreeSummary | undefined {\n\t\treturn this.disposed ? undefined : this.entry.value;\n\t}\n\n\tget onDidChange(): Event<WorktreeSummary> {\n\t\treturn this.emitter.event;\n\t}\n\n\t[Symbol.dispose](): void {\n\t\tthis.dispose();\n\t}\n\n\tdispose(): void {\n\t\tif (this.disposed) return;\n\n\t\tthis.disposed = true;\n\t\tif (this.timing != null) {\n\t\t\tfor (const d of this.timing) {\n\t\t\t\td.dispose();\n\t\t\t}\n\t\t\tthis.timing = undefined;\n\t\t}\n\t\tthis.emitter.dispose();\n\t\tthis.onDispose(this);\n\t}\n\n\tattach(session: RepositoryWatchSession): void {\n\t\tif (this.disposed || this.timing != null) return;\n\n\t\tthis.timing = [\n\t\t\tsession.subscribe({ delayMs: this.options?.delayMs, maxWaitMs: this.options?.maxWaitMs }),\n\t\t\tsession.subscribeToWorkingTree({\n\t\t\t\tdelayMs: this.options?.workingTreeDelayMs,\n\t\t\t\tmaxWaitMs: this.options?.workingTreeMaxWaitMs,\n\t\t\t}),\n\t\t];\n\t}\n\n\tfire(summary: WorktreeSummary): void {\n\t\tthis.emitter.fire(summary);\n\t}\n}\n\n/**\n * A cached, change-notifying summary of each subscribed worktree — its branch, upstream and ahead/behind, whether it\n * has working changes, and how far it is past its base — kept current by the repository watch layer.\n *\n * **Lifecycle.** Entries are per repository key and ref-counted across {@link subscribe} calls. The first subscriber\n * starts watching the worktree and reads everything; the last one to leave aborts any in-flight read, releases the\n * watch and drops the value. Each subscriber's timing options join the watch session's cadence (the shortest wins);\n * the service's own listening subscriptions never constrain it.\n *\n * **Field-scoped recompute.** The summary is read in three groups — `dirty` (working changes), `branch` (branch,\n * HEAD's commit, upstream, ahead/behind) and `base` (ahead of base, the base's tip, and the refs it depends on) — and\n * an event recomputes only the groups it can affect: a working-tree edit, an index or ignore-rule change reads only\n * `dirty`; the upstream moving reads only `branch`; a ref the base depends on reads only `base`; a `gk` config change\n * (mostly bookkeeping writes to the shared `.git/gk/config`), or core's own write of the branch's merge target or\n * base, re-reads the branch's stored merge targets and recorded base, and reads `base` only when one of them changed;\n * the checked-out branch moving, or a HEAD change, reads all of them. Stash, unrelated tags and branches, and the like\n * read nothing. One run is in flight per worktree at a time; events during a run are merged into a single follow-up\n * run.\n *\n * **Value-change-only notification.** {@link WorktreeSummarySubscription.onDidChange} and {@link onDidChangeSummary}\n * fire only when some field's value differs from the previous summary — a recompute that reads the same answer is\n * silent, so a consumer can re-render on every event without diffing. A failed recompute keeps the last values and\n * sets `stale` (firing once); the next successful one clears it.\n */\nexport class WorktreeSummaryService implements UnifiedDisposable {\n\tprivate readonly _onDidChangeSummary = new Emitter<WorktreeSummary>();\n\t/** Fires when the summary of any subscribed worktree changes value */\n\tget onDidChangeSummary(): Event<WorktreeSummary> {\n\t\treturn this._onDidChangeSummary.event;\n\t}\n\n\tprivate readonly entries = new Map<string, SummaryEntry>();\n\tprivate readonly watchService: RepositoryWatchService;\n\tprivate readonly getRepository: (repoPath: string) => RepositoryService | undefined;\n\tprivate readonly getCommonPath: ((repoPath: string) => string | undefined) | undefined;\n\tprivate readonly mergeTargetsListener: UnifiedDisposable | undefined;\n\tprivate _disposed = false;\n\n\tconstructor(options: WorktreeSummaryServiceOptions) {\n\t\tthis.watchService = options.watchService;\n\t\tthis.getRepository = options.getRepository;\n\t\tthis.getCommonPath = options.getCommonPath;\n\t\tthis.mergeTargetsListener = options.onDidChangeMergeTargets?.(e => this.onMergeTargetsChanged(e));\n\t}\n\n\t[Symbol.dispose](): void {\n\t\tthis.dispose();\n\t}\n\n\tdispose(): void {\n\t\tif (this._disposed) return;\n\n\t\tthis._disposed = true;\n\t\tthis.mergeTargetsListener?.dispose();\n\t\tfor (const entry of [...this.entries.values()]) {\n\t\t\tfor (const subscription of [...entry.subscribers]) {\n\t\t\t\tsubscription.dispose();\n\t\t\t}\n\t\t}\n\t\tthis.entries.clear();\n\t\tthis._onDidChangeSummary.dispose();\n\t}\n\n\t/** Subscribes to a worktree's summary, starting to watch and read it if this is its first subscriber */\n\tsubscribe(repoPath: string, options?: WorktreeSummarySubscribeOptions): WorktreeSummarySubscription {\n\t\tconst key = getRepositoryKey(repoPath);\n\n\t\tlet entry = this._disposed ? undefined : this.entries.get(key);\n\t\tconst isNew = entry == null;\n\t\tif (entry == null) {\n\t\t\tentry = createEntry(key);\n\t\t\tif (this._disposed) {\n\t\t\t\tentry.disposed = true;\n\t\t\t} else {\n\t\t\t\tthis.entries.set(key, entry);\n\t\t\t}\n\t\t}\n\n\t\tconst target = entry;\n\t\tconst subscription = new SummarySubscription(target, options, s => this.release(target, s));\n\t\ttarget.subscribers.add(subscription);\n\n\t\tif (isNew) {\n\t\t\tif (!target.disposed) {\n\t\t\t\ttarget.pending = allFields;\n\t\t\t\ttarget.running = this.start(target);\n\t\t\t}\n\t\t} else if (target.handle != null) {\n\t\t\tsubscription.attach(target.handle.session);\n\t\t} else if (target.running == null && !target.disposed) {\n\t\t\t// The start failed (no repository or git directory yet); a new subscriber retries it\n\t\t\ttarget.pending = allFields;\n\t\t\ttarget.running = this.start(target);\n\t\t}\n\n\t\treturn subscription;\n\t}\n\n\t/**\n\t * Returns a worktree's summary. For a subscribed worktree, the cached value — after waiting for any run in flight\n\t * or pending; it rejects with the read's error when there is still no value. Otherwise a one-off full read that\n\t * is neither cached nor watched.\n\t */\n\tasync getSummary(repoPath: string, cancellation?: AbortSignal): Promise<WorktreeSummary> {\n\t\tconst key = getRepositoryKey(repoPath);\n\t\tconst entry = this.entries.get(key);\n\t\tif (entry == null) {\n\t\t\tconst repo = this.getRepository(key);\n\t\t\tif (repo == null) throw new Error(`No repository found for '${key}'`);\n\n\t\t\tconst result = await readSummary(repo, key, allFields, undefined, undefined, cancellation);\n\t\t\treturn result.summary;\n\t\t}\n\n\t\tif (entry.running != null) {\n\t\t\tawait (cancellation != null ? raceWithSignal(entry.running, cancellation) : entry.running);\n\t\t}\n\t\tif (entry.disposed) throw new CancellationError();\n\t\tif (entry.value != null) return entry.value;\n\n\t\tthrow entry.error ?? new Error(`No summary available for '${key}'`);\n\t}\n\n\tgetResourceUsage(): ResourceUsage {\n\t\treturn { 'worktreeSummary.entries.count': this.entries.size };\n\t}\n\n\tprivate async start(entry: SummaryEntry): Promise<void> {\n\t\tlet gitDir: GitDir | undefined;\n\t\ttry {\n\t\t\tgitDir = await this.getRepository(entry.key)?.config.getGitDir?.();\n\t\t} catch (ex) {\n\t\t\tif (entry.disposed) return;\n\n\t\t\tentry.running = undefined;\n\t\t\tif (!isCancellationError(ex)) {\n\t\t\t\tthis.fail(entry, ex);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tif (entry.disposed) return;\n\n\t\tconst handle = gitDir != null ? this.watchService.watch(entry.key, gitDir) : undefined;\n\t\tif (handle == null) {\n\t\t\tentry.running = undefined;\n\t\t\tthis.fail(entry, new Error(`Unable to watch '${entry.key}': no repository or git directory`));\n\t\t\treturn;\n\t\t}\n\n\t\tentry.handle = handle;\n\t\tconst session = handle.session;\n\t\t// Timing-neutral: the cadence comes from the subscribers' own session subscriptions (see `attach`)\n\t\tconst repoSub = session.subscribe({ delayMs: Infinity, maxWaitMs: Infinity });\n\t\tconst workingTreeSub = session.subscribeToWorkingTree({ delayMs: Infinity, maxWaitMs: Infinity });\n\t\tentry.listeners = [\n\t\t\trepoSub.onDidChange(e => this.onRepoChanged(entry, e)),\n\t\t\tworkingTreeSub.onDidChangeWorkingTree(() => this.queue(entry, dirtyField)),\n\t\t\trepoSub,\n\t\t\tworkingTreeSub,\n\t\t];\n\t\tfor (const subscription of entry.subscribers) {\n\t\t\tsubscription.attach(session);\n\t\t}\n\n\t\tawait this.drain(entry);\n\t}\n\n\tprivate onRepoChanged(entry: SummaryEntry, e: WatcherRepoChangeEvent): void {\n\t\tconst value = entry.value;\n\t\tif (value == null || e.changed('unknown', 'head')) {\n\t\t\tthis.queue(entry, allFields);\n\t\t\treturn;\n\t\t}\n\n\t\tlet fields = 0;\n\t\tif (e.changed('index', 'ignores')) {\n\t\t\tfields |= dirtyField;\n\t\t}\n\t\tif (e.changed('config')) {\n\t\t\tfields |= branchField | baseField;\n\t\t}\n\t\tif (e.changed('gkConfig')) {\n\t\t\tfields |= baseKeysField;\n\t\t}\n\t\tif (value.branch != null && e.mayHaveChangedRef(`refs/heads/${value.branch}`)) {\n\t\t\tfields |= allFields;\n\t\t}\n\t\t// The upstream's short name doesn't say whether it's remote or local (`--track main`), so test both forms\n\t\tif (\n\t\t\tvalue.upstream != null &&\n\t\t\t(e.mayHaveChangedRef(`refs/remotes/${value.upstream.name}`) ||\n\t\t\t\te.mayHaveChangedRef(`refs/heads/${value.upstream.name}`))\n\t\t) {\n\t\t\tfields |= branchField;\n\t\t}\n\t\tif ((fields & baseField) === 0) {\n\t\t\tfor (const ref of value.baseDependsOn) {\n\t\t\t\tif (e.mayHaveChangedRef(ref)) {\n\t\t\t\t\tfields |= baseField;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthis.queue(entry, fields);\n\t}\n\n\t/**\n\t * Queued synchronously, so a store awaited before `getSummary` finds the run in flight. The watcher's later\n\t * `gkConfig` event for the same write then finds the keys unchanged and leaves the base alone.\n\t */\n\tprivate onMergeTargetsChanged(e: MergeTargetsChangeEvent): void {\n\t\tfor (const entry of this.entries.values()) {\n\t\t\t// Before the watch starts there's nothing to refresh: the start's own read comes after the write\n\t\t\tif (entry.handle == null) continue;\n\n\t\t\t// With no value yet, the first read may have read the stored values before the write, so `queue` reads\n\t\t\t// everything again\n\t\t\tconst value = entry.value;\n\t\t\tif (value != null && (value.branch == null || (e.refs != null && !e.refs.has(value.branch)))) continue;\n\n\t\t\tconst commonPath = this.getCommonPath?.(entry.key);\n\t\t\tif (commonPath != null && commonPath !== e.repoPath) continue;\n\n\t\t\tthis.queue(entry, baseKeysField);\n\t\t}\n\t}\n\n\tprivate queue(entry: SummaryEntry, fields: number): void {\n\t\tif (entry.disposed || fields === 0) return;\n\n\t\t// Without a value a partial read has nothing to merge into, and a stale value's fields can't be trusted: a\n\t\t// partial read would clear `stale` while vouching for groups it never re-read\n\t\tentry.pending |= entry.value == null || entry.value.stale ? allFields : fields;\n\t\tentry.running ??= this.drain(entry);\n\t}\n\n\t/** Runs until nothing is pending. Never rejects — every failure becomes entry state. */\n\tprivate async drain(entry: SummaryEntry): Promise<void> {\n\t\twhile (entry.pending !== 0 && !entry.disposed) {\n\t\t\tconst fields = entry.pending;\n\t\t\tentry.pending = 0;\n\t\t\tawait this.run(entry, fields);\n\t\t}\n\t\t// Same turn as the loop's last check, so an event can't queue fields that no run will pick up\n\t\tentry.running = undefined;\n\t}\n\n\tprivate async run(entry: SummaryEntry, fields: number): Promise<void> {\n\t\tconst previous = entry.value;\n\n\t\tlet result: ReadResult;\n\t\ttry {\n\t\t\tconst repo = this.getRepository(entry.key);\n\t\t\tif (repo == null) throw new Error(`No repository found for '${entry.key}'`);\n\n\t\t\tresult = await readSummary(repo, entry.key, fields, previous, entry.baseKeys, entry.abort.signal);\n\t\t} catch (ex) {\n\t\t\tif (entry.disposed || isCancellationError(ex)) return;\n\n\t\t\tthis.fail(entry, ex);\n\t\t\treturn;\n\t\t}\n\t\tif (entry.disposed) return;\n\n\t\tif (result.summary.stale && (previous == null || !previous.stale)) {\n\t\t\tLogger.warn(\n\t\t\t\t`WorktreeSummaryService: reading the base of '${entry.key}' failed: ${String(result.baseError)}`,\n\t\t\t);\n\t\t}\n\n\t\tentry.error = undefined;\n\t\tentry.baseKeys = result.baseKeys;\n\t\tif (previous != null && summariesEqual(previous, result.summary)) return;\n\n\t\tentry.value = result.summary;\n\t\tthis.fireChange(entry, result.summary);\n\t}\n\n\tprivate fail(entry: SummaryEntry, ex: unknown): void {\n\t\tconst previous = entry.value;\n\t\tif (previous == null) {\n\t\t\tif (entry.error == null) {\n\t\t\t\tLogger.warn(`WorktreeSummaryService: reading '${entry.key}' failed: ${String(ex)}`);\n\t\t\t}\n\t\t\tentry.error = ex instanceof Error ? ex : new Error(String(ex));\n\t\t\treturn;\n\t\t}\n\t\tif (previous.stale) return;\n\n\t\tLogger.warn(`WorktreeSummaryService: refreshing '${entry.key}' failed: ${String(ex)}`);\n\t\tconst summary: WorktreeSummary = { ...previous, stale: true };\n\t\tentry.value = summary;\n\t\tthis.fireChange(entry, summary);\n\t}\n\n\tprivate fireChange(entry: SummaryEntry, summary: WorktreeSummary): void {\n\t\tfor (const subscription of entry.subscribers) {\n\t\t\tsubscription.fire(summary);\n\t\t}\n\t\tthis._onDidChangeSummary.fire(summary);\n\t}\n\n\tprivate release(entry: SummaryEntry, subscription: SummarySubscription): void {\n\t\tentry.subscribers.delete(subscription);\n\t\tif (entry.subscribers.size !== 0) return;\n\n\t\tentry.disposed = true;\n\t\tentry.abort.abort();\n\t\tif (entry.listeners != null) {\n\t\t\tfor (const d of entry.listeners) {\n\t\t\t\td.dispose();\n\t\t\t}\n\t\t\tentry.listeners = undefined;\n\t\t}\n\t\tentry.handle?.dispose();\n\t\tentry.handle = undefined;\n\t\tentry.value = undefined;\n\t\tentry.baseKeys = undefined;\n\t\tentry.pending = 0;\n\t\tentry.running = undefined;\n\n\t\tif (this.entries.get(entry.key) === entry) {\n\t\t\tthis.entries.delete(entry.key);\n\t\t}\n\t}\n}\n\nfunction createEntry(key: string): SummaryEntry {\n\treturn {\n\t\tkey: key,\n\t\tabort: new AbortController(),\n\t\tsubscribers: new Set(),\n\t\thandle: undefined,\n\t\tlisteners: undefined,\n\t\tvalue: undefined,\n\t\tbaseKeys: undefined,\n\t\terror: undefined,\n\t\tpending: 0,\n\t\trunning: undefined,\n\t\tdisposed: false,\n\t};\n}\n\n/**\n * Reads the due groups and fills the rest from `previous` (every group when there is none). The branch read comes\n * before the base read, which needs its name; the dirty read runs alongside both. A branch read that finds a\n * different branch (or detached state) also reads the base and dirty groups, and stored base values that differ\n * from `previousBaseKeys` read the base group. A failed base read doesn't fail the rest: the summary comes back stale,\n * keeping the previous base and `baseDependsOn` only while the branch is the same.\n */\nasync function readSummary(\n\trepo: RepositoryService,\n\tkey: string,\n\tfields: number,\n\tprevious: WorktreeSummary | undefined,\n\tpreviousBaseKeys: BaseKeys | undefined,\n\tsignal: AbortSignal | undefined,\n): Promise<ReadResult> {\n\tif (previous == null) {\n\t\tfields = allFields;\n\t}\n\n\tlet dirtyRead = fields & dirtyField ? readDirty(repo, signal) : undefined;\n\n\tlet branch = previous?.branch;\n\tlet detached = previous?.detached ?? false;\n\tlet sha = previous?.sha;\n\tlet upstream = previous?.upstream;\n\tlet ahead = previous?.ahead ?? 0;\n\tlet behind = previous?.behind ?? 0;\n\tif (fields & branchField) {\n\t\tconst current = await repo.branches.getBranch(undefined, undefined, signal);\n\t\tdetached = current?.detached ?? false;\n\t\tbranch = detached ? undefined : current?.name || undefined;\n\t\tsha = current?.sha;\n\t\tupstream = current?.upstream != null ? await toSummaryUpstream(repo, current.upstream, signal) : undefined;\n\t\tahead = current?.upstream?.state.ahead ?? 0;\n\t\tbehind = current?.upstream?.state.behind ?? 0;\n\n\t\tif (previous != null && (branch !== previous.branch || detached !== previous.detached)) {\n\t\t\tfields |= baseField;\n\t\t\tdirtyRead ??= readDirty(repo, signal);\n\t\t}\n\t}\n\n\tlet base = previous?.base;\n\tlet dependsOn = previous?.baseDependsOn ?? emptyRefs;\n\tlet baseKeys = previousBaseKeys;\n\tlet baseKeysRead = false;\n\tlet baseFailed = false;\n\tlet baseError: unknown;\n\t// A detached HEAD is measured as `undefined`; an unborn HEAD with no name has nothing to measure\n\tconst baseReadable = repo.branches.getAheadOfBase != null && (detached || branch != null);\n\tif (fields & baseKeysField && !(fields & baseField) && baseReadable) {\n\t\tbaseKeys = await readBaseKeys(repo, branch);\n\t\tbaseKeysRead = true;\n\t\tif (baseKeys == null || previousBaseKeys == null || !baseKeysEqual(baseKeys, previousBaseKeys)) {\n\t\t\tfields |= baseField;\n\t\t}\n\t}\n\tif (fields & baseField) {\n\t\tif (!baseReadable) {\n\t\t\tbase = undefined;\n\t\t\tdependsOn = emptyRefs;\n\t\t\tbaseKeys = undefined;\n\t\t} else {\n\t\t\t// Snapshot before the base read, so a write it misses is one the next `gk` config event sees as a change\n\t\t\tif (!baseKeysRead) {\n\t\t\t\tbaseKeys = await readBaseKeys(repo, branch);\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst aheadOfBase = await repo.branches.getAheadOfBase?.(branch, undefined, signal);\n\t\t\t\tbase = aheadOfBase?.base;\n\t\t\t\tdependsOn = aheadOfBase?.dependsOn ?? emptyRefs;\n\t\t\t} catch (ex) {\n\t\t\t\tif (isCancellationError(ex)) throw ex;\n\n\t\t\t\tbaseFailed = true;\n\t\t\t\tbaseError = ex;\n\t\t\t\t// Keep `baseDependsOn` for the same branch: a fetch moving one of its refs is what retries the base\n\t\t\t\tconst sameBranch = previous != null && branch === previous.branch && detached === previous.detached;\n\t\t\t\tbase = sameBranch ? previous.base : undefined;\n\t\t\t\tdependsOn = sameBranch ? previous.baseDependsOn : emptyRefs;\n\t\t\t\tbaseKeys = undefined;\n\t\t\t}\n\t\t}\n\t}\n\n\tconst dirty = dirtyRead != null ? await dirtyRead : (previous?.dirty ?? false);\n\n\treturn {\n\t\tsummary: {\n\t\t\trepoPath: key,\n\t\t\tbranch: branch,\n\t\t\tdetached: detached,\n\t\t\tsha: sha,\n\t\t\tupstream: upstream,\n\t\t\tahead: ahead,\n\t\t\tbehind: behind,\n\t\t\tdirty: dirty,\n\t\t\tbase: base,\n\t\t\tbaseDependsOn: dependsOn,\n\t\t\tstale: baseFailed,\n\t\t},\n\t\tbaseKeys: baseKeys,\n\t\tbaseError: baseError,\n\t};\n}\n\n/**\n * Reads {@link BaseKeys} for `branch`, normalized the way `getAheadOfBase` reads them. `undefined` when any read\n * failed — an unknown snapshot, so the next `gk` config change reads the base rather than trusting it. A detached\n * HEAD (`branch` undefined) has no stored values to read.\n */\nasync function readBaseKeys(repo: RepositoryService, branch: string | undefined): Promise<BaseKeys | undefined> {\n\tif (branch == null) return noBaseKeys;\n\n\ttry {\n\t\tconst [user, detected, recorded] = await Promise.all([\n\t\t\trepo.branches.getStoredUserMergeTargetBranchName?.(branch),\n\t\t\trepo.branches.getStoredDetectedMergeTargetBranchName?.(branch),\n\t\t\trepo.config.getGkConfig?.(`branch.${branch}.gk-merge-base`),\n\t\t]);\n\t\treturn [user?.trim() || undefined, detected?.trim() || undefined, recorded?.trim() || undefined];\n\t} catch (ex) {\n\t\tif (isCancellationError(ex)) throw ex;\n\n\t\treturn undefined;\n\t}\n}\n\nfunction baseKeysEqual(a: BaseKeys, b: BaseKeys): boolean {\n\treturn a[0] === b[0] && a[1] === b[1] && a[2] === b[2];\n}\n\nfunction readDirty(repo: RepositoryService, signal: AbortSignal | undefined): Promise<boolean> {\n\tconst read = repo.status.hasWorkingChanges({ untracked: true }, signal);\n\t// Awaited only after the branch and base reads; if one of those throws first, this rejection has no other handler\n\tread.catch(noop);\n\treturn read;\n}\n\nfunction noop(): void {}\n\n/** Splits the upstream at the longest remote name it starts with; no split when none matches or nothing follows it */\nasync function toSummaryUpstream(\n\trepo: RepositoryService,\n\tupstream: GitTrackingUpstream,\n\tsignal: AbortSignal | undefined,\n): Promise<WorktreeSummaryUpstream> {\n\tconst name = upstream.name;\n\n\tlet remote: string | undefined;\n\tfor (const r of await repo.remotes.getRemotes(undefined, signal)) {\n\t\tif (r.name && name.startsWith(`${r.name}/`) && (remote == null || r.name.length > remote.length)) {\n\t\t\tremote = r.name;\n\t\t}\n\t}\n\n\tconst branch = remote != null ? name.substring(remote.length + 1) : undefined;\n\treturn {\n\t\tname: name,\n\t\tremote: branch ? remote : undefined,\n\t\tbranch: branch || undefined,\n\t\tmissing: upstream.missing,\n\t};\n}\n\nfunction summariesEqual(a: WorktreeSummary, b: WorktreeSummary): boolean {\n\treturn (\n\t\ta.repoPath === b.repoPath &&\n\t\ta.branch === b.branch &&\n\t\ta.detached === b.detached &&\n\t\ta.sha === b.sha &&\n\t\ta.ahead === b.ahead &&\n\t\ta.behind === b.behind &&\n\t\ta.dirty === b.dirty &&\n\t\ta.stale === b.stale &&\n\t\tareEqual(a.baseDependsOn, b.baseDependsOn) &&\n\t\t(a.upstream === b.upstream ||\n\t\t\t(a.upstream != null &&\n\t\t\t\tb.upstream != null &&\n\t\t\t\ta.upstream.name === b.upstream.name &&\n\t\t\t\ta.upstream.remote === b.upstream.remote &&\n\t\t\t\ta.upstream.branch === b.upstream.branch &&\n\t\t\t\ta.upstream.missing === b.upstream.missing)) &&\n\t\t(a.base === b.base ||\n\t\t\t(a.base != null &&\n\t\t\t\tb.base != null &&\n\t\t\t\ta.base.ref === b.base.ref &&\n\t\t\t\ta.base.ahead === b.base.ahead &&\n\t\t\t\ta.base.sha === b.base.sha &&\n\t\t\t\ta.base.forkPoint === b.base.forkPoint &&\n\t\t\t\ta.base.tier === b.base.tier))\n\t);\n}\n"],"mappings":"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"}
@@ -6,7 +6,7 @@ import { RepositoryChange } from "../git/models/repository.js";
6
6
  import { GitServiceContext } from "../git/context.js";
7
7
  import { Cache, CachedGitTypes, MergeTargetsChangeEvent } from "../git/cache.js";
8
8
  import { GitOperationRunOptions } from "../git/providers/operations.js";
9
- import { GitProvider } from "../git/providers/provider.js";
9
+ import { AnnouncedRepositoryChanges, GitProvider } from "../git/providers/provider.js";
10
10
  import { FilteredGitFeatures, GitFeatureOrPrefix, GitFeatures } from "../git/features.js";
11
11
  import { GitLocation } from "./exec/locator.js";
12
12
  import { Git, GitFeatureUnsupportedMessage, GitOptions } from "./exec/git.js";
@@ -88,6 +88,9 @@ export declare class CliGitProvider implements GitProvider {
88
88
  /** The underlying cache instance (for repo path registration, etc.) */
89
89
  get cache(): Cache;
90
90
  get onDidChangeMergeTargets(): Event<MergeTargetsChangeEvent>;
91
+ getCommonPath(repoPath: string): string | undefined;
92
+ private readonly _onDidAnnounceChanges;
93
+ get onDidAnnounceChanges(): Event<AnnouncedRepositoryChanges>;
91
94
  /** The underlying git execution instance (for sub-providers that need direct exec/stream access) */
92
95
  get git(): Git;
93
96
  /** Returns whether the installed git version supports the given feature. */
@@ -129,9 +132,36 @@ export declare class CliGitProvider implements GitProvider {
129
132
  * for both its target and the caller's `cwd`. Each path's caches are reset and the host's `onReset` fires
130
133
  * for each, since a host may keep per-worktree state, but a repository's pending commands are dropped once
131
134
  * however many of its worktrees are named.
135
+ *
136
+ * Each path hears the announcement once: `repoPaths` hear all of `changes`, `sharedPaths` (reset and
137
+ * `onReset`-ed like `repoPaths`) and then the other registered worktrees sharing a repository with any of
138
+ * them (reset like a typed write's, with no `onReset`) hear the shared part of it, see
139
+ * {@link getSiblingChanges}.
132
140
  */
133
141
  private notifyChangedCore;
134
- /** Evicts what a notified change reset for `repoPath`: the types reset (`[]` = all), or `undefined` for none. */
142
+ /**
143
+ * Tells only the host's own `repository.onChanged` about `changes` — for a follow-up that is not a write of
144
+ * core's, such as re-announcing an outside change once it was reconciled. Nothing is evicted, no sibling hears
145
+ * it, and the watch layer doesn't take it for a write's announcement.
146
+ */
147
+ notifyHost(repoPath: string, changes: RepositoryChange[]): void;
148
+ /** Tells the host about a write for one path, after its caches were reset — the watch layer hears of it first */
149
+ private announceChanged;
150
+ /**
151
+ * What the worktrees sharing a repository hear of `changes` that ran in one of them: the part worktrees
152
+ * share (see {@link worktreeSharedChanges}), or `undefined` when `changes` has none. An empty `changes` or
153
+ * `'unknown'` means everything may have changed, so it reaches them as every shared kind — never as
154
+ * `'unknown'` itself, which a host treats as the end of a watch session; `'closed'` concerns the one path
155
+ * alone and reaches none.
156
+ */
157
+ private getSharedChanges;
158
+ /** What each registered worktree sharing `repoPath`'s repository, other than `repoPath`, hears of `changes`. */
159
+ private getSiblingChanges;
160
+ /**
161
+ * Evicts what a notified change reset for `repoPath`: the types reset (`[]` = all), or `undefined` for none.
162
+ * A `sibling` shares its repository with a path already reset for the same change, which evicted the shared
163
+ * entries for every worktree, so the changes evict only its own entries and advance its status clock.
164
+ */
135
165
  private resetForChanges;
136
166
  /** `repoPath` and every worktree registered as sharing its repository, since a shared-ref write moves them all. */
137
167
  private getRepositoryPaths;
@@ -180,6 +210,8 @@ export declare class CliGitProvider implements GitProvider {
180
210
  getLastFetchedTimestamp(repoPath: string): Promise<number | undefined>;
181
211
  getIgnoreFilter(repoPath: string): GitIgnoreFilter;
182
212
  getIgnoredDirectories(repoPath: string, cancellation?: AbortSignal): Promise<string[]>;
213
+ getIgnoredEntries(repoPath: string, cancellation?: AbortSignal): Promise<string[]>;
214
+ getIgnoredPaths(repoPath: string, paths: readonly string[], cancellation?: AbortSignal): Promise<Set<string>>;
183
215
  /** Every `.gitignore` git reads in the working tree (tracked, or untracked and not itself ignored), root included */
184
216
  private getGitIgnorePaths;
185
217
  private isTrackedWithDetails;
@@ -1,5 +1,6 @@
1
1
  import { iterateByDelimiter } from "../utils/string.js";
2
2
  import { getScopedLogger } from "../utils/logger.scoped.js";
3
+ import { Emitter } from "../utils/event.js";
3
4
  import { fileUri, joinUriPath, parseUri, toFsPath } from "../utils/uri.js";
4
5
  import { joinPaths, maybeUri, normalizePath, splitPath } from "../utils/path.js";
5
6
  import "../git/models/revision.js";
@@ -7,6 +8,7 @@ import { isUncommitted } from "../git/utils/revision.utils.js";
7
8
  import { Cache } from "../git/cache.js";
8
9
  import { parseGitRemoteUrl } from "../git/utils/remote.utils.js";
9
10
  import { GitIgnoreFilter } from "../git/watching/gitIgnoreFilter.js";
11
+ import { worktreeSharedChanges } from "./exec/commandClassifier.js";
10
12
  import { fsExists } from "../utils/env/node/exec.js";
11
13
  import { Git } from "./exec/git.js";
12
14
  import { BlameGitSubProvider } from "./providers/blame.js";
@@ -77,6 +79,16 @@ var CliGitProvider = class {
77
79
  this._git.clearPendingCommands(this.getRepositoryPaths(repoPath));
78
80
  hostHooks?.cache?.onReset?.(repoPath, ...types);
79
81
  }
82
+ },
83
+ repository: {
84
+ ...hostHooks?.repository,
85
+ onChanged: (repoPath, changes) => {
86
+ this.resetForChanges(repoPath, changes, void 0);
87
+ const siblings = this.getSiblingChanges(repoPath, changes);
88
+ for (const [path, shared] of siblings) this.resetForChanges(path, shared, void 0, true);
89
+ this.announceChanged(repoPath, changes);
90
+ for (const [path, shared] of siblings) this.announceChanged(path, shared);
91
+ }
80
92
  }
81
93
  }
82
94
  };
@@ -87,7 +99,7 @@ var CliGitProvider = class {
87
99
  });
88
100
  this._cacheOwned = !(options.cache instanceof Cache);
89
101
  this._cache = options.cache instanceof Cache ? options.cache : new Cache();
90
- this._git.bindChangeNotifier((repoPaths, changes) => this.notifyChangedCore(repoPaths, changes));
102
+ this._git.bindChangeNotifier((repoPaths, changes, sharedPaths) => this.notifyChangedCore(repoPaths, changes, void 0, sharedPaths));
91
103
  }
92
104
  /** The underlying cache instance (for repo path registration, etc.) */
93
105
  get cache() {
@@ -96,6 +108,13 @@ var CliGitProvider = class {
96
108
  get onDidChangeMergeTargets() {
97
109
  return this._cache.onDidChangeMergeTargets;
98
110
  }
111
+ getCommonPath(repoPath) {
112
+ return this._cache.isRegistered(repoPath) ? this._cache.getCommonPath(repoPath) : void 0;
113
+ }
114
+ _onDidAnnounceChanges = new Emitter();
115
+ get onDidAnnounceChanges() {
116
+ return this._onDidAnnounceChanges.event;
117
+ }
99
118
  /** The underlying git execution instance (for sub-providers that need direct exec/stream access) */
100
119
  get git() {
101
120
  return this._git;
@@ -168,14 +187,25 @@ var CliGitProvider = class {
168
187
  * for both its target and the caller's `cwd`. Each path's caches are reset and the host's `onReset` fires
169
188
  * for each, since a host may keep per-worktree state, but a repository's pending commands are dropped once
170
189
  * however many of its worktrees are named.
190
+ *
191
+ * Each path hears the announcement once: `repoPaths` hear all of `changes`, `sharedPaths` (reset and
192
+ * `onReset`-ed like `repoPaths`) and then the other registered worktrees sharing a repository with any of
193
+ * them (reset like a typed write's, with no `onReset`) hear the shared part of it, see
194
+ * {@link getSiblingChanges}.
171
195
  */
172
- notifyChangedCore(repoPaths, changes, options) {
196
+ notifyChangedCore(repoPaths, changes, options, sharedPaths) {
197
+ const announcements = /* @__PURE__ */ new Map();
198
+ for (const repoPath of repoPaths) announcements.set(repoPath, [...changes]);
199
+ const shared = sharedPaths?.length ? this.getSharedChanges(changes) : void 0;
200
+ if (shared != null) {
201
+ for (const path of sharedPaths) if (!announcements.has(path)) announcements.set(path, shared);
202
+ }
173
203
  const resets = [];
174
204
  const pendingPaths = /* @__PURE__ */ new Set();
175
- for (const repoPath of repoPaths) {
176
- const types = this.resetForChanges(repoPath, changes, options?.cache);
205
+ for (const [repoPath, announced] of announcements) {
206
+ const types = this.resetForChanges(repoPath, announced, options?.cache);
177
207
  if (types == null) {
178
- if (options?.cache == null && changes.includes("gkConfig")) for (const path of this.getRepositoryPaths(repoPath)) pendingPaths.add(path);
208
+ if (options?.cache == null && announced.includes("gkConfig")) for (const path of this.getRepositoryPaths(repoPath)) pendingPaths.add(path);
179
209
  continue;
180
210
  }
181
211
  resets.push({
@@ -184,12 +214,60 @@ var CliGitProvider = class {
184
214
  });
185
215
  for (const path of this.getRepositoryPaths(repoPath)) pendingPaths.add(path);
186
216
  }
217
+ const named = [...announcements.keys()];
218
+ for (const repoPath of named) for (const [path, siblingChanges] of this.getSiblingChanges(repoPath, changes)) {
219
+ if (announcements.has(path)) continue;
220
+ announcements.set(path, siblingChanges);
221
+ this.resetForChanges(path, siblingChanges, options?.cache, true);
222
+ }
187
223
  if (pendingPaths.size) this._git.clearPendingCommands([...pendingPaths]);
188
224
  for (const { repoPath, types } of resets) this._hostHooks?.cache?.onReset?.(repoPath, ...types);
189
- for (const repoPath of repoPaths) this.context.hooks?.repository?.onChanged?.(repoPath, [...changes]);
225
+ for (const [path, announced] of announcements) this.announceChanged(path, [...announced]);
226
+ }
227
+ /**
228
+ * Tells only the host's own `repository.onChanged` about `changes` — for a follow-up that is not a write of
229
+ * core's, such as re-announcing an outside change once it was reconciled. Nothing is evicted, no sibling hears
230
+ * it, and the watch layer doesn't take it for a write's announcement.
231
+ */
232
+ notifyHost(repoPath, changes) {
233
+ this._hostHooks?.repository?.onChanged?.(repoPath, changes);
190
234
  }
191
- /** Evicts what a notified change reset for `repoPath`: the types reset (`[]` = all), or `undefined` for none. */
192
- resetForChanges(repoPath, changes, cache) {
235
+ /** Tells the host about a write for one path, after its caches were reset — the watch layer hears of it first */
236
+ announceChanged(repoPath, changes) {
237
+ this._onDidAnnounceChanges.fire({
238
+ repoPath,
239
+ changes
240
+ });
241
+ this._hostHooks?.repository?.onChanged?.(repoPath, changes);
242
+ }
243
+ /**
244
+ * What the worktrees sharing a repository hear of `changes` that ran in one of them: the part worktrees
245
+ * share (see {@link worktreeSharedChanges}), or `undefined` when `changes` has none. An empty `changes` or
246
+ * `'unknown'` means everything may have changed, so it reaches them as every shared kind — never as
247
+ * `'unknown'` itself, which a host treats as the end of a watch session; `'closed'` concerns the one path
248
+ * alone and reaches none.
249
+ */
250
+ getSharedChanges(changes) {
251
+ if (changes.includes("closed")) return void 0;
252
+ if (!changes.length || changes.includes("unknown")) return [...worktreeSharedChanges];
253
+ const shared = changes.filter((c) => worktreeSharedChanges.has(c));
254
+ return shared.length ? shared : void 0;
255
+ }
256
+ /** What each registered worktree sharing `repoPath`'s repository, other than `repoPath`, hears of `changes`. */
257
+ getSiblingChanges(repoPath, changes) {
258
+ const siblings = /* @__PURE__ */ new Map();
259
+ const shared = this.getSharedChanges(changes);
260
+ if (shared == null) return siblings;
261
+ const self = normalizePath(repoPath);
262
+ for (const path of this._cache.getWorktreePaths(this._cache.getCommonPath(repoPath))) if (path !== self) siblings.set(path, [...shared]);
263
+ return siblings;
264
+ }
265
+ /**
266
+ * Evicts what a notified change reset for `repoPath`: the types reset (`[]` = all), or `undefined` for none.
267
+ * A `sibling` shares its repository with a path already reset for the same change, which evicted the shared
268
+ * entries for every worktree, so the changes evict only its own entries and advance its status clock.
269
+ */
270
+ resetForChanges(repoPath, changes, cache, sibling = false) {
193
271
  if (cache != null && cache !== "all") {
194
272
  if (!cache.length) return void 0;
195
273
  this._cache.evictCaches(repoPath, ...cache);
@@ -199,7 +277,7 @@ var CliGitProvider = class {
199
277
  this._cache.evictCaches(repoPath);
200
278
  return [];
201
279
  }
202
- const types = this._cache.applyRepositoryChanges(repoPath, changes, "evict");
280
+ const types = this._cache.applyRepositoryChanges(repoPath, changes, "evict", sibling ? { worktreeOnly: true } : void 0);
203
281
  return types.length ? types : void 0;
204
282
  }
205
283
  /** `repoPath` and every worktree registered as sharing its repository, since a shared-ref write moves them all. */
@@ -363,15 +441,9 @@ var CliGitProvider = class {
363
441
  const candidates = [];
364
442
  for (const entry of iterateByDelimiter(listed.stdout, "\0")) if (entry.length > 1 && entry.endsWith("/")) candidates.push(entry);
365
443
  if (!candidates.length) return [];
366
- const confirmed = await this._git.run({
367
- cwd: repoPath,
368
- errors: "throw",
369
- cancellation,
370
- stdin: `${candidates.join("\0")}\0`,
371
- expectedExitCodes: [1]
372
- }, "check-ignore", "-z", "--stdin");
444
+ const confirmed = await this.getIgnoredPaths(repoPath, candidates, cancellation);
373
445
  const directories = [];
374
- for (const entry of iterateByDelimiter(confirmed.stdout, "\0")) {
446
+ for (const entry of confirmed) {
375
447
  if (entry.length < 2 || !entry.endsWith("/") || /[*?[\]{}()!+@\\]/.test(entry)) continue;
376
448
  const directory = entry.substring(0, entry.length - 1);
377
449
  if (directory === ".git") continue;
@@ -389,6 +461,35 @@ var CliGitProvider = class {
389
461
  }
390
462
  return [...kept];
391
463
  }
464
+ async getIgnoredEntries(repoPath, cancellation) {
465
+ const listed = await this._git.run({
466
+ cwd: repoPath,
467
+ errors: "throw",
468
+ cancellation
469
+ }, "ls-files", "-z", "--others", "--ignored", "--exclude-standard", "--directory", "--no-empty-directory");
470
+ const entries = [];
471
+ for (const entry of iterateByDelimiter(listed.stdout, "\0")) {
472
+ if (!entry.length || entry === ".git/") continue;
473
+ entries.push(entry);
474
+ }
475
+ return entries.sort();
476
+ }
477
+ async getIgnoredPaths(repoPath, paths, cancellation) {
478
+ if (!paths.length) return /* @__PURE__ */ new Set();
479
+ const result = await this._git.run({
480
+ cwd: repoPath,
481
+ errors: "throw",
482
+ cancellation,
483
+ stdin: `${paths.join("\0")}\0`,
484
+ expectedExitCodes: [1]
485
+ }, "check-ignore", "-z", "--stdin");
486
+ const ignored = /* @__PURE__ */ new Set();
487
+ for (const entry of iterateByDelimiter(result.stdout, "\0")) {
488
+ if (!entry.length) continue;
489
+ ignored.add(entry);
490
+ }
491
+ return ignored;
492
+ }
392
493
  /** Every `.gitignore` git reads in the working tree (tracked, or untracked and not itself ignored), root included */
393
494
  async getGitIgnorePaths(repoPath) {
394
495
  const result = await this._git.run({
@@ -420,6 +521,7 @@ var CliGitProvider = class {
420
521
  }
421
522
  }
422
523
  dispose() {
524
+ this._onDidAnnounceChanges.dispose();
423
525
  this._git.dispose();
424
526
  if (this._cacheOwned) this._cache.dispose();
425
527
  }