@gitkraken/core-gitlens 0.15.7 → 0.16.1

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 (55) hide show
  1. package/CHANGELOG.md +25 -1
  2. package/dist/git/cache.d.ts +13 -0
  3. package/dist/git/cache.js +45 -8
  4. package/dist/git/cache.js.map +1 -1
  5. package/dist/git/features.d.ts +1 -1
  6. package/dist/git/features.js +1 -0
  7. package/dist/git/features.js.map +1 -1
  8. package/dist/git/providers/branches.d.ts +29 -0
  9. package/dist/git/run.types.d.ts +3 -0
  10. package/dist/git/service.d.ts +4 -2
  11. package/dist/git/service.js +6 -4
  12. package/dist/git/service.js.map +1 -1
  13. package/dist/git/watching/classifyChange.js +3 -0
  14. package/dist/git/watching/classifyChange.js.map +1 -1
  15. package/dist/git/watching/watchSession.d.ts +2 -0
  16. package/dist/git/watching/watchSession.js +9 -1
  17. package/dist/git/watching/watchSession.js.map +1 -1
  18. package/dist/git/watching/worktreeSummary.d.ts +19 -4
  19. package/dist/git/watching/worktreeSummary.js +121 -36
  20. package/dist/git/watching/worktreeSummary.js.map +1 -1
  21. package/dist/git-cli/cliGitProvider.d.ts +1 -1
  22. package/dist/git-cli/cliGitProvider.js +8 -5
  23. package/dist/git-cli/cliGitProvider.js.map +1 -1
  24. package/dist/git-cli/exec/commandClassifier.d.ts +18 -3
  25. package/dist/git-cli/exec/commandClassifier.js +562 -16
  26. package/dist/git-cli/exec/commandClassifier.js.map +1 -1
  27. package/dist/git-cli/exec/exec.d.ts +2 -2
  28. package/dist/git-cli/exec/git.d.ts +31 -4
  29. package/dist/git-cli/exec/git.js +105 -43
  30. package/dist/git-cli/exec/git.js.map +1 -1
  31. package/dist/git-cli/exec/headState.d.ts +32 -0
  32. package/dist/git-cli/exec/headState.js +78 -0
  33. package/dist/git-cli/exec/headState.js.map +1 -0
  34. package/dist/git-cli/providers/branches.d.ts +50 -0
  35. package/dist/git-cli/providers/branches.js +294 -25
  36. package/dist/git-cli/providers/branches.js.map +1 -1
  37. package/dist/git-cli/providers/config.js +3 -2
  38. package/dist/git-cli/providers/config.js.map +1 -1
  39. package/dist/git-cli/providers/operations.d.ts +6 -7
  40. package/dist/git-cli/providers/operations.js +46 -64
  41. package/dist/git-cli/providers/operations.js.map +1 -1
  42. package/dist/git-cli/providers/refs.d.ts +4 -1
  43. package/dist/git-cli/providers/refs.js +13 -9
  44. package/dist/git-cli/providers/refs.js.map +1 -1
  45. package/dist/git-cli/providers/revision.js +1 -1
  46. package/dist/git-cli/providers/stash.js +1 -1
  47. package/dist/git-cli/providers/worktrees.js +23 -6
  48. package/dist/git-cli/providers/worktrees.js.map +1 -1
  49. package/dist/git-cli/service.d.ts +3 -0
  50. package/dist/git-cli/service.js +1 -0
  51. package/dist/git-cli/service.js.map +1 -1
  52. package/dist/utils/env/node/exec.d.ts +38 -1
  53. package/dist/utils/env/node/exec.js +14 -6
  54. package/dist/utils/env/node/exec.js.map +1 -1
  55. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"watchSession.js","names":[],"sources":["../../../../git/src/watching/watchSession.ts"],"sourcesContent":["import type { UnifiedDisposable } from '@gitlens/utils/disposable.js';\nimport { createDisposable } from '@gitlens/utils/disposable.js';\nimport type { Event } from '@gitlens/utils/event.js';\nimport { Emitter } from '@gitlens/utils/event.js';\nimport type { RepositoryChange } from '../models/repository.js';\nimport type { RefChangeKind, WorkingTreeChangeEvent } from './changeEvent.js';\nimport { findCoveringRefName, getRefChangeKind, isRefChangeKind, WatcherRepoChangeEvent } from './changeEvent.js';\nimport { allWatchedRepositoryChanges } from './classifyChange.js';\n\nexport interface RepositorySubscription extends UnifiedDisposable {\n\treadonly onDidChange: Event<WatcherRepoChangeEvent>;\n}\n\nexport interface WorkingTreeSubscription extends UnifiedDisposable {\n\treadonly onDidChangeWorkingTree: Event<WorkingTreeChangeEvent>;\n}\n\n/**\n * Lifecycle callbacks fired by the session when subscriber counts\n * cross the 0↔1 boundary. The host (WatchService) uses these to\n * lazily create/destroy watchers.\n */\nexport interface WatchSessionLifecycle {\n\t/** Called when the first repo-change subscriber joins */\n\tonFirstRepoSubscriber?(): void;\n\t/** Called when the last repo-change subscriber leaves */\n\tonLastRepoSubscriber?(): void;\n\t/** Called when the first working-tree subscriber joins */\n\tonFirstWorkingTreeSubscriber?(): void;\n\t/** Called when the last working-tree subscriber leaves */\n\tonLastWorkingTreeSubscriber?(): void;\n}\n\nexport interface WatchSessionOptions {\n\treadonly repoPath: string;\n\t/** Default debounce for repo changes. Default: 250ms */\n\treadonly defaultRepoDelayMs?: number;\n\t/** Default debounce for working tree changes. Default: 2500ms */\n\treadonly defaultWorkingTreeDelayMs?: number;\n\t/**\n\t * Default cap on how long repo changes can be held back by continuous activity — a flush happens at most this long\n\t * after the first pending change, however long the burst lasts. `Infinity` disables the cap. Default: 2000ms\n\t */\n\treadonly defaultRepoMaxWaitMs?: number;\n\t/** Default cap on how long working tree changes can be held back by continuous activity. `Infinity` disables the cap. Default: 10000ms */\n\treadonly defaultWorkingTreeMaxWaitMs?: number;\n\treadonly lifecycle?: WatchSessionLifecycle;\n\t/** Initial suspended state — new sessions inherit the service-global suspend state. Default: false */\n\treadonly suspended?: boolean;\n\t/**\n\t * Called whenever a debounced repo change event is dispatched to subscribers.\n\t * Does NOT count as a subscriber — used by WatchService for\n\t * global event forwarding without affecting lifecycle callbacks.\n\t */\n\treadonly onDidFireRepoChange?: (event: WatcherRepoChangeEvent) => void;\n\t/**\n\t * Called whenever a debounced working-tree change is dispatched to subscribers. Like\n\t * {@link onDidFireRepoChange}, does NOT count as a subscriber — used by WatchService for global\n\t * event forwarding (the single point that drives the cache's status clock for working-tree edits).\n\t */\n\treadonly onDidFireWorkingTreeChange?: (repoPath: string) => void;\n}\n\nconst defaultRepoDelay = 250;\nconst defaultWorkingTreeDelay = 2500;\nconst defaultRepoMaxWait = 2000;\nconst defaultWorkingTreeMaxWait = 10000;\n\n/** Largest delay `setTimeout` honors (a signed 32-bit int); anything longer fires immediately */\nconst maxTimerDelay = 0x7fffffff;\n\ninterface DebounceTiming {\n\treadonly delayMs: number;\n\treadonly maxWaitMs: number;\n}\n\n/**\n * Trailing-deadline debounce with a max-wait, shared by the repo and working-tree pipelines.\n *\n * Recording an event costs a `performance.now()` and, only when no timer is armed, one `setTimeout` — never a\n * `clearTimeout`/`setTimeout` pair per event, which is what made a busy repo allocate (and starve) on every raw\n * watcher event. The armed timer is a deadline, not a countdown: when it fires it recomputes the due time from the\n * latest timestamps and either flushes or re-arms for the remainder. The due time is\n * `min(lastEventAt + delay, firstPendingAt + maxWait)`, so continuous activity is flushed at the max-wait instead of\n * never. Monotonic time, so a wall-clock change can't push a deadline out.\n */\nclass DebounceClock {\n\tprivate active = false;\n\tprivate paused = false;\n\tprivate firstAt = 0;\n\tprivate lastAt = 0;\n\tprivate timer: ReturnType<typeof setTimeout> | undefined;\n\tprivate timerDueAt = 0;\n\t/** Absolute flush time set by {@link resume}; dropped by the next event, which returns to normal debounce */\n\tprivate resumeDueAt: number | undefined;\n\n\tconstructor(\n\t\tprivate readonly getDelayMs: () => number,\n\t\tprivate readonly getMaxWaitMs: () => number,\n\t\tprivate readonly onDue: () => void,\n\t) {}\n\n\t/** Records an event; arms the timer only if none is armed. Cheap enough to call per raw event. */\n\tnote(): void {\n\t\tconst now = performance.now();\n\t\tif (!this.active) {\n\t\t\tthis.active = true;\n\t\t\tthis.firstAt = now;\n\t\t}\n\t\tthis.lastAt = now;\n\n\t\tif (this.paused) return;\n\n\t\tif (this.resumeDueAt != null) {\n\t\t\t// The first event after a resume goes back to normal debounce — which may be due sooner than the resume deadline\n\t\t\tthis.resumeDueAt = undefined;\n\t\t\tthis.rearmIfEarlier();\n\t\t}\n\n\t\tif (this.timer == null) {\n\t\t\tthis.arm(this.getDueAt(), now);\n\t\t}\n\t}\n\n\t/** Moves the armed timer earlier when the due time shrank (a subscriber joined with a shorter delay or max-wait) */\n\trearmIfEarlier(): void {\n\t\tif (!this.active || this.paused || this.timer == null) return;\n\n\t\tconst dueAt = this.getDueAt();\n\t\tif (dueAt >= this.timerDueAt) return;\n\n\t\tclearTimeout(this.timer);\n\t\tthis.arm(dueAt, performance.now());\n\t}\n\n\t/** Cancels the timer; events keep accumulating but nothing is due until {@link resume} */\n\tsuspend(): void {\n\t\tthis.paused = true;\n\t\tthis.clearTimer();\n\t}\n\n\t/**\n\t * Restarts the max-wait window from now and schedules the flush for `delayMs` from now (the effective delay when\n\t * omitted). Does nothing if no events are pending.\n\t */\n\tresume(delayMs?: number): void {\n\t\tthis.paused = false;\n\t\tif (!this.active) return;\n\n\t\tconst now = performance.now();\n\t\tthis.firstAt = now;\n\t\tthis.resumeDueAt = now + (delayMs ?? this.getDelayMs());\n\t\tthis.clearTimer();\n\t\tthis.arm(this.resumeDueAt, now);\n\t}\n\n\t/** Drops the pending window and the timer */\n\tcancel(): void {\n\t\tthis.active = false;\n\t\tthis.resumeDueAt = undefined;\n\t\tthis.clearTimer();\n\t}\n\n\tprivate getDueAt(): number {\n\t\treturn this.resumeDueAt ?? Math.min(this.lastAt + this.getDelayMs(), this.firstAt + this.getMaxWaitMs());\n\t}\n\n\tprivate arm(dueAt: number, now: number): void {\n\t\tthis.timerDueAt = dueAt;\n\t\tthis.timer = setTimeout(() => this.onTimer(), Math.min(Math.max(0, dueAt - now), maxTimerDelay));\n\t}\n\n\tprivate onTimer(): void {\n\t\tthis.timer = undefined;\n\t\tif (!this.active || this.paused) return;\n\n\t\tconst now = performance.now();\n\t\tconst dueAt = this.getDueAt();\n\t\tif (dueAt > now) {\n\t\t\tthis.arm(dueAt, now);\n\t\t\treturn;\n\t\t}\n\n\t\t// Reset before firing so an event raised by a subscriber starts a fresh window\n\t\tthis.cancel();\n\t\tthis.onDue();\n\t}\n\n\tprivate clearTimer(): void {\n\t\tif (this.timer != null) {\n\t\t\tclearTimeout(this.timer);\n\t\t\tthis.timer = undefined;\n\t\t}\n\t}\n}\n\n/**\n * Cap on retained working-tree paths per flush. The set otherwise grows without bound — while the window\n * is unfocused the session is suspended, so a branch switch or build accumulates every touched path until\n * refocus. Set low deliberately: ordinary edits are a handful of paths, anything past this was a\n * refresh-everything event anyway, and dropping past it only costs the matching consumers a\n * conservative refresh.\n */\nconst maxWorkingTreePaths = 1000;\n\n/**\n * Cap on retained ref names per flush, for the same reason (a fetch of many refs while suspended). Past it, a ref kind\n * is recorded unnamed — any ref of that kind may have moved — which only makes `mayHaveChangedRef` more conservative.\n * A pending `config` confined to more branches than this is recorded unconfined, for the same reason.\n */\nconst maxPendingRefNames = 1000;\n\n/**\n * What core's announced writes in the pending window said about one kind: when it was first announced, and the full\n * ref names it was confined to (for `config`, the branches whose sections it changed); `names` is absent when an\n * announcement left the kind open — any ref of it, or any part of the config, may have changed.\n */\ninterface PendingAnnouncement {\n\treadonly at: number;\n\tnames: Set<string> | undefined;\n}\n\n/**\n * Per-repository session that owns the debounce/coalesce/suspend\n * pipeline for both repo (.git) and working-tree changes.\n *\n * Multiple subscribers share the same pipeline — the shortest\n * requested debounce delay wins, and so does the shortest max-wait.\n */\nexport class RepositoryWatchSession implements UnifiedDisposable {\n\treadonly repoPath: string;\n\n\tprivate readonly repoEmitter = new Emitter<WatcherRepoChangeEvent>();\n\tprivate readonly repoSubscribers = new Map<number, DebounceTiming>(); // subscriberId → timing\n\tprivate repoNextId = 0;\n\t/** Each repo subscriber's `relevantRefs`, for {@link getRelevantRefs} */\n\tprivate readonly relevantRefsSources = new Map<number, () => Iterable<string>>(); // subscriberId → relevantRefs\n\tprivate readonly pendingRepoChanges = new Set<RepositoryChange>();\n\t/** Full ref names, ref directories and pseudo-refs whose files changed, flushed with {@link pendingRepoChanges} */\n\tprivate readonly pendingRepoRefs = new Set<string>();\n\t/** Ref kinds that changed without a name, flushed with {@link pendingRepoChanges} */\n\tprivate readonly pendingRepoUnnamedRefKinds = new Set<RefChangeKind>();\n\t/** Kinds among {@link pendingRepoChanges} injected or pushed unfenced, which an announcement never replaces */\n\tprivate readonly pendingRepoUnfencedChanges = new Set<RepositoryChange>();\n\t/**\n\t * Kinds among {@link pendingRepoChanges} that only core's announced writes, and their own echoes, reported (see\n\t * {@link noteSelfWrite})\n\t */\n\tprivate readonly pendingRepoAnnounced = new Map<RepositoryChange, PendingAnnouncement>();\n\t/**\n\t * For a pending `config` change, the branches (full ref names) whose sections are all that every report of it\n\t * changed; `undefined` once any report may have changed anything else\n\t */\n\tprivate pendingRepoConfigBranches: Set<string> | undefined;\n\tprivate readonly repoClock: DebounceClock;\n\tprivate effectiveRepoMs: number;\n\tprivate effectiveRepoMaxWaitMs: number;\n\tprivate readonly defaultRepoMs: number;\n\tprivate readonly defaultRepoMaxWaitMs: number;\n\n\tprivate readonly workingTreeEmitter = new Emitter<WorkingTreeChangeEvent>();\n\tprivate readonly workingTreeSubscribers = new Map<number, DebounceTiming>();\n\tprivate workingTreeNextId = 0;\n\tprivate pendingWorkingTreePaths = new Set<string>();\n\tprivate pendingWorkingTreeIncomplete = false;\n\tprivate readonly workingTreeClock: DebounceClock;\n\tprivate effectiveWorkingTreeMs: number;\n\tprivate effectiveWorkingTreeMaxWaitMs: number;\n\tprivate readonly defaultWorkingTreeMs: number;\n\tprivate readonly defaultWorkingTreeMaxWaitMs: number;\n\n\tprivate _etagWorkingTree = 0;\n\tprivate _suspended: boolean;\n\tprivate _disposed = false;\n\n\tprivate readonly lifecycle?: WatchSessionLifecycle;\n\tprivate readonly onDidFireRepoChangeCallback?: (event: WatcherRepoChangeEvent) => void;\n\tprivate readonly onDidFireWorkingTreeChangeCallback?: (repoPath: string) => void;\n\n\tconstructor(options: WatchSessionOptions) {\n\t\tthis.repoPath = options.repoPath;\n\t\tthis.defaultRepoMs = options.defaultRepoDelayMs ?? defaultRepoDelay;\n\t\tthis.defaultWorkingTreeMs = options.defaultWorkingTreeDelayMs ?? defaultWorkingTreeDelay;\n\t\tthis.defaultRepoMaxWaitMs = options.defaultRepoMaxWaitMs ?? defaultRepoMaxWait;\n\t\tthis.defaultWorkingTreeMaxWaitMs = options.defaultWorkingTreeMaxWaitMs ?? defaultWorkingTreeMaxWait;\n\t\tthis.effectiveRepoMs = this.defaultRepoMs;\n\t\tthis.effectiveRepoMaxWaitMs = this.defaultRepoMaxWaitMs;\n\t\tthis.effectiveWorkingTreeMs = this.defaultWorkingTreeMs;\n\t\tthis.effectiveWorkingTreeMaxWaitMs = this.defaultWorkingTreeMaxWaitMs;\n\t\tthis.repoClock = new DebounceClock(\n\t\t\t() => this.effectiveRepoMs,\n\t\t\t() => this.effectiveRepoMaxWaitMs,\n\t\t\t() => this.flushRepo(),\n\t\t);\n\t\tthis.workingTreeClock = new DebounceClock(\n\t\t\t() => this.effectiveWorkingTreeMs,\n\t\t\t() => this.effectiveWorkingTreeMaxWaitMs,\n\t\t\t() => this.flushWorkingTree(),\n\t\t);\n\t\tthis.lifecycle = options.lifecycle;\n\t\tthis.onDidFireRepoChangeCallback = options.onDidFireRepoChange;\n\t\tthis.onDidFireWorkingTreeChangeCallback = options.onDidFireWorkingTreeChange;\n\t\tthis._suspended = options.suspended ?? false;\n\t\tif (this._suspended) {\n\t\t\tthis.repoClock.suspend();\n\t\t\tthis.workingTreeClock.suspend();\n\t\t}\n\t}\n\n\t[Symbol.dispose](): void {\n\t\tthis.dispose();\n\t}\n\n\tdispose(): void {\n\t\tif (this._disposed) return;\n\n\t\tthis._disposed = true;\n\n\t\tthis.repoClock.cancel();\n\t\tthis.workingTreeClock.cancel();\n\n\t\tthis.clearPendingRepoChanges();\n\t\tthis.pendingWorkingTreePaths.clear();\n\t\tthis.pendingWorkingTreeIncomplete = false;\n\t\tthis.repoSubscribers.clear();\n\t\tthis.relevantRefsSources.clear();\n\t\tthis.workingTreeSubscribers.clear();\n\n\t\tthis.repoEmitter.dispose();\n\t\tthis.workingTreeEmitter.dispose();\n\t}\n\n\tget suspended(): boolean {\n\t\treturn this._suspended;\n\t}\n\n\tget hasPendingChanges(): boolean {\n\t\treturn this.pendingRepoChanges.size > 0 || this.hasPendingWorkingTreeChanges;\n\t}\n\n\t/**\n\t * An incomplete batch with no surviving paths still has to fire: the caller dropped paths we never saw, so\n\t * gating the pipeline on the retained set alone would swallow the change entirely.\n\t */\n\tprivate get hasPendingWorkingTreeChanges(): boolean {\n\t\treturn this.pendingWorkingTreePaths.size > 0 || this.pendingWorkingTreeIncomplete;\n\t}\n\n\tget repoSubscriberCount(): number {\n\t\treturn this.repoSubscribers.size;\n\t}\n\n\tget workingTreeSubscriberCount(): number {\n\t\treturn this.workingTreeSubscribers.size;\n\t}\n\n\t/**\n\t * Monotonic counter incremented immediately when working tree changes\n\t * are pushed (before debounce). Used by `GitCommit.hasFullDetails()`\n\t * to detect staleness of uncommitted commit data.\n\t */\n\tget etagWorkingTree(): number {\n\t\treturn this._etagWorkingTree;\n\t}\n\n\t/**\n\t * Subscribe to .git directory changes with configurable debounce.\n\t * First subscribe fires `onFirstRepoSubscriber`, last dispose\n\t * fires `onLastRepoSubscriber`.\n\t *\n\t * When multiple subscribers exist, the shortest debounce wins —\n\t * all subscribers fire on the same (fastest) cadence. `maxWaitMs` bounds how long continuous\n\t * activity can defer a flush (the shortest wins too); `Infinity` means no cap.\n\t *\n\t * `relevantRefs` narrows THIS subscription's events to the refs it cares about, given as full ref names\n\t * (`refs/heads/main`, `refs/remotes/origin/main`, `refs/tags/v1`, `refs/stash`). It is read at delivery, so it can\n\t * return the subscriber's current list. A `heads`/`remotes`/`tags`/`stash` change is delivered only when a listed\n\t * ref of that kind may have moved, when the change isn't attributable to a name (e.g. a `packed-refs` rewrite),\n\t * or, for `heads`, when a pseudo-ref (`HEAD`, `ORIG_HEAD`) moved — so `stash` and `tags` changes are dropped unless\n\t * a ref of that kind is listed. A `config` change confined to some branches' `branch.<name>` sections\n\t * (`configBranches`) is delivered only when `HEAD` or one of those branches is listed. Other change kinds always\n\t * pass, and an event left with no changes isn't delivered.\n\t * The filter never affects other subscribers, the debounce, or the global `onDidFireRepoChange` forwarding.\n\t */\n\tsubscribe(opts?: {\n\t\tdelayMs?: number;\n\t\tmaxWaitMs?: number;\n\t\trelevantRefs?: () => Iterable<string>;\n\t}): RepositorySubscription {\n\t\tif (this._disposed) {\n\t\t\treturn Object.assign(\n\t\t\t\tcreateDisposable(() => {}),\n\t\t\t\t{\n\t\t\t\t\tonDidChange: this.repoEmitter.event,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\n\t\tconst id = this.repoNextId++;\n\t\tconst wasEmpty = this.repoSubscribers.size === 0;\n\n\t\tthis.repoSubscribers.set(id, {\n\t\t\tdelayMs: opts?.delayMs ?? this.defaultRepoMs,\n\t\t\tmaxWaitMs: opts?.maxWaitMs ?? this.defaultRepoMaxWaitMs,\n\t\t});\n\t\tthis.recalcRepoTiming();\n\n\t\tconst relevantRefs = opts?.relevantRefs;\n\t\tif (relevantRefs != null) {\n\t\t\tthis.relevantRefsSources.set(id, relevantRefs);\n\t\t}\n\n\t\tif (wasEmpty) {\n\t\t\tthis.lifecycle?.onFirstRepoSubscriber?.();\n\t\t}\n\n\t\t// If the effective delay or max-wait got shorter while a flush is pending, move it sooner\n\t\tthis.repoClock.rearmIfEarlier();\n\n\t\tlet subDisposed = false;\n\t\treturn Object.assign(\n\t\t\tcreateDisposable(() => {\n\t\t\t\tif (subDisposed) return;\n\n\t\t\t\tsubDisposed = true;\n\n\t\t\t\tthis.repoSubscribers.delete(id);\n\t\t\t\tthis.relevantRefsSources.delete(id);\n\t\t\t\tthis.recalcRepoTiming();\n\n\t\t\t\tif (this.repoSubscribers.size === 0) {\n\t\t\t\t\tthis.repoClock.cancel();\n\t\t\t\t\tthis.clearPendingRepoChanges();\n\t\t\t\t\tthis.lifecycle?.onLastRepoSubscriber?.();\n\t\t\t\t}\n\t\t\t}),\n\t\t\t{\n\t\t\t\tonDidChange:\n\t\t\t\t\trelevantRefs != null\n\t\t\t\t\t\t? createRelevantRefsEvent(this.repoEmitter.event, relevantRefs)\n\t\t\t\t\t\t: this.repoEmitter.event,\n\t\t\t},\n\t\t);\n\t}\n\n\t/** Every repo subscriber's current `relevantRefs` (see {@link subscribe}), unioned */\n\tgetRelevantRefs(): Set<string> {\n\t\tconst refs = new Set<string>();\n\t\tfor (const relevantRefs of this.relevantRefsSources.values()) {\n\t\t\tfor (const ref of relevantRefs()) {\n\t\t\t\trefs.add(ref);\n\t\t\t}\n\t\t}\n\t\treturn refs;\n\t}\n\n\t/** Subscribe to working tree changes; `delayMs` and `maxWaitMs` behave as in {@link subscribe}. */\n\tsubscribeToWorkingTree(opts?: { delayMs?: number; maxWaitMs?: number }): WorkingTreeSubscription {\n\t\tif (this._disposed) {\n\t\t\treturn Object.assign(\n\t\t\t\tcreateDisposable(() => {}),\n\t\t\t\t{\n\t\t\t\t\tonDidChangeWorkingTree: this.workingTreeEmitter.event,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\n\t\tconst id = this.workingTreeNextId++;\n\t\tconst wasEmpty = this.workingTreeSubscribers.size === 0;\n\n\t\tthis.workingTreeSubscribers.set(id, {\n\t\t\tdelayMs: opts?.delayMs ?? this.defaultWorkingTreeMs,\n\t\t\tmaxWaitMs: opts?.maxWaitMs ?? this.defaultWorkingTreeMaxWaitMs,\n\t\t});\n\t\tthis.recalcWorkingTreeTiming();\n\n\t\tif (wasEmpty) {\n\t\t\tthis.lifecycle?.onFirstWorkingTreeSubscriber?.();\n\t\t}\n\n\t\t// If the effective delay or max-wait got shorter while a flush is pending, move it sooner\n\t\tthis.workingTreeClock.rearmIfEarlier();\n\n\t\tlet subDisposed = false;\n\t\treturn Object.assign(\n\t\t\tcreateDisposable(() => {\n\t\t\t\tif (subDisposed) return;\n\n\t\t\t\tsubDisposed = true;\n\n\t\t\t\tthis.workingTreeSubscribers.delete(id);\n\t\t\t\tthis.recalcWorkingTreeTiming();\n\n\t\t\t\tif (this.workingTreeSubscribers.size === 0) {\n\t\t\t\t\tthis.workingTreeClock.cancel();\n\t\t\t\t\tthis.pendingWorkingTreePaths.clear();\n\t\t\t\t\t// Dropped with the paths it describes — a stale flag would make the next subscriber's\n\t\t\t\t\t// first ordinary flush claim a partial set and force needless full refreshes.\n\t\t\t\t\tthis.pendingWorkingTreeIncomplete = false;\n\t\t\t\t\tthis.lifecycle?.onLastWorkingTreeSubscriber?.();\n\t\t\t\t}\n\t\t\t}),\n\t\t\t{ onDidChangeWorkingTree: this.workingTreeEmitter.event },\n\t\t);\n\t}\n\n\tsuspend(): void {\n\t\tthis._suspended = true;\n\t\t// Cancel active timers — events continue to coalesce but don't fire\n\t\tthis.repoClock.suspend();\n\t\tthis.workingTreeClock.suspend();\n\t}\n\n\tresume(delayMs?: number): void {\n\t\tif (!this._suspended) return;\n\n\t\tthis._suspended = false;\n\n\t\t// Flush accumulated changes (a clock with nothing pending stays idle); the max-wait window restarts from now\n\t\tthis.repoClock.resume(delayMs);\n\t\tthis.workingTreeClock.resume(delayMs);\n\t}\n\n\t/**\n\t * Inject changes (e.g., after a git operation the caller performed). Goes through debounce + suspend. Ref kinds\n\t * among them are unnamed — any ref of that kind may have moved.\n\t */\n\tfireChange(...changes: RepositoryChange[]): void {\n\t\tif (this._disposed || changes.length === 0) return;\n\n\t\tthis.coalesceRepo(changes, undefined, 'unfenced');\n\t}\n\n\t/** Immediate fire, bypassing debounce + suspend (for Closed/Opened). Ref kinds among them are unnamed. */\n\tfireChangeImmediate(...changes: RepositoryChange[]): void {\n\t\tif (this._disposed || changes.length === 0) return;\n\n\t\tconst event = new WatcherRepoChangeEvent(this.repoPath, changes);\n\t\t// Same contract as `flushRepo`: notify the global forwarder (which advances the cache's status clock) BEFORE\n\t\t// subscribers. Skipping it here would route changes to subscribers that no one advanced the clock for —\n\t\t// subscribers see them as session-routed and (correctly) don't re-advance, so the cache would go stale.\n\t\tthis.onDidFireRepoChangeCallback?.(event);\n\t\tthis.repoEmitter.fire(event);\n\t}\n\n\t/**\n\t * Push interpreted repo changes into the pipeline. `ref` is the full ref name (or ref directory, or pseudo-ref) whose\n\t * file changed; a ref kind in `changes` is attributed to it when the kinds match, and is otherwise unnamed.\n\t * `false` preserves cache invalidations for a config edit without claiming any ref moved. `configBranches`, for a\n\t * `config` among `changes`, are the local branches (full ref names) whose `branch.<name>` sections are all that\n\t * changed in `.git/config`; without it, the config change may be anywhere.\n\t */\n\tpushRepoChanges(changes: RepositoryChange[], ref?: string | false, configBranches?: ReadonlySet<string>): void {\n\t\tif (this._disposed) return;\n\n\t\tthis.coalesceRepo(changes, ref, 'raw', configBranches);\n\t}\n\n\t/**\n\t * Push watcher-observed repo changes that no write of core's can account for — an overflow's rescan, or an ignore\n\t * rule changed in the working tree — so {@link noteSelfWrite} never replaces them or takes them for its echo. Ref\n\t * kinds are unnamed.\n\t */\n\tpushUnfencedRepoChanges(changes: RepositoryChange[]): void {\n\t\tif (this._disposed) return;\n\n\t\tthis.coalesceRepo(changes, undefined, 'unfenced');\n\t}\n\n\t/**\n\t * Core announced one of its own writes for this repository: its caches were evicted and its host notified, both\n\t * after the write finished. The announcement becomes this session's one event for the write:\n\t *\n\t * - the announced kinds are pushed through the debounce and suspend like {@link fireChange}, and the event carries\n\t * them in `announced`. A ref kind is unnamed unless `refs` names refs of that kind (the full ref names the write\n\t * moved), in which case it carries those names and no other ref of the kind counts as changed by the write.\n\t * `config` is confined to the `branch.<name>` sections of the local branches `refs` names when\n\t * `options.configConfinedToRefs` says so, and the event carries them (`configBranches`);\n\t * - a raw change of an announced kind is the write's own report of it only when it is provably within what the\n\t * write announced: a ref the write named, or a config change confined to branches the write's config was confined\n\t * to. Pending when the announcement arrives, it is replaced. Arriving later, it merges and the kind stays\n\t * announced only within the repo debounce delay of the announcement; a window can stay pending for minutes while\n\t * the session is suspended, and a later move of the same ref is as likely an outside one. Any other report of\n\t * the kind is an outside change: it stays, and the kind isn't `announced`, since the host has not handled the\n\t * outside part. A write never names `HEAD`, but a commit locks it to write its reflog, so under a write that\n\t * announced `head`, a report of the `HEAD` file within the same delay is the write's echo, its `heads` part\n\t * included;\n\t * - a kind the write announced open (unnamed, or `config` unconfined) covers any change of the kind: raw changes\n\t * of it still pending are replaced, since the host evicts everything of the kind after the write. One arriving\n\t * within the repo debounce delay of the announcement, before its event fires, is taken for the write's own late\n\t * echo; any later report of the kind before the event fires clears the mark.\n\t *\n\t * Once the event fires, nothing is special: an echo the watcher delivers after it causes one extra, unannounced\n\t * event (a duplicate refresh, narrowed like any other), rather than risk losing an outside change of the same kind.\n\t *\n\t * Empty `changes`, or `'unknown'`, announces every kind a watcher can report (never `'unknown'` itself, which a\n\t * cache reads as a close), open whatever `refs` says; `'closed'` and `'opened'` are ignored. Only raw `.git` changes\n\t * are replaced or kept announced: {@link fireChange}, {@link fireChangeImmediate}, {@link pushUnfencedRepoChanges}\n\t * and working-tree changes always pass.\n\t */\n\tnoteSelfWrite(\n\t\tchanges: readonly RepositoryChange[],\n\t\trefs?: readonly string[],\n\t\toptions?: { readonly configConfinedToRefs?: boolean },\n\t): void {\n\t\tif (this._disposed) return;\n\n\t\tconst all = changes.length === 0 || changes.includes('unknown');\n\t\tconst announced = all ? allWatchedRepositoryChanges : changes.filter(c => isAnnounceable(c));\n\t\tconst named = all ? undefined : groupNamedRefs(changes, refs);\n\t\tconst configBranches =\n\t\t\t!all && options?.configConfinedToRefs === true && changes.includes('config')\n\t\t\t\t? getBranchRefs(refs)\n\t\t\t\t: undefined;\n\n\t\tfor (const change of this.pendingRepoChanges) {\n\t\t\tif (this.pendingRepoUnfencedChanges.has(change) || !isAnnounceable(change)) continue;\n\t\t\tif (!all && !changes.includes(change)) continue;\n\n\t\t\tif (change === 'config' && configBranches != null) {\n\t\t\t\t// An earlier announcement's config stays and merges with this one; a raw one is replaced only when it\n\t\t\t\t// is provably this write's own report\n\t\t\t\tif (this.pendingRepoAnnounced.has(change)) continue;\n\t\t\t\tif (!isSubsetOf(this.pendingRepoConfigBranches, configBranches)) continue;\n\t\t\t}\n\n\t\t\tconst names = isRefChangeKind(change) ? named?.get(change) : undefined;\n\t\t\tif (names != null) {\n\t\t\t\tfor (const name of names) {\n\t\t\t\t\tthis.pendingRepoRefs.delete(name);\n\t\t\t\t}\n\t\t\t\t// What's left of the kind came from outside the write, so it stays, unannounced\n\t\t\t\tif (this.hasPendingRefOfKind(change)) continue;\n\t\t\t}\n\n\t\t\tthis.pendingRepoChanges.delete(change);\n\t\t\tthis.pendingRepoAnnounced.delete(change);\n\t\t\tif (change === 'config') {\n\t\t\t\tthis.pendingRepoConfigBranches = undefined;\n\t\t\t}\n\t\t\tif (!isRefChangeKind(change)) continue;\n\n\t\t\t// An unnamed announcement covers any ref of the kind, so the replaced kind takes its named refs with it\n\t\t\tthis.pendingRepoUnnamedRefKinds.delete(change);\n\t\t\tfor (const ref of this.pendingRepoRefs) {\n\t\t\t\tif (getRefChangeKind(ref) === change) {\n\t\t\t\t\tthis.pendingRepoRefs.delete(ref);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (announced.length !== 0) {\n\t\t\tthis.coalesceRepo(\n\t\t\t\tnamed != null ? announced.filter(c => !isRefChangeKind(c) || !named.has(c)) : announced,\n\t\t\t\tundefined,\n\t\t\t\t'announced',\n\t\t\t\tconfigBranches,\n\t\t\t);\n\t\t\tif (named != null) {\n\t\t\t\tfor (const [kind, names] of named) {\n\t\t\t\t\tfor (const name of names) {\n\t\t\t\t\t\tthis.coalesceRepo([kind], name, 'announced');\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (this.pendingRepoChanges.size === 0) {\n\t\t\t// Nothing left to flush: idle the clock rather than let it fire an empty window\n\t\t\tthis.repoClock.cancel();\n\t\t}\n\t}\n\n\t/** Push working tree file changes into the pipeline */\n\t/** `incomplete` marks the batch as partial when the caller already dropped paths of its own. */\n\tpushWorkingTreeChanges(paths: Iterable<string>, incomplete?: boolean): void {\n\t\tif (this._disposed) return;\n\n\t\tthis._etagWorkingTree++;\n\t\tif (incomplete) {\n\t\t\tthis.pendingWorkingTreeIncomplete = true;\n\t\t}\n\t\tthis.coalesceWorkingTrees(paths);\n\t}\n\n\tprivate static minDelay(subscribers: Map<number, DebounceTiming>, fallback: number): number {\n\t\tlet min = fallback;\n\t\tfor (const { delayMs } of subscribers.values()) {\n\t\t\tif (delayMs < min) {\n\t\t\t\tmin = delayMs;\n\t\t\t}\n\t\t}\n\t\treturn min;\n\t}\n\n\t/** Unlike the delay, the default isn't a ceiling — a subscriber can opt out of the cap with `Infinity` */\n\tprivate static minMaxWait(subscribers: Map<number, DebounceTiming>, fallback: number): number {\n\t\tif (subscribers.size === 0) return fallback;\n\n\t\tlet min = Infinity;\n\t\tfor (const { maxWaitMs } of subscribers.values()) {\n\t\t\tif (maxWaitMs < min) {\n\t\t\t\tmin = maxWaitMs;\n\t\t\t}\n\t\t}\n\t\treturn min;\n\t}\n\n\tprivate recalcRepoTiming(): void {\n\t\tthis.effectiveRepoMs = RepositoryWatchSession.minDelay(this.repoSubscribers, this.defaultRepoMs);\n\t\tthis.effectiveRepoMaxWaitMs = RepositoryWatchSession.minMaxWait(\n\t\t\tthis.repoSubscribers,\n\t\t\tthis.defaultRepoMaxWaitMs,\n\t\t);\n\t}\n\n\tprivate recalcWorkingTreeTiming(): void {\n\t\tthis.effectiveWorkingTreeMs = RepositoryWatchSession.minDelay(\n\t\t\tthis.workingTreeSubscribers,\n\t\t\tthis.defaultWorkingTreeMs,\n\t\t);\n\t\tthis.effectiveWorkingTreeMaxWaitMs = RepositoryWatchSession.minMaxWait(\n\t\t\tthis.workingTreeSubscribers,\n\t\t\tthis.defaultWorkingTreeMaxWaitMs,\n\t\t);\n\t}\n\n\tprivate flushRepo(): void {\n\t\tif (this.pendingRepoChanges.size === 0) return;\n\n\t\t// The event is built only here — raw events just add to the pending sets (which the event copies)\n\t\tconst event = new WatcherRepoChangeEvent(\n\t\t\tthis.repoPath,\n\t\t\t[...this.pendingRepoChanges],\n\t\t\t{\n\t\t\t\tnames: this.pendingRepoRefs,\n\t\t\t\tunnamed: this.pendingRepoUnnamedRefKinds,\n\t\t\t\tconfigBranches: this.pendingRepoConfigBranches,\n\t\t\t},\n\t\t\tthis.pendingRepoAnnounced.keys(),\n\t\t);\n\t\tthis.clearPendingRepoChanges();\n\t\t// Advance any cache clock BEFORE notifying subscribers (same rationale as `flushWorkingTree`): a subscriber\n\t\t// that synchronously reads status must see the post-change generation, not a joinable pre-change run.\n\t\tthis.onDidFireRepoChangeCallback?.(event);\n\t\tthis.repoEmitter.fire(event);\n\t}\n\n\t/**\n\t * `origin` is a raw `.git` event, a push no announcement replaces ({@link fireChange},\n\t * {@link pushUnfencedRepoChanges}), or core's announced write. A kind stays announced only while nothing else\n\t * reports it in the pending window, save the write's own report of it (see {@link noteSelfWrite}).\n\t * `configBranches` confines a `config` among `changes` to those branches' sections; without it, it may be anywhere.\n\t */\n\tprivate coalesceRepo(\n\t\tchanges: readonly RepositoryChange[],\n\t\tref: string | false | undefined,\n\t\torigin: 'raw' | 'unfenced' | 'announced',\n\t\tconfigBranches?: ReadonlySet<string>,\n\t): void {\n\t\tif (changes.length === 0) return;\n\n\t\tconst now = performance.now();\n\t\tconst refKind = typeof ref === 'string' ? getRefChangeKind(ref) : undefined;\n\t\tfor (const change of changes) {\n\t\t\tconst pending = this.pendingRepoChanges.has(change);\n\t\t\t// What this report is confined to: the ref it names, or the branches whose config sections it changed\n\t\t\tlet names: ReadonlySet<string> | undefined;\n\t\t\tif (origin !== 'unfenced') {\n\t\t\t\tif (change === 'config') {\n\t\t\t\t\tnames = configBranches;\n\t\t\t\t} else if (typeof ref === 'string' && refKind === change) {\n\t\t\t\t\tnames = new Set([ref]);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst announcement = this.pendingRepoAnnounced.get(change);\n\t\t\tif (origin === 'announced') {\n\t\t\t\tif (!pending) {\n\t\t\t\t\tthis.pendingRepoAnnounced.set(change, {\n\t\t\t\t\t\tat: now,\n\t\t\t\t\t\tnames: names != null ? new Set(names) : undefined,\n\t\t\t\t\t});\n\t\t\t\t} else if (announcement != null) {\n\t\t\t\t\t// Added in place: a write naming many refs arrives here once per name, and copying the set each\n\t\t\t\t\t// time would cost quadratic time in the number of refs it moved\n\t\t\t\t\tif (announcement.names != null && names != null) {\n\t\t\t\t\t\tfor (const name of names) {\n\t\t\t\t\t\t\tannouncement.names.add(name);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tannouncement.names = undefined;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tconst own =\n\t\t\t\t\torigin === 'raw' && announcement != null && this.isOwnReport(change, announcement, names, now);\n\t\t\t\tif (announcement != null && !own) {\n\t\t\t\t\tthis.pendingRepoAnnounced.delete(change);\n\t\t\t\t}\n\t\t\t\tif (origin === 'unfenced') {\n\t\t\t\t\tthis.pendingRepoUnfencedChanges.add(change);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (change === 'config') {\n\t\t\t\tlet branches: Set<string> | undefined;\n\t\t\t\tif (!pending) {\n\t\t\t\t\tbranches = names != null ? new Set(names) : undefined;\n\t\t\t\t} else if (this.pendingRepoConfigBranches != null && names != null) {\n\t\t\t\t\tbranches = new Set([...this.pendingRepoConfigBranches, ...names]);\n\t\t\t\t}\n\t\t\t\tthis.pendingRepoConfigBranches =\n\t\t\t\t\tbranches != null && branches.size <= maxPendingRefNames ? branches : undefined;\n\t\t\t}\n\n\t\t\tthis.pendingRepoChanges.add(change);\n\t\t\tif (ref === false || !isRefChangeKind(change)) continue;\n\n\t\t\tif (\n\t\t\t\tref != null &&\n\t\t\t\trefKind === change &&\n\t\t\t\t(this.pendingRepoRefs.size < maxPendingRefNames || this.pendingRepoRefs.has(ref))\n\t\t\t) {\n\t\t\t\tthis.pendingRepoRefs.add(ref);\n\t\t\t} else {\n\t\t\t\tthis.pendingRepoUnnamedRefKinds.add(change);\n\t\t\t}\n\t\t}\n\t\tthis.repoClock.note();\n\t}\n\n\t/**\n\t * Whether a raw report of `change`, confined to `names` (`undefined`: not confined), is the announced write's own.\n\t * It must arrive within the repo debounce delay of the announcement, as it always had to. An open announcement\n\t * asks nothing more; one confined to names also needs every name the report carries to be one the write announced,\n\t * save the `heads` part of a `HEAD` report under a write that announced `head` in time, which is a commit's echo.\n\t */\n\tprivate isOwnReport(\n\t\tchange: RepositoryChange,\n\t\tannouncement: PendingAnnouncement,\n\t\tnames: ReadonlySet<string> | undefined,\n\t\tnow: number,\n\t): boolean {\n\t\tif (now - announcement.at > this.effectiveRepoMs) return false;\n\t\tif (announcement.names == null) return true;\n\t\tif (names == null) return false;\n\t\tif (isSubsetOf(names, announcement.names)) return true;\n\n\t\tif (change !== 'heads' || names.size !== 1 || !names.has('HEAD')) return false;\n\n\t\tconst head = this.pendingRepoAnnounced.get('head');\n\t\treturn head != null && now - head.at <= this.effectiveRepoMs;\n\t}\n\n\t/** Whether a pending report of `kind` remains, unnamed or under a name */\n\tprivate hasPendingRefOfKind(kind: RepositoryChange): boolean {\n\t\tif (!isRefChangeKind(kind)) return false;\n\t\tif (this.pendingRepoUnnamedRefKinds.has(kind)) return true;\n\n\t\tfor (const ref of this.pendingRepoRefs) {\n\t\t\tif (getRefChangeKind(ref) === kind) return true;\n\t\t}\n\t\treturn false;\n\t}\n\n\tprivate clearPendingRepoChanges(): void {\n\t\tthis.pendingRepoChanges.clear();\n\t\tthis.pendingRepoRefs.clear();\n\t\tthis.pendingRepoUnnamedRefKinds.clear();\n\t\tthis.pendingRepoUnfencedChanges.clear();\n\t\tthis.pendingRepoAnnounced.clear();\n\t\tthis.pendingRepoConfigBranches = undefined;\n\t}\n\n\tprivate flushWorkingTree(): void {\n\t\tif (!this.hasPendingWorkingTreeChanges) return;\n\n\t\tconst paths = this.pendingWorkingTreePaths;\n\t\tconst incomplete = this.pendingWorkingTreeIncomplete;\n\t\tthis.pendingWorkingTreePaths = new Set();\n\t\tthis.pendingWorkingTreeIncomplete = false;\n\t\t// Advance the cache's status clock (via WatchService's global forwarding) BEFORE notifying subscribers,\n\t\t// so a subscriber that synchronously reads status sees the post-change generation, not a joinable pre-change run.\n\t\tthis.onDidFireWorkingTreeChangeCallback?.(this.repoPath);\n\t\tthis.workingTreeEmitter.fire({ repoPath: this.repoPath, paths: paths, incomplete: incomplete });\n\t}\n\n\tprivate coalesceWorkingTrees(paths: Iterable<string>): void {\n\t\tfor (const p of paths) {\n\t\t\t// Stop retaining paths past the cap, but keep draining the iterable — the flag is what makes the\n\t\t\t// partial set safe to consume, and dropping out early would leave later paths for the next window.\n\t\t\tif (this.pendingWorkingTreePaths.size >= maxWorkingTreePaths && !this.pendingWorkingTreePaths.has(p)) {\n\t\t\t\tthis.pendingWorkingTreeIncomplete = true;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tthis.pendingWorkingTreePaths.add(p);\n\t\t}\n\n\t\tif (this.hasPendingWorkingTreeChanges) {\n\t\t\tthis.workingTreeClock.note();\n\t\t}\n\t}\n}\n\n/** `'closed'` and `'opened'` are lifecycle, not a write's effect, so an announcement never replaces or names them */\nfunction isAnnounceable(change: RepositoryChange): boolean {\n\treturn change !== 'closed' && change !== 'opened';\n}\n\n/**\n * The names in `refs` grouped by their ref kind, for the ref kinds among `changes` only; `undefined` when none is named.\n * Pseudo-refs (`HEAD`) are left out: a write names the refs it moved, and those are full ref names.\n */\nfunction groupNamedRefs(\n\tchanges: readonly RepositoryChange[],\n\trefs: readonly string[] | undefined,\n): Map<RefChangeKind, Set<string>> | undefined {\n\tif (!refs?.length) return undefined;\n\n\tlet named: Map<RefChangeKind, Set<string>> | undefined;\n\tfor (const ref of refs) {\n\t\tif (!ref.startsWith('refs/')) continue;\n\n\t\tconst kind = getRefChangeKind(ref);\n\t\tif (kind == null || !changes.includes(kind)) continue;\n\n\t\tnamed ??= new Map();\n\t\tlet names = named.get(kind);\n\t\tif (names == null) {\n\t\t\tnames = new Set();\n\t\t\tnamed.set(kind, names);\n\t\t}\n\t\tnames.add(ref);\n\t}\n\treturn named;\n}\n\n/** The local branches among `refs` (full `refs/heads/` names), or `undefined` when there are none */\nfunction getBranchRefs(refs: readonly string[] | undefined): Set<string> | undefined {\n\tlet branches: Set<string> | undefined;\n\tfor (const ref of refs ?? []) {\n\t\tif (ref.startsWith('refs/heads/')) {\n\t\t\t(branches ??= new Set()).add(ref);\n\t\t}\n\t}\n\treturn branches;\n}\n\n/** Whether every name in `names` is in `of`; never for `undefined`, which stands for any name */\nfunction isSubsetOf(names: ReadonlySet<string> | undefined, of: ReadonlySet<string>): boolean {\n\tif (names == null) return false;\n\n\tfor (const name of names) {\n\t\tif (!of.has(name)) return false;\n\t}\n\treturn true;\n}\n\n/** Wraps a session's repo event so each delivery is narrowed to the subscriber's relevant refs (see `subscribe`) */\nfunction createRelevantRefsEvent(\n\tevent: Event<WatcherRepoChangeEvent>,\n\trelevantRefs: () => Iterable<string>,\n): Event<WatcherRepoChangeEvent> {\n\treturn (listener, thisArgs, disposables) =>\n\t\tevent(\n\t\t\te => {\n\t\t\t\tconst narrowed = narrowToRelevantRefs(e, relevantRefs);\n\t\t\t\tif (narrowed != null) {\n\t\t\t\t\tlistener.call(thisArgs, narrowed);\n\t\t\t\t}\n\t\t\t},\n\t\t\tundefined,\n\t\t\tdisposables,\n\t\t);\n}\n\n/**\n * Narrows an event to the refs a subscriber cares about. Returns the event itself when no change kind is removed (the\n * kept names then stay a superset, which only makes `mayHaveChangedRef` more conservative), `undefined` when every kind\n * is removed, and otherwise a new event with the kept kinds, names and unnamed kinds. A `config` change confined to\n * some local branches' sections is removed unless a relevant ref is `HEAD` or one of those branches (see\n * `mayHaveChangedConfig`).\n */\nfunction narrowToRelevantRefs(\n\te: WatcherRepoChangeEvent,\n\trelevantRefs: () => Iterable<string>,\n): WatcherRepoChangeEvent | undefined {\n\t// Ref kinds that pass only if a relevant ref of that kind may have moved\n\tlet unresolved: Set<RefChangeKind> | undefined;\n\tfor (const change of e.changes) {\n\t\tif (!isRefChangeKind(change) || e.unnamedRefKinds.has(change)) continue;\n\n\t\t(unresolved ??= new Set()).add(change);\n\t}\n\t// A confined `config` passes only if a relevant ref is one of the branches it is confined to\n\tconst configBranches = e.configBranches;\n\tlet keepConfig = configBranches == null;\n\tif (unresolved == null && keepConfig) return e;\n\n\tunresolved ??= new Set();\n\n\tconst pseudoRefs: string[] = [];\n\tfor (const name of e.refs) {\n\t\tif (!name.startsWith('refs/')) {\n\t\t\tpseudoRefs.push(name);\n\t\t}\n\t}\n\tif (pseudoRefs.length !== 0) {\n\t\t// A pseudo-ref (`HEAD`, `ORIG_HEAD`) moving is a `heads` change for every subscriber\n\t\tunresolved.delete('heads');\n\t\tif (unresolved.size === 0 && keepConfig) return e;\n\t}\n\n\t// Names that equal or cover a relevant ref — collected as we go, since `relevantRefs` may be a one-shot iterable\n\tconst keptNames = new Set<string>(pseudoRefs);\n\tfor (const ref of relevantRefs()) {\n\t\t// `HEAD` is usually a symref to the checked-out branch, so any `heads` change may have moved it, and any branch's\n\t\t// config section may be its branch's\n\t\tif (ref === 'HEAD') {\n\t\t\tif (e.changes.has('heads')) {\n\t\t\t\tfor (const name of e.refs) {\n\t\t\t\t\tif (getRefChangeKind(name) === 'heads') {\n\t\t\t\t\t\tkeptNames.add(name);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tkeepConfig = true;\n\t\t\tunresolved.delete('heads');\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (!keepConfig && configBranches?.has(ref)) {\n\t\t\tkeepConfig = true;\n\t\t}\n\n\t\tconst kind = getRefChangeKind(ref);\n\t\tif (kind == null || !e.changes.has(kind)) continue;\n\n\t\tconst covering = findCoveringRefName(e.refs, ref);\n\t\tif (covering == null) continue;\n\n\t\tkeptNames.add(covering);\n\t\tunresolved.delete(kind);\n\t}\n\tif (unresolved.size === 0 && keepConfig) return e;\n\n\tconst keptChanges: RepositoryChange[] = [];\n\tfor (const change of e.changes) {\n\t\tif (change === 'config' ? keepConfig : !isRefChangeKind(change) || !unresolved.has(change)) {\n\t\t\tkeptChanges.push(change);\n\t\t}\n\t}\n\tif (keptChanges.length === 0) return undefined;\n\n\treturn new WatcherRepoChangeEvent(\n\t\te.repoPath,\n\t\tkeptChanges,\n\t\t{ names: keptNames, unnamed: e.unnamedRefKinds, configBranches: configBranches },\n\t\te.announced,\n\t);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"watchSession.js","names":[],"sources":["../../../../git/src/watching/watchSession.ts"],"sourcesContent":["import type { UnifiedDisposable } from '@gitlens/utils/disposable.js';\nimport { createDisposable } from '@gitlens/utils/disposable.js';\nimport type { Event } from '@gitlens/utils/event.js';\nimport { Emitter } from '@gitlens/utils/event.js';\nimport type { RepositoryChange } from '../models/repository.js';\nimport type { RefChangeKind, WorkingTreeChangeEvent } from './changeEvent.js';\nimport { findCoveringRefName, getRefChangeKind, isRefChangeKind, WatcherRepoChangeEvent } from './changeEvent.js';\nimport { allWatchedRepositoryChanges } from './classifyChange.js';\n\nexport interface RepositorySubscription extends UnifiedDisposable {\n\treadonly onDidChange: Event<WatcherRepoChangeEvent>;\n}\n\nexport interface WorkingTreeSubscription extends UnifiedDisposable {\n\treadonly onDidChangeWorkingTree: Event<WorkingTreeChangeEvent>;\n}\n\n/**\n * Lifecycle callbacks fired by the session when subscriber counts\n * cross the 0↔1 boundary. The host (WatchService) uses these to\n * lazily create/destroy watchers.\n */\nexport interface WatchSessionLifecycle {\n\t/** Called when the first repo-change subscriber joins */\n\tonFirstRepoSubscriber?(): void;\n\t/** Called when the last repo-change subscriber leaves */\n\tonLastRepoSubscriber?(): void;\n\t/** Called when the first working-tree subscriber joins */\n\tonFirstWorkingTreeSubscriber?(): void;\n\t/** Called when the last working-tree subscriber leaves */\n\tonLastWorkingTreeSubscriber?(): void;\n}\n\nexport interface WatchSessionOptions {\n\treadonly repoPath: string;\n\t/** Default debounce for repo changes. Default: 250ms */\n\treadonly defaultRepoDelayMs?: number;\n\t/** Default debounce for working tree changes. Default: 2500ms */\n\treadonly defaultWorkingTreeDelayMs?: number;\n\t/**\n\t * Default cap on how long repo changes can be held back by continuous activity — a flush happens at most this long\n\t * after the first pending change, however long the burst lasts. `Infinity` disables the cap. Default: 2000ms\n\t */\n\treadonly defaultRepoMaxWaitMs?: number;\n\t/** Default cap on how long working tree changes can be held back by continuous activity. `Infinity` disables the cap. Default: 10000ms */\n\treadonly defaultWorkingTreeMaxWaitMs?: number;\n\treadonly lifecycle?: WatchSessionLifecycle;\n\t/** Initial suspended state — new sessions inherit the service-global suspend state. Default: false */\n\treadonly suspended?: boolean;\n\t/**\n\t * Called whenever a debounced repo change event is dispatched to subscribers.\n\t * Does NOT count as a subscriber — used by WatchService for\n\t * global event forwarding without affecting lifecycle callbacks.\n\t */\n\treadonly onDidFireRepoChange?: (event: WatcherRepoChangeEvent) => void;\n\t/**\n\t * Called whenever a debounced working-tree change is dispatched to subscribers. Like\n\t * {@link onDidFireRepoChange}, does NOT count as a subscriber — used by WatchService for global\n\t * event forwarding (the single point that drives the cache's status clock for working-tree edits).\n\t */\n\treadonly onDidFireWorkingTreeChange?: (repoPath: string) => void;\n}\n\nconst defaultRepoDelay = 250;\nconst defaultWorkingTreeDelay = 2500;\nconst defaultRepoMaxWait = 2000;\nconst defaultWorkingTreeMaxWait = 10000;\n\n/** Largest delay `setTimeout` honors (a signed 32-bit int); anything longer fires immediately */\nconst maxTimerDelay = 0x7fffffff;\n\ninterface DebounceTiming {\n\treadonly delayMs: number;\n\treadonly maxWaitMs: number;\n}\n\n/**\n * Trailing-deadline debounce with a max-wait, shared by the repo and working-tree pipelines.\n *\n * Recording an event costs a `performance.now()` and, only when no timer is armed, one `setTimeout` — never a\n * `clearTimeout`/`setTimeout` pair per event, which is what made a busy repo allocate (and starve) on every raw\n * watcher event. The armed timer is a deadline, not a countdown: when it fires it recomputes the due time from the\n * latest timestamps and either flushes or re-arms for the remainder. The due time is\n * `min(lastEventAt + delay, firstPendingAt + maxWait)`, so continuous activity is flushed at the max-wait instead of\n * never. Monotonic time, so a wall-clock change can't push a deadline out.\n */\nclass DebounceClock {\n\tprivate active = false;\n\tprivate paused = false;\n\tprivate firstAt = 0;\n\tprivate lastAt = 0;\n\tprivate timer: ReturnType<typeof setTimeout> | undefined;\n\tprivate timerDueAt = 0;\n\t/** Absolute flush time set by {@link resume}; dropped by the next event, which returns to normal debounce */\n\tprivate resumeDueAt: number | undefined;\n\n\tconstructor(\n\t\tprivate readonly getDelayMs: () => number,\n\t\tprivate readonly getMaxWaitMs: () => number,\n\t\tprivate readonly onDue: () => void,\n\t) {}\n\n\t/** Records an event; arms the timer only if none is armed. Cheap enough to call per raw event. */\n\tnote(): void {\n\t\tconst now = performance.now();\n\t\tif (!this.active) {\n\t\t\tthis.active = true;\n\t\t\tthis.firstAt = now;\n\t\t}\n\t\tthis.lastAt = now;\n\n\t\tif (this.paused) return;\n\n\t\tif (this.resumeDueAt != null) {\n\t\t\t// The first event after a resume goes back to normal debounce — which may be due sooner than the resume deadline\n\t\t\tthis.resumeDueAt = undefined;\n\t\t\tthis.rearmIfEarlier();\n\t\t}\n\n\t\tif (this.timer == null) {\n\t\t\tthis.arm(this.getDueAt(), now);\n\t\t}\n\t}\n\n\t/** Moves the armed timer earlier when the due time shrank (a subscriber joined with a shorter delay or max-wait) */\n\trearmIfEarlier(): void {\n\t\tif (!this.active || this.paused || this.timer == null) return;\n\n\t\tconst dueAt = this.getDueAt();\n\t\tif (dueAt >= this.timerDueAt) return;\n\n\t\tclearTimeout(this.timer);\n\t\tthis.arm(dueAt, performance.now());\n\t}\n\n\t/** Cancels the timer; events keep accumulating but nothing is due until {@link resume} */\n\tsuspend(): void {\n\t\tthis.paused = true;\n\t\tthis.clearTimer();\n\t}\n\n\t/**\n\t * Restarts the max-wait window from now and schedules the flush for `delayMs` from now (the effective delay when\n\t * omitted). Does nothing if no events are pending.\n\t */\n\tresume(delayMs?: number): void {\n\t\tthis.paused = false;\n\t\tif (!this.active) return;\n\n\t\tconst now = performance.now();\n\t\tthis.firstAt = now;\n\t\tthis.resumeDueAt = now + (delayMs ?? this.getDelayMs());\n\t\tthis.clearTimer();\n\t\tthis.arm(this.resumeDueAt, now);\n\t}\n\n\t/** Drops the pending window and the timer */\n\tcancel(): void {\n\t\tthis.active = false;\n\t\tthis.resumeDueAt = undefined;\n\t\tthis.clearTimer();\n\t}\n\n\tprivate getDueAt(): number {\n\t\treturn this.resumeDueAt ?? Math.min(this.lastAt + this.getDelayMs(), this.firstAt + this.getMaxWaitMs());\n\t}\n\n\tprivate arm(dueAt: number, now: number): void {\n\t\tthis.timerDueAt = dueAt;\n\t\tthis.timer = setTimeout(() => this.onTimer(), Math.min(Math.max(0, dueAt - now), maxTimerDelay));\n\t}\n\n\tprivate onTimer(): void {\n\t\tthis.timer = undefined;\n\t\tif (!this.active || this.paused) return;\n\n\t\tconst now = performance.now();\n\t\tconst dueAt = this.getDueAt();\n\t\tif (dueAt > now) {\n\t\t\tthis.arm(dueAt, now);\n\t\t\treturn;\n\t\t}\n\n\t\t// Reset before firing so an event raised by a subscriber starts a fresh window\n\t\tthis.cancel();\n\t\tthis.onDue();\n\t}\n\n\tprivate clearTimer(): void {\n\t\tif (this.timer != null) {\n\t\t\tclearTimeout(this.timer);\n\t\t\tthis.timer = undefined;\n\t\t}\n\t}\n}\n\n/**\n * Cap on retained working-tree paths per flush. The set otherwise grows without bound — while the window\n * is unfocused the session is suspended, so a branch switch or build accumulates every touched path until\n * refocus. Set low deliberately: ordinary edits are a handful of paths, anything past this was a\n * refresh-everything event anyway, and dropping past it only costs the matching consumers a\n * conservative refresh.\n */\nconst maxWorkingTreePaths = 1000;\n\n/**\n * Cap on retained ref names per flush, for the same reason (a fetch of many refs while suspended). Past it, a ref kind\n * is recorded unnamed — any ref of that kind may have moved — which only makes `mayHaveChangedRef` more conservative.\n * A pending `config` confined to more branches than this is recorded unconfined, for the same reason.\n */\nconst maxPendingRefNames = 1000;\n\n/**\n * What core's announced writes in the pending window said about one kind: when it was first announced, and the full\n * ref names it was confined to (for `config`, the branches whose sections it changed); `names` is absent when an\n * announcement left the kind open — any ref of it, or any part of the config, may have changed.\n */\ninterface PendingAnnouncement {\n\treadonly at: number;\n\tnames: Set<string> | undefined;\n}\n\n/**\n * Per-repository session that owns the debounce/coalesce/suspend\n * pipeline for both repo (.git) and working-tree changes.\n *\n * Multiple subscribers share the same pipeline — the shortest\n * requested debounce delay wins, and so does the shortest max-wait.\n */\nexport class RepositoryWatchSession implements UnifiedDisposable {\n\treadonly repoPath: string;\n\n\tprivate readonly repoEmitter = new Emitter<WatcherRepoChangeEvent>();\n\tprivate readonly repoSubscribers = new Map<number, DebounceTiming>(); // subscriberId → timing\n\tprivate repoNextId = 0;\n\t/** Each repo subscriber's `relevantRefs`, for {@link getRelevantRefs} */\n\tprivate readonly relevantRefsSources = new Map<number, () => Iterable<string>>(); // subscriberId → relevantRefs\n\tprivate readonly pendingRepoChanges = new Set<RepositoryChange>();\n\t/** Full ref names, ref directories and pseudo-refs whose files changed, flushed with {@link pendingRepoChanges} */\n\tprivate readonly pendingRepoRefs = new Set<string>();\n\t/** Ref kinds that changed without a name, flushed with {@link pendingRepoChanges} */\n\tprivate readonly pendingRepoUnnamedRefKinds = new Set<RefChangeKind>();\n\t/** Kinds among {@link pendingRepoChanges} injected or pushed unfenced, which an announcement never replaces */\n\tprivate readonly pendingRepoUnfencedChanges = new Set<RepositoryChange>();\n\t/**\n\t * Kinds among {@link pendingRepoChanges} that only core's announced writes, and their own echoes, reported (see\n\t * {@link noteSelfWrite})\n\t */\n\tprivate readonly pendingRepoAnnounced = new Map<RepositoryChange, PendingAnnouncement>();\n\t/**\n\t * For a pending `config` change, the branches (full ref names) whose sections are all that every report of it\n\t * changed; `undefined` once any report may have changed anything else\n\t */\n\tprivate pendingRepoConfigBranches: Set<string> | undefined;\n\tprivate readonly repoClock: DebounceClock;\n\tprivate effectiveRepoMs: number;\n\tprivate effectiveRepoMaxWaitMs: number;\n\tprivate readonly defaultRepoMs: number;\n\tprivate readonly defaultRepoMaxWaitMs: number;\n\n\tprivate readonly workingTreeEmitter = new Emitter<WorkingTreeChangeEvent>();\n\tprivate readonly workingTreeSubscribers = new Map<number, DebounceTiming>();\n\tprivate workingTreeNextId = 0;\n\tprivate pendingWorkingTreePaths = new Set<string>();\n\tprivate pendingWorkingTreeIncomplete = false;\n\tprivate readonly workingTreeClock: DebounceClock;\n\tprivate effectiveWorkingTreeMs: number;\n\tprivate effectiveWorkingTreeMaxWaitMs: number;\n\tprivate readonly defaultWorkingTreeMs: number;\n\tprivate readonly defaultWorkingTreeMaxWaitMs: number;\n\n\tprivate _etagWorkingTree = 0;\n\tprivate _suspended: boolean;\n\tprivate _disposed = false;\n\n\tprivate readonly lifecycle?: WatchSessionLifecycle;\n\tprivate readonly onDidFireRepoChangeCallback?: (event: WatcherRepoChangeEvent) => void;\n\tprivate readonly onDidFireWorkingTreeChangeCallback?: (repoPath: string) => void;\n\n\tconstructor(options: WatchSessionOptions) {\n\t\tthis.repoPath = options.repoPath;\n\t\tthis.defaultRepoMs = options.defaultRepoDelayMs ?? defaultRepoDelay;\n\t\tthis.defaultWorkingTreeMs = options.defaultWorkingTreeDelayMs ?? defaultWorkingTreeDelay;\n\t\tthis.defaultRepoMaxWaitMs = options.defaultRepoMaxWaitMs ?? defaultRepoMaxWait;\n\t\tthis.defaultWorkingTreeMaxWaitMs = options.defaultWorkingTreeMaxWaitMs ?? defaultWorkingTreeMaxWait;\n\t\tthis.effectiveRepoMs = this.defaultRepoMs;\n\t\tthis.effectiveRepoMaxWaitMs = this.defaultRepoMaxWaitMs;\n\t\tthis.effectiveWorkingTreeMs = this.defaultWorkingTreeMs;\n\t\tthis.effectiveWorkingTreeMaxWaitMs = this.defaultWorkingTreeMaxWaitMs;\n\t\tthis.repoClock = new DebounceClock(\n\t\t\t() => this.effectiveRepoMs,\n\t\t\t() => this.effectiveRepoMaxWaitMs,\n\t\t\t() => this.flushRepo(),\n\t\t);\n\t\tthis.workingTreeClock = new DebounceClock(\n\t\t\t() => this.effectiveWorkingTreeMs,\n\t\t\t() => this.effectiveWorkingTreeMaxWaitMs,\n\t\t\t() => this.flushWorkingTree(),\n\t\t);\n\t\tthis.lifecycle = options.lifecycle;\n\t\tthis.onDidFireRepoChangeCallback = options.onDidFireRepoChange;\n\t\tthis.onDidFireWorkingTreeChangeCallback = options.onDidFireWorkingTreeChange;\n\t\tthis._suspended = options.suspended ?? false;\n\t\tif (this._suspended) {\n\t\t\tthis.repoClock.suspend();\n\t\t\tthis.workingTreeClock.suspend();\n\t\t}\n\t}\n\n\t[Symbol.dispose](): void {\n\t\tthis.dispose();\n\t}\n\n\tdispose(): void {\n\t\tif (this._disposed) return;\n\n\t\tthis._disposed = true;\n\n\t\tthis.repoClock.cancel();\n\t\tthis.workingTreeClock.cancel();\n\n\t\tthis.clearPendingRepoChanges();\n\t\tthis.pendingWorkingTreePaths.clear();\n\t\tthis.pendingWorkingTreeIncomplete = false;\n\t\tthis.repoSubscribers.clear();\n\t\tthis.relevantRefsSources.clear();\n\t\tthis.workingTreeSubscribers.clear();\n\n\t\tthis.repoEmitter.dispose();\n\t\tthis.workingTreeEmitter.dispose();\n\t}\n\n\tget suspended(): boolean {\n\t\treturn this._suspended;\n\t}\n\n\tget hasPendingChanges(): boolean {\n\t\treturn this.pendingRepoChanges.size > 0 || this.hasPendingWorkingTreeChanges;\n\t}\n\n\t/**\n\t * An incomplete batch with no surviving paths still has to fire: the caller dropped paths we never saw, so\n\t * gating the pipeline on the retained set alone would swallow the change entirely.\n\t */\n\tprivate get hasPendingWorkingTreeChanges(): boolean {\n\t\treturn this.pendingWorkingTreePaths.size > 0 || this.pendingWorkingTreeIncomplete;\n\t}\n\n\tget repoSubscriberCount(): number {\n\t\treturn this.repoSubscribers.size;\n\t}\n\n\tget workingTreeSubscriberCount(): number {\n\t\treturn this.workingTreeSubscribers.size;\n\t}\n\n\t/**\n\t * Monotonic counter incremented immediately when working tree changes\n\t * are pushed (before debounce). Used by `GitCommit.hasFullDetails()`\n\t * to detect staleness of uncommitted commit data.\n\t */\n\tget etagWorkingTree(): number {\n\t\treturn this._etagWorkingTree;\n\t}\n\n\t/**\n\t * Subscribe to .git directory changes with configurable debounce.\n\t * First subscribe fires `onFirstRepoSubscriber`, last dispose\n\t * fires `onLastRepoSubscriber`.\n\t *\n\t * When multiple subscribers exist, the shortest debounce wins —\n\t * all subscribers fire on the same (fastest) cadence. `maxWaitMs` bounds how long continuous\n\t * activity can defer a flush (the shortest wins too); `Infinity` means no cap.\n\t *\n\t * `relevantRefs` narrows THIS subscription's events to the refs it cares about, given as full ref names\n\t * (`refs/heads/main`, `refs/remotes/origin/main`, `refs/tags/v1`, `refs/stash`). It is read at delivery, so it can\n\t * return the subscriber's current list. A `heads`/`remotes`/`tags`/`stash` change is delivered only when a listed\n\t * ref of that kind may have moved, when the change isn't attributable to a name (e.g. a `packed-refs` rewrite),\n\t * or, for `heads`, when a pseudo-ref (`HEAD`, `ORIG_HEAD`) moved — so `stash` and `tags` changes are dropped unless\n\t * a ref of that kind is listed. A `config` change confined to some branches' `branch.<name>` sections\n\t * (`configBranches`) is delivered only when `HEAD` or one of those branches is listed. Other change kinds always\n\t * pass, and an event left with no changes isn't delivered.\n\t * The filter never affects other subscribers, the debounce, or the global `onDidFireRepoChange` forwarding.\n\t */\n\tsubscribe(opts?: {\n\t\tdelayMs?: number;\n\t\tmaxWaitMs?: number;\n\t\trelevantRefs?: () => Iterable<string>;\n\t}): RepositorySubscription {\n\t\tif (this._disposed) {\n\t\t\treturn Object.assign(\n\t\t\t\tcreateDisposable(() => {}),\n\t\t\t\t{\n\t\t\t\t\tonDidChange: this.repoEmitter.event,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\n\t\tconst id = this.repoNextId++;\n\t\tconst wasEmpty = this.repoSubscribers.size === 0;\n\n\t\tthis.repoSubscribers.set(id, {\n\t\t\tdelayMs: opts?.delayMs ?? this.defaultRepoMs,\n\t\t\tmaxWaitMs: opts?.maxWaitMs ?? this.defaultRepoMaxWaitMs,\n\t\t});\n\t\tthis.recalcRepoTiming();\n\n\t\tconst relevantRefs = opts?.relevantRefs;\n\t\tif (relevantRefs != null) {\n\t\t\tthis.relevantRefsSources.set(id, relevantRefs);\n\t\t}\n\n\t\tif (wasEmpty) {\n\t\t\tthis.lifecycle?.onFirstRepoSubscriber?.();\n\t\t}\n\n\t\t// If the effective delay or max-wait got shorter while a flush is pending, move it sooner\n\t\tthis.repoClock.rearmIfEarlier();\n\n\t\tlet subDisposed = false;\n\t\treturn Object.assign(\n\t\t\tcreateDisposable(() => {\n\t\t\t\tif (subDisposed) return;\n\n\t\t\t\tsubDisposed = true;\n\n\t\t\t\tthis.repoSubscribers.delete(id);\n\t\t\t\tthis.relevantRefsSources.delete(id);\n\t\t\t\tthis.recalcRepoTiming();\n\n\t\t\t\tif (this.repoSubscribers.size === 0) {\n\t\t\t\t\tthis.repoClock.cancel();\n\t\t\t\t\tthis.clearPendingRepoChanges();\n\t\t\t\t\tthis.lifecycle?.onLastRepoSubscriber?.();\n\t\t\t\t}\n\t\t\t}),\n\t\t\t{\n\t\t\t\tonDidChange:\n\t\t\t\t\trelevantRefs != null\n\t\t\t\t\t\t? createRelevantRefsEvent(this.repoEmitter.event, relevantRefs)\n\t\t\t\t\t\t: this.repoEmitter.event,\n\t\t\t},\n\t\t);\n\t}\n\n\t/** Every repo subscriber's current `relevantRefs` (see {@link subscribe}), unioned */\n\tgetRelevantRefs(): Set<string> {\n\t\tconst refs = new Set<string>();\n\t\tfor (const relevantRefs of this.relevantRefsSources.values()) {\n\t\t\tfor (const ref of relevantRefs()) {\n\t\t\t\trefs.add(ref);\n\t\t\t}\n\t\t}\n\t\treturn refs;\n\t}\n\n\t/** Subscribe to working tree changes; `delayMs` and `maxWaitMs` behave as in {@link subscribe}. */\n\tsubscribeToWorkingTree(opts?: { delayMs?: number; maxWaitMs?: number }): WorkingTreeSubscription {\n\t\tif (this._disposed) {\n\t\t\treturn Object.assign(\n\t\t\t\tcreateDisposable(() => {}),\n\t\t\t\t{\n\t\t\t\t\tonDidChangeWorkingTree: this.workingTreeEmitter.event,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\n\t\tconst id = this.workingTreeNextId++;\n\t\tconst wasEmpty = this.workingTreeSubscribers.size === 0;\n\n\t\tthis.workingTreeSubscribers.set(id, {\n\t\t\tdelayMs: opts?.delayMs ?? this.defaultWorkingTreeMs,\n\t\t\tmaxWaitMs: opts?.maxWaitMs ?? this.defaultWorkingTreeMaxWaitMs,\n\t\t});\n\t\tthis.recalcWorkingTreeTiming();\n\n\t\tif (wasEmpty) {\n\t\t\tthis.lifecycle?.onFirstWorkingTreeSubscriber?.();\n\t\t}\n\n\t\t// If the effective delay or max-wait got shorter while a flush is pending, move it sooner\n\t\tthis.workingTreeClock.rearmIfEarlier();\n\n\t\tlet subDisposed = false;\n\t\treturn Object.assign(\n\t\t\tcreateDisposable(() => {\n\t\t\t\tif (subDisposed) return;\n\n\t\t\t\tsubDisposed = true;\n\n\t\t\t\tthis.workingTreeSubscribers.delete(id);\n\t\t\t\tthis.recalcWorkingTreeTiming();\n\n\t\t\t\tif (this.workingTreeSubscribers.size === 0) {\n\t\t\t\t\tthis.workingTreeClock.cancel();\n\t\t\t\t\tthis.pendingWorkingTreePaths.clear();\n\t\t\t\t\t// Dropped with the paths it describes — a stale flag would make the next subscriber's\n\t\t\t\t\t// first ordinary flush claim a partial set and force needless full refreshes.\n\t\t\t\t\tthis.pendingWorkingTreeIncomplete = false;\n\t\t\t\t\tthis.lifecycle?.onLastWorkingTreeSubscriber?.();\n\t\t\t\t}\n\t\t\t}),\n\t\t\t{ onDidChangeWorkingTree: this.workingTreeEmitter.event },\n\t\t);\n\t}\n\n\tsuspend(): void {\n\t\tthis._suspended = true;\n\t\t// Cancel active timers — events continue to coalesce but don't fire\n\t\tthis.repoClock.suspend();\n\t\tthis.workingTreeClock.suspend();\n\t}\n\n\tresume(delayMs?: number): void {\n\t\tif (!this._suspended) return;\n\n\t\tthis._suspended = false;\n\n\t\t// Flush accumulated changes (a clock with nothing pending stays idle); the max-wait window restarts from now\n\t\tthis.repoClock.resume(delayMs);\n\t\tthis.workingTreeClock.resume(delayMs);\n\t}\n\n\t/**\n\t * Inject changes (e.g., after a git operation the caller performed). Goes through debounce + suspend. Ref kinds\n\t * among them are unnamed — any ref of that kind may have moved.\n\t */\n\tfireChange(...changes: RepositoryChange[]): void {\n\t\tif (this._disposed || changes.length === 0) return;\n\n\t\tthis.coalesceRepo(changes, undefined, 'unfenced');\n\t}\n\n\t/** Immediate fire, bypassing debounce + suspend (for Closed/Opened). Ref kinds among them are unnamed. */\n\tfireChangeImmediate(...changes: RepositoryChange[]): void {\n\t\tif (this._disposed || changes.length === 0) return;\n\n\t\tconst event = new WatcherRepoChangeEvent(this.repoPath, changes);\n\t\t// Same contract as `flushRepo`: notify the global forwarder (which advances the cache's status clock) BEFORE\n\t\t// subscribers. Skipping it here would route changes to subscribers that no one advanced the clock for —\n\t\t// subscribers see them as session-routed and (correctly) don't re-advance, so the cache would go stale.\n\t\tthis.onDidFireRepoChangeCallback?.(event);\n\t\tthis.repoEmitter.fire(event);\n\t}\n\n\t/**\n\t * Push interpreted repo changes into the pipeline. `ref` is the full ref name (or ref directory, or pseudo-ref) whose\n\t * file changed; a ref kind in `changes` is attributed to it when the kinds match, and is otherwise unnamed.\n\t * `false` preserves cache invalidations for a config edit without claiming any ref moved. `configBranches`, for a\n\t * `config` among `changes`, are the local branches (full ref names) whose `branch.<name>` sections are all that\n\t * changed in `.git/config`; without it, the config change may be anywhere.\n\t */\n\tpushRepoChanges(changes: RepositoryChange[], ref?: string | false, configBranches?: ReadonlySet<string>): void {\n\t\tif (this._disposed) return;\n\n\t\tthis.coalesceRepo(changes, ref, 'raw', configBranches);\n\t}\n\n\t/**\n\t * Push watcher-observed repo changes that no write of core's can account for — an overflow's rescan, or an ignore\n\t * rule changed in the working tree — so {@link noteSelfWrite} never replaces them or takes them for its echo. Ref\n\t * kinds are unnamed.\n\t */\n\tpushUnfencedRepoChanges(changes: RepositoryChange[]): void {\n\t\tif (this._disposed) return;\n\n\t\tthis.coalesceRepo(changes, undefined, 'unfenced');\n\t}\n\n\t/**\n\t * Core announced one of its own writes for this repository: its caches were evicted and its host notified, both\n\t * after the write finished. The announcement becomes this session's one event for the write:\n\t *\n\t * - the announced kinds are pushed through the debounce and suspend like {@link fireChange}, and the event carries\n\t * them in `announced`. A ref kind is unnamed unless `refs` names refs of that kind (the full ref names the write\n\t * moved), in which case it carries those names and no other ref of the kind counts as changed by the write.\n\t * `config` is confined to the `branch.<name>` sections of the local branches `refs` names when\n\t * `options.configConfinedToRefs` says so, and the event carries them (`configBranches`);\n\t * - a raw change of an announced kind is the write's own report of it only when it is provably within what the\n\t * write announced: a ref the write named, or a config change confined to branches the write's config was confined\n\t * to. Pending when the announcement arrives, it is replaced. Arriving later, it merges and the kind stays\n\t * announced only within the repo debounce delay of the announcement; a window can stay pending for minutes while\n\t * the session is suspended, and a later move of the same ref is as likely an outside one. Any other report of\n\t * the kind is an outside change: it stays, and the kind isn't `announced`, since the host has not handled the\n\t * outside part. A write never names `HEAD`, but a commit locks it to write its reflog, so under a write that\n\t * announced `head`, a report of the `HEAD` file within the same delay is the write's echo, its `heads` part\n\t * included;\n\t * - a kind the write announced open (unnamed, or `config` unconfined) covers any change of the kind: raw changes\n\t * of it still pending are replaced, since the host evicts everything of the kind after the write. One arriving\n\t * within the repo debounce delay of the announcement, before its event fires, is taken for the write's own late\n\t * echo; any later report of the kind before the event fires clears the mark.\n\t *\n\t * Once the event fires, nothing is special: an echo the watcher delivers after it causes one extra, unannounced\n\t * event (a duplicate refresh, narrowed like any other), rather than risk losing an outside change of the same kind.\n\t *\n\t * Empty `changes`, or `'unknown'`, announces every kind a watcher can report (never `'unknown'` itself, which a\n\t * cache reads as a close), open whatever `refs` says; `'closed'` and `'opened'` are ignored. Only raw `.git` changes\n\t * are replaced or kept announced: {@link fireChange}, {@link fireChangeImmediate}, {@link pushUnfencedRepoChanges}\n\t * and working-tree changes always pass.\n\t */\n\tnoteSelfWrite(\n\t\tchanges: readonly RepositoryChange[],\n\t\trefs?: readonly string[],\n\t\toptions?: { readonly configConfinedToRefs?: boolean },\n\t): void {\n\t\tif (this._disposed) return;\n\n\t\tconst all = changes.length === 0 || changes.includes('unknown');\n\t\tconst announced = all ? allWatchedRepositoryChanges : changes.filter(c => isAnnounceable(c));\n\t\tconst named = all ? undefined : groupNamedRefs(changes, refs);\n\t\tconst configBranches =\n\t\t\t!all && options?.configConfinedToRefs === true && changes.includes('config')\n\t\t\t\t? getBranchRefs(refs)\n\t\t\t\t: undefined;\n\n\t\tfor (const change of this.pendingRepoChanges) {\n\t\t\tif (this.pendingRepoUnfencedChanges.has(change) || !isAnnounceable(change)) continue;\n\t\t\tif (!all && !changes.includes(change)) continue;\n\n\t\t\tif (change === 'config' && configBranches != null) {\n\t\t\t\t// An earlier announcement's config stays and merges with this one; a raw one is replaced only when it\n\t\t\t\t// is provably this write's own report\n\t\t\t\tif (this.pendingRepoAnnounced.has(change)) continue;\n\t\t\t\tif (!isSubsetOf(this.pendingRepoConfigBranches, configBranches)) continue;\n\t\t\t}\n\n\t\t\tconst names = isRefChangeKind(change) ? named?.get(change) : undefined;\n\t\t\tif (names != null) {\n\t\t\t\tfor (const name of names) {\n\t\t\t\t\tthis.pendingRepoRefs.delete(name);\n\t\t\t\t}\n\t\t\t\t// What's left of the kind came from outside the write, so it stays, unannounced\n\t\t\t\tif (this.hasPendingRefOfKind(change)) continue;\n\t\t\t}\n\n\t\t\tthis.pendingRepoChanges.delete(change);\n\t\t\tthis.pendingRepoAnnounced.delete(change);\n\t\t\tif (change === 'config') {\n\t\t\t\tthis.pendingRepoConfigBranches = undefined;\n\t\t\t}\n\t\t\tif (!isRefChangeKind(change)) continue;\n\n\t\t\t// An unnamed announcement covers any ref of the kind, so the replaced kind takes its named refs with it\n\t\t\tthis.pendingRepoUnnamedRefKinds.delete(change);\n\t\t\tfor (const ref of this.pendingRepoRefs) {\n\t\t\t\tif (getRefChangeKind(ref) === change) {\n\t\t\t\t\tthis.pendingRepoRefs.delete(ref);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (announced.length !== 0) {\n\t\t\tthis.coalesceRepo(\n\t\t\t\tnamed != null ? announced.filter(c => !isRefChangeKind(c) || !named.has(c)) : announced,\n\t\t\t\tundefined,\n\t\t\t\t'announced',\n\t\t\t\tconfigBranches,\n\t\t\t);\n\t\t\tif (named != null) {\n\t\t\t\tfor (const [kind, names] of named) {\n\t\t\t\t\tfor (const name of names) {\n\t\t\t\t\t\tthis.coalesceRepo([kind], name, 'announced');\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (this.pendingRepoChanges.size === 0) {\n\t\t\t// Nothing left to flush: idle the clock rather than let it fire an empty window\n\t\t\tthis.repoClock.cancel();\n\t\t}\n\t}\n\n\t/** Push working tree file changes into the pipeline */\n\t/** `incomplete` marks the batch as partial when the caller already dropped paths of its own. */\n\tpushWorkingTreeChanges(paths: Iterable<string>, incomplete?: boolean): void {\n\t\tif (this._disposed) return;\n\n\t\tthis._etagWorkingTree++;\n\t\tif (incomplete) {\n\t\t\tthis.pendingWorkingTreeIncomplete = true;\n\t\t}\n\t\tthis.coalesceWorkingTrees(paths);\n\t}\n\n\tprivate static minDelay(subscribers: Map<number, DebounceTiming>, fallback: number): number {\n\t\tlet min = fallback;\n\t\tfor (const { delayMs } of subscribers.values()) {\n\t\t\tif (delayMs < min) {\n\t\t\t\tmin = delayMs;\n\t\t\t}\n\t\t}\n\t\treturn min;\n\t}\n\n\t/** Unlike the delay, the default isn't a ceiling — a subscriber can opt out of the cap with `Infinity` */\n\tprivate static minMaxWait(subscribers: Map<number, DebounceTiming>, fallback: number): number {\n\t\tif (subscribers.size === 0) return fallback;\n\n\t\tlet min = Infinity;\n\t\tfor (const { maxWaitMs } of subscribers.values()) {\n\t\t\tif (maxWaitMs < min) {\n\t\t\t\tmin = maxWaitMs;\n\t\t\t}\n\t\t}\n\t\treturn min;\n\t}\n\n\tprivate recalcRepoTiming(): void {\n\t\tthis.effectiveRepoMs = RepositoryWatchSession.minDelay(this.repoSubscribers, this.defaultRepoMs);\n\t\tthis.effectiveRepoMaxWaitMs = RepositoryWatchSession.minMaxWait(\n\t\t\tthis.repoSubscribers,\n\t\t\tthis.defaultRepoMaxWaitMs,\n\t\t);\n\t}\n\n\tprivate recalcWorkingTreeTiming(): void {\n\t\tthis.effectiveWorkingTreeMs = RepositoryWatchSession.minDelay(\n\t\t\tthis.workingTreeSubscribers,\n\t\t\tthis.defaultWorkingTreeMs,\n\t\t);\n\t\tthis.effectiveWorkingTreeMaxWaitMs = RepositoryWatchSession.minMaxWait(\n\t\t\tthis.workingTreeSubscribers,\n\t\t\tthis.defaultWorkingTreeMaxWaitMs,\n\t\t);\n\t}\n\n\tprivate flushRepo(): void {\n\t\tif (this.pendingRepoChanges.size === 0) return;\n\n\t\t// The event is built only here — raw events just add to the pending sets (which the event copies)\n\t\tconst event = new WatcherRepoChangeEvent(\n\t\t\tthis.repoPath,\n\t\t\t[...this.pendingRepoChanges],\n\t\t\t{\n\t\t\t\tnames: this.pendingRepoRefs,\n\t\t\t\tunnamed: this.pendingRepoUnnamedRefKinds,\n\t\t\t\tconfigBranches: this.pendingRepoConfigBranches,\n\t\t\t},\n\t\t\tthis.pendingRepoAnnounced.keys(),\n\t\t);\n\t\tthis.clearPendingRepoChanges();\n\t\t// Advance any cache clock BEFORE notifying subscribers (same rationale as `flushWorkingTree`): a subscriber\n\t\t// that synchronously reads status must see the post-change generation, not a joinable pre-change run.\n\t\tthis.onDidFireRepoChangeCallback?.(event);\n\t\tthis.repoEmitter.fire(event);\n\t}\n\n\t/**\n\t * `origin` is a raw `.git` event, a push no announcement replaces ({@link fireChange},\n\t * {@link pushUnfencedRepoChanges}), or core's announced write. A kind stays announced only while nothing else\n\t * reports it in the pending window, save the write's own report of it (see {@link noteSelfWrite}).\n\t * `configBranches` confines a `config` among `changes` to those branches' sections; without it, it may be anywhere.\n\t */\n\tprivate coalesceRepo(\n\t\tchanges: readonly RepositoryChange[],\n\t\tref: string | false | undefined,\n\t\torigin: 'raw' | 'unfenced' | 'announced',\n\t\tconfigBranches?: ReadonlySet<string>,\n\t): void {\n\t\tif (changes.length === 0) return;\n\n\t\tconst now = performance.now();\n\t\tconst refKind = typeof ref === 'string' ? getRefChangeKind(ref) : undefined;\n\t\tfor (const change of changes) {\n\t\t\tconst pending = this.pendingRepoChanges.has(change);\n\t\t\t// What this report is confined to: the ref it names, or the branches whose config sections it changed\n\t\t\tlet names: ReadonlySet<string> | undefined;\n\t\t\tif (origin !== 'unfenced') {\n\t\t\t\tif (change === 'config') {\n\t\t\t\t\tnames = configBranches;\n\t\t\t\t} else if (typeof ref === 'string' && refKind === change) {\n\t\t\t\t\tnames = new Set([ref]);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst announcement = this.pendingRepoAnnounced.get(change);\n\t\t\tif (origin === 'announced') {\n\t\t\t\tif (!pending) {\n\t\t\t\t\tthis.pendingRepoAnnounced.set(change, {\n\t\t\t\t\t\tat: now,\n\t\t\t\t\t\tnames: names != null ? new Set(names) : undefined,\n\t\t\t\t\t});\n\t\t\t\t} else if (announcement != null) {\n\t\t\t\t\t// Added in place: a write naming many refs arrives here once per name, and copying the set each\n\t\t\t\t\t// time would cost quadratic time in the number of refs it moved\n\t\t\t\t\tif (announcement.names != null && names != null) {\n\t\t\t\t\t\tfor (const name of names) {\n\t\t\t\t\t\t\tannouncement.names.add(name);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tannouncement.names = undefined;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tconst own =\n\t\t\t\t\torigin === 'raw' && announcement != null && this.isOwnReport(change, announcement, names, now);\n\t\t\t\tif (announcement != null && !own) {\n\t\t\t\t\tthis.pendingRepoAnnounced.delete(change);\n\t\t\t\t} else if (!pending && origin === 'raw' && this.isHeadEcho(change, names, now)) {\n\t\t\t\t\t// A detached HEAD's move announces head without heads, and the HEAD file is all it wrote\n\t\t\t\t\tthis.pendingRepoAnnounced.set(change, { at: now, names: new Set(names) });\n\t\t\t\t}\n\t\t\t\tif (origin === 'unfenced') {\n\t\t\t\t\tthis.pendingRepoUnfencedChanges.add(change);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (change === 'config') {\n\t\t\t\tlet branches: Set<string> | undefined;\n\t\t\t\tif (!pending) {\n\t\t\t\t\tbranches = names != null ? new Set(names) : undefined;\n\t\t\t\t} else if (this.pendingRepoConfigBranches != null && names != null) {\n\t\t\t\t\tbranches = new Set([...this.pendingRepoConfigBranches, ...names]);\n\t\t\t\t}\n\t\t\t\tthis.pendingRepoConfigBranches =\n\t\t\t\t\tbranches != null && branches.size <= maxPendingRefNames ? branches : undefined;\n\t\t\t}\n\n\t\t\tthis.pendingRepoChanges.add(change);\n\t\t\tif (ref === false || !isRefChangeKind(change)) continue;\n\n\t\t\tif (\n\t\t\t\tref != null &&\n\t\t\t\trefKind === change &&\n\t\t\t\t(this.pendingRepoRefs.size < maxPendingRefNames || this.pendingRepoRefs.has(ref))\n\t\t\t) {\n\t\t\t\tthis.pendingRepoRefs.add(ref);\n\t\t\t} else {\n\t\t\t\tthis.pendingRepoUnnamedRefKinds.add(change);\n\t\t\t}\n\t\t}\n\t\tthis.repoClock.note();\n\t}\n\n\t/**\n\t * Whether a raw report of `change`, confined to `names` (`undefined`: not confined), is the announced write's own.\n\t * It must arrive within the repo debounce delay of the announcement, as it always had to. An open announcement\n\t * asks nothing more; one confined to names also needs every name the report carries to be one the write announced,\n\t * save the `heads` part of a `HEAD` report under a write that announced `head` in time, which is a commit's echo.\n\t */\n\tprivate isOwnReport(\n\t\tchange: RepositoryChange,\n\t\tannouncement: PendingAnnouncement,\n\t\tnames: ReadonlySet<string> | undefined,\n\t\tnow: number,\n\t): boolean {\n\t\tif (now - announcement.at > this.effectiveRepoMs) return false;\n\t\tif (announcement.names == null) return true;\n\t\tif (names == null) return false;\n\t\tif (isSubsetOf(names, announcement.names)) return true;\n\n\t\treturn this.isHeadEcho(change, names, now);\n\t}\n\n\t/** Whether a raw `heads` report confined to `HEAD` is the echo of a write that announced `head` in time */\n\tprivate isHeadEcho(change: RepositoryChange, names: ReadonlySet<string> | undefined, now: number): boolean {\n\t\tif (change !== 'heads' || names?.size !== 1 || !names.has('HEAD')) return false;\n\n\t\tconst head = this.pendingRepoAnnounced.get('head');\n\t\treturn head != null && now - head.at <= this.effectiveRepoMs;\n\t}\n\n\t/** Whether a pending report of `kind` remains, unnamed or under a name */\n\tprivate hasPendingRefOfKind(kind: RepositoryChange): boolean {\n\t\tif (!isRefChangeKind(kind)) return false;\n\t\tif (this.pendingRepoUnnamedRefKinds.has(kind)) return true;\n\n\t\tfor (const ref of this.pendingRepoRefs) {\n\t\t\tif (getRefChangeKind(ref) === kind) return true;\n\t\t}\n\t\treturn false;\n\t}\n\n\tprivate clearPendingRepoChanges(): void {\n\t\tthis.pendingRepoChanges.clear();\n\t\tthis.pendingRepoRefs.clear();\n\t\tthis.pendingRepoUnnamedRefKinds.clear();\n\t\tthis.pendingRepoUnfencedChanges.clear();\n\t\tthis.pendingRepoAnnounced.clear();\n\t\tthis.pendingRepoConfigBranches = undefined;\n\t}\n\n\tprivate flushWorkingTree(): void {\n\t\tif (!this.hasPendingWorkingTreeChanges) return;\n\n\t\tconst paths = this.pendingWorkingTreePaths;\n\t\tconst incomplete = this.pendingWorkingTreeIncomplete;\n\t\tthis.pendingWorkingTreePaths = new Set();\n\t\tthis.pendingWorkingTreeIncomplete = false;\n\t\t// Advance the cache's status clock (via WatchService's global forwarding) BEFORE notifying subscribers,\n\t\t// so a subscriber that synchronously reads status sees the post-change generation, not a joinable pre-change run.\n\t\tthis.onDidFireWorkingTreeChangeCallback?.(this.repoPath);\n\t\tthis.workingTreeEmitter.fire({ repoPath: this.repoPath, paths: paths, incomplete: incomplete });\n\t}\n\n\tprivate coalesceWorkingTrees(paths: Iterable<string>): void {\n\t\tfor (const p of paths) {\n\t\t\t// Stop retaining paths past the cap, but keep draining the iterable — the flag is what makes the\n\t\t\t// partial set safe to consume, and dropping out early would leave later paths for the next window.\n\t\t\tif (this.pendingWorkingTreePaths.size >= maxWorkingTreePaths && !this.pendingWorkingTreePaths.has(p)) {\n\t\t\t\tthis.pendingWorkingTreeIncomplete = true;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tthis.pendingWorkingTreePaths.add(p);\n\t\t}\n\n\t\tif (this.hasPendingWorkingTreeChanges) {\n\t\t\tthis.workingTreeClock.note();\n\t\t}\n\t}\n}\n\n/** `'closed'` and `'opened'` are lifecycle, not a write's effect, so an announcement never replaces or names them */\nfunction isAnnounceable(change: RepositoryChange): boolean {\n\treturn change !== 'closed' && change !== 'opened';\n}\n\n/**\n * The names in `refs` grouped by their ref kind, for the ref kinds among `changes` only; `undefined` when none is named.\n * Pseudo-refs (`HEAD`) are left out: a write names the refs it moved, and those are full ref names.\n */\nfunction groupNamedRefs(\n\tchanges: readonly RepositoryChange[],\n\trefs: readonly string[] | undefined,\n): Map<RefChangeKind, Set<string>> | undefined {\n\tif (!refs?.length) return undefined;\n\n\tlet named: Map<RefChangeKind, Set<string>> | undefined;\n\tfor (const ref of refs) {\n\t\tif (!ref.startsWith('refs/')) continue;\n\n\t\tconst kind = getRefChangeKind(ref);\n\t\tif (kind == null || !changes.includes(kind)) continue;\n\n\t\tnamed ??= new Map();\n\t\tlet names = named.get(kind);\n\t\tif (names == null) {\n\t\t\tnames = new Set();\n\t\t\tnamed.set(kind, names);\n\t\t}\n\t\tnames.add(ref);\n\t}\n\treturn named;\n}\n\n/** The local branches among `refs` (full `refs/heads/` names), or `undefined` when there are none */\nfunction getBranchRefs(refs: readonly string[] | undefined): Set<string> | undefined {\n\tlet branches: Set<string> | undefined;\n\tfor (const ref of refs ?? []) {\n\t\tif (ref.startsWith('refs/heads/')) {\n\t\t\t(branches ??= new Set()).add(ref);\n\t\t}\n\t}\n\treturn branches;\n}\n\n/** Whether every name in `names` is in `of`; never for `undefined`, which stands for any name */\nfunction isSubsetOf(names: ReadonlySet<string> | undefined, of: ReadonlySet<string>): boolean {\n\tif (names == null) return false;\n\n\tfor (const name of names) {\n\t\tif (!of.has(name)) return false;\n\t}\n\treturn true;\n}\n\n/** Wraps a session's repo event so each delivery is narrowed to the subscriber's relevant refs (see `subscribe`) */\nfunction createRelevantRefsEvent(\n\tevent: Event<WatcherRepoChangeEvent>,\n\trelevantRefs: () => Iterable<string>,\n): Event<WatcherRepoChangeEvent> {\n\treturn (listener, thisArgs, disposables) =>\n\t\tevent(\n\t\t\te => {\n\t\t\t\tconst narrowed = narrowToRelevantRefs(e, relevantRefs);\n\t\t\t\tif (narrowed != null) {\n\t\t\t\t\tlistener.call(thisArgs, narrowed);\n\t\t\t\t}\n\t\t\t},\n\t\t\tundefined,\n\t\t\tdisposables,\n\t\t);\n}\n\n/**\n * Narrows an event to the refs a subscriber cares about. Returns the event itself when no change kind is removed (the\n * kept names then stay a superset, which only makes `mayHaveChangedRef` more conservative), `undefined` when every kind\n * is removed, and otherwise a new event with the kept kinds, names and unnamed kinds. A `config` change confined to\n * some local branches' sections is removed unless a relevant ref is `HEAD` or one of those branches (see\n * `mayHaveChangedConfig`).\n */\nfunction narrowToRelevantRefs(\n\te: WatcherRepoChangeEvent,\n\trelevantRefs: () => Iterable<string>,\n): WatcherRepoChangeEvent | undefined {\n\t// Ref kinds that pass only if a relevant ref of that kind may have moved\n\tlet unresolved: Set<RefChangeKind> | undefined;\n\tfor (const change of e.changes) {\n\t\tif (!isRefChangeKind(change) || e.unnamedRefKinds.has(change)) continue;\n\n\t\t(unresolved ??= new Set()).add(change);\n\t}\n\t// A confined `config` passes only if a relevant ref is one of the branches it is confined to\n\tconst configBranches = e.configBranches;\n\tlet keepConfig = configBranches == null;\n\tif (unresolved == null && keepConfig) return e;\n\n\tunresolved ??= new Set();\n\n\tconst pseudoRefs: string[] = [];\n\tfor (const name of e.refs) {\n\t\tif (!name.startsWith('refs/')) {\n\t\t\tpseudoRefs.push(name);\n\t\t}\n\t}\n\tif (pseudoRefs.length !== 0) {\n\t\t// A pseudo-ref (`HEAD`, `ORIG_HEAD`) moving is a `heads` change for every subscriber\n\t\tunresolved.delete('heads');\n\t\tif (unresolved.size === 0 && keepConfig) return e;\n\t}\n\n\t// Names that equal or cover a relevant ref — collected as we go, since `relevantRefs` may be a one-shot iterable\n\tconst keptNames = new Set<string>(pseudoRefs);\n\tfor (const ref of relevantRefs()) {\n\t\t// `HEAD` is usually a symref to the checked-out branch, so any `heads` change may have moved it, and any branch's\n\t\t// config section may be its branch's\n\t\tif (ref === 'HEAD') {\n\t\t\tif (e.changes.has('heads')) {\n\t\t\t\tfor (const name of e.refs) {\n\t\t\t\t\tif (getRefChangeKind(name) === 'heads') {\n\t\t\t\t\t\tkeptNames.add(name);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tkeepConfig = true;\n\t\t\tunresolved.delete('heads');\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (!keepConfig && configBranches?.has(ref)) {\n\t\t\tkeepConfig = true;\n\t\t}\n\n\t\tconst kind = getRefChangeKind(ref);\n\t\tif (kind == null || !e.changes.has(kind)) continue;\n\n\t\tconst covering = findCoveringRefName(e.refs, ref);\n\t\tif (covering == null) continue;\n\n\t\tkeptNames.add(covering);\n\t\tunresolved.delete(kind);\n\t}\n\tif (unresolved.size === 0 && keepConfig) return e;\n\n\tconst keptChanges: RepositoryChange[] = [];\n\tfor (const change of e.changes) {\n\t\tif (change === 'config' ? keepConfig : !isRefChangeKind(change) || !unresolved.has(change)) {\n\t\t\tkeptChanges.push(change);\n\t\t}\n\t}\n\tif (keptChanges.length === 0) return undefined;\n\n\treturn new WatcherRepoChangeEvent(\n\t\te.repoPath,\n\t\tkeptChanges,\n\t\t{ names: keptNames, unnamed: e.unnamedRefKinds, configBranches: configBranches },\n\t\te.announced,\n\t);\n}\n"],"mappings":"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"}
@@ -2,7 +2,7 @@ import { UnifiedDisposable } from "../../utils/disposable.js";
2
2
  import { Event } from "../../utils/event.js";
3
3
  import { ResourceUsage } from "../../utils/resourceUsage.js";
4
4
  import { RepositoryWatchService } from "./watchService.js";
5
- import { AheadOfBaseTier } from "../providers/branches.js";
5
+ import { AheadOfBase, AheadOfBaseTier } from "../providers/branches.js";
6
6
  import { MergeTargetsChangeEvent } from "../cache.js";
7
7
  import { RepositoryService } from "../repositoryService.js";
8
8
  //#region ../git/src/watching/worktreeSummary.d.ts
@@ -96,6 +96,16 @@ export interface WorktreeSummaryServiceOptions {
96
96
  * repository's summaries react. Without it, or when it returns `undefined`, every summary reacts.
97
97
  */
98
98
  readonly getCommonPath?: (repoPath: string) => string | undefined;
99
+ /**
100
+ * For tests only; production never passes it. Called whenever the base re-measure answers in place of a full base
101
+ * read, with its answer and what a full `getAheadOfBase` of the same branch answers right after (`undefined` when
102
+ * that rejects or the provider has none), so a test can fail where the shortcut keeps an answer the full read would
103
+ * not. The summary keeps the re-measure's answer either way. The full read goes through the same provider and
104
+ * caches as the shortcut, so it checks the shortcut's reasoning, not whether those caches are current. The callback
105
+ * should record a mismatch for the test to fail on rather than throw: a throw is taken for a failed base read.
106
+ * Unset, it costs one check of the option.
107
+ */
108
+ readonly verifyBaseRemeasure?: (remeasured: AheadOfBase, full: AheadOfBase | undefined) => void;
99
109
  }
100
110
  /**
101
111
  * A cached, change-notifying summary of each subscribed worktree — its branch, upstream and ahead/behind, whether it
@@ -109,11 +119,15 @@ export interface WorktreeSummaryServiceOptions {
109
119
  * **Field-scoped recompute.** The summary is read in three groups — `dirty` (working changes), `branch` (branch,
110
120
  * HEAD's commit, upstream, ahead/behind) and `base` (ahead of base, the base's tip, and the refs it depends on) — and
111
121
  * an event recomputes only the groups it can affect: a working-tree edit, an index or ignore-rule change reads only
112
- * `dirty`; the upstream moving reads only `branch`; a ref the base depends on reads only `base`; a `gk` config change
122
+ * `dirty`; the upstream moving reads only `branch`; a ref the base depends on reads only `base`, and when the refs that
123
+ * moved are named and include the base's own ref, as a fetch names them, the provider first tries re-measuring the
124
+ * base from the previous one (one count per worktree when it only moved forward); a `gk` config change
113
125
  * (mostly bookkeeping writes to the shared `.git/gk/config`), or core's own write of the branch's merge target or
114
126
  * base, re-reads the branch's stored merge targets and recorded base, and reads `base` only when one of them changed;
115
- * a `.git/config` change reads `branch` and the provider's config inputs to the base (one cached read per checkout), and
116
- * reads `base` only when they, the stored values, HEAD's commit or the upstream changed, unless it was confined to the
127
+ * a `.git/config` change reads `branch` and the provider's config inputs to the base (one cached read shared by the
128
+ * repository's checkouts, or one per checkout where config can differ by checkout), and reads `base` only when the
129
+ * inputs its last base read consulted (every branch's tracking but those of branches it didn't depend on), the stored
130
+ * values, HEAD's commit or the upstream changed, unless it was confined to the
117
131
  * `branch.<name>` sections of branches the summary neither is on, tracks nor bases on (its `configBranches`), which
118
132
  * reads nothing; the checked-out branch moving, or a HEAD change, reads all of them. Stash, unrelated tags and
119
133
  * branches, and the like read nothing. One run is in flight per worktree at a time, at most four per repository (the
@@ -140,6 +154,7 @@ export declare class WorktreeSummaryService implements UnifiedDisposable {
140
154
  private readonly watchService;
141
155
  private readonly getRepository;
142
156
  private readonly getCommonPath;
157
+ private readonly verifyBaseRemeasure;
143
158
  private readonly mergeTargetsListener;
144
159
  private _disposed;
145
160
  constructor(options: WorktreeSummaryServiceOptions);