@gitkraken/core-gitlens 0.15.2 → 0.15.4

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 (75) hide show
  1. package/CHANGELOG.md +35 -1
  2. package/dist/git/cache.d.ts +24 -11
  3. package/dist/git/cache.js +44 -16
  4. package/dist/git/cache.js.map +1 -1
  5. package/dist/git/models/issue.d.ts +2 -2
  6. package/dist/git/models/issue.js.map +1 -1
  7. package/dist/git/models/issueOrPullRequest.d.ts +2 -1
  8. package/dist/git/models/pullRequest.d.ts +8 -5
  9. package/dist/git/models/pullRequest.js.map +1 -1
  10. package/dist/git/models/reference.d.ts +8 -0
  11. package/dist/git/providers/branches.d.ts +43 -0
  12. package/dist/git/watching/changeEvent.d.ts +4 -2
  13. package/dist/git/watching/changeEvent.js +3 -1
  14. package/dist/git/watching/changeEvent.js.map +1 -1
  15. package/dist/git/watching/watchGroup.d.ts +3 -3
  16. package/dist/git/watching/watchGroup.js +1 -0
  17. package/dist/git/watching/watchGroup.js.map +1 -1
  18. package/dist/git/watching/watchService.js.map +1 -1
  19. package/dist/git/watching/watchSession.d.ts +2 -1
  20. package/dist/git/watching/watchSession.js +3 -2
  21. package/dist/git/watching/watchSession.js.map +1 -1
  22. package/dist/git/watching/watcherPatterns.js +1 -0
  23. package/dist/git/watching/watcherPatterns.js.map +1 -1
  24. package/dist/git/watching/worktreeSummary.d.ts +19 -3
  25. package/dist/git/watching/worktreeSummary.js +107 -17
  26. package/dist/git/watching/worktreeSummary.js.map +1 -1
  27. package/dist/git-cli/parsers/refParser.js +2 -1
  28. package/dist/git-cli/parsers/refParser.js.map +1 -1
  29. package/dist/git-cli/providers/branches.d.ts +8 -1
  30. package/dist/git-cli/providers/branches.js +84 -12
  31. package/dist/git-cli/providers/branches.js.map +1 -1
  32. package/dist/git-cli/providers/config.d.ts +32 -9
  33. package/dist/git-cli/providers/config.js +113 -24
  34. package/dist/git-cli/providers/config.js.map +1 -1
  35. package/dist/plus/integrations/models/gitHostIntegration.d.ts +29 -14
  36. package/dist/plus/integrations/models/gitHostIntegration.js +41 -19
  37. package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
  38. package/dist/plus/integrations/models/integration.d.ts +1 -1
  39. package/dist/plus/integrations/models/integration.js.map +1 -1
  40. package/dist/plus/integrations/providers/azure/azure.d.ts +16 -15
  41. package/dist/plus/integrations/providers/azure/azure.js +72 -23
  42. package/dist/plus/integrations/providers/azure/azure.js.map +1 -1
  43. package/dist/plus/integrations/providers/azure/azure.utils.d.ts +12 -0
  44. package/dist/plus/integrations/providers/azure/azure.utils.js +17 -1
  45. package/dist/plus/integrations/providers/azure/azure.utils.js.map +1 -1
  46. package/dist/plus/integrations/providers/azure/models.d.ts +3 -1
  47. package/dist/plus/integrations/providers/azure/models.js +8 -5
  48. package/dist/plus/integrations/providers/azure/models.js.map +1 -1
  49. package/dist/plus/integrations/providers/azure/search.d.ts +10 -4
  50. package/dist/plus/integrations/providers/azure/search.js +74 -11
  51. package/dist/plus/integrations/providers/azure/search.js.map +1 -1
  52. package/dist/plus/integrations/providers/azure/workItemSearch.d.ts +11 -0
  53. package/dist/plus/integrations/providers/azure/workItemSearch.js +54 -0
  54. package/dist/plus/integrations/providers/azure/workItemSearch.js.map +1 -0
  55. package/dist/plus/integrations/providers/azure/workItemStateCategories.d.ts +2 -2
  56. package/dist/plus/integrations/providers/azure/workItemStateCategories.js +19 -15
  57. package/dist/plus/integrations/providers/azure/workItemStateCategories.js.map +1 -1
  58. package/dist/plus/integrations/providers/azureDevOps.d.ts +33 -20
  59. package/dist/plus/integrations/providers/azureDevOps.js +109 -48
  60. package/dist/plus/integrations/providers/azureDevOps.js.map +1 -1
  61. package/dist/plus/integrations/providers/azureDevOpsServer.d.ts +16 -47
  62. package/dist/plus/integrations/providers/azureDevOpsServer.js +51 -90
  63. package/dist/plus/integrations/providers/azureDevOpsServer.js.map +1 -1
  64. package/dist/plus/integrations/providers/issueSorts.d.ts +1 -0
  65. package/dist/plus/integrations/providers/models.d.ts +5 -0
  66. package/dist/plus/integrations/providers/models.js +25 -11
  67. package/dist/plus/integrations/providers/models.js.map +1 -1
  68. package/dist/plus/integrations/reads/drains.d.ts +3 -2
  69. package/dist/plus/integrations/reads/drains.js +4 -4
  70. package/dist/plus/integrations/reads/drains.js.map +1 -1
  71. package/dist/plus/integrations/reads/issues.js +7 -4
  72. package/dist/plus/integrations/reads/issues.js.map +1 -1
  73. package/dist/plus/integrations/reads/resolveRepository.js +5 -5
  74. package/dist/plus/integrations/reads/resolveRepository.js.map +1 -1
  75. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -6,6 +6,38 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.15.4] - 2026-10-22
10
+
11
+ ### Added
12
+
13
+ - Adds an optional `getPushTarget` to `GitBranchesSubProvider`: where a branch (the checked-out one by default) pushes to and its upstream, each as a full ref name with its commit, read from the cached ref list so a warm call runs no git. The destination is git's own `%(push)`, which follows `push.default`, `remote.pushDefault`, `branch.<name>.pushRemote`, push refspecs and mirror remotes, and is named even before its ref exists (with no commit). `known: false` means the ref list couldn't be read; a detached HEAD has no destination. The shared `for-each-ref` now reads `%(push)` for every ref, so `RefRecord` gains a `push` field (git, git-cli)
14
+ - Adds an optional `getAheadOfBaseConfigKey` to `GitBranchesSubProvider`, an opaque snapshot of the Git config inputs to `getAheadOfBase` (every branch's tracking, the remotes and `init.defaultBranch`, not the stored gk merge targets or base): two equal snapshots mean that config can't have changed the base. `undefined` means it couldn't be read; the CLI provider reads it once for every worktree of a repository, and a provider without it leaves a worktree summary re-reading its base on every config change (git, git-cli)
15
+
16
+ ### Changed
17
+
18
+ - Changes the `ref` that `WatchGroupHooks.onRepoChanged` and `RepositoryWatchSession.pushRepoChanges` take to `string | false`, where `false` reports a `.git/config` (or `config.worktree`) change that moved no ref. Its change kinds (`config`, `remotes`) still clear caches as before, but the event's `mayHaveChangedRef` no longer answers `true` for every remote-tracking ref because of it. A poll now also reads the `refs/remotes` directory, which `git remote remove` and `git remote rename` change whether the remote's refs are loose or packed, so a polled repository still reports a removed or renamed remote's refs as possibly moved, while a config write alone, like `git branch --set-upstream-to`, names no ref (git)
19
+ - Changes `getGkConfig`, `getGkConfigRegex`, `getStoredDetectedMergeTargetBranchName` and `getStoredUserMergeTargetBranchName` to log at trace level rather than debug, and a gk config lookup to no longer enter (and log) the one-time `.git/gk/config` migration once it ran for the repository: they are served from the cached map, and a cold read's `git config` still logs its own line (git-cli)
20
+
21
+ ### Fixed
22
+
23
+ - Fixes Azure DevOps filtered work item searches failing across large sets of project workflows or when workflow metadata changes during pagination. Search and count queries stay within the server's query length limit, preserve its ordering, and can be canceled without interrupting other reads ([#6041](https://github.com/gitkraken/vscode-gitlens/issues/6041)).
24
+ - Fixes every checkout sharing a `.git/gk/config` re-reading the whole file (a `git config --get-regex` each) when the watcher reports a change to it, core's own writes included, so a branch-activity stamp reported by three checkouts read it four times. A report of a file unchanged since the cached map was read (same inode, size and change times, with the read's stat taken safely after the file's last change: 2 seconds for a filesystem keeping whole-second times, 50 ms otherwise) now keeps the map, and still diffs it against the reconcile's baseline, so a read landing between another process's write and its report doesn't lose the change; a changed file is re-read as before (git, git-cli)
25
+ - Fixes `getAheadOfBase` spawning a failing `git rev-parse <ref>@{u}` on every read whose candidate base is a remote-tracking ref or a tag (`origin/main`, the usual default branch), since git can't cache a failed lookup: a candidate the cached ref list shows can't have an upstream is no longer asked for one, and the result is unchanged. The base a branch's reflog says it was created from is checked the same way (git-cli)
26
+ - Fixes a `.git/config` write making every subscribed worktree summary of the repository re-read its base at once, a full `getAheadOfBase` each: in a Kepler daemon over ~100 worktrees of one repository, one write cost ~65 reads in a burst. A summary now keeps its base when the config inputs to it (every branch's tracking, the remotes and `init.defaultBranch`), its stored merge targets, HEAD and upstream are unchanged, and at most four summaries of a repository, and eight across repositories, read at once, so a burst in one repository, or reads hung in it, never holds up another repository's summaries, and a start over many repositories never runs four reads for each. The config read itself (`git config --null --get-regexp`) is shared by every worktree of the repository rather than spawned by each, and any config change hard-evicts it, core's own writes included, so a read after the change never joins one that started before it (git, git-cli)
27
+ - Fixes Azure DevOps Services account-wide pull request reads (`listPullRequestsPage`, the sweeps and the Launchpad search) returning no authored or review-requested pull requests, and failing the group read with "Azure DevOps returned no group membership for the current user", for accounts whose profile id differs from their identity in an organization: the creator and reviewer filters, the group read and each row's `viewer` and `authoredByMe` now use the user's identity in that organization, read from its `ConnectionData` once per credential and organization (in parallel, dropped on a re-sync), as Azure DevOps Server already did per collection; `getPullRequestsBatch` and `getPullRequestsForBranches` match authorship the same way. An organization whose identity can't be read is reported as a failure scoped to it while the others' pull requests are kept, as Azure DevOps Server already did per collection; an organization or collection refusing the credential (a 401 or 403) is now scoped the same way, where it failed the whole read, and a credential refused everywhere still fails it. A row with reviewers or assignees but no author now gets a `viewer`, and its `authoredByMe` is `undefined` rather than `false`, for every provider ([#6032](https://github.com/gitkraken/vscode-gitlens/issues/6032)) (plus/integrations)
28
+ - Fixes Azure DevOps Server repository scopes that a caller parsed from a remote. Before, they were settled once to order `listIssuesPage` and then again for the read. Now the read gets the scopes that were counted, and a discovery that expires in between can't change them. Other fixes in the same reads:
29
+ - A scope that settles to a repository the connection doesn't serve is read as given and flagged as unsettled. A batch or branch miss for it is now rejected, and a list read reports it in a warning, instead of reporting a proven absence.
30
+ - When the connection addresses a collection, a scope naming that collection is read as given while the collection list can't be read. Before, it was flagged as unsettled, and its batch and branch misses were rejected. The address is presumed to name a collection when its collection list answers 404 and the collection's own connection data can't be read, for as long as that failed discovery is kept.
31
+ - A connection path segment with an encoded `/` no longer splits into two virtual directory segments, which left a scope below it unsettled.
32
+
33
+ `resolveRepositoryScopes` now returns `{ repos, unsettled }`. A failure to settle is reported as `{ error }` instead of returning the scopes as given ([#6040](https://github.com/gitkraken/vscode-gitlens/issues/6040)) (plus/integrations)
34
+
35
+ ## [0.15.3] - 2026-10-21
36
+
37
+ ### Fixed
38
+
39
+ - Fixes Azure DevOps work items reporting an open state when their workflow category is unavailable and no closed date is known, including cheap etag reads. Filtered work item searches and counts now select by workflow category, so Removed work items without closed dates appear under Closed ([#6041](https://github.com/gitkraken/vscode-gitlens/issues/6041)).
40
+
9
41
  ## [0.15.2] - 2026-10-20
10
42
 
11
43
  ### Changed
@@ -798,7 +830,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
798
830
 
799
831
  - Initial release. Bundles `@gitlens/utils`, `@gitlens/git`, `@gitlens/git-cli`, `@gitlens/ai`, and `@gitlens/git-github` into a single core npm package with subpath exports.
800
832
 
801
- [unreleased]: https://github.com/gitkraken/vscode-gitlens/compare/releases/core/v0.15.2...HEAD
833
+ [unreleased]: https://github.com/gitkraken/vscode-gitlens/compare/releases/core/v0.15.4...HEAD
834
+ [0.15.4]: https://github.com/gitkraken/vscode-gitlens/compare/releases/core/v0.15.3...gitkraken:releases/core/v0.15.4
835
+ [0.15.3]: https://github.com/gitkraken/vscode-gitlens/compare/releases/core/v0.15.2...gitkraken:releases/core/v0.15.3
802
836
  [0.15.2]: https://github.com/gitkraken/vscode-gitlens/compare/releases/core/v0.15.1...gitkraken:releases/core/v0.15.2
803
837
  [0.15.1]: https://github.com/gitkraken/vscode-gitlens/compare/releases/core/v0.15.0...gitkraken:releases/core/v0.15.1
804
838
  [0.15.0]: https://github.com/gitkraken/vscode-gitlens/compare/releases/core/v0.14.0...gitkraken:releases/core/v0.15.0
@@ -109,6 +109,7 @@ export declare class Cache implements Disposable {
109
109
  dispose(): void;
110
110
  /** Resource usage retained by instantiated sub-caches and their bookkeeping maps. */
111
111
  getResourceUsage(): ResourceUsage;
112
+ get aheadOfBaseConfigKey(): PromiseMap<RepoPath, string | undefined>;
112
113
  get bestRemotes(): PromiseMap<RepoPath, GitRemote<RemoteProvider>[]>;
113
114
  get blame(): RepoPromiseCacheMap<string, ProgressiveGitBlame | undefined>;
114
115
  get diff(): RepoPromiseCacheMap<string, ParsedGitDiffHunks | undefined>;
@@ -267,12 +268,12 @@ export declare class Cache implements Disposable {
267
268
  worktreeOnly?: boolean;
268
269
  }): CachedGitTypes[];
269
270
  /**
270
- * Handles a watcher-observed `'gkConfig'` change. Always evicts the cached bulk map (forcing the
271
- * next read to hit disk). If a reconciler is registered (see {@link setGkConfigReconciler}),
272
- * defers to it instead of coarsely clearing `baseBranchName`/`branchOverviews` — it re-reads the
273
- * map and diffs it against the snapshot captured here (i.e. as it stood right before this change),
274
- * cascading only the refs whose merge-relevant keys actually changed. Falls back to the coarse
275
- * clear when no reconciler is registered (e.g. the GitHub provider) so nothing can go stale.
271
+ * Handles a watcher-observed `'gkConfig'` change. If a reconciler is registered (see {@link setGkConfigReconciler}),
272
+ * defers to it instead of coarsely clearing `baseBranchName`/`branchOverviews` — it evicts the cached bulk map
273
+ * unless the file is unchanged since the map was read, reads it, and diffs it against the snapshot captured here
274
+ * (i.e. as it stood right before this change), cascading only the refs whose merge-relevant keys actually
275
+ * changed. Falls back to evicting the map and the coarse clear when no reconciler is registered (e.g. the GitHub
276
+ * provider) so nothing can go stale.
276
277
  */
277
278
  private handleGkConfigChanged;
278
279
  /**
@@ -298,9 +299,9 @@ export declare class Cache implements Disposable {
298
299
  /**
299
300
  * The whole `.git/gk/config` is read once and cached as a single map per commonPath; per-key and
300
301
  * per-namespace gk lookups are served from it in-memory (see `getGkConfigMap` in the CLI config
301
- * sub-provider). Invalidated by `deleteGkConfig` (write) and, on a watcher `'gkConfig'` event, by
302
- * `handleGkConfigChanged` (reconciled precisely when a reconciler is registered, or the coarse
303
- * clear otherwise). The first fresh read seeds the snapshot used by
302
+ * sub-provider). Invalidated by `deleteGkConfig` (write) and, on a watcher `'gkConfig'` event, by the
303
+ * reconciler when the file changed since the map was read (see {@link setGkConfigReconciler}), or by
304
+ * `handleGkConfigChanged`'s coarse clear when none is registered. The first fresh read seeds the snapshot used by
304
305
  * `reconcileGkConfigMap`; later refreshes of that snapshot are owned by the reconcile itself.
305
306
  */
306
307
  getGkConfigMap(repoPath: string, factory: (cacheable: CacheController) => PromiseOrValue<Map<string, string>>): Promise<Map<string, string>>;
@@ -343,11 +344,18 @@ export declare class Cache implements Disposable {
343
344
  * `'gkConfig'` change. Only one reconciler is kept — later registrations replace earlier ones,
344
345
  * which is harmless since every CLI config sub-provider sharing this `Cache` reconciles the same
345
346
  * underlying `.git/gk/config` file identically.
347
+ *
348
+ * The reconciler owns the cached bulk map: it is called synchronously as the change is reported, and must
349
+ * hard-delete the map ({@link deleteGkConfigMap}) before it returns unless it knows the map still reflects the
350
+ * file — a read after the change must see it, and the reconcile's own read must not ride a bulk read that
351
+ * started before it. Only the reconciler can inspect the file, and keeping the map spares a re-read for every
352
+ * report of an unchanged file (one per worktree sharing it, and one for each of core's own writes).
346
353
  */
347
354
  setGkConfigReconciler(reconciler: (repoPath: string, priorSnapshot: ReadonlyMap<string, string> | undefined) => PromiseOrValue<GkReconcileOutcome>): void;
348
355
  /**
349
- * Diffs a freshly-read gk config map's tracked subset against `priorSnapshot` (captured by
350
- * `handleGkConfigChanged` before the reconcile started, so it reflects the pre-change state), cascading
356
+ * Diffs the tracked subset of a gk config map (read after the change, or kept as still reflecting the file)
357
+ * against `priorSnapshot` (captured by `handleGkConfigChanged` before the reconcile started, so it reflects the
358
+ * pre-change state), cascading
351
359
  * `baseBranchName`/`branchOverviews` for every ref whose merge-relevant key actually changed, then firing
352
360
  * {@link onDidChangeMergeTargets} naming those refs. Changed branch metadata keys (another process's
353
361
  * activity stamps or disposition) are patched into the cached branches in one call, and never fire the event.
@@ -415,6 +423,11 @@ export declare class Cache implements Disposable {
415
423
  */
416
424
  deleteBranchMetadata(repoPath: string): void;
417
425
  getBranchMergedStatus(repoPath: string, cacheKey: string, factory: (commonPath: string, cacheable: CacheController, cancellation?: AbortSignal) => PromiseOrValue<GitBranchMergedStatus>, cancellation?: AbortSignal): Promise<GitBranchMergedStatus>;
426
+ /**
427
+ * The `getAheadOfBase` config snapshot, read once per commonPath for every worktree. Any config change hard-evicts
428
+ * it (see {@link clearCaches}), so a read issued after the change never joins one that started before it.
429
+ */
430
+ getAheadOfBaseConfigKey(repoPath: string, factory: (commonPath: string, cacheable: CacheController, cancellation?: AbortSignal) => PromiseOrValue<string | undefined>, cancellation?: AbortSignal): Promise<string | undefined>;
418
431
  getBranchMetadataMap(repoPath: string, factory: (commonPath: string) => PromiseOrValue<Map<string, BranchMetadata>>): Promise<Map<string, BranchMetadata>>;
419
432
  getDefaultBranchName(repoPath: string, remote: string, factory: (commonPath: string, cacheable: CacheController, cancellation?: AbortSignal) => PromiseOrValue<string | undefined>, cancellation?: AbortSignal): Promise<string | undefined>;
420
433
  /** Clears the cached default branch for one `remote` key (the networked lookup or its `:local` variant). */
package/dist/git/cache.js CHANGED
@@ -54,6 +54,7 @@ function getSettledOrPending(cache, key) {
54
54
  }
55
55
  /** Compile-time enforced: adding a key to SharedCaches without listing it here is a type error */
56
56
  const sharedCacheKeys = new Set(exhaustiveArray()([
57
+ "aheadOfBaseConfigKey",
57
58
  "baseBranchName",
58
59
  "branchMergedStatus",
59
60
  "branchMetadataMap",
@@ -81,6 +82,7 @@ const sharedCacheKeys = new Set(exhaustiveArray()([
81
82
  ]));
82
83
  function createEmptyCaches() {
83
84
  return {
85
+ aheadOfBaseConfigKey: void 0,
84
86
  baseBranchName: void 0,
85
87
  bestRemotes: void 0,
86
88
  blame: void 0,
@@ -183,6 +185,9 @@ var Cache = class Cache {
183
185
  usage["cache.gkConfigSnapshots.entries.count"] = this._gkConfigSnapshots.size;
184
186
  return usage;
185
187
  }
188
+ get aheadOfBaseConfigKey() {
189
+ return this._caches.aheadOfBaseConfigKey ??= new PromiseMap();
190
+ }
186
191
  get bestRemotes() {
187
192
  return this._caches.bestRemotes ??= new PromiseMap();
188
193
  }
@@ -426,6 +431,7 @@ var Cache = class Cache {
426
431
  keysToClear.add("refTips");
427
432
  }
428
433
  if (types.includes("config")) {
434
+ keysToClear.add("aheadOfBaseConfigKey");
429
435
  keysToClear.add("baseBranchName");
430
436
  keysToClear.add("configKeys");
431
437
  keysToClear.add("configPatterns");
@@ -483,7 +489,7 @@ var Cache = class Cache {
483
489
  if (cache == null) continue;
484
490
  if (repoPath == null) cache.clear();
485
491
  else if (sharedCacheKeys.has(key)) {
486
- if (!worktreeOnly) this.evictShared(key, repoPath, inFlight);
492
+ if (!worktreeOnly) this.evictShared(key, repoPath, key === "aheadOfBaseConfigKey" ? "evict" : inFlight);
487
493
  } else cache.delete(repoPath);
488
494
  }
489
495
  }
@@ -708,22 +714,30 @@ var Cache = class Cache {
708
714
  return [...types];
709
715
  }
710
716
  /**
711
- * Handles a watcher-observed `'gkConfig'` change. Always evicts the cached bulk map (forcing the
712
- * next read to hit disk). If a reconciler is registered (see {@link setGkConfigReconciler}),
713
- * defers to it instead of coarsely clearing `baseBranchName`/`branchOverviews` — it re-reads the
714
- * map and diffs it against the snapshot captured here (i.e. as it stood right before this change),
715
- * cascading only the refs whose merge-relevant keys actually changed. Falls back to the coarse
716
- * clear when no reconciler is registered (e.g. the GitHub provider) so nothing can go stale.
717
+ * Handles a watcher-observed `'gkConfig'` change. If a reconciler is registered (see {@link setGkConfigReconciler}),
718
+ * defers to it instead of coarsely clearing `baseBranchName`/`branchOverviews` — it evicts the cached bulk map
719
+ * unless the file is unchanged since the map was read, reads it, and diffs it against the snapshot captured here
720
+ * (i.e. as it stood right before this change), cascading only the refs whose merge-relevant keys actually
721
+ * changed. Falls back to evicting the map and the coarse clear when no reconciler is registered (e.g. the GitHub
722
+ * provider) so nothing can go stale.
717
723
  */
718
724
  handleGkConfigChanged(repoPath) {
719
725
  const commonPath = this.getCommonPath(repoPath);
720
- this._caches.gkConfigMap?.delete(commonPath);
721
726
  if (this._gkConfigReconciler == null) {
727
+ this._caches.gkConfigMap?.delete(commonPath);
722
728
  this.clearGkConfigCoarsely(repoPath);
723
729
  return;
724
730
  }
725
731
  const priorSnapshot = this._gkConfigSnapshots.get(commonPath);
726
- Promise.resolve(this._gkConfigReconciler(repoPath, priorSnapshot)).then((outcome) => {
732
+ let outcome;
733
+ try {
734
+ outcome = this._gkConfigReconciler(repoPath, priorSnapshot);
735
+ } catch {
736
+ this._caches.gkConfigMap?.delete(commonPath);
737
+ this.clearGkConfigCoarsely(repoPath);
738
+ return;
739
+ }
740
+ Promise.resolve(outcome).then((outcome) => {
727
741
  if (outcome === "failed") this.clearGkConfigCoarsely(repoPath);
728
742
  }, () => this.clearGkConfigCoarsely(repoPath));
729
743
  }
@@ -798,9 +812,9 @@ var Cache = class Cache {
798
812
  /**
799
813
  * The whole `.git/gk/config` is read once and cached as a single map per commonPath; per-key and
800
814
  * per-namespace gk lookups are served from it in-memory (see `getGkConfigMap` in the CLI config
801
- * sub-provider). Invalidated by `deleteGkConfig` (write) and, on a watcher `'gkConfig'` event, by
802
- * `handleGkConfigChanged` (reconciled precisely when a reconciler is registered, or the coarse
803
- * clear otherwise). The first fresh read seeds the snapshot used by
815
+ * sub-provider). Invalidated by `deleteGkConfig` (write) and, on a watcher `'gkConfig'` event, by the
816
+ * reconciler when the file changed since the map was read (see {@link setGkConfigReconciler}), or by
817
+ * `handleGkConfigChanged`'s coarse clear when none is registered. The first fresh read seeds the snapshot used by
804
818
  * `reconcileGkConfigMap`; later refreshes of that snapshot are owned by the reconcile itself.
805
819
  */
806
820
  getGkConfigMap(repoPath, factory) {
@@ -877,13 +891,20 @@ var Cache = class Cache {
877
891
  * `'gkConfig'` change. Only one reconciler is kept — later registrations replace earlier ones,
878
892
  * which is harmless since every CLI config sub-provider sharing this `Cache` reconciles the same
879
893
  * underlying `.git/gk/config` file identically.
894
+ *
895
+ * The reconciler owns the cached bulk map: it is called synchronously as the change is reported, and must
896
+ * hard-delete the map ({@link deleteGkConfigMap}) before it returns unless it knows the map still reflects the
897
+ * file — a read after the change must see it, and the reconcile's own read must not ride a bulk read that
898
+ * started before it. Only the reconciler can inspect the file, and keeping the map spares a re-read for every
899
+ * report of an unchanged file (one per worktree sharing it, and one for each of core's own writes).
880
900
  */
881
901
  setGkConfigReconciler(reconciler) {
882
902
  this._gkConfigReconciler = reconciler;
883
903
  }
884
904
  /**
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
905
+ * Diffs the tracked subset of a gk config map (read after the change, or kept as still reflecting the file)
906
+ * against `priorSnapshot` (captured by `handleGkConfigChanged` before the reconcile started, so it reflects the
907
+ * pre-change state), cascading
887
908
  * `baseBranchName`/`branchOverviews` for every ref whose merge-relevant key actually changed, then firing
888
909
  * {@link onDidChangeMergeTargets} naming those refs. Changed branch metadata keys (another process's
889
910
  * activity stamps or disposition) are patched into the cached branches in one call, and never fire the event.
@@ -1083,6 +1104,13 @@ var Cache = class Cache {
1083
1104
  };
1084
1105
  }, { cancellation });
1085
1106
  }
1107
+ /**
1108
+ * The `getAheadOfBase` config snapshot, read once per commonPath for every worktree. Any config change hard-evicts
1109
+ * it (see {@link clearCaches}), so a read issued after the change never joins one that started before it.
1110
+ */
1111
+ getAheadOfBaseConfigKey(repoPath, factory, cancellation) {
1112
+ return this.getSharedSimple(this.aheadOfBaseConfigKey, repoPath, factory, cancellation);
1113
+ }
1086
1114
  getBranchMetadataMap(repoPath, factory) {
1087
1115
  return this.getSharedSimple(this.branchMetadataMap, repoPath, factory);
1088
1116
  }
@@ -1176,10 +1204,10 @@ var Cache = class Cache {
1176
1204
  }
1177
1205
  return sharedPromise;
1178
1206
  }
1179
- async getSharedSimple(cache, repoPath, factory) {
1207
+ async getSharedSimple(cache, repoPath, factory, cancellation) {
1180
1208
  const commonPath = this.getCommonPath(repoPath);
1181
1209
  if (cache instanceof PromiseCache) return cache.getOrCreate(commonPath, (cacheable) => Promise.resolve(factory(commonPath, cacheable)));
1182
- return cache.getOrCreate(commonPath, (cacheable) => Promise.resolve(factory(commonPath, cacheable)));
1210
+ return cache.getOrCreate(commonPath, (cacheable, signal) => Promise.resolve(factory(commonPath, cacheable, signal)), cancellation);
1183
1211
  }
1184
1212
  async getSharedSimpleWithKey(cache, repoPath, cacheKey, factory, options) {
1185
1213
  const commonPath = this.getCommonPath(repoPath);