@gitkraken/core-gitlens 0.16.5 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/CHANGELOG.md +32 -1
  2. package/dist/git/cache.d.ts +8 -2
  3. package/dist/git/cache.js +15 -6
  4. package/dist/git/cache.js.map +1 -1
  5. package/dist/git/models/repository.d.ts +4 -2
  6. package/dist/git/models/repository.js +1 -0
  7. package/dist/git/models/repository.js.map +1 -1
  8. package/dist/git/models/worktree.d.ts +5 -1
  9. package/dist/git/models/worktree.js +5 -3
  10. package/dist/git/models/worktree.js.map +1 -1
  11. package/dist/git/providers/provider.d.ts +17 -0
  12. package/dist/git/providers/worktrees.d.ts +17 -2
  13. package/dist/git/run.types.d.ts +7 -0
  14. package/dist/git/service.d.ts +5 -2
  15. package/dist/git/service.js +11 -5
  16. package/dist/git/service.js.map +1 -1
  17. package/dist/git/watching/classifyChange.d.ts +4 -3
  18. package/dist/git/watching/classifyChange.js +7 -4
  19. package/dist/git/watching/classifyChange.js.map +1 -1
  20. package/dist/git/watching/provider.d.ts +6 -0
  21. package/dist/git/watching/watchGroup.d.ts +142 -3
  22. package/dist/git/watching/watchGroup.js +424 -19
  23. package/dist/git/watching/watchGroup.js.map +1 -1
  24. package/dist/git/watching/watchService.d.ts +25 -0
  25. package/dist/git/watching/watchService.js +27 -7
  26. package/dist/git/watching/watchService.js.map +1 -1
  27. package/dist/git/watching/watchSession.d.ts +13 -8
  28. package/dist/git/watching/watchSession.js +50 -10
  29. package/dist/git/watching/watchSession.js.map +1 -1
  30. package/dist/git/watching/watcherPatterns.d.ts +14 -0
  31. package/dist/git/watching/watcherPatterns.js +38 -1
  32. package/dist/git/watching/watcherPatterns.js.map +1 -1
  33. package/dist/git/watching/worktreeSummary.d.ts +64 -2
  34. package/dist/git/watching/worktreeSummary.js +326 -13
  35. package/dist/git/watching/worktreeSummary.js.map +1 -1
  36. package/dist/git-cli/cliGitProvider.d.ts +26 -0
  37. package/dist/git-cli/cliGitProvider.js +66 -5
  38. package/dist/git-cli/cliGitProvider.js.map +1 -1
  39. package/dist/git-cli/exec/commandClassifier.d.ts +9 -3
  40. package/dist/git-cli/exec/commandClassifier.js +98 -17
  41. package/dist/git-cli/exec/commandClassifier.js.map +1 -1
  42. package/dist/git-cli/exec/git.d.ts +20 -0
  43. package/dist/git-cli/exec/git.js +69 -7
  44. package/dist/git-cli/exec/git.js.map +1 -1
  45. package/dist/git-cli/exec/headState.d.ts +5 -0
  46. package/dist/git-cli/exec/headState.js +14 -1
  47. package/dist/git-cli/exec/headState.js.map +1 -1
  48. package/dist/git-cli/parsers/worktreeParser.js +2 -2
  49. package/dist/git-cli/parsers/worktreeParser.js.map +1 -1
  50. package/dist/git-cli/providers/branches.js +8 -4
  51. package/dist/git-cli/providers/branches.js.map +1 -1
  52. package/dist/git-cli/providers/graph.js +1 -1
  53. package/dist/git-cli/providers/graph.js.map +1 -1
  54. package/dist/git-cli/providers/operations.d.ts +3 -0
  55. package/dist/git-cli/providers/operations.js +14 -5
  56. package/dist/git-cli/providers/operations.js.map +1 -1
  57. package/dist/git-cli/providers/worktrees.d.ts +25 -3
  58. package/dist/git-cli/providers/worktrees.js +125 -32
  59. package/dist/git-cli/providers/worktrees.js.map +1 -1
  60. package/dist/git-cli/service.d.ts +2 -0
  61. package/dist/git-cli/service.js +4 -2
  62. package/dist/git-cli/service.js.map +1 -1
  63. 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} 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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"}
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 * Refs core's writes named since the last flush, kept when an outside report makes their kind unannounced: each is\n\t * a ref a write changed, never the echo of a ref directory\n\t */\n\tprivate readonly pendingRepoAnnouncedRefs = new Set<string>();\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 ref directory holding one, or a config change confined to branches\n\t * the write's config was confined to. Pending when the announcement arrives, it is replaced. Arriving later, it\n\t * merges and the kind stays announced only within the repo debounce delay of the announcement; a window can stay\n\t * pending for minutes while the session is suspended, and a later move of the same ref is as likely an outside\n\t * one. Any other report of the kind is an outside change: it stays, and the kind isn't `announced`, since the\n\t * host has not handled the outside part. A write never names `HEAD`, but a commit locks it to write its reflog,\n\t * so under a write that announced `head`, a report of the `HEAD` file within the same delay is the write's echo,\n\t * its `heads` part 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// Git reports the directory it creates or removes to hold the write's ref as well. Only a raw report can\n\t\t\t\t// be that echo: a name an earlier write announced is a ref it changed, and it can sit where this write's\n\t\t\t\t// directory is (delete `team`, then create `team/task`)\n\t\t\t\t// Walked from each name, as a write can name many refs (a fetch) while many outside ones are pending\n\t\t\t\tfor (const name of names) {\n\t\t\t\t\tfor (const dir of getRefDirectories(name)) {\n\t\t\t\t\t\tif (!this.pendingRepoAnnouncedRefs.has(dir)) {\n\t\t\t\t\t\t\tthis.pendingRepoRefs.delete(dir);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\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\t// Only a name kept among the pending refs can be taken for a directory's echo; past the cap\n\t\t\t\t\t\t// the kind went unnamed, which covers it, so the set stays as bounded as those refs\n\t\t\t\t\t\tif (this.pendingRepoRefs.has(name)) {\n\t\t\t\t\t\t\tthis.pendingRepoAnnouncedRefs.add(name);\n\t\t\t\t\t\t}\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\t// A ref and a directory of the same name can't coexist, which is what lets a directory report stand for its\n\t\t// refs; config sections `branch.team` and `branch.team/task` can\n\t\tif (isRefChangeKind(change) ? isCoveredBy(names, announcement.names) : isSubsetOf(names, announcement.names)) {\n\t\t\treturn true;\n\t\t}\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.pendingRepoAnnouncedRefs.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/**\n * Whether `ref` is a ref directory holding one of `names` (compared on `/` boundaries). A namespace root\n * (`refs/heads`) or a pseudo-ref is no such directory, so a report of it is never a write's own by this rule.\n */\nfunction isRefDirectoryOfAny(ref: string, names: ReadonlySet<string>): boolean {\n\tif (!ref.startsWith('refs/') || getRefChangeKind(ref) == null) return false;\n\n\tconst prefix = `${ref}/`;\n\tfor (const name of names) {\n\t\tif (name.startsWith(prefix)) return true;\n\t}\n\treturn false;\n}\n\n/**\n * The ref directories holding `ref`, innermost first, each of `ref`'s own kind; never a namespace root (`refs/heads`),\n * as {@link isRefDirectoryOfAny} counts them\n */\nfunction* getRefDirectories(ref: string): Iterable<string> {\n\tfor (let index = ref.lastIndexOf('/'); index > 0; index = ref.lastIndexOf('/', index - 1)) {\n\t\tconst dir = ref.substring(0, index);\n\t\tif (!dir.startsWith('refs/') || getRefChangeKind(dir) == null) return;\n\n\t\tyield dir;\n\t}\n}\n\n/**\n * Whether every name in `names` is one in `of` or a ref directory holding one; never for `undefined`, which stands for\n * any name\n */\nfunction isCoveredBy(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) && !isRefDirectoryOfAny(name, of)) return false;\n\t}\n\treturn true;\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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"}
@@ -32,6 +32,20 @@ export declare const gitDirProbesByEmittedPath: ReadonlyMap<string, readonly str
32
32
  * and `FETCH_HEAD`.
33
33
  */
34
34
  export declare function getGitDirRefProbes(refs: Iterable<string>): GitDirProbe[];
35
+ /**
36
+ * The registered folders whose worktrees core ignores, each as a `/`-separated literal path relative to a repository's
37
+ * common git dir, without `.` or empty segments; any other is dropped. A folder must lie strictly inside the common git
38
+ * dir (never absolute, never through `..`), name no glob syntax (a picomatch group or extglob's parentheses and `|`
39
+ * included) or `:` (a watcher's exclude would read it as a pattern, so `(refs|x)` would leave `refs` unwatched, or a
40
+ * drive), and not lie under `worktrees`, whose admin entries are what tells an ignored worktree apart.
41
+ */
42
+ export declare function sanitizeIgnoredWorktreeFolders(folders: readonly string[] | undefined): string[];
43
+ /**
44
+ * The registered folders a `.git` watcher over the common git dir may leave unwatched: those of
45
+ * {@link sanitizeIgnoredWorktreeFolders} that share no top-level directory with anything the `.git` globs read, which an
46
+ * exclude would hide. Such a folder's worktrees are still ignored, only watched.
47
+ */
48
+ export declare function getIgnoredWorktreeFolderExcludes(folders: readonly string[] | undefined): string[];
35
49
  /**
36
50
  * Directories to leave unwatched in the working tree (relative to it), for a provider that prunes natively —
37
51
  * the same ones {@link shouldIgnoreWorkingTreePath} drops from events.
@@ -1,3 +1,4 @@
1
+ import { isAbsolute } from "../../utils/path.js";
1
2
  //#region ../git/src/watching/watcherPatterns.ts
2
3
  const dotGitFiles = "index,HEAD,*_HEAD,MERGE_*,rebase-apply,rebase-apply/**,rebase-merge,rebase-merge/**,sequencer,sequencer/**";
3
4
  const dotGitCommonFiles = `config,gk/config,refs/**,packed-refs,shallow,info/exclude,FETCH_HEAD,worktrees,worktrees/*,worktrees/*/HEAD,worktrees/*/locked,worktrees/*/gitdir`;
@@ -90,6 +91,42 @@ function getGitDirRefProbes(refs) {
90
91
  return Array.from(paths, (path) => ({ path }));
91
92
  }
92
93
  /**
94
+ * The top-level names under the `.git` directory that the globs above read or a poll stats, as one case-insensitive
95
+ * pattern (a folder may sit on a case-insensitive file system)
96
+ */
97
+ const watchedGitDirNames = new Set([
98
+ ...`${dotGitFiles},${dotGitCommonFiles}`.split(","),
99
+ ...gitDirRootProbes.map((p) => p.path),
100
+ ...gitDirCommonProbes.map((p) => p.path)
101
+ ].map((p) => p.split("/")[0]));
102
+ const watchedGitDirNameRegex = new RegExp(`^(?:${Array.from(watchedGitDirNames, (name) => name.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, "[^/]*")).join("|")})$`, "i");
103
+ /**
104
+ * The registered folders whose worktrees core ignores, each as a `/`-separated literal path relative to a repository's
105
+ * common git dir, without `.` or empty segments; any other is dropped. A folder must lie strictly inside the common git
106
+ * dir (never absolute, never through `..`), name no glob syntax (a picomatch group or extglob's parentheses and `|`
107
+ * included) or `:` (a watcher's exclude would read it as a pattern, so `(refs|x)` would leave `refs` unwatched, or a
108
+ * drive), and not lie under `worktrees`, whose admin entries are what tells an ignored worktree apart.
109
+ */
110
+ function sanitizeIgnoredWorktreeFolders(folders) {
111
+ if (!folders?.length) return [];
112
+ const sanitized = /* @__PURE__ */ new Set();
113
+ for (const folder of folders) {
114
+ if (isAbsolute(folder) || /^[\\/]/.test(folder)) continue;
115
+ const segments = folder.split(/[\\/]/).filter((s) => s && s !== ".");
116
+ if (!segments.length || segments[0].toLowerCase() === "worktrees" || segments.some((s) => s === ".." || /[*?[\]{}()|!:]/.test(s))) continue;
117
+ sanitized.add(segments.join("/"));
118
+ }
119
+ return [...sanitized];
120
+ }
121
+ /**
122
+ * The registered folders a `.git` watcher over the common git dir may leave unwatched: those of
123
+ * {@link sanitizeIgnoredWorktreeFolders} that share no top-level directory with anything the `.git` globs read, which an
124
+ * exclude would hide. Such a folder's worktrees are still ignored, only watched.
125
+ */
126
+ function getIgnoredWorktreeFolderExcludes(folders) {
127
+ return sanitizeIgnoredWorktreeFolders(folders).filter((f) => !watchedGitDirNameRegex.test(f.split("/")[0]));
128
+ }
129
+ /**
93
130
  * Directories to leave unwatched in the working tree (relative to it), for a provider that prunes natively —
94
131
  * the same ones {@link shouldIgnoreWorkingTreePath} drops from events.
95
132
  */
@@ -115,6 +152,6 @@ function shouldIgnoreWorkingTreePath(absolutePath) {
115
152
  return ignoredWorkingTreePathRegex.test(absolutePath);
116
153
  }
117
154
  //#endregion
118
- export { dotGitGlobCombined, dotGitGlobCommon, dotGitGlobRoot, getGitDirRefProbes, gitDirCommonProbes, gitDirExcludes, gitDirProbesByEmittedPath, gitDirRootProbes, gitIgnoreGlob, gitInitGlob, shouldIgnoreRepoPath, shouldIgnoreWorkingTreePath, workingTreeExcludes };
155
+ export { dotGitGlobCombined, dotGitGlobCommon, dotGitGlobRoot, getGitDirRefProbes, getIgnoredWorktreeFolderExcludes, gitDirCommonProbes, gitDirExcludes, gitDirProbesByEmittedPath, gitDirRootProbes, gitIgnoreGlob, gitInitGlob, sanitizeIgnoredWorktreeFolders, shouldIgnoreRepoPath, shouldIgnoreWorkingTreePath, workingTreeExcludes };
119
156
 
120
157
  //# sourceMappingURL=watcherPatterns.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"watcherPatterns.js","names":[],"sources":["../../../../git/src/watching/watcherPatterns.ts"],"sourcesContent":["import type { GitDirProbe } from './gitDirPoller.js';\n\n// Glob patterns for watching files inside the .git directory.\n// They represent pure Git knowledge — the library owns these patterns and\n// provides them to the pluggable FileWatcherFactory.\n\nconst dotGitFiles =\n\t'index,HEAD,*_HEAD,MERGE_*,rebase-apply,rebase-apply/**,rebase-merge,rebase-merge/**,sequencer,sequencer/**';\n\n// `worktrees` (the bare entry, no `/*` suffix) is its own pattern: `worktree remove` deletes the whole\n// `worktrees/<id>` subtree in one syscall, and some platforms/watcher backends coalesce a same-instant\n// subtree delete into a single event for the PARENT directory — `worktrees/*` alone never matches that.\n// `classifyGitDirChange` classifies a bare `worktrees` path, so this is watch-coverage only.\n//\n// Only what `git worktree list` reports per worktree is watched here (its HEAD, lock and location). A\n// linked worktree's own index, `*_HEAD`, `MERGE_*`, rebase and sequencer state is watched by that\n// worktree's root watcher (`dotGitGlobRoot`) instead, so it never wakes sibling worktrees.\nconst dotGitWorktreeFiles = 'worktrees,worktrees/*,worktrees/*/HEAD,worktrees/*/locked,worktrees/*/gitdir';\n\nconst dotGitCommonFiles = `config,gk/config,refs/**,packed-refs,shallow,info/exclude,FETCH_HEAD,${dotGitWorktreeFiles}`;\n\n/** For standard repos (no worktrees): single watcher covering all .git files */\nexport const dotGitGlobCombined = `{${dotGitFiles},${dotGitCommonFiles}}`;\n\n/** For worktree repos: root watcher (worktree-specific files only) */\nexport const dotGitGlobRoot = `{${dotGitFiles}}`;\n\n/** For worktree repos: common watcher (shared config, refs, info, etc.) */\nexport const dotGitGlobCommon = `{${dotGitCommonFiles}}`;\n\n/**\n * Directories to leave unwatched under the `.git` directory (relative to it), for a provider that prunes natively.\n * Only directories nothing in the globs above reads: never anything under `refs`, `worktrees/*` itself, `gk`, `info`,\n * or the rebase/sequencer directories.\n */\nexport const gitDirExcludes: readonly string[] = [\n\t'objects',\n\t'logs',\n\t'hooks',\n\t'lfs',\n\t'modules',\n\t'rr-cache',\n\t'fsmonitor--daemon',\n\t'worktrees/*/logs',\n];\n\n/**\n * What a poll stats in a worktree's own `.git` directory when its watcher can't be trusted — the files\n * {@link dotGitGlobRoot} reads.\n */\nexport const gitDirRootProbes: readonly GitDirProbe[] = [\n\t{ path: 'HEAD' },\n\t{ path: 'index' },\n\t{ path: 'ORIG_HEAD' },\n\t{ path: 'MERGE_HEAD' },\n\t{ path: 'CHERRY_PICK_HEAD' },\n\t{ path: 'REVERT_HEAD' },\n\t{ path: 'REBASE_HEAD' },\n\t{ path: 'rebase-merge' },\n\t{ path: 'rebase-apply' },\n\t{ path: 'sequencer' },\n\t// Moves with every commit, reset or checkout on the checked-out branch, without the poll knowing its name\n\t{ path: 'logs/HEAD', emitAs: ['HEAD'] },\n];\n\n/**\n * What a poll stats in the common `.git` directory — the files {@link dotGitGlobCommon} reads, with the `refs/**` it\n * can't list narrowed to its namespace directories (whose mtime moves as a top-level ref is created or deleted) and\n * to the named refs of {@link getGitDirRefProbes}.\n */\nexport const gitDirCommonProbes: readonly GitDirProbe[] = [\n\t{ path: 'packed-refs' },\n\t{ path: 'config' },\n\t{ path: 'gk/config' },\n\t{ path: 'info/exclude' },\n\t{ path: 'refs/heads' },\n\t// Moves as a remote's directory appears or goes: `git remote remove` and `git remote rename` delete or move each of\n\t// its loose refs (a packed one is locked through a loose path too), and git removes the emptied directory. Reported\n\t// as every remote-tracking ref possibly moved, since the poll can't tell which remote it was. A remote whose name\n\t// nests under a directory another remote still uses (`foo/bar` beside `foo/baz`) leaves it unmoved, so only a named\n\t// ref's probe sees that one go\n\t{ path: 'refs/remotes' },\n\t{ path: 'refs/tags' },\n\t{ path: 'refs/stash' },\n\t{ path: 'worktrees' },\n\t{ path: 'shallow' },\n\t// A fetch writes it with the remote-tracking refs it updated, which the poll can't list. Git rewrites it even on a\n\t// fetch that moves nothing, so each fetch is reported as a `remotes` change: the cost of seeing remote-tracking\n\t// refs no subscriber names\n\t{ path: 'FETCH_HEAD', emitAs: ['FETCH_HEAD', 'refs/remotes'] },\n];\n\n/** For each path a probe of {@link gitDirRootProbes} or {@link gitDirCommonProbes} is emitted as, the probes emitted as it */\nexport const gitDirProbesByEmittedPath: ReadonlyMap<string, readonly string[]> = getProbesByEmittedPath([\n\t...gitDirRootProbes,\n\t...gitDirCommonProbes,\n]);\n\nfunction getProbesByEmittedPath(probes: readonly GitDirProbe[]): Map<string, string[]> {\n\tconst map = new Map<string, string[]>();\n\tfor (const probe of probes) {\n\t\tfor (const path of probe.emitAs ?? []) {\n\t\t\tconst paths = map.get(path);\n\t\t\tif (paths == null) {\n\t\t\t\tmap.set(path, [probe.path]);\n\t\t\t} else {\n\t\t\t\tpaths.push(probe.path);\n\t\t\t}\n\t\t}\n\t}\n\treturn map;\n}\n\n/**\n * Probes for named branches and remote-tracking refs: each loose ref file, and each directory between it and its\n * namespace (`refs/heads/feature` for `refs/heads/feature/x`, `refs/remotes/origin`), whose mtime moves as a sibling\n * ref is created or deleted. A ref created in a directory no named ref sits in is left to `packed-refs`, `logs/HEAD`\n * and `FETCH_HEAD`.\n */\nexport function getGitDirRefProbes(refs: Iterable<string>): GitDirProbe[] {\n\tconst paths = new Set<string>();\n\tfor (const ref of refs) {\n\t\tconst namespaceEnd = ref.startsWith('refs/heads/') ? 10 : ref.startsWith('refs/remotes/') ? 12 : -1;\n\t\tif (namespaceEnd === -1) continue;\n\n\t\tfor (let path = ref; path.length > namespaceEnd; path = path.substring(0, path.lastIndexOf('/'))) {\n\t\t\tpaths.add(path);\n\t\t}\n\t}\n\treturn Array.from(paths, path => ({ path: path }));\n}\n\n/**\n * Directories to leave unwatched in the working tree (relative to it), for a provider that prunes natively —\n * the same ones {@link shouldIgnoreWorkingTreePath} drops from events.\n */\nexport const workingTreeExcludes: readonly string[] = ['**/node_modules', '**/.git'];\n\n/** Glob for watching .gitignore files in the working tree */\nexport const gitIgnoreGlob = '.gitignore';\n\n/** Glob for detecting .git directory creation (repository init/clone) */\nexport const gitInitGlob = '**/.git';\n\n// Regex-based filters that complement the globs above. The globs tell\n// the watcher *what* to observe; these filters tell it what to *skip*\n// at runtime (transient lock files, daemon artifacts, etc.).\n\nconst ignoredRepoPathRegex = /(?:\\/|\\\\)fsmonitor--daemon(?:\\/|\\\\)|(?:^|\\/)index\\.lock$/;\n\n/**\n * Returns true if the `.git`-relative path should be ignored\n * (transient filesystem artifacts, not meaningful git state changes).\n */\nexport function shouldIgnoreRepoPath(relativePath: string): boolean {\n\treturn ignoredRepoPathRegex.test(relativePath);\n}\n\nconst ignoredWorkingTreePathRegex =\n\t/(?:(?:\\/|\\\\)node_modules(?:\\/|\\\\|$)|\\.git(?:\\/index\\.lock)?(?:\\/|\\\\|$)|\\.watchman-cookie-)/;\n\n/**\n * Returns true if the working tree path should be ignored\n * (node_modules, .git internals, watchman cookies).\n */\nexport function shouldIgnoreWorkingTreePath(absolutePath: string): boolean {\n\treturn ignoredWorkingTreePathRegex.test(absolutePath);\n}\n"],"mappings":";AAMA,MAAM,cACL;AAYD,MAAM,oBAAoB;;AAG1B,MAAa,qBAAqB,IAAI,YAAY,GAAG,kBAAkB;;AAGvE,MAAa,iBAAiB,IAAI,YAAY;;AAG9C,MAAa,mBAAmB,IAAI,kBAAkB;;;;;;AAOtD,MAAa,iBAAoC;CAChD;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACD;;;;;AAMA,MAAa,mBAA2C;CACvD,EAAE,MAAM,OAAO;CACf,EAAE,MAAM,QAAQ;CAChB,EAAE,MAAM,YAAY;CACpB,EAAE,MAAM,aAAa;CACrB,EAAE,MAAM,mBAAmB;CAC3B,EAAE,MAAM,cAAc;CACtB,EAAE,MAAM,cAAc;CACtB,EAAE,MAAM,eAAe;CACvB,EAAE,MAAM,eAAe;CACvB,EAAE,MAAM,YAAY;CAEpB;EAAE,MAAM;EAAa,QAAQ,CAAC,MAAM;CAAE;AACvC;;;;;;AAOA,MAAa,qBAA6C;CACzD,EAAE,MAAM,cAAc;CACtB,EAAE,MAAM,SAAS;CACjB,EAAE,MAAM,YAAY;CACpB,EAAE,MAAM,eAAe;CACvB,EAAE,MAAM,aAAa;CAMrB,EAAE,MAAM,eAAe;CACvB,EAAE,MAAM,YAAY;CACpB,EAAE,MAAM,aAAa;CACrB,EAAE,MAAM,YAAY;CACpB,EAAE,MAAM,UAAU;CAIlB;EAAE,MAAM;EAAc,QAAQ,CAAC,cAAc,cAAc;CAAE;AAC9D;;AAGA,MAAa,4BAAoE,uBAAuB,CACvG,GAAG,kBACH,GAAG,kBACJ,CAAC;AAED,SAAS,uBAAuB,QAAuD;CACtF,MAAM,sBAAM,IAAI,IAAsB;CACtC,KAAK,MAAM,SAAS,QACnB,KAAK,MAAM,QAAQ,MAAM,UAAU,CAAC,GAAG;EACtC,MAAM,QAAQ,IAAI,IAAI,IAAI;EAC1B,IAAI,SAAS,MACZ,IAAI,IAAI,MAAM,CAAC,MAAM,IAAI,CAAC;OAE1B,MAAM,KAAK,MAAM,IAAI;CAEvB;CAED,OAAO;AACR;;;;;;;AAQA,SAAgB,mBAAmB,MAAuC;CACzE,MAAM,wBAAQ,IAAI,IAAY;CAC9B,KAAK,MAAM,OAAO,MAAM;EACvB,MAAM,eAAe,IAAI,WAAW,aAAa,IAAI,KAAK,IAAI,WAAW,eAAe,IAAI,KAAK;EACjG,IAAI,iBAAiB,IAAI;EAEzB,KAAK,IAAI,OAAO,KAAK,KAAK,SAAS,cAAc,OAAO,KAAK,UAAU,GAAG,KAAK,YAAY,GAAG,CAAC,GAC9F,MAAM,IAAI,IAAI;CAEhB;CACA,OAAO,MAAM,KAAK,QAAO,UAAS,EAAQ,KAAK,EAAE;AAClD;;;;;AAMA,MAAa,sBAAyC,CAAC,mBAAmB,SAAS;;AAGnF,MAAa,gBAAgB;;AAG7B,MAAa,cAAc;AAM3B,MAAM,uBAAuB;;;;;AAM7B,SAAgB,qBAAqB,cAA+B;CACnE,OAAO,qBAAqB,KAAK,YAAY;AAC9C;AAEA,MAAM,8BACL;;;;;AAMD,SAAgB,4BAA4B,cAA+B;CAC1E,OAAO,4BAA4B,KAAK,YAAY;AACrD"}
1
+ {"version":3,"file":"watcherPatterns.js","names":[],"sources":["../../../../git/src/watching/watcherPatterns.ts"],"sourcesContent":["import { isAbsolute } from '@gitlens/utils/path.js';\nimport type { GitDirProbe } from './gitDirPoller.js';\n\n// Glob patterns for watching files inside the .git directory.\n// They represent pure Git knowledge — the library owns these patterns and\n// provides them to the pluggable FileWatcherFactory.\n\nconst dotGitFiles =\n\t'index,HEAD,*_HEAD,MERGE_*,rebase-apply,rebase-apply/**,rebase-merge,rebase-merge/**,sequencer,sequencer/**';\n\n// `worktrees` (the bare entry, no `/*` suffix) is its own pattern: `worktree remove` deletes the whole\n// `worktrees/<id>` subtree in one syscall, and some platforms/watcher backends coalesce a same-instant\n// subtree delete into a single event for the PARENT directory — `worktrees/*` alone never matches that.\n// `classifyGitDirChange` classifies a bare `worktrees` path, so this is watch-coverage only.\n//\n// Only what `git worktree list` reports per worktree is watched here (its HEAD, lock and location). A\n// linked worktree's own index, `*_HEAD`, `MERGE_*`, rebase and sequencer state is watched by that\n// worktree's root watcher (`dotGitGlobRoot`) instead, so it never wakes sibling worktrees.\nconst dotGitWorktreeFiles = 'worktrees,worktrees/*,worktrees/*/HEAD,worktrees/*/locked,worktrees/*/gitdir';\n\nconst dotGitCommonFiles = `config,gk/config,refs/**,packed-refs,shallow,info/exclude,FETCH_HEAD,${dotGitWorktreeFiles}`;\n\n/** For standard repos (no worktrees): single watcher covering all .git files */\nexport const dotGitGlobCombined = `{${dotGitFiles},${dotGitCommonFiles}}`;\n\n/** For worktree repos: root watcher (worktree-specific files only) */\nexport const dotGitGlobRoot = `{${dotGitFiles}}`;\n\n/** For worktree repos: common watcher (shared config, refs, info, etc.) */\nexport const dotGitGlobCommon = `{${dotGitCommonFiles}}`;\n\n/**\n * Directories to leave unwatched under the `.git` directory (relative to it), for a provider that prunes natively.\n * Only directories nothing in the globs above reads: never anything under `refs`, `worktrees/*` itself, `gk`, `info`,\n * or the rebase/sequencer directories.\n */\nexport const gitDirExcludes: readonly string[] = [\n\t'objects',\n\t'logs',\n\t'hooks',\n\t'lfs',\n\t'modules',\n\t'rr-cache',\n\t'fsmonitor--daemon',\n\t'worktrees/*/logs',\n];\n\n/**\n * What a poll stats in a worktree's own `.git` directory when its watcher can't be trusted — the files\n * {@link dotGitGlobRoot} reads.\n */\nexport const gitDirRootProbes: readonly GitDirProbe[] = [\n\t{ path: 'HEAD' },\n\t{ path: 'index' },\n\t{ path: 'ORIG_HEAD' },\n\t{ path: 'MERGE_HEAD' },\n\t{ path: 'CHERRY_PICK_HEAD' },\n\t{ path: 'REVERT_HEAD' },\n\t{ path: 'REBASE_HEAD' },\n\t{ path: 'rebase-merge' },\n\t{ path: 'rebase-apply' },\n\t{ path: 'sequencer' },\n\t// Moves with every commit, reset or checkout on the checked-out branch, without the poll knowing its name\n\t{ path: 'logs/HEAD', emitAs: ['HEAD'] },\n];\n\n/**\n * What a poll stats in the common `.git` directory — the files {@link dotGitGlobCommon} reads, with the `refs/**` it\n * can't list narrowed to its namespace directories (whose mtime moves as a top-level ref is created or deleted) and\n * to the named refs of {@link getGitDirRefProbes}.\n */\nexport const gitDirCommonProbes: readonly GitDirProbe[] = [\n\t{ path: 'packed-refs' },\n\t{ path: 'config' },\n\t{ path: 'gk/config' },\n\t{ path: 'info/exclude' },\n\t{ path: 'refs/heads' },\n\t// Moves as a remote's directory appears or goes: `git remote remove` and `git remote rename` delete or move each of\n\t// its loose refs (a packed one is locked through a loose path too), and git removes the emptied directory. Reported\n\t// as every remote-tracking ref possibly moved, since the poll can't tell which remote it was. A remote whose name\n\t// nests under a directory another remote still uses (`foo/bar` beside `foo/baz`) leaves it unmoved, so only a named\n\t// ref's probe sees that one go\n\t{ path: 'refs/remotes' },\n\t{ path: 'refs/tags' },\n\t{ path: 'refs/stash' },\n\t{ path: 'worktrees' },\n\t{ path: 'shallow' },\n\t// A fetch writes it with the remote-tracking refs it updated, which the poll can't list. Git rewrites it even on a\n\t// fetch that moves nothing, so each fetch is reported as a `remotes` change: the cost of seeing remote-tracking\n\t// refs no subscriber names\n\t{ path: 'FETCH_HEAD', emitAs: ['FETCH_HEAD', 'refs/remotes'] },\n];\n\n/** For each path a probe of {@link gitDirRootProbes} or {@link gitDirCommonProbes} is emitted as, the probes emitted as it */\nexport const gitDirProbesByEmittedPath: ReadonlyMap<string, readonly string[]> = getProbesByEmittedPath([\n\t...gitDirRootProbes,\n\t...gitDirCommonProbes,\n]);\n\nfunction getProbesByEmittedPath(probes: readonly GitDirProbe[]): Map<string, string[]> {\n\tconst map = new Map<string, string[]>();\n\tfor (const probe of probes) {\n\t\tfor (const path of probe.emitAs ?? []) {\n\t\t\tconst paths = map.get(path);\n\t\t\tif (paths == null) {\n\t\t\t\tmap.set(path, [probe.path]);\n\t\t\t} else {\n\t\t\t\tpaths.push(probe.path);\n\t\t\t}\n\t\t}\n\t}\n\treturn map;\n}\n\n/**\n * Probes for named branches and remote-tracking refs: each loose ref file, and each directory between it and its\n * namespace (`refs/heads/feature` for `refs/heads/feature/x`, `refs/remotes/origin`), whose mtime moves as a sibling\n * ref is created or deleted. A ref created in a directory no named ref sits in is left to `packed-refs`, `logs/HEAD`\n * and `FETCH_HEAD`.\n */\nexport function getGitDirRefProbes(refs: Iterable<string>): GitDirProbe[] {\n\tconst paths = new Set<string>();\n\tfor (const ref of refs) {\n\t\tconst namespaceEnd = ref.startsWith('refs/heads/') ? 10 : ref.startsWith('refs/remotes/') ? 12 : -1;\n\t\tif (namespaceEnd === -1) continue;\n\n\t\tfor (let path = ref; path.length > namespaceEnd; path = path.substring(0, path.lastIndexOf('/'))) {\n\t\t\tpaths.add(path);\n\t\t}\n\t}\n\treturn Array.from(paths, path => ({ path: path }));\n}\n\n/**\n * The top-level names under the `.git` directory that the globs above read or a poll stats, as one case-insensitive\n * pattern (a folder may sit on a case-insensitive file system)\n */\nconst watchedGitDirNames = new Set(\n\t[\n\t\t...`${dotGitFiles},${dotGitCommonFiles}`.split(','),\n\t\t...gitDirRootProbes.map(p => p.path),\n\t\t...gitDirCommonProbes.map(p => p.path),\n\t].map(p => p.split('/')[0]),\n);\nconst watchedGitDirNameRegex = new RegExp(\n\t`^(?:${Array.from(watchedGitDirNames, name => name.replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&').replace(/\\*/g, '[^/]*')).join('|')})$`,\n\t'i',\n);\n\n/**\n * The registered folders whose worktrees core ignores, each as a `/`-separated literal path relative to a repository's\n * common git dir, without `.` or empty segments; any other is dropped. A folder must lie strictly inside the common git\n * dir (never absolute, never through `..`), name no glob syntax (a picomatch group or extglob's parentheses and `|`\n * included) or `:` (a watcher's exclude would read it as a pattern, so `(refs|x)` would leave `refs` unwatched, or a\n * drive), and not lie under `worktrees`, whose admin entries are what tells an ignored worktree apart.\n */\nexport function sanitizeIgnoredWorktreeFolders(folders: readonly string[] | undefined): string[] {\n\tif (!folders?.length) return [];\n\n\tconst sanitized = new Set<string>();\n\tfor (const folder of folders) {\n\t\tif (isAbsolute(folder) || /^[\\\\/]/.test(folder)) continue;\n\n\t\tconst segments = folder.split(/[\\\\/]/).filter(s => s && s !== '.');\n\t\tif (\n\t\t\t!segments.length ||\n\t\t\tsegments[0].toLowerCase() === 'worktrees' ||\n\t\t\tsegments.some(s => s === '..' || /[*?[\\]{}()|!:]/.test(s))\n\t\t) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tsanitized.add(segments.join('/'));\n\t}\n\treturn [...sanitized];\n}\n\n/**\n * The registered folders a `.git` watcher over the common git dir may leave unwatched: those of\n * {@link sanitizeIgnoredWorktreeFolders} that share no top-level directory with anything the `.git` globs read, which an\n * exclude would hide. Such a folder's worktrees are still ignored, only watched.\n */\nexport function getIgnoredWorktreeFolderExcludes(folders: readonly string[] | undefined): string[] {\n\treturn sanitizeIgnoredWorktreeFolders(folders).filter(f => !watchedGitDirNameRegex.test(f.split('/')[0]));\n}\n\n/**\n * Directories to leave unwatched in the working tree (relative to it), for a provider that prunes natively —\n * the same ones {@link shouldIgnoreWorkingTreePath} drops from events.\n */\nexport const workingTreeExcludes: readonly string[] = ['**/node_modules', '**/.git'];\n\n/** Glob for watching .gitignore files in the working tree */\nexport const gitIgnoreGlob = '.gitignore';\n\n/** Glob for detecting .git directory creation (repository init/clone) */\nexport const gitInitGlob = '**/.git';\n\n// Regex-based filters that complement the globs above. The globs tell\n// the watcher *what* to observe; these filters tell it what to *skip*\n// at runtime (transient lock files, daemon artifacts, etc.).\n\nconst ignoredRepoPathRegex = /(?:\\/|\\\\)fsmonitor--daemon(?:\\/|\\\\)|(?:^|\\/)index\\.lock$/;\n\n/**\n * Returns true if the `.git`-relative path should be ignored\n * (transient filesystem artifacts, not meaningful git state changes).\n */\nexport function shouldIgnoreRepoPath(relativePath: string): boolean {\n\treturn ignoredRepoPathRegex.test(relativePath);\n}\n\nconst ignoredWorkingTreePathRegex =\n\t/(?:(?:\\/|\\\\)node_modules(?:\\/|\\\\|$)|\\.git(?:\\/index\\.lock)?(?:\\/|\\\\|$)|\\.watchman-cookie-)/;\n\n/**\n * Returns true if the working tree path should be ignored\n * (node_modules, .git internals, watchman cookies).\n */\nexport function shouldIgnoreWorkingTreePath(absolutePath: string): boolean {\n\treturn ignoredWorkingTreePathRegex.test(absolutePath);\n}\n"],"mappings":"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"}
@@ -67,6 +67,15 @@ export interface WorktreeSummary {
67
67
  */
68
68
  readonly stale: boolean;
69
69
  }
70
+ export interface WorktreeSummarySnapshot {
71
+ readonly summary: WorktreeSummary;
72
+ /**
73
+ * When {@link WorktreeSummary.dirty} was read (`Date.now()` as the read started), for a worktree without a
74
+ * subscription, whose working tree nothing watches; `undefined` for a subscribed worktree, whose summary follows
75
+ * its working tree
76
+ */
77
+ readonly dirtyReadAt: number | undefined;
78
+ }
70
79
  export interface WorktreeSummarySubscribeOptions {
71
80
  /** Debounce for repo (`.git`) changes this subscriber asks of the watch session; the shortest across subscribers wins */
72
81
  readonly delayMs?: number;
@@ -96,6 +105,15 @@ export interface WorktreeSummaryServiceOptions {
96
105
  * repository's summaries react. Without it, or when it returns `undefined`, every summary reacts.
97
106
  */
98
107
  readonly getCommonPath?: (repoPath: string) => string | undefined;
108
+ /**
109
+ * Reads a path's modification time (ms) and size; `undefined` when it doesn't exist, and a rejection means unknown.
110
+ * With it, a read of a worktree without a subscription re-reads the dirty flag only when the worktree's index file
111
+ * moved since the last one (or when asked to); without it, on every such read.
112
+ */
113
+ readonly stat?: (path: string) => Promise<{
114
+ readonly mtime: number;
115
+ readonly size: number;
116
+ } | undefined>;
99
117
  /**
100
118
  * For tests only; production never passes it. Called whenever the base re-measure answers in place of a full base
101
119
  * read, with its answer and what a full `getAheadOfBase` of the same branch answers right after (`undefined` when
@@ -140,6 +158,18 @@ export interface WorktreeSummaryServiceOptions {
140
158
  * fire only when some field's value differs from the previous summary — a recompute that reads the same answer is
141
159
  * silent, so a consumer can re-render on every event without diffing. A failed recompute keeps the last values and
142
160
  * sets `stale` (firing once); the next successful one clears it.
161
+ *
162
+ * **Worktrees without a subscription.** {@link getSummary} and {@link getSummarySnapshot} answer one from its last read
163
+ * (kept for the {@link maxUnwatchedReads} most recently asked), re-reading only the groups whose inputs moved since:
164
+ * HEAD's branch or commit reads all of them; the upstream's tip, or its name, reads `branch`; a ref the base depends on
165
+ * (where a remote's `HEAD` symref points, for one) reads `base`, re-measured from the previous one where that can
166
+ * answer; the config inputs its base read consulted read `branch` and the base when they changed; and the worktree's
167
+ * index file reads `dirty`. The inputs come from core's caches, so nothing moved means no git at all; only a `.git`
168
+ * watch of the worktree (or core's own writes) keeps them current, so a host must watch the `.git` of a worktree it
169
+ * asks about this way for others' changes to show. Its working tree is unwatched, so an edit that leaves the index
170
+ * alone goes unseen until `dirty` is read again: the snapshot says when that was, and `probeDirty` reads it anyway.
171
+ * Concurrent reads of a worktree share one, and take no read slot: each is a caller's foreground read, which never
172
+ * waits behind subscribed worktrees' runs.
143
173
  */
144
174
  export declare class WorktreeSummaryService implements UnifiedDisposable {
145
175
  private readonly _onDidChangeSummary;
@@ -151,9 +181,14 @@ export declare class WorktreeSummaryService implements UnifiedDisposable {
151
181
  private activeReads;
152
182
  /** Entries waiting for a read slot, oldest first */
153
183
  private readonly readWaiters;
184
+ /** The last read of each worktree without a subscription, by repository key */
185
+ private readonly unwatchedReads;
186
+ /** The read in flight for each worktree without a subscription, by repository key */
187
+ private readonly unwatchedFlights;
154
188
  private readonly watchService;
155
189
  private readonly getRepository;
156
190
  private readonly getCommonPath;
191
+ private readonly stat;
157
192
  private readonly verifyBaseRemeasure;
158
193
  private readonly mergeTargetsListener;
159
194
  private _disposed;
@@ -164,10 +199,36 @@ export declare class WorktreeSummaryService implements UnifiedDisposable {
164
199
  subscribe(repoPath: string, options?: WorktreeSummarySubscribeOptions): WorktreeSummarySubscription;
165
200
  /**
166
201
  * Returns a worktree's summary. For a subscribed worktree, the cached value — after waiting for any run in flight
167
- * or pending; it rejects with the read's error when there is still no value. Otherwise a one-off full read that
168
- * is neither cached nor watched.
202
+ * or pending; it rejects with the read's error when there is still no value. Otherwise the last read of it,
203
+ * re-reading what moved since (see {@link getSummarySnapshot}).
169
204
  */
170
205
  getSummary(repoPath: string, cancellation?: AbortSignal): Promise<WorktreeSummary>;
206
+ /**
207
+ * Returns a worktree's summary, with when its dirty flag was read. A subscribed worktree answers as
208
+ * {@link getSummary} does, with `dirtyReadAt` `undefined`, since its summary follows its working tree. Any other
209
+ * answers from its last read, re-reading only the groups whose inputs moved since (no git when none did), and in
210
+ * full the first time, or after a failed or stale read. Its working tree is unwatched, so the dirty flag is re-read
211
+ * only when the index file moved, or with `probeDirty`; a rejection drops the last read. Its inputs are core's
212
+ * cached answers, which only a `.git` watch of the worktree (or core's own writes) keeps current.
213
+ */
214
+ getSummarySnapshot(repoPath: string, options?: {
215
+ probeDirty?: boolean;
216
+ cancellation?: AbortSignal;
217
+ }): Promise<WorktreeSummarySnapshot>;
218
+ private getSubscribedSummary;
219
+ /** Joins the read in flight for `key`, or starts one; the read is aborted only once no caller waits for it */
220
+ private getUnwatchedSnapshot;
221
+ /**
222
+ * The read in flight for `key`, unless core wrote a merge target or base while it ran: it may have read the stored
223
+ * values before the write, and a caller after an awaited write sees them, as one of a subscribed worktree does
224
+ */
225
+ private getJoinableFlight;
226
+ private readUnwatched;
227
+ /**
228
+ * Reads an unwatched read's inputs: cached answers but for the index file's stat (see {@link UnwatchedInputs}), with
229
+ * the `HEAD` symrefs of `remotes` (every remote's when `undefined`)
230
+ */
231
+ private probeUnwatched;
171
232
  getResourceUsage(): ResourceUsage;
172
233
  private start;
173
234
  private onRepoChanged;
@@ -176,6 +237,7 @@ export declare class WorktreeSummaryService implements UnifiedDisposable {
176
237
  * `gkConfig` event for the same write then finds the keys unchanged and leaves the base alone.
177
238
  */
178
239
  private onMergeTargetsChanged;
240
+ private isInRepository;
179
241
  private queue;
180
242
  /** Runs until nothing is pending. Never rejects — every failure becomes entry state. */
181
243
  private drain;