@gitkraken/core-gitlens 0.15.3 → 0.15.5

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 (91) hide show
  1. package/CHANGELOG.md +45 -1
  2. package/dist/git/cache.d.ts +28 -18
  3. package/dist/git/cache.js +71 -25
  4. package/dist/git/cache.js.map +1 -1
  5. package/dist/git/context.d.ts +19 -2
  6. package/dist/git/context.js.map +1 -1
  7. package/dist/git/models/pullRequest.d.ts +7 -5
  8. package/dist/git/models/pullRequest.js.map +1 -1
  9. package/dist/git/models/reference.d.ts +8 -0
  10. package/dist/git/providers/branches.d.ts +43 -0
  11. package/dist/git/providers/provider.d.ts +7 -0
  12. package/dist/git/service.d.ts +5 -1
  13. package/dist/git/service.js +8 -4
  14. package/dist/git/service.js.map +1 -1
  15. package/dist/git/watching/changeEvent.d.ts +29 -3
  16. package/dist/git/watching/changeEvent.js +39 -3
  17. package/dist/git/watching/changeEvent.js.map +1 -1
  18. package/dist/git/watching/configSections.d.ts +35 -0
  19. package/dist/git/watching/configSections.js +210 -0
  20. package/dist/git/watching/configSections.js.map +1 -0
  21. package/dist/git/watching/watchGroup.d.ts +37 -4
  22. package/dist/git/watching/watchGroup.js +108 -2
  23. package/dist/git/watching/watchGroup.js.map +1 -1
  24. package/dist/git/watching/watchService.d.ts +14 -3
  25. package/dist/git/watching/watchService.js +10 -7
  26. package/dist/git/watching/watchService.js.map +1 -1
  27. package/dist/git/watching/watchSession.d.ts +52 -20
  28. package/dist/git/watching/watchSession.js +166 -43
  29. package/dist/git/watching/watchSession.js.map +1 -1
  30. package/dist/git/watching/watcherPatterns.js +1 -0
  31. package/dist/git/watching/watcherPatterns.js.map +1 -1
  32. package/dist/git/watching/worktreeSummary.d.ts +21 -3
  33. package/dist/git/watching/worktreeSummary.js +118 -17
  34. package/dist/git/watching/worktreeSummary.js.map +1 -1
  35. package/dist/git-cli/cliGitProvider.d.ts +6 -1
  36. package/dist/git-cli/cliGitProvider.js +45 -5
  37. package/dist/git-cli/cliGitProvider.js.map +1 -1
  38. package/dist/git-cli/parsers/refParser.js +2 -1
  39. package/dist/git-cli/parsers/refParser.js.map +1 -1
  40. package/dist/git-cli/providers/branches.d.ts +10 -3
  41. package/dist/git-cli/providers/branches.js +99 -25
  42. package/dist/git-cli/providers/branches.js.map +1 -1
  43. package/dist/git-cli/providers/config.d.ts +45 -9
  44. package/dist/git-cli/providers/config.js +150 -24
  45. package/dist/git-cli/providers/config.js.map +1 -1
  46. package/dist/git-cli/providers/operations.d.ts +35 -1
  47. package/dist/git-cli/providers/operations.js +175 -16
  48. package/dist/git-cli/providers/operations.js.map +1 -1
  49. package/dist/git-cli/providers/stash.d.ts +1 -0
  50. package/dist/git-cli/providers/stash.js +9 -3
  51. package/dist/git-cli/providers/stash.js.map +1 -1
  52. package/dist/git-cli/providers/worktrees.js +11 -4
  53. package/dist/git-cli/providers/worktrees.js.map +1 -1
  54. package/dist/git-cli/service.js +5 -1
  55. package/dist/git-cli/service.js.map +1 -1
  56. package/dist/plus/integrations/models/gitHostIntegration.d.ts +29 -14
  57. package/dist/plus/integrations/models/gitHostIntegration.js +41 -19
  58. package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
  59. package/dist/plus/integrations/providers/azure/azure.d.ts +16 -16
  60. package/dist/plus/integrations/providers/azure/azure.js +45 -25
  61. package/dist/plus/integrations/providers/azure/azure.js.map +1 -1
  62. package/dist/plus/integrations/providers/azure/azure.utils.d.ts +12 -0
  63. package/dist/plus/integrations/providers/azure/azure.utils.js +17 -1
  64. package/dist/plus/integrations/providers/azure/azure.utils.js.map +1 -1
  65. package/dist/plus/integrations/providers/azure/search.d.ts +4 -0
  66. package/dist/plus/integrations/providers/azure/search.js +46 -2
  67. package/dist/plus/integrations/providers/azure/search.js.map +1 -1
  68. package/dist/plus/integrations/providers/azure/workItemSearch.d.ts +11 -0
  69. package/dist/plus/integrations/providers/azure/workItemSearch.js +54 -0
  70. package/dist/plus/integrations/providers/azure/workItemSearch.js.map +1 -0
  71. package/dist/plus/integrations/providers/azure/workItemStateCategories.d.ts +2 -2
  72. package/dist/plus/integrations/providers/azure/workItemStateCategories.js +19 -15
  73. package/dist/plus/integrations/providers/azure/workItemStateCategories.js.map +1 -1
  74. package/dist/plus/integrations/providers/azureDevOps.d.ts +33 -20
  75. package/dist/plus/integrations/providers/azureDevOps.js +109 -48
  76. package/dist/plus/integrations/providers/azureDevOps.js.map +1 -1
  77. package/dist/plus/integrations/providers/azureDevOpsServer.d.ts +16 -47
  78. package/dist/plus/integrations/providers/azureDevOpsServer.js +52 -91
  79. package/dist/plus/integrations/providers/azureDevOpsServer.js.map +1 -1
  80. package/dist/plus/integrations/providers/issueSorts.d.ts +0 -1
  81. package/dist/plus/integrations/providers/models.d.ts +5 -0
  82. package/dist/plus/integrations/providers/models.js +14 -3
  83. package/dist/plus/integrations/providers/models.js.map +1 -1
  84. package/dist/plus/integrations/reads/drains.d.ts +2 -3
  85. package/dist/plus/integrations/reads/drains.js +4 -4
  86. package/dist/plus/integrations/reads/drains.js.map +1 -1
  87. package/dist/plus/integrations/reads/issues.js +7 -4
  88. package/dist/plus/integrations/reads/issues.js.map +1 -1
  89. package/dist/plus/integrations/reads/resolveRepository.js +5 -5
  90. package/dist/plus/integrations/reads/resolveRepository.js.map +1 -1
  91. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -6,6 +6,48 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.15.5] - 2026-10-22
10
+
11
+ ### Added
12
+
13
+ - Adds optional `refs` and `options.configConfinedToRefs` to `GitServiceHooks.repository.onChanged`: `refs` are the full ref names a write created, moved or deleted, and a ref kind with names among them is fully described by them, so a host can skip work for refs it doesn't follow; `configConfinedToRefs` says the write changed `.git/config` only in the `branch.<name>` sections of the local branches `refs` names. Siblings sharing the repository hear the names and the flag with the shared kinds. Branch create, delete, rename and upstream changes, `checkout -b`, a worktree's new branch, a commit (its branch, read from the worktree's `HEAD` before and after the run, and named only when both agree) and stash writes (`refs/stash`, by a pop only when it dropped the entry) name their refs; a host that ignores them treats every kind as unnamed, as before (git, git-cli)
14
+
15
+ ### Changed
16
+
17
+ - Changes how the watch layer tells a write's own reports from outside changes once announcements name refs: a raw report is the write's echo only when every ref it names, or every branch whose config section it changed, is among those the write announced (or it reports `HEAD`, which a commit locks, under a write that announced `head`), and it arrives within the debounce delay of the announcement, so an outside change to another ref or config section, or a later change to the same ref, is kept and reaches its subscribers unannounced. A watch group now diffs the shared `.git/config` by section on each report (one read for every worktree of the repository) and delivers a change confined to `branch.<name>` sections as confined to those branches (`WatcherRepoChangeEvent.configBranches`, `mayHaveChangedConfig`), whoever wrote it; anything else, an unreadable or unparsable file, a read that doesn't settle within 5 seconds, or another config file is unconfined. Ref-filtered subscriptions and worktree summaries narrow a confined change like a named ref (git)
18
+
19
+ ### Fixed
20
+
21
+ - Fixes worktree summaries keeping an outdated ahead-of-base target after checkout-specific Git configuration changes. Each checkout now compares its effective configuration, including branch-conditional includes, with the configuration used for its current base.
22
+ - Fixes a fetch announcing `remotes` to every worktree of the repository, and resetting the branch and tag caches, whether or not anything moved, so each worktree summary re-read its base after every fetch: in a Kepler daemon with ~60 worktrees one task launch cost ~490 git processes. A fetch now compares the refs it can write (its refspecs' destinations, the fetched remotes' configured destinations, symref targets included, and tags) before and after it runs, announces nothing when none moved, and otherwise names exactly what moved; a snapshot it can't read falls back to the previous announcement (git-cli)
23
+ - Fixes deleting, creating or renaming a branch making every worktree of the repository re-read its base, since the write's `.git/config` change and the watcher's later report of it reached every subscriber; one task removal cost ~460 git processes over ~60 worktrees (git, git-cli)
24
+
25
+ ## [0.15.4] - 2026-10-22
26
+
27
+ ### Added
28
+
29
+ - 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)
30
+ - 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)
31
+
32
+ ### Changed
33
+
34
+ - 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)
35
+ - 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)
36
+
37
+ ### Fixed
38
+
39
+ - 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)).
40
+ - 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)
41
+ - 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)
42
+ - 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)
43
+ - 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)
44
+ - 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:
45
+ - 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.
46
+ - 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.
47
+ - A connection path segment with an encoded `/` no longer splits into two virtual directory segments, which left a scope below it unsettled.
48
+
49
+ `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)
50
+
9
51
  ## [0.15.3] - 2026-10-21
10
52
 
11
53
  ### Fixed
@@ -804,7 +846,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
804
846
 
805
847
  - 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.
806
848
 
807
- [unreleased]: https://github.com/gitkraken/vscode-gitlens/compare/releases/core/v0.15.3...HEAD
849
+ [unreleased]: https://github.com/gitkraken/vscode-gitlens/compare/releases/core/v0.15.5...HEAD
850
+ [0.15.5]: https://github.com/gitkraken/vscode-gitlens/compare/releases/core/v0.15.4...gitkraken:releases/core/v0.15.5
851
+ [0.15.4]: https://github.com/gitkraken/vscode-gitlens/compare/releases/core/v0.15.3...gitkraken:releases/core/v0.15.4
808
852
  [0.15.3]: https://github.com/gitkraken/vscode-gitlens/compare/releases/core/v0.15.2...gitkraken:releases/core/v0.15.3
809
853
  [0.15.2]: https://github.com/gitkraken/vscode-gitlens/compare/releases/core/v0.15.1...gitkraken:releases/core/v0.15.2
810
854
  [0.15.1]: https://github.com/gitkraken/vscode-gitlens/compare/releases/core/v0.15.0...gitkraken:releases/core/v0.15.1
@@ -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>;
@@ -237,17 +238,17 @@ export declare class Cache implements Disposable {
237
238
  * Call this when a worktree is deleted or a repo is closed so stale registry entries
238
239
  * and derived cache entries don't persist.
239
240
  *
240
- * Targeted — does NOT cascade to sibling worktrees sharing the same commonPath. The
241
- * shared commonPath entry in each shared cache is only evicted when unregistering the
242
- * last remaining worktree for that commonPath (otherwise siblings still depend on it).
243
- *
244
- * Uses soft-invalidate where supported so any in-flight factory/mapper keeps its abort
241
+ * Targeted — does NOT cascade to sibling worktrees sharing the same commonPath. Shared caches
242
+ * stay warm until the last worktree closes, except raw git results co-located with a closing main
243
+ * checkout: those must retire with its config fingerprint so reopening cannot attest an old command
244
+ * with new config. Detached promises keep their existing callers and abort wiring. Other entries
245
+ * use soft-invalidate where supported so any in-flight factory/mapper keeps its abort
245
246
  * wiring intact — a caller whose signal aborts after unregister can still propagate
246
247
  * cancellation into the underlying work rather than leaving it orphaned. Controller-backed
247
248
  * entries self-evict on settle; plain `.set()`-based entries hard-delete immediately.
248
249
  *
249
- * Cache eviction is a no-op if `repoPath` is not registered, but the status clock is advanced
250
- * unconditionally (a fresh registration must not let a post-reopen read join a pre-close one).
250
+ * The status clock advances even for an unregistered path so a fresh registration cannot join a
251
+ * status read that started before it.
251
252
  */
252
253
  unregisterRepoPath(repoPath: string): void;
253
254
  reset(): void;
@@ -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,8 @@ 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
+ /** Config can differ by checkout through config.worktree and conditional includes. */
427
+ getAheadOfBaseConfigKey(repoPath: string, factory: (repoPath: string, cacheable: CacheController, cancellation?: AbortSignal) => PromiseOrValue<string | undefined>, cancellation?: AbortSignal): Promise<string | undefined>;
418
428
  getBranchMetadataMap(repoPath: string, factory: (commonPath: string) => PromiseOrValue<Map<string, BranchMetadata>>): Promise<Map<string, BranchMetadata>>;
419
429
  getDefaultBranchName(repoPath: string, remote: string, factory: (commonPath: string, cacheable: CacheController, cancellation?: AbortSignal) => PromiseOrValue<string | undefined>, cancellation?: AbortSignal): Promise<string | undefined>;
420
430
  /** Clears the cached default branch for one `remote` key (the networked lookup or its `:local` variant). */
package/dist/git/cache.js CHANGED
@@ -81,6 +81,7 @@ const sharedCacheKeys = new Set(exhaustiveArray()([
81
81
  ]));
82
82
  function createEmptyCaches() {
83
83
  return {
84
+ aheadOfBaseConfigKey: void 0,
84
85
  baseBranchName: void 0,
85
86
  bestRemotes: void 0,
86
87
  blame: void 0,
@@ -183,6 +184,9 @@ var Cache = class Cache {
183
184
  usage["cache.gkConfigSnapshots.entries.count"] = this._gkConfigSnapshots.size;
184
185
  return usage;
185
186
  }
187
+ get aheadOfBaseConfigKey() {
188
+ return this._caches.aheadOfBaseConfigKey ??= new PromiseMap();
189
+ }
186
190
  get bestRemotes() {
187
191
  return this._caches.bestRemotes ??= new PromiseMap();
188
192
  }
@@ -414,7 +418,10 @@ var Cache = class Cache {
414
418
  keysToClear.add("reachability");
415
419
  keysToClear.add("resolvedRevisions");
416
420
  }
417
- if (types.includes("branch")) keysToClear.add("baseBranchName");
421
+ if (types.includes("branch")) {
422
+ keysToClear.add("aheadOfBaseConfigKey");
423
+ keysToClear.add("baseBranchName");
424
+ }
418
425
  if (types.includes("branches")) {
419
426
  keysToClear.add("branchMetadataMap");
420
427
  keysToClear.add("branches");
@@ -426,6 +433,7 @@ var Cache = class Cache {
426
433
  keysToClear.add("refTips");
427
434
  }
428
435
  if (types.includes("config")) {
436
+ keysToClear.add("aheadOfBaseConfigKey");
429
437
  keysToClear.add("baseBranchName");
430
438
  keysToClear.add("configKeys");
431
439
  keysToClear.add("configPatterns");
@@ -482,8 +490,12 @@ var Cache = class Cache {
482
490
  const cache = this._caches[key];
483
491
  if (cache == null) continue;
484
492
  if (repoPath == null) cache.clear();
493
+ else if (key === "aheadOfBaseConfigKey" && !worktreeOnly && (!types.length || types.includes("config"))) this.evictShared(key, repoPath, "evict");
485
494
  else if (sharedCacheKeys.has(key)) {
486
- if (!worktreeOnly) this.evictShared(key, repoPath, inFlight);
495
+ if (!worktreeOnly) {
496
+ const hardEvict = key === "gitResults" && (types.includes("config") || types.includes("branch"));
497
+ this.evictShared(key, repoPath, hardEvict ? "evict" : inFlight);
498
+ }
487
499
  } else cache.delete(repoPath);
488
500
  }
489
501
  }
@@ -590,6 +602,12 @@ var Cache = class Cache {
590
602
  registerRepoPath(repoPath, gitDir) {
591
603
  const normalizedPath = getRepositoryOrWorktreePath(repoPath);
592
604
  const commonPath = gitDir.commonUri != null ? getCommonRepositoryPath(gitDir.commonUri) : normalizedPath;
605
+ const previousCommonPath = this._commonPathRegistry.get(normalizedPath);
606
+ if (previousCommonPath != null && previousCommonPath !== commonPath) this.unregisterRepoPath(normalizedPath);
607
+ if (previousCommonPath !== commonPath) {
608
+ this._caches.aheadOfBaseConfigKey?.delete(normalizedPath);
609
+ this._caches.gitResults?.delete(normalizedPath);
610
+ }
593
611
  this._commonPathRegistry.set(normalizedPath, commonPath);
594
612
  let worktrees = this._worktreesByCommonPath.get(commonPath);
595
613
  if (worktrees == null) {
@@ -605,17 +623,17 @@ var Cache = class Cache {
605
623
  * Call this when a worktree is deleted or a repo is closed so stale registry entries
606
624
  * and derived cache entries don't persist.
607
625
  *
608
- * Targeted — does NOT cascade to sibling worktrees sharing the same commonPath. The
609
- * shared commonPath entry in each shared cache is only evicted when unregistering the
610
- * last remaining worktree for that commonPath (otherwise siblings still depend on it).
611
- *
612
- * Uses soft-invalidate where supported so any in-flight factory/mapper keeps its abort
626
+ * Targeted — does NOT cascade to sibling worktrees sharing the same commonPath. Shared caches
627
+ * stay warm until the last worktree closes, except raw git results co-located with a closing main
628
+ * checkout: those must retire with its config fingerprint so reopening cannot attest an old command
629
+ * with new config. Detached promises keep their existing callers and abort wiring. Other entries
630
+ * use soft-invalidate where supported so any in-flight factory/mapper keeps its abort
613
631
  * wiring intact — a caller whose signal aborts after unregister can still propagate
614
632
  * cancellation into the underlying work rather than leaving it orphaned. Controller-backed
615
633
  * entries self-evict on settle; plain `.set()`-based entries hard-delete immediately.
616
634
  *
617
- * Cache eviction is a no-op if `repoPath` is not registered, but the status clock is advanced
618
- * unconditionally (a fresh registration must not let a post-reopen read join a pre-close one).
635
+ * The status clock advances even for an unregistered path so a fresh registration cannot join a
636
+ * status read that started before it.
619
637
  */
620
638
  unregisterRepoPath(repoPath) {
621
639
  this.incrementStatusGeneration(repoPath);
@@ -626,6 +644,11 @@ var Cache = class Cache {
626
644
  for (const key of Object.keys(this._caches)) {
627
645
  const cache = this._caches[key];
628
646
  if (cache == null) continue;
647
+ if (key === "aheadOfBaseConfigKey" || key === "gitResults") {
648
+ cache.delete(repoPath);
649
+ if (key === "gitResults" && isLastWorktree && repoPath !== commonPath) cache.delete(commonPath);
650
+ continue;
651
+ }
629
652
  const invalidate = cache.invalidate;
630
653
  const evict = typeof invalidate === "function" ? (k) => invalidate.call(cache, k) : (k) => cache.delete(k);
631
654
  if (sharedCacheKeys.has(key)) {
@@ -681,7 +704,11 @@ var Cache = class Cache {
681
704
  types.add("fileLog");
682
705
  }
683
706
  if (hasAny("index")) types.add("tracking");
684
- if (hasAny("config")) types.add("config");
707
+ if (hasAny("config")) {
708
+ types.add("config");
709
+ types.add("branches");
710
+ types.add("worktrees");
711
+ }
685
712
  if (hasAny("heads")) {
686
713
  types.add("branches");
687
714
  types.add("contributors");
@@ -708,22 +735,30 @@ var Cache = class Cache {
708
735
  return [...types];
709
736
  }
710
737
  /**
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.
738
+ * Handles a watcher-observed `'gkConfig'` change. If a reconciler is registered (see {@link setGkConfigReconciler}),
739
+ * defers to it instead of coarsely clearing `baseBranchName`/`branchOverviews` — it evicts the cached bulk map
740
+ * unless the file is unchanged since the map was read, reads it, and diffs it against the snapshot captured here
741
+ * (i.e. as it stood right before this change), cascading only the refs whose merge-relevant keys actually
742
+ * changed. Falls back to evicting the map and the coarse clear when no reconciler is registered (e.g. the GitHub
743
+ * provider) so nothing can go stale.
717
744
  */
718
745
  handleGkConfigChanged(repoPath) {
719
746
  const commonPath = this.getCommonPath(repoPath);
720
- this._caches.gkConfigMap?.delete(commonPath);
721
747
  if (this._gkConfigReconciler == null) {
748
+ this._caches.gkConfigMap?.delete(commonPath);
722
749
  this.clearGkConfigCoarsely(repoPath);
723
750
  return;
724
751
  }
725
752
  const priorSnapshot = this._gkConfigSnapshots.get(commonPath);
726
- Promise.resolve(this._gkConfigReconciler(repoPath, priorSnapshot)).then((outcome) => {
753
+ let outcome;
754
+ try {
755
+ outcome = this._gkConfigReconciler(repoPath, priorSnapshot);
756
+ } catch {
757
+ this._caches.gkConfigMap?.delete(commonPath);
758
+ this.clearGkConfigCoarsely(repoPath);
759
+ return;
760
+ }
761
+ Promise.resolve(outcome).then((outcome) => {
727
762
  if (outcome === "failed") this.clearGkConfigCoarsely(repoPath);
728
763
  }, () => this.clearGkConfigCoarsely(repoPath));
729
764
  }
@@ -798,9 +833,9 @@ var Cache = class Cache {
798
833
  /**
799
834
  * The whole `.git/gk/config` is read once and cached as a single map per commonPath; per-key and
800
835
  * 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
836
+ * sub-provider). Invalidated by `deleteGkConfig` (write) and, on a watcher `'gkConfig'` event, by the
837
+ * reconciler when the file changed since the map was read (see {@link setGkConfigReconciler}), or by
838
+ * `handleGkConfigChanged`'s coarse clear when none is registered. The first fresh read seeds the snapshot used by
804
839
  * `reconcileGkConfigMap`; later refreshes of that snapshot are owned by the reconcile itself.
805
840
  */
806
841
  getGkConfigMap(repoPath, factory) {
@@ -877,13 +912,20 @@ var Cache = class Cache {
877
912
  * `'gkConfig'` change. Only one reconciler is kept — later registrations replace earlier ones,
878
913
  * which is harmless since every CLI config sub-provider sharing this `Cache` reconciles the same
879
914
  * underlying `.git/gk/config` file identically.
915
+ *
916
+ * The reconciler owns the cached bulk map: it is called synchronously as the change is reported, and must
917
+ * hard-delete the map ({@link deleteGkConfigMap}) before it returns unless it knows the map still reflects the
918
+ * file — a read after the change must see it, and the reconcile's own read must not ride a bulk read that
919
+ * started before it. Only the reconciler can inspect the file, and keeping the map spares a re-read for every
920
+ * report of an unchanged file (one per worktree sharing it, and one for each of core's own writes).
880
921
  */
881
922
  setGkConfigReconciler(reconciler) {
882
923
  this._gkConfigReconciler = reconciler;
883
924
  }
884
925
  /**
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
926
+ * Diffs the tracked subset of a gk config map (read after the change, or kept as still reflecting the file)
927
+ * against `priorSnapshot` (captured by `handleGkConfigChanged` before the reconcile started, so it reflects the
928
+ * pre-change state), cascading
887
929
  * `baseBranchName`/`branchOverviews` for every ref whose merge-relevant key actually changed, then firing
888
930
  * {@link onDidChangeMergeTargets} naming those refs. Changed branch metadata keys (another process's
889
931
  * activity stamps or disposition) are patched into the cached branches in one call, and never fire the event.
@@ -1083,6 +1125,10 @@ var Cache = class Cache {
1083
1125
  };
1084
1126
  }, { cancellation });
1085
1127
  }
1128
+ /** Config can differ by checkout through config.worktree and conditional includes. */
1129
+ getAheadOfBaseConfigKey(repoPath, factory, cancellation) {
1130
+ return this.aheadOfBaseConfigKey.getOrCreate(repoPath, (cacheable, signal) => Promise.resolve(factory(repoPath, cacheable, signal)), cancellation);
1131
+ }
1086
1132
  getBranchMetadataMap(repoPath, factory) {
1087
1133
  return this.getSharedSimple(this.branchMetadataMap, repoPath, factory);
1088
1134
  }
@@ -1176,10 +1222,10 @@ var Cache = class Cache {
1176
1222
  }
1177
1223
  return sharedPromise;
1178
1224
  }
1179
- async getSharedSimple(cache, repoPath, factory) {
1225
+ async getSharedSimple(cache, repoPath, factory, cancellation) {
1180
1226
  const commonPath = this.getCommonPath(repoPath);
1181
1227
  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)));
1228
+ return cache.getOrCreate(commonPath, (cacheable, signal) => Promise.resolve(factory(commonPath, cacheable, signal)), cancellation);
1183
1229
  }
1184
1230
  async getSharedSimpleWithKey(cache, repoPath, cacheKey, factory, options) {
1185
1231
  const commonPath = this.getCommonPath(repoPath);