@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
package/dist/git/cache.js CHANGED
@@ -6,6 +6,7 @@ import { Emitter } from "../utils/event.js";
6
6
  import { getBranchId } from "../utils/gitRefs.js";
7
7
  import { normalizePath } from "../utils/path.js";
8
8
  import { PromiseCache, PromiseMap, RepoPromiseCacheMap } from "../utils/promiseCache.js";
9
+ import { parseBranchMetadataGkConfigKey, setBranchMetadataValue } from "./utils/branch.utils.js";
9
10
  import { createReference } from "./utils/reference.utils.js";
10
11
  import { getCommonRepositoryPath, getRepositoryOrWorktreePath } from "./utils/repository.utils.js";
11
12
  import __decorate from "../_virtual/@oxc-project+runtime/helpers/esm/decorate.js";
@@ -42,6 +43,15 @@ async function shouldEvictBlameCacheEntry(progressive) {
42
43
  * Capture group 1 is the ref name (which may itself contain `.`/`/`).
43
44
  */
44
45
  const branchOverviewGkConfigKeysRegex = /^branch\.(.+)\.(?:gk-merge-(?:base|target(?:-user)?)|gk-target-base)$/;
46
+ /** The ref of a gkConfig key the reconcile baseline tracks, or `undefined` for any other key */
47
+ function getReconciledGkConfigRef(key) {
48
+ return branchOverviewGkConfigKeysRegex.exec(key)?.[1] ?? parseBranchMetadataGkConfigKey(key)?.branch;
49
+ }
50
+ /** The value `cache` holds for `key`, `'pending'` while it is still being read, or `undefined` when nothing is cached */
51
+ function getSettledOrPending(cache, key) {
52
+ if (!cache?.has(key)) return void 0;
53
+ return cache.getSettled(key) ?? "pending";
54
+ }
45
55
  /** Compile-time enforced: adding a key to SharedCaches without listing it here is a type error */
46
56
  const sharedCacheKeys = new Set(exhaustiveArray()([
47
57
  "baseBranchName",
@@ -128,18 +138,22 @@ var Cache = class Cache {
128
138
  /** Monotonic per-path "close clock" — see {@link getCloseGeneration}. */
129
139
  _closeGenerations = /* @__PURE__ */ new Map();
130
140
  /**
131
- * Per-commonPath snapshot of the merge-relevant subset of `.git/gk/config` (keys matching
132
- * {@link branchOverviewGkConfigKeysRegex}), used by {@link reconcileGkConfigMap} to diff a watcher
133
- * `'gkConfig'` event down to only the keys that actually changed, instead of coarsely re-deriving
134
- * `baseBranchName`/`branchOverviews` on every gk write. Absent means "never observed".
141
+ * Per-commonPath snapshot of the subset of `.git/gk/config` that derived caches depend on — merge-relevant keys
142
+ * ({@link branchOverviewGkConfigKeysRegex}) and branch metadata keys ({@link parseBranchMetadataGkConfigKey}) —
143
+ * used by {@link reconcileGkConfigMap} to diff a watcher `'gkConfig'` event down to only the keys that actually
144
+ * changed, instead of coarsely re-deriving `baseBranchName`/`branchOverviews` and the branches on every gk write.
145
+ * Absent means "never observed".
135
146
  */
136
- _gkConfigMergeSnapshots = /* @__PURE__ */ new Map();
147
+ _gkConfigSnapshots = /* @__PURE__ */ new Map();
137
148
  _onDidChangeMergeTargets = new Emitter();
138
149
  /**
139
- * Fires when an external `.git/gk/config` write changed a branch's merge-relevant keys — never for a write that
140
- * only touched other keys (activity timestamps), and never for core's own writes, which
141
- * {@link recordGkConfigWrite} folds into the snapshot before the watcher reports them. Fires after the derived
142
- * caches were cleared, so a listener's re-read sees the new values.
150
+ * Fires when a `.git/gk/config` write changed a branch's merge-relevant keys — never for a write that only touched
151
+ * other keys (activity timestamps). Core's own key store fires it once, as the write completes (see
152
+ * {@link recordGkConfigWrite}), and the watcher's later report of that write doesn't fire it again; core's own
153
+ * section remove and rename (a branch delete or rename) never fire it, nor do its internal saves while answering
154
+ * a read (nobody awaits those, and only another process's watcher reports them). Fires after the derived caches were
155
+ * cleared, so a listener's re-read sees the new values. A listener must not write merge keys back in response,
156
+ * or it loops.
143
157
  */
144
158
  get onDidChangeMergeTargets() {
145
159
  return this._onDidChangeMergeTargets.event;
@@ -166,7 +180,7 @@ var Cache = class Cache {
166
180
  usage["cache.worktreesByCommonPath.entries.count"] = this._worktreesByCommonPath.size;
167
181
  usage["cache.statusGenerations.entries.count"] = this._statusGenerations.size;
168
182
  usage["cache.closeGenerations.entries.count"] = this._closeGenerations.size;
169
- usage["cache.gkConfigMergeSnapshots.entries.count"] = this._gkConfigMergeSnapshots.size;
183
+ usage["cache.gkConfigSnapshots.entries.count"] = this._gkConfigSnapshots.size;
170
184
  return usage;
171
185
  }
172
186
  get bestRemotes() {
@@ -365,7 +379,7 @@ var Cache = class Cache {
365
379
  return this._caches.worktrees ??= new PromiseMap();
366
380
  }
367
381
  clearCaches(repoPath, ...types) {
368
- this.clearCachesCore("share", repoPath, ...types);
382
+ this.clearCachesCore("share", repoPath, types);
369
383
  }
370
384
  /**
371
385
  * Hard-evicts the same cache keys {@link clearCaches} would clear for `repoPath`, but a shared
@@ -375,9 +389,10 @@ var Cache = class Cache {
375
389
  * letting an in-flight read complete and share its answer with new callers is fine.
376
390
  */
377
391
  evictCaches(repoPath, ...types) {
378
- this.clearCachesCore("evict", repoPath, ...types);
392
+ this.clearCachesCore("evict", repoPath, types);
379
393
  }
380
- clearCachesCore(inFlight, repoPath, ...types) {
394
+ /** `worktreeOnly` leaves the shared (commonPath-keyed) entries alone (see {@link applyRepositoryChanges}) */
395
+ clearCachesCore(inFlight, repoPath, types, worktreeOnly = false) {
381
396
  const keysToClear = /* @__PURE__ */ new Set();
382
397
  if (!types.length) {
383
398
  for (const key of Object.keys(this._caches)) keysToClear.add(key);
@@ -467,8 +482,9 @@ var Cache = class Cache {
467
482
  const cache = this._caches[key];
468
483
  if (cache == null) continue;
469
484
  if (repoPath == null) cache.clear();
470
- else if (sharedCacheKeys.has(key)) this.evictShared(key, repoPath, inFlight);
471
- else cache.delete(repoPath);
485
+ else if (sharedCacheKeys.has(key)) {
486
+ if (!worktreeOnly) this.evictShared(key, repoPath, inFlight);
487
+ } else cache.delete(repoPath);
472
488
  }
473
489
  }
474
490
  /**
@@ -617,7 +633,7 @@ var Cache = class Cache {
617
633
  if (isLastWorktree) evict(commonPath);
618
634
  } else evict(repoPath);
619
635
  }
620
- if (isLastWorktree) this._gkConfigMergeSnapshots.delete(commonPath);
636
+ if (isLastWorktree) this._gkConfigSnapshots.delete(commonPath);
621
637
  this._commonPathRegistry.delete(repoPath);
622
638
  if (worktrees != null) {
623
639
  worktrees.delete(repoPath);
@@ -629,7 +645,7 @@ var Cache = class Cache {
629
645
  this._commonPathRegistry.clear();
630
646
  this._worktreesByCommonPath.clear();
631
647
  this._statusGenerations.clear();
632
- this._gkConfigMergeSnapshots.clear();
648
+ this._gkConfigSnapshots.clear();
633
649
  this._gkConfigReconciler = void 0;
634
650
  this._caches = createEmptyCaches();
635
651
  }
@@ -647,8 +663,13 @@ var Cache = class Cache {
647
663
  * `'gkConfig'` change, returning the cache types it cleared. `inFlight` is as {@link clearCaches}
648
664
  * (`'share'`) vs {@link evictCaches} (`'evict'`). `'unknown'` and `'closed'` map to nothing here: the
649
665
  * watcher unregisters the path on them, and an announced write resets everything instead.
666
+ *
667
+ * `options.worktreeOnly` clears only `repoPath`'s own entries and status clock, leaving the shared
668
+ * (commonPath-keyed) ones alone — for a sibling worktree once the change was applied to the worktree it ran in,
669
+ * which already cleared them for every worktree of the repository.
650
670
  */
651
- applyRepositoryChanges(repoPath, changes, inFlight) {
671
+ applyRepositoryChanges(repoPath, changes, inFlight, options) {
672
+ const worktreeOnly = options?.worktreeOnly ?? false;
652
673
  const changesSet = new Set(changes);
653
674
  const hasAny = (...c) => c.some((ch) => changesSet.has(ch));
654
675
  if (hasAny("index", "head", "heads", "remotes", "ignores", "config")) this.incrementStatusGeneration(repoPath);
@@ -673,7 +694,7 @@ var Cache = class Cache {
673
694
  types.add("worktrees");
674
695
  }
675
696
  if (hasAny("ignores")) types.add("gitignore");
676
- if (hasAny("gkConfig")) this.handleGkConfigChanged(repoPath);
697
+ if (hasAny("gkConfig") && !worktreeOnly) this.handleGkConfigChanged(repoPath);
677
698
  if (hasAny("lastFetched")) types.add("lastFetched");
678
699
  if (hasAny("remoteProviders")) types.add("providers");
679
700
  if (hasAny("cherryPick", "merge", "rebase", "revert", "pausedOp")) {
@@ -683,7 +704,7 @@ var Cache = class Cache {
683
704
  if (hasAny("stash")) types.add("stashes");
684
705
  if (hasAny("tags")) types.add("tags");
685
706
  if (hasAny("worktrees")) types.add("worktrees");
686
- if (types.size) this.clearCachesCore(inFlight, repoPath, ...types);
707
+ if (types.size) this.clearCachesCore(inFlight, repoPath, [...types], worktreeOnly);
687
708
  return [...types];
688
709
  }
689
710
  /**
@@ -701,14 +722,18 @@ var Cache = class Cache {
701
722
  this.clearGkConfigCoarsely(repoPath);
702
723
  return;
703
724
  }
704
- const priorSnapshot = this._gkConfigMergeSnapshots.get(commonPath);
725
+ const priorSnapshot = this._gkConfigSnapshots.get(commonPath);
705
726
  Promise.resolve(this._gkConfigReconciler(repoPath, priorSnapshot)).then((outcome) => {
706
727
  if (outcome === "failed") this.clearGkConfigCoarsely(repoPath);
707
728
  }, () => this.clearGkConfigCoarsely(repoPath));
708
729
  }
709
- /** The coarse fallback: every derived merge cache is cleared, so any branch's merge targets may have changed */
730
+ /**
731
+ * The coarse fallback: every derived merge cache and the branches are cleared, so any branch's merge targets or
732
+ * metadata may have changed. Evicting the branches even when only merge keys changed is the price of not knowing.
733
+ */
710
734
  clearGkConfigCoarsely(repoPath) {
711
735
  this.clearCaches(repoPath, "gkConfig");
736
+ this.deleteBranchMetadata(repoPath);
712
737
  this._onDidChangeMergeTargets.fire({
713
738
  repoPath: this.getCommonPath(repoPath),
714
739
  refs: void 0
@@ -718,9 +743,17 @@ var Cache = class Cache {
718
743
  deleteGkConfigMap(repoPath) {
719
744
  this._caches.gkConfigMap?.delete(this.getCommonPath(repoPath));
720
745
  }
721
- /** The merge-relevant gk config snapshot currently serving as the reconcile baseline. */
722
- getGkConfigMergeSnapshot(repoPath) {
723
- return this._gkConfigMergeSnapshots.get(this.getCommonPath(repoPath));
746
+ /**
747
+ * Whether a write of `key` must first read the gk config to seed the reconcile snapshot: without one,
748
+ * {@link recordGkConfigWrite} can't tell whether the write changed anything, and the watcher's reconcile of it
749
+ * would report every branch as changed.
750
+ */
751
+ needsGkConfigSnapshot(repoPath, key) {
752
+ return getReconciledGkConfigRef(key) != null && !this._gkConfigSnapshots.has(this.getCommonPath(repoPath));
753
+ }
754
+ /** The gk config snapshot currently serving as the reconcile baseline. */
755
+ getGkConfigSnapshot(repoPath) {
756
+ return this._gkConfigSnapshots.get(this.getCommonPath(repoPath));
724
757
  }
725
758
  getBranches(repoPath, factory, mapper, cancellation) {
726
759
  const commonPath = this.getCommonPath(repoPath);
@@ -767,20 +800,20 @@ var Cache = class Cache {
767
800
  * per-namespace gk lookups are served from it in-memory (see `getGkConfigMap` in the CLI config
768
801
  * sub-provider). Invalidated by `deleteGkConfig` (write) and, on a watcher `'gkConfig'` event, by
769
802
  * `handleGkConfigChanged` (reconciled precisely when a reconciler is registered, or the coarse
770
- * clear otherwise). The first fresh read seeds the merge-relevant snapshot used by
803
+ * clear otherwise). The first fresh read seeds the snapshot used by
771
804
  * `reconcileGkConfigMap`; later refreshes of that snapshot are owned by the reconcile itself.
772
805
  */
773
806
  getGkConfigMap(repoPath, factory) {
774
807
  return this.getSharedSimple(this.gkConfigMap, repoPath, async (commonPath, cacheable) => {
775
808
  const map = await factory(cacheable);
776
- if (!this._gkConfigMergeSnapshots.has(commonPath)) this._gkConfigMergeSnapshots.set(commonPath, this.extractMergeRelevantGkConfig(map));
809
+ if (!this._gkConfigSnapshots.has(commonPath)) this._gkConfigSnapshots.set(commonPath, this.extractReconciledGkConfig(map));
777
810
  return map;
778
811
  });
779
812
  }
780
- /** Extracts the subset of a gk config map whose keys participate in the `branchOverviews`/`baseBranchName` lineage. */
781
- extractMergeRelevantGkConfig(map) {
813
+ /** Extracts the subset of a gk config map that the reconcile baseline tracks (see {@link _gkConfigSnapshots}). */
814
+ extractReconciledGkConfig(map) {
782
815
  const subset = /* @__PURE__ */ new Map();
783
- for (const [key, value] of map) if (branchOverviewGkConfigKeysRegex.test(key)) subset.set(key, value);
816
+ for (const [key, value] of map) if (getReconciledGkConfigRef(key) != null) subset.set(key, value);
784
817
  return subset;
785
818
  }
786
819
  /**
@@ -800,36 +833,44 @@ var Cache = class Cache {
800
833
  this.cascadeGkConfigKeyChange(commonPath, key, options?.skipInvalidation);
801
834
  }
802
835
  /**
803
- * Keeps the merge-relevant gk config snapshot in sync with OUR OWN writes, so the next
804
- * watcher-triggered `reconcileGkConfigMap` diffs a self-write as unchanged rather than as an
805
- * external change (which would otherwise defeat `skipInvalidation`). No-ops for keys outside the
806
- * merge-relevant subset, and if no snapshot exists yet for this repo — nothing to keep in sync;
807
- * the next real read (`getGkConfigMap`) populates one from scratch.
836
+ * Keeps the gk config snapshot in sync with OUR OWN writes, so the next watcher-triggered
837
+ * `reconcileGkConfigMap` diffs a self-write as unchanged rather than as an external change (which would
838
+ * otherwise defeat `skipInvalidation`, or evict the branches a second time after a branch-activity stamp),
839
+ * and fires {@link onDidChangeMergeTargets} naming the key's branch when a merge-relevant value differs from
840
+ * the snapshot's, unless `announce` is `false` (an internal save while answering a read, which nobody awaits;
841
+ * only another process's watcher reports it). No-ops for keys the snapshot doesn't track, and if no snapshot exists yet for this repo
842
+ * (the writer seeds one first; see {@link needsGkConfigSnapshot}).
808
843
  */
809
- recordGkConfigWrite(repoPath, key, value) {
810
- if (!branchOverviewGkConfigKeysRegex.test(key)) return;
844
+ recordGkConfigWrite(repoPath, key, value, options) {
845
+ const ref = getReconciledGkConfigRef(key);
846
+ if (ref == null) return;
811
847
  const commonPath = this.getCommonPath(repoPath);
812
- const snapshot = this._gkConfigMergeSnapshots.get(commonPath);
813
- if (snapshot == null) return;
848
+ const snapshot = this._gkConfigSnapshots.get(commonPath);
849
+ if (snapshot == null || snapshot.get(key) === value) return;
814
850
  const next = new Map(snapshot);
815
851
  if (value == null) next.delete(key);
816
852
  else next.set(key, value);
817
- this._gkConfigMergeSnapshots.set(commonPath, next);
853
+ this._gkConfigSnapshots.set(commonPath, next);
854
+ if (options?.announce === false || !branchOverviewGkConfigKeysRegex.test(key)) return;
855
+ this._onDidChangeMergeTargets.fire({
856
+ repoPath: commonPath,
857
+ refs: /* @__PURE__ */ new Set([ref])
858
+ });
818
859
  }
819
860
  /**
820
861
  * {@link recordGkConfigWrite} for a whole `branch.<ref>` section: our own `--remove-section` (a branch delete) or
821
862
  * `--rename-section` (a branch rename, to `renamedTo`), so the reconcile that follows doesn't report it as an
822
- * external merge-target change.
863
+ * external merge-target or metadata change — the branch op's own ref change already evicts the branches.
823
864
  */
824
865
  recordGkConfigSectionWrite(repoPath, ref, renamedTo) {
825
866
  const commonPath = this.getCommonPath(repoPath);
826
- const snapshot = this._gkConfigMergeSnapshots.get(commonPath);
867
+ const snapshot = this._gkConfigSnapshots.get(commonPath);
827
868
  if (snapshot == null) return;
828
869
  const prefixLength = `branch.${ref}.`.length;
829
870
  const next = /* @__PURE__ */ new Map();
830
- for (const [key, value] of snapshot) if (branchOverviewGkConfigKeysRegex.exec(key)?.[1] !== ref) next.set(key, value);
871
+ for (const [key, value] of snapshot) if (getReconciledGkConfigRef(key) !== ref) next.set(key, value);
831
872
  else if (renamedTo != null) next.set(`branch.${renamedTo}.${key.substring(prefixLength)}`, value);
832
- this._gkConfigMergeSnapshots.set(commonPath, next);
873
+ this._gkConfigSnapshots.set(commonPath, next);
833
874
  }
834
875
  /**
835
876
  * Registers the reconciler invoked by {@link handleGkConfigChanged} on a watcher-observed
@@ -841,35 +882,63 @@ var Cache = class Cache {
841
882
  this._gkConfigReconciler = reconciler;
842
883
  }
843
884
  /**
844
- * Diffs a freshly-read gk config map's merge-relevant subset against `priorSnapshot` (captured by
845
- * `handleGkConfigChanged` before the reconcile started, so it reflects the pre-change state even
846
- * though this call's own re-read already raced ahead and refreshed the live snapshot), cascading
885
+ * Diffs a freshly-read gk config map's tracked subset against `priorSnapshot` (captured by
886
+ * `handleGkConfigChanged` before the reconcile started, so it reflects the pre-change state), cascading
847
887
  * `baseBranchName`/`branchOverviews` for every ref whose merge-relevant key actually changed, then firing
848
- * {@link onDidChangeMergeTargets} naming those refs.
888
+ * {@link onDidChangeMergeTargets} naming those refs. Changed branch metadata keys (another process's
889
+ * activity stamps or disposition) are patched into the cached branches in one call, and never fire the event.
890
+ * A key core itself wrote since `priorSnapshot` was captured (recorded in the live snapshot) is neither diffed nor
891
+ * overwritten by this read, which may predate the write: its writer already cascaded and announced it.
849
892
  * `priorSnapshot` of `undefined` (never observed before this reconcile) is treated as "everything
850
893
  * may have changed" — a full coarse clear, same as the pre-existing watcher behavior. Returns
851
894
  * whether anything changed (i.e. whether a follow-up repository-changed event is warranted).
852
895
  */
853
896
  reconcileGkConfigMap(repoPath, priorSnapshot, freshMap) {
854
897
  const commonPath = this.getCommonPath(repoPath);
855
- const newSubset = this.extractMergeRelevantGkConfig(freshMap);
856
- this._gkConfigMergeSnapshots.set(commonPath, newSubset);
898
+ const newSubset = this.extractReconciledGkConfig(freshMap);
857
899
  if (priorSnapshot == null) {
900
+ this._gkConfigSnapshots.set(commonPath, newSubset);
858
901
  this.evictShared("baseBranchName", repoPath);
859
902
  this.evictShared("branchOverviews", repoPath);
903
+ this.deleteBranchMetadata(repoPath);
860
904
  this._onDidChangeMergeTargets.fire({
861
905
  repoPath: commonPath,
862
906
  refs: void 0
863
907
  });
864
908
  return true;
865
909
  }
910
+ const ownWrites = /* @__PURE__ */ new Set();
911
+ const live = this._gkConfigSnapshots.get(commonPath);
912
+ if (live != null && live !== priorSnapshot) for (const key of /* @__PURE__ */ new Set([...priorSnapshot.keys(), ...live.keys()])) {
913
+ const value = live.get(key);
914
+ if (priorSnapshot.get(key) === value) continue;
915
+ ownWrites.add(key);
916
+ if (value == null) newSubset.delete(key);
917
+ else newSubset.set(key, value);
918
+ }
919
+ this._gkConfigSnapshots.set(commonPath, newSubset);
866
920
  let refs;
867
- for (const key of /* @__PURE__ */ new Set([...priorSnapshot.keys(), ...newSubset.keys()])) if (priorSnapshot.get(key) !== newSubset.get(key)) {
868
- this.cascadeGkConfigKeyChange(commonPath, key);
921
+ const metadataChanges = [];
922
+ for (const key of /* @__PURE__ */ new Set([...priorSnapshot.keys(), ...newSubset.keys()])) {
923
+ if (ownWrites.has(key)) continue;
924
+ const value = newSubset.get(key);
925
+ if (priorSnapshot.get(key) === value) continue;
869
926
  const ref = branchOverviewGkConfigKeysRegex.exec(key)?.[1];
870
- if (ref != null) (refs ??= /* @__PURE__ */ new Set()).add(ref);
927
+ if (ref != null) {
928
+ this.cascadeGkConfigKeyChange(commonPath, key);
929
+ (refs ??= /* @__PURE__ */ new Set()).add(ref);
930
+ continue;
931
+ }
932
+ const metadataKey = parseBranchMetadataGkConfigKey(key);
933
+ if (metadataKey != null) metadataChanges.push({
934
+ branch: metadataKey.branch,
935
+ key: metadataKey.key,
936
+ value
937
+ });
871
938
  }
872
- if (refs == null) return false;
939
+ const metadataChanged = metadataChanges.length > 0;
940
+ if (metadataChanged) this.patchBranchMetadata(repoPath, metadataChanges);
941
+ if (refs == null) return metadataChanged;
873
942
  this._onDidChangeMergeTargets.fire({
874
943
  repoPath: commonPath,
875
944
  refs
@@ -931,6 +1000,71 @@ var Cache = class Cache {
931
1000
  deleteBaseBranchName(repoPath, ref) {
932
1001
  this._caches.baseBranchName?.delete(this.getCommonPath(repoPath), ref);
933
1002
  }
1003
+ /**
1004
+ * Applies changed branch metadata keys (written here or reconciled from another process) to the cached branches
1005
+ * without re-reading them, by updating {@link BranchMetadata} objects in place — the one exception to cached values
1006
+ * never changing, since rebuilding every branch list, current branch and worktree list on every activity stamp
1007
+ * costs far more. Patches every object a cached branch of the repository's worktrees holds for a changed branch:
1008
+ * the cached map's, and any older map's that a cached branch list, current branch or worktree list still holds.
1009
+ *
1010
+ * Falls back to {@link deleteBranchMetadata} when one of them is still being read (it may have read the file
1011
+ * before the change), or the cached map or a cached branch of a changed name has no object to patch.
1012
+ */
1013
+ patchBranchMetadata(repoPath, changes) {
1014
+ const targets = /* @__PURE__ */ new Map();
1015
+ for (const change of changes) targets.set(change.branch, /* @__PURE__ */ new Set());
1016
+ if (!this.collectBranchMetadata(this.getCommonPath(repoPath), targets)) {
1017
+ this.deleteBranchMetadata(repoPath);
1018
+ return;
1019
+ }
1020
+ for (const change of changes) for (const metadata of targets.get(change.branch)) setBranchMetadataValue(metadata, change.key, change.value);
1021
+ }
1022
+ /**
1023
+ * Adds to `targets` every metadata object a cached holder has for each named branch (see
1024
+ * {@link patchBranchMetadata}); `false` when the holders must be evicted instead.
1025
+ */
1026
+ collectBranchMetadata(commonPath, targets) {
1027
+ const metadataMap = getSettledOrPending(this._caches.branchMetadataMap, commonPath);
1028
+ if (metadataMap === "pending") return false;
1029
+ if (metadataMap != null) for (const [name, objects] of targets) {
1030
+ const metadata = metadataMap.value.get(name);
1031
+ if (metadata == null) return false;
1032
+ objects.add(metadata);
1033
+ }
1034
+ const add = (branch) => {
1035
+ if (branch == null || branch.remote || branch.detached) return true;
1036
+ const objects = targets.get(branch.name);
1037
+ if (objects == null) return true;
1038
+ if (branch.metadata == null) return false;
1039
+ objects.add(branch.metadata);
1040
+ return true;
1041
+ };
1042
+ for (const path of /* @__PURE__ */ new Set([commonPath, ...this.getWorktreePaths(commonPath)])) {
1043
+ for (const cache of [this._caches.sharedBranches, this._caches.branches]) {
1044
+ const branches = getSettledOrPending(cache, path);
1045
+ if (branches === "pending" || branches != null && !branches.value.values.every(add)) return false;
1046
+ }
1047
+ const current = getSettledOrPending(this._caches.branch, path);
1048
+ if (current === "pending" || current != null && !add(current.value)) return false;
1049
+ const worktrees = getSettledOrPending(this._caches.worktrees, path);
1050
+ if (worktrees === "pending" || worktrees != null && !worktrees.value.every((w) => add(w.branch))) return false;
1051
+ }
1052
+ return true;
1053
+ }
1054
+ /**
1055
+ * Drops the cached branches and worktrees for `repoPath` and its sibling worktrees when their metadata changed in
1056
+ * a way {@link patchBranchMetadata} can't apply in place. Hard-evicts, so a read that started before the change
1057
+ * isn't joined.
1058
+ */
1059
+ deleteBranchMetadata(repoPath) {
1060
+ for (const key of [
1061
+ "branchMetadataMap",
1062
+ "sharedBranches",
1063
+ "branches",
1064
+ "branch",
1065
+ "worktrees"
1066
+ ]) this.evictShared(key, repoPath, "evict");
1067
+ }
934
1068
  getBranchMergedStatus(repoPath, cacheKey, factory, cancellation) {
935
1069
  return this.getSharedOrCreateWithKey(this.branchMergedStatus, repoPath, cacheKey, factory, (data, newRepoPath) => {
936
1070
  if (!data.merged) return data;