@gitkraken/core-gitlens 0.16.5 → 0.18.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 (71) hide show
  1. package/CHANGELOG.md +35 -1
  2. package/dist/git/cache.d.ts +15 -2
  3. package/dist/git/cache.js +36 -9
  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/gitIgnoreFilter.d.ts +21 -3
  21. package/dist/git/watching/gitIgnoreFilter.js +74 -14
  22. package/dist/git/watching/gitIgnoreFilter.js.map +1 -1
  23. package/dist/git/watching/provider.d.ts +14 -0
  24. package/dist/git/watching/watchGroup.d.ts +142 -3
  25. package/dist/git/watching/watchGroup.js +424 -19
  26. package/dist/git/watching/watchGroup.js.map +1 -1
  27. package/dist/git/watching/watchService.d.ts +39 -1
  28. package/dist/git/watching/watchService.js +81 -20
  29. package/dist/git/watching/watchService.js.map +1 -1
  30. package/dist/git/watching/watchSession.d.ts +13 -8
  31. package/dist/git/watching/watchSession.js +50 -10
  32. package/dist/git/watching/watchSession.js.map +1 -1
  33. package/dist/git/watching/watcherPatterns.d.ts +14 -0
  34. package/dist/git/watching/watcherPatterns.js +38 -1
  35. package/dist/git/watching/watcherPatterns.js.map +1 -1
  36. package/dist/git/watching/worktreeSummary.d.ts +97 -7
  37. package/dist/git/watching/worktreeSummary.js +454 -27
  38. package/dist/git/watching/worktreeSummary.js.map +1 -1
  39. package/dist/git-cli/cliGitProvider.d.ts +26 -0
  40. package/dist/git-cli/cliGitProvider.js +70 -9
  41. package/dist/git-cli/cliGitProvider.js.map +1 -1
  42. package/dist/git-cli/exec/commandClassifier.d.ts +9 -3
  43. package/dist/git-cli/exec/commandClassifier.js +98 -17
  44. package/dist/git-cli/exec/commandClassifier.js.map +1 -1
  45. package/dist/git-cli/exec/git.d.ts +20 -0
  46. package/dist/git-cli/exec/git.js +69 -7
  47. package/dist/git-cli/exec/git.js.map +1 -1
  48. package/dist/git-cli/exec/headState.d.ts +5 -0
  49. package/dist/git-cli/exec/headState.js +14 -1
  50. package/dist/git-cli/exec/headState.js.map +1 -1
  51. package/dist/git-cli/parsers/worktreeParser.js +2 -2
  52. package/dist/git-cli/parsers/worktreeParser.js.map +1 -1
  53. package/dist/git-cli/providers/branches.js +9 -36
  54. package/dist/git-cli/providers/branches.js.map +1 -1
  55. package/dist/git-cli/providers/config.d.ts +21 -0
  56. package/dist/git-cli/providers/config.js +72 -1
  57. package/dist/git-cli/providers/config.js.map +1 -1
  58. package/dist/git-cli/providers/graph.js +1 -1
  59. package/dist/git-cli/providers/graph.js.map +1 -1
  60. package/dist/git-cli/providers/operations.d.ts +3 -0
  61. package/dist/git-cli/providers/operations.js +14 -5
  62. package/dist/git-cli/providers/operations.js.map +1 -1
  63. package/dist/git-cli/providers/worktrees.d.ts +25 -3
  64. package/dist/git-cli/providers/worktrees.js +125 -32
  65. package/dist/git-cli/providers/worktrees.js.map +1 -1
  66. package/dist/git-cli/service.d.ts +2 -0
  67. package/dist/git-cli/service.js +4 -2
  68. package/dist/git-cli/service.js.map +1 -1
  69. package/dist/plus/integrations/providers/issueSorts.d.ts +0 -1
  70. package/dist/plus/integrations/reads/drains.d.ts +0 -1
  71. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"watchService.js","names":[],"sources":["../../../../git/src/watching/watchService.ts"],"sourcesContent":["import type { UnifiedDisposable } from '@gitlens/utils/disposable.js';\nimport type { Event } from '@gitlens/utils/event.js';\nimport { Emitter } from '@gitlens/utils/event.js';\nimport { isDescendant, relative } from '@gitlens/utils/path.js';\nimport type { ResourceUsage } from '@gitlens/utils/resourceUsage.js';\nimport { getRepositoryKey } from '@gitlens/utils/uri.js';\nimport type { GitDir, RepositoryChange } from '../models/repository.js';\nimport type { WatcherRepoChangeEvent } from './changeEvent.js';\nimport type { PollTarget } from './gitDirPoller.js';\nimport { GitDirPoller } from './gitDirPoller.js';\nimport type { GitIgnoreFilter } from './gitIgnoreFilter.js';\nimport type {\n\tFileWatcher,\n\tFileWatcherOptions,\n\tFileWatcherReliability,\n\tFileWatchEvent,\n\tFileWatchingProvider,\n} from './provider.js';\nimport { shouldIgnoreWorkingTreePath, workingTreeExcludes } from './watcherPatterns.js';\nimport type { WatchGroupHooks } from './watchGroup.js';\nimport { WatchGroup } from './watchGroup.js';\nimport { RepositoryWatchSession } from './watchSession.js';\n\n/**\n * Cap on paths held while the gitignore filter loads. Higher than the session's own cap because these are\n * pre-filter — most of a build's output is typically gitignored and would be discarded on replay, so a\n * tighter bound here would mark batches incomplete when they filter down to almost nothing.\n */\nconst maxBufferedWorkingTreePaths = 5000;\n\n/** Trailing debounce before the git-ignored directory set is re-read after an ignore rule changes */\nconst ignoredDirectoriesRefreshDelayMs = 500;\n\ninterface WorkingTreeWatcherEntry {\n\treadonly session: RepositoryWatchSession;\n\t/** The event handler shared by every watcher this entry has had — a re-created watcher reuses it */\n\treadonly onEvent: (event: FileWatchEvent) => void;\n\treadonly filter?: GitIgnoreFilter;\n\twatcher: FileWatcher;\n\t/** The git-ignored directories the live watcher was created excluding (on top of the static set) */\n\tignoredDirectories: readonly string[];\n\t/** Bumped by every ignore-rule change and by stopping; a read that lands under a different value is stale */\n\tgeneration: number;\n\treading: boolean;\n\t/** A refresh came due while a read was in flight */\n\trereadQueued: boolean;\n\trefreshTimer?: ReturnType<typeof setTimeout>;\n\t/** What the live watcher last reported; a re-created watcher starts over from its own report */\n\treliability: FileWatcherReliability;\n\t/** Compares the host's working-tree fingerprint while the watcher isn't `reliable` */\n\tpollTarget?: PollTarget;\n\t/**\n\t * Git-ignored directories (repo-relative) whose events reached the post-filter, so the watcher isn't pruning them —\n\t * typically created after the last read (a first build's `dist/`). Each waits for a read that started after it.\n\t */\n\tunpruned?: Set<string>;\n\t/**\n\t * Directories a read showed git won't prune (they hold tracked files, or glob metacharacters in their names);\n\t * cleared when ignore rules change\n\t */\n\tunprunable?: Set<string>;\n}\n\n/**\n * A host whose watchers can report they can't be trusted (see `FileWatcherOptions.onDidChangeReliability`) passes\n * these to have core poll in their place. A `.git` directory is polled by statting the files its watchers read and\n * feeding each one that moved through the same interpretation as a watcher event; a working tree is never walked.\n */\nexport interface WatchPollingOptions {\n\t/**\n\t * Reads a path's modification time (ms) and size; `undefined` when it doesn't exist. A rejection means unknown:\n\t * the last reading stands, so it never counts as a change.\n\t */\n\treadonly stat: (path: string) => Promise<{ readonly mtime: number; readonly size: number } | undefined>;\n\t/** Whether a poll is worth running now. Default: the service isn't suspended (`suspendAll`/`resumeAll`) */\n\treadonly isActive?: () => boolean;\n\t/** How often each untrusted watcher is polled. Default: 60000ms */\n\treadonly intervalMs?: number;\n\t/**\n\t * How often `isActive` is looked at, which bounds how late the first poll after it turns true can be. Only with\n\t * `isActive`: the default gate wakes only for a poll. Default: 5000ms\n\t */\n\treadonly activeCheckMs?: number;\n\t/**\n\t * Refs to poll for a repository beyond its subscribers' `relevantRefs`: one with none (a host listing every branch)\n\t * still needs its checked-out branch's upstream, which a push moves without writing `FETCH_HEAD`\n\t */\n\treadonly getRefs?: (repoPath: string) => Iterable<string>;\n\t/**\n\t * Something that changes whenever the repository's working tree does (e.g. a digest of its status and the\n\t * changed files' stats), compared at each poll while working-tree subscribers are watching a working tree whose\n\t * watcher can't be trusted; a change is reported with no paths. `undefined` or a rejection skips that poll. With or\n\t * without it, a working tree whose watcher is `unwatchable` is reported changed (no paths) whenever its index or\n\t * `HEAD` changes.\n\t */\n\treadonly getWorkingTreeFingerprint?: (repoPath: string) => Promise<string | undefined>;\n}\n\nexport interface WatchServiceOptions {\n\treadonly watchingProvider: FileWatchingProvider;\n\t/** Default debounce for repo changes when subscriber doesn't specify. Default: 250ms */\n\treadonly defaultRepoDelayMs?: number;\n\t/** Default debounce for working tree changes when subscriber doesn't specify. Default: 2500ms */\n\treadonly defaultWorkingTreeDelayMs?: number;\n\t/** Default cap on how long continuous repo activity can defer a flush. `Infinity` disables it. Default: 2000ms */\n\treadonly defaultRepoMaxWaitMs?: number;\n\t/** Default cap on how long continuous working tree activity can defer a flush. `Infinity` disables it. Default: 10000ms */\n\treadonly defaultWorkingTreeMaxWaitMs?: number;\n\t/** Returns a GitIgnoreFilter for a given repo. When absent or returns undefined, gitignore filtering is skipped. */\n\treadonly getIgnoreFilter?: (repoPath: string, gitDirPath: string) => GitIgnoreFilter | undefined;\n\t/**\n\t * Returns the repo-relative directories an ignore rule matches, for a watching provider that prunes\n\t * `excludes` natively (`supportsExcludes`). Never called for one that doesn't. A rejection keeps the static set.\n\t */\n\treadonly getIgnoredDirectories?: (repoPath: string) => Promise<readonly string[]>;\n\t/** Opts into polling the watchers that report they can't be trusted. When absent, no watcher is asked. */\n\treadonly polling?: WatchPollingOptions;\n\t/**\n\t * Reads a file's bytes; a rejection means it couldn't be read. With it, each report of a repository's shared\n\t * `.git/config` is classified by what changed in the file: a change confined to some branches' `branch.<name>`\n\t * sections reaches only the subscribers following those branches, and carries no `remotes`. Without it, every\n\t * config change concerns everyone, as before.\n\t */\n\treadonly readFile?: (path: string) => Promise<Uint8Array>;\n}\n\nexport interface WatchHooks {\n\t/** Called when FETCH_HEAD changed (for last-fetched tracking) */\n\treadonly onFetchHeadChanged?: (repoPath: string) => void;\n\t/** Called when .git/info/exclude changed */\n\treadonly onIgnoresChanged?: (repoPath: string) => void;\n\t/** Called when .gitignore changed in the working tree (extension watches this separately) */\n\treadonly onGitIgnoreChanged?: (repoPath: string) => void;\n}\n\nexport interface WatchHandle {\n\treadonly session: RepositoryWatchSession;\n\tdispose(): void;\n}\n\ninterface SessionRecord {\n\treadonly session: RepositoryWatchSession;\n\treadonly gitDir: GitDir;\n\t/**\n\t * Hooks contributed by all current `watch()` callers for this repo path. Earlier\n\t * implementations stored just the first caller's hooks (`WatchHooks | undefined`) and\n\t * silently dropped subsequent callers' hooks — that broke the case where a low-level\n\t * subscriber (e.g. the Graph's secondary-worktree WIP watcher) opened the session\n\t * first without hooks, and a later `Repository.watch()` for the same path lost\n\t * its `markFetched` / `onIgnoresChanged` wiring. Each `watch()` call adds its hooks\n\t * to this set and removes them on dispose; fire sites iterate the set.\n\t */\n\treadonly watchHooks: Set<WatchHooks>;\n\trefCount: number;\n\t/** The mode {@link RepositoryWatchService.onDidChangeWatchMode} last announced; `'events'` while unwatched */\n\twatchMode: 'events' | 'poll';\n}\n\n/**\n * Global service that multiplexes repository watching across all repos.\n *\n * Manages:\n * - Session map: one `RepositoryWatchSession` per repo path (ref-counted)\n * - Watch groups: shared `.git` watchers across worktrees\n * - Working tree watchers: per-repo filesystem watchers with gitignore filtering\n *\n * Sessions and watch groups are created lazily on first `subscribe()`\n * within a session, and destroyed when the last subscriber leaves.\n */\nexport class RepositoryWatchService implements UnifiedDisposable {\n\tprivate readonly _onDidChangeRepository = new Emitter<WatcherRepoChangeEvent>();\n\t/** Global multiplexed event for ALL watched repos */\n\tget onDidChangeRepository(): Event<WatcherRepoChangeEvent> {\n\t\treturn this._onDidChangeRepository.event;\n\t}\n\n\tprivate readonly _onDidChangeWorkingTree = new Emitter<string>();\n\t/** Global multiplexed working-tree change event (repo path) for ALL watched repos. Fires once per\n\t * debounced flush per session — the single point that drives the cache's status clock for working-tree edits. */\n\tget onDidChangeWorkingTree(): Event<string> {\n\t\treturn this._onDidChangeWorkingTree.event;\n\t}\n\n\tprivate readonly _onDidChangeFetchHead = new Emitter<string>();\n\t/**\n\t * Global FETCH_HEAD change event (repo path) for ALL watched repos, fired as each change is seen (no debounce).\n\t * FETCH_HEAD maps to no {@link RepositoryChange} on the session, so this is how a host invalidates its cached\n\t * last-fetched time — the standalone service bridges it to the cache's `lastFetched`.\n\t */\n\tget onDidChangeFetchHead(): Event<string> {\n\t\treturn this._onDidChangeFetchHead.event;\n\t}\n\n\tprivate readonly _onDidChangeWatchMode = new Emitter<{ repoPath: string; mode: 'events' | 'poll' }>();\n\t/**\n\t * Fires as a watched repository starts being polled in place of a watcher (see {@link getWatchMode}), and with\n\t * `'events'` as it is watched natively again. Never fires as the repository stops being watched; one watched again\n\t * later starts from `'events'`.\n\t */\n\tget onDidChangeWatchMode(): Event<{ repoPath: string; mode: 'events' | 'poll' }> {\n\t\treturn this._onDidChangeWatchMode.event;\n\t}\n\n\tprivate readonly defaultRepoDelayMs?: number;\n\tprivate readonly defaultWorkingTreeDelayMs?: number;\n\tprivate readonly defaultRepoMaxWaitMs?: number;\n\tprivate readonly defaultWorkingTreeMaxWaitMs?: number;\n\tprivate readonly getIgnoreFilter?: (repoPath: string, gitDirPath: string) => GitIgnoreFilter | undefined;\n\tprivate readonly getIgnoredDirectories?: (repoPath: string) => Promise<readonly string[]>;\n\tprivate readonly provider: FileWatchingProvider;\n\tprivate readonly poller: GitDirPoller | undefined;\n\tprivate readonly readFile: ((path: string) => Promise<Uint8Array>) | undefined;\n\n\tprivate readonly sessionMap = new Map<string, SessionRecord>();\n\tprivate readonly watchGroups = new Map<string, WatchGroup>();\n\tprivate readonly wtWatchers = new Map<string, WorkingTreeWatcherEntry>();\n\tprivate _disposed = false;\n\t/** Global suspend state (driven by window focus); inherited by sessions created via {@link watch}. */\n\tprivate _suspended = false;\n\n\tconstructor(options: WatchServiceOptions) {\n\t\tthis.provider = options.watchingProvider;\n\t\tthis.defaultRepoDelayMs = options.defaultRepoDelayMs;\n\t\tthis.defaultWorkingTreeDelayMs = options.defaultWorkingTreeDelayMs;\n\t\tthis.defaultRepoMaxWaitMs = options.defaultRepoMaxWaitMs;\n\t\tthis.defaultWorkingTreeMaxWaitMs = options.defaultWorkingTreeMaxWaitMs;\n\t\tthis.getIgnoreFilter = options.getIgnoreFilter;\n\t\tthis.getIgnoredDirectories = options.getIgnoredDirectories;\n\t\tthis.readFile = options.readFile;\n\t\tif (options.polling != null) {\n\t\t\tthis.poller = new GitDirPoller(options.polling);\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\tfor (const record of this.sessionMap.values()) {\n\t\t\tthis.disposeSessionRecord(record);\n\t\t}\n\t\tthis.sessionMap.clear();\n\n\t\tfor (const group of this.watchGroups.values()) {\n\t\t\tgroup.dispose();\n\t\t}\n\t\tthis.watchGroups.clear();\n\n\t\tfor (const repoPath of [...this.wtWatchers.keys()]) {\n\t\t\tthis.stopWorkingTreeWatcher(repoPath);\n\t\t}\n\n\t\tthis.poller?.dispose();\n\n\t\tthis._onDidChangeRepository.dispose();\n\t\tthis._onDidChangeWorkingTree.dispose();\n\t\tthis._onDidChangeFetchHead.dispose();\n\t\tthis._onDidChangeWatchMode.dispose();\n\t}\n\n\t/**\n\t * Start watching a repository. Reference-counted:\n\t * same repoPath → same session (refCount++).\n\t * Worktrees sharing a .git dir share the WatchGroup.\n\t *\n\t * Returns a handle with the session and a dispose function.\n\t * Disposing the handle decrements the ref count.\n\t *\n\t * `repoPath` is keyed by {@link getRepositoryKey}, and every session, event and hook carries that key — the\n\t * same one the cache fences status reads on — so a host passing `C:\\repo\\` and one passing `c:/repo` share\n\t * a session and advance the same status clock.\n\t */\n\twatch(repoPath: string, gitDir: GitDir, watchHooks?: WatchHooks): WatchHandle | undefined {\n\t\tif (this._disposed) return undefined;\n\n\t\trepoPath = getRepositoryKey(repoPath);\n\n\t\tlet record = this.sessionMap.get(repoPath);\n\t\tif (record != null) {\n\t\t\trecord.refCount++;\n\t\t\tif (watchHooks != null) {\n\t\t\t\trecord.watchHooks.add(watchHooks);\n\t\t\t}\n\t\t} else {\n\t\t\tconst session = new RepositoryWatchSession({\n\t\t\t\trepoPath: repoPath,\n\t\t\t\tdefaultRepoDelayMs: this.defaultRepoDelayMs,\n\t\t\t\tdefaultWorkingTreeDelayMs: this.defaultWorkingTreeDelayMs,\n\t\t\t\tdefaultRepoMaxWaitMs: this.defaultRepoMaxWaitMs,\n\t\t\t\tdefaultWorkingTreeMaxWaitMs: this.defaultWorkingTreeMaxWaitMs,\n\t\t\t\tsuspended: this._suspended,\n\t\t\t\tlifecycle: {\n\t\t\t\t\tonFirstRepoSubscriber: (): void => {\n\t\t\t\t\t\tconst rec = this.sessionMap.get(repoPath);\n\t\t\t\t\t\tif (rec != null) {\n\t\t\t\t\t\t\tthis.connectToWatchGroup(rec);\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\tonLastRepoSubscriber: (): void => {\n\t\t\t\t\t\tconst rec = this.sessionMap.get(repoPath);\n\t\t\t\t\t\tif (rec != null) {\n\t\t\t\t\t\t\tthis.disconnectFromWatchGroup(rec);\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\tonFirstWorkingTreeSubscriber: (): void => {\n\t\t\t\t\t\tconst rec = this.sessionMap.get(repoPath);\n\t\t\t\t\t\tif (rec != null) {\n\t\t\t\t\t\t\tthis.startWorkingTreeWatcher(rec);\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\tonLastWorkingTreeSubscriber: (): void => {\n\t\t\t\t\t\tthis.stopWorkingTreeWatcher(repoPath);\n\t\t\t\t\t\tconst rec = this.sessionMap.get(repoPath);\n\t\t\t\t\t\tif (rec != null) {\n\t\t\t\t\t\t\tthis.updateWatchMode(rec);\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tonDidFireRepoChange: (event): void => {\n\t\t\t\t\tthis._onDidChangeRepository.fire(event);\n\t\t\t\t},\n\t\t\t\tonDidFireWorkingTreeChange: (repoPath): void => {\n\t\t\t\t\tthis._onDidChangeWorkingTree.fire(repoPath);\n\t\t\t\t},\n\t\t\t});\n\n\t\t\tconst watchHooksSet = new Set<WatchHooks>();\n\t\t\tif (watchHooks != null) {\n\t\t\t\twatchHooksSet.add(watchHooks);\n\t\t\t}\n\n\t\t\trecord = {\n\t\t\t\tsession: session,\n\t\t\t\tgitDir: gitDir,\n\t\t\t\twatchHooks: watchHooksSet,\n\t\t\t\trefCount: 1,\n\t\t\t\twatchMode: 'events',\n\t\t\t};\n\t\t\tthis.sessionMap.set(repoPath, record);\n\t\t}\n\n\t\tconst sessionRef = record.session;\n\t\tlet handleDisposed = false;\n\n\t\treturn {\n\t\t\tsession: sessionRef,\n\t\t\tdispose: (): void => {\n\t\t\t\tif (handleDisposed) return;\n\n\t\t\t\thandleDisposed = true;\n\n\t\t\t\tconst rec = this.sessionMap.get(repoPath);\n\t\t\t\tif (rec == null) return;\n\n\t\t\t\trec.refCount--;\n\t\t\t\tif (watchHooks != null) {\n\t\t\t\t\trec.watchHooks.delete(watchHooks);\n\t\t\t\t}\n\t\t\t\tif (rec.refCount <= 0) {\n\t\t\t\t\tthis.sessionMap.delete(repoPath);\n\t\t\t\t\tthis.disposeSessionRecord(rec);\n\t\t\t\t}\n\t\t\t},\n\t\t};\n\t}\n\n\t/**\n\t * Core announced one of its own writes for `repoPath`; the session watching it fires the announcement in place of\n\t * the write's own `.git` events (see {@link RepositoryWatchSession.noteSelfWrite}); `refs` are the full ref names it\n\t * moved, when it knows them, and `options.configConfinedToRefs` confines its `config` change to the sections of the\n\t * local branches among them (see `GitServiceHooks.repository.onChanged`). A path no session watches is ignored.\n\t */\n\tnoteSelfWrite(\n\t\trepoPath: string,\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\tthis.sessionMap.get(getRepositoryKey(repoPath))?.session.noteSelfWrite(changes, refs, options);\n\t}\n\n\t/** Get an existing session by repo path */\n\tgetSession(repoPath: string): RepositoryWatchSession | undefined {\n\t\treturn this.sessionMap.get(getRepositoryKey(repoPath))?.session;\n\t}\n\n\t/**\n\t * `'poll'` while a watcher the repository is watched through — its `.git` watchers or its working tree's — can't\n\t * watch, and is polled instead; `'events'` while it is watched natively (polled beside an `unreliable` watcher\n\t * included); `undefined` when nothing watches it, which {@link onDidChangeWatchMode} doesn't announce.\n\t */\n\tgetWatchMode(repoPath: string): 'events' | 'poll' | undefined {\n\t\tconst record = this.sessionMap.get(getRepositoryKey(repoPath));\n\t\treturn record != null ? this.computeWatchMode(record) : undefined;\n\t}\n\n\t/** Resource usage retained by repository watchers. */\n\tgetResourceUsage(): ResourceUsage {\n\t\tconst usage: ResourceUsage = {\n\t\t\t'watch.sessions.count': this.sessionMap.size,\n\t\t\t'watch.groups.count': this.watchGroups.size,\n\t\t\t'watch.workingTreeWatchers.count': this.wtWatchers.size,\n\t\t};\n\t\tif (this.poller != null) {\n\t\t\tusage['watch.pollTargets.count'] = this.poller.size;\n\t\t}\n\t\treturn usage;\n\t}\n\n\t/** Suspend all sessions and mark the service suspended so late-joining sessions start suspended */\n\tsuspendAll(): void {\n\t\tthis._suspended = true;\n\t\tfor (const record of this.sessionMap.values()) {\n\t\t\trecord.session.suspend();\n\t\t}\n\t\tthis.poller?.suspend();\n\t}\n\n\t/** Resume all sessions (clearing the global suspend state), with optional per-session delay */\n\tresumeAll(getDelay?: (session: RepositoryWatchSession) => number): void {\n\t\tthis._suspended = false;\n\t\tfor (const record of this.sessionMap.values()) {\n\t\t\tconst delayMs = getDelay?.(record.session);\n\t\t\trecord.session.resume(delayMs);\n\t\t}\n\t\tthis.poller?.resume();\n\t}\n\n\tprivate getOrCreateWatchGroup(commonGitDir: string): WatchGroup {\n\t\tlet group = this.watchGroups.get(commonGitDir);\n\t\tif (group == null) {\n\t\t\tgroup = new WatchGroup(commonGitDir, this.provider, this.poller, this.readFile);\n\t\t\tthis.watchGroups.set(commonGitDir, group);\n\t\t}\n\t\treturn group;\n\t}\n\n\tprivate maybeDisposeWatchGroup(commonGitDir: string): void {\n\t\tconst group = this.watchGroups.get(commonGitDir);\n\t\tif (group == null) return;\n\n\t\tif (group.sessions.size === 0) {\n\t\t\tgroup.dispose();\n\t\t\tthis.watchGroups.delete(commonGitDir);\n\t\t}\n\t}\n\n\tprivate connectToWatchGroup(record: SessionRecord): void {\n\t\tconst { session, gitDir, watchHooks } = record;\n\t\tconst commonGitDir = (gitDir.commonUri ?? gitDir.uri).fsPath;\n\t\tconst group = this.getOrCreateWatchGroup(commonGitDir);\n\n\t\tconst hooks: WatchGroupHooks = {\n\t\t\tonRepoChanged: (\n\t\t\t\t_repoPath: string,\n\t\t\t\tchanges: RepositoryChange[],\n\t\t\t\tref?: string | false,\n\t\t\t\toverflow?: boolean,\n\t\t\t\tconfigBranches?: ReadonlySet<string>,\n\t\t\t): void => {\n\t\t\t\tif (overflow) {\n\t\t\t\t\tsession.pushUnfencedRepoChanges(changes);\n\t\t\t\t} else {\n\t\t\t\t\tsession.pushRepoChanges(changes, ref, configBranches);\n\t\t\t\t}\n\n\t\t\t\t// A working tree nothing watches is read again whenever the index or HEAD says it may have moved\n\t\t\t\tif (\n\t\t\t\t\tthis.poller != null &&\n\t\t\t\t\t(changes.includes('index') || changes.includes('head')) &&\n\t\t\t\t\tthis.wtWatchers.get(session.repoPath)?.reliability === 'unwatchable'\n\t\t\t\t) {\n\t\t\t\t\tsession.pushWorkingTreeChanges([], true);\n\t\t\t\t}\n\t\t\t},\n\t\t\tonFetchHeadChanged: (repoPath: string): void => {\n\t\t\t\t// Global first, so a host's cache drops its last-fetched time before any hook re-reads it\n\t\t\t\tthis._onDidChangeFetchHead.fire(repoPath);\n\t\t\t\t// Iterate at fire time so callers that joined after the group was wired up\n\t\t\t\t// still receive events (e.g. a `Repository.watch()` whose hooks landed\n\t\t\t\t// after a hookless `GitRepositoryService.watch` registered first).\n\t\t\t\tfor (const h of watchHooks) {\n\t\t\t\t\th.onFetchHeadChanged?.(repoPath);\n\t\t\t\t}\n\t\t\t},\n\t\t\tonIgnoresChanged: (repoPath: string): void => {\n\t\t\t\tthis.refreshWorkingTreeFilter(repoPath);\n\t\t\t\tfor (const h of watchHooks) {\n\t\t\t\t\th.onIgnoresChanged?.(repoPath);\n\t\t\t\t}\n\t\t\t},\n\t\t\tgetProbeRefs: (): Iterable<string> => {\n\t\t\t\tconst refs = session.getRelevantRefs();\n\t\t\t\tfor (const ref of this.poller?.options.getRefs?.(session.repoPath) ?? []) {\n\t\t\t\t\trefs.add(ref);\n\t\t\t\t}\n\t\t\t\treturn refs;\n\t\t\t},\n\t\t\tonUnwatchableChanged: (): void => this.updateWatchMode(record),\n\t\t};\n\n\t\tgroup.addSession(session.repoPath, gitDir, hooks);\n\t\tthis.updateWatchMode(record);\n\t}\n\n\tprivate disconnectFromWatchGroup(record: SessionRecord): void {\n\t\tconst { session, gitDir } = record;\n\t\tconst commonGitDir = (gitDir.commonUri ?? gitDir.uri).fsPath;\n\t\tconst group = this.watchGroups.get(commonGitDir);\n\t\tif (group == null) return;\n\n\t\tgroup.removeSession(session.repoPath);\n\t\tthis.maybeDisposeWatchGroup(commonGitDir);\n\t\tthis.updateWatchMode(record);\n\t}\n\n\tprivate computeWatchMode(record: SessionRecord): 'events' | 'poll' | undefined {\n\t\tconst repoPath = record.session.repoPath;\n\t\tconst group = this.watchGroups.get((record.gitDir.commonUri ?? record.gitDir.uri).fsPath);\n\t\tconst connected = group?.sessions.has(repoPath) ?? false;\n\t\tconst wt = this.wtWatchers.get(repoPath);\n\t\tif (!connected && wt == null) return undefined;\n\n\t\treturn group?.isUnwatchable(repoPath) || wt?.reliability === 'unwatchable' ? 'poll' : 'events';\n\t}\n\n\tprivate updateWatchMode(record: SessionRecord): void {\n\t\tif (this._disposed) return;\n\n\t\tconst mode = this.computeWatchMode(record);\n\t\tif (mode == null) {\n\t\t\trecord.watchMode = 'events';\n\t\t\treturn;\n\t\t}\n\t\tif (mode === record.watchMode) return;\n\n\t\trecord.watchMode = mode;\n\t\tthis._onDidChangeWatchMode.fire({ repoPath: record.session.repoPath, mode: mode });\n\t}\n\n\tprivate startWorkingTreeWatcher(record: SessionRecord): void {\n\t\tconst { session, gitDir, watchHooks } = record;\n\t\tconst repoPath = session.repoPath;\n\t\tif (this.wtWatchers.has(repoPath)) return;\n\n\t\tconst canPrune = this.canPruneIgnoredDirectories();\n\t\t// Assigned once the first watcher exists; read by the handlers below instead of a map lookup per event\n\t\tlet entry: WorkingTreeWatcherEntry | undefined;\n\n\t\tlet filter: GitIgnoreFilter | undefined;\n\t\tif (this.getIgnoreFilter != null) {\n\t\t\tfilter = this.getIgnoreFilter(repoPath, gitDir.uri.fsPath);\n\t\t}\n\n\t\t// Buffer events while the gitignore filter loads, then replay\n\t\tlet filterReady = filter == null;\n\t\tlet buffered: string[] | undefined = filter != null ? [] : undefined;\n\t\tlet bufferIncomplete = false;\n\n\t\tif (filter != null) {\n\t\t\tvoid filter\n\t\t\t\t.ready()\n\t\t\t\t.then(() => {\n\t\t\t\t\tfilterReady = true;\n\t\t\t\t\tif (buffered?.length) {\n\t\t\t\t\t\tconst paths = filterWorkingTreePaths(buffered, repoPath, filter);\n\t\t\t\t\t\tif (paths.length > 0 || bufferIncomplete) {\n\t\t\t\t\t\t\tsession.pushWorkingTreeChanges(paths, bufferIncomplete);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbuffered = undefined;\n\t\t\t\t\tbufferIncomplete = false;\n\t\t\t\t})\n\t\t\t\t.catch(() => {\n\t\t\t\t\tfilterReady = true;\n\t\t\t\t\tbuffered = undefined;\n\t\t\t\t});\n\t\t}\n\n\t\t// An ignore rule changed (or may have): refresh the filter and the pruned set, and notify the host\n\t\tconst onIgnoreRulesChanged = (): void => {\n\t\t\tif (filter != null) {\n\t\t\t\tvoid filter.refresh();\n\t\t\t}\n\t\t\tif (entry != null) {\n\t\t\t\tthis.scheduleIgnoredDirectoriesRefresh(entry);\n\t\t\t}\n\t\t\t// See `connectToWatchGroup` for the iterate-at-fire-time rationale.\n\t\t\tfor (const h of watchHooks) {\n\t\t\t\th.onGitIgnoreChanged?.(repoPath);\n\t\t\t}\n\t\t\t// Route the ignore-rule change through the repo channel as an `ignores` change (like `info/exclude`),\n\t\t\t// so it reaches EVERY session subscriber — including a closed secondary worktree's `watch()` `repoSub`,\n\t\t\t// which has no `onGitIgnoreChanged` hook and would otherwise keep a stale untracked-file list. Coalesces\n\t\t\t// with any hook-fired `ignores` above, so open repos don't double. Unfenced: it comes from the working tree,\n\t\t\t// which a self-write never holds back.\n\t\t\tsession.pushUnfencedRepoChanges(['ignores']);\n\t\t};\n\n\t\tconst onEvent = (event: FileWatchEvent): void => {\n\t\t\t// Dropped events: nothing to filter or buffer, the whole tree needs a re-read. A `.gitignore` edit may be\n\t\t\t// among them, and a filter left on the old rules would keep dropping changes they used to ignore\n\t\t\tif (event.reason === 'overflow') {\n\t\t\t\tsession.pushWorkingTreeChanges([], true);\n\t\t\t\tonIgnoreRulesChanged();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Fast noise check (node_modules, .git, .watchman-cookie-)\n\t\t\tif (shouldIgnoreWorkingTreePath(event.path)) return;\n\n\t\t\t// Detect .gitignore changes (nested ones too, unless git skips them for sitting in an ignored directory).\n\t\t\t// The root .gitignore is only an ignore change; a nested one is also an ordinary working-tree change.\n\t\t\tconst relativePath = isDescendant(event.path, repoPath) ? relative(repoPath, event.path) : undefined;\n\t\t\tif (relativePath === '.gitignore') {\n\t\t\t\tonIgnoreRulesChanged();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (relativePath?.endsWith('/.gitignore') && !(filterReady && filter?.isIgnored(relativePath))) {\n\t\t\t\tonIgnoreRulesChanged();\n\t\t\t}\n\n\t\t\tif (!filterReady) {\n\t\t\t\t// Buffer until the gitignore filter is ready. Capped: `ready()` awaits a `core.excludesFile`\n\t\t\t\t// read, which queues behind every other git command, so this window can stay open for a long\n\t\t\t\t// time — and these paths haven't been gitignore-filtered yet, so a build's output all lands\n\t\t\t\t// here. Past the cap, stop retaining and mark the batch partial rather than growing.\n\t\t\t\tif (buffered != null && buffered.length >= maxBufferedWorkingTreePaths) {\n\t\t\t\t\tbufferIncomplete = true;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tbuffered?.push(event.path);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Gitignore filter (sync after ready)\n\t\t\tif (filter != null && relativePath != null && filter.isIgnored(relativePath)) {\n\t\t\t\tif (canPrune && entry != null) {\n\t\t\t\t\tthis.onIgnoredEvent(entry, filter, relativePath);\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tsession.pushWorkingTreeChanges([event.path]);\n\t\t};\n\n\t\tconst created = this.createWorkingTreeWatcher(repoPath, onEvent, workingTreeExcludes);\n\t\tentry = {\n\t\t\tsession: session,\n\t\t\tonEvent: onEvent,\n\t\t\tfilter: filter,\n\t\t\twatcher: created.watcher,\n\t\t\tignoredDirectories: [],\n\t\t\tgeneration: 0,\n\t\t\treading: false,\n\t\t\trereadQueued: false,\n\t\t\treliability: 'reliable',\n\t\t};\n\t\tthis.wtWatchers.set(repoPath, entry);\n\t\tthis.setWorkingTreeReliability(entry, created.reliability, true);\n\n\t\tif (canPrune) {\n\t\t\tvoid this.refreshIgnoredDirectories(entry, repoPath);\n\t\t}\n\t}\n\n\tprivate stopWorkingTreeWatcher(repoPath: string): void {\n\t\tconst entry = this.wtWatchers.get(repoPath);\n\t\tif (entry == null) return;\n\n\t\t// Drops any read still in flight, and any refresh waiting on its debounce\n\t\tentry.generation++;\n\t\tclearTimeout(entry.refreshTimer);\n\t\tentry.refreshTimer = undefined;\n\t\tentry.watcher.dispose();\n\t\tif (entry.pollTarget != null) {\n\t\t\tthis.poller?.delete(entry.pollTarget);\n\t\t\tentry.pollTarget = undefined;\n\t\t}\n\t\tthis.wtWatchers.delete(repoPath);\n\t}\n\n\t/** Creates a working-tree watcher, with the reliability it reported before returning */\n\tprivate createWorkingTreeWatcher(\n\t\trepoPath: string,\n\t\tonEvent: (event: FileWatchEvent) => void,\n\t\texcludes: readonly string[],\n\t): { watcher: FileWatcher; reliability: FileWatcherReliability } {\n\t\tif (this.poller == null) {\n\t\t\treturn {\n\t\t\t\twatcher: this.provider.createWatcher(repoPath, '**', onEvent, { excludes: excludes }),\n\t\t\t\treliability: 'reliable',\n\t\t\t};\n\t\t}\n\n\t\tlet watcher: FileWatcher | undefined;\n\t\tlet reliability: FileWatcherReliability = 'reliable';\n\t\tconst options: FileWatcherOptions = {\n\t\t\texcludes: excludes,\n\t\t\tonDidChangeReliability: state => {\n\t\t\t\tif (watcher == null) {\n\t\t\t\t\treliability = state;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Only the live watcher speaks for the working tree, never one already replaced\n\t\t\t\tconst entry = this.wtWatchers.get(repoPath);\n\t\t\t\tif (entry?.watcher === watcher) {\n\t\t\t\t\tthis.setWorkingTreeReliability(entry, state);\n\t\t\t\t}\n\t\t\t},\n\t\t};\n\t\twatcher = this.provider.createWatcher(repoPath, '**', onEvent, options);\n\t\treturn { watcher: watcher, reliability: reliability };\n\t}\n\n\tprivate setWorkingTreeReliability(\n\t\tentry: WorkingTreeWatcherEntry,\n\t\tstate: FileWatcherReliability,\n\t\tinitial?: boolean,\n\t): void {\n\t\tconst previous = entry.reliability;\n\t\tif (previous === state) return;\n\n\t\tentry.reliability = state;\n\t\tthis.updateWorkingTreePollTarget(entry);\n\n\t\tif ((state === 'unwatchable') === (previous === 'unwatchable')) return;\n\n\t\t// The watch went down or came back: whatever changed meanwhile went unseen\n\t\tif (!initial) {\n\t\t\tentry.session.pushWorkingTreeChanges([], true);\n\t\t}\n\t\tconst record = this.sessionMap.get(entry.session.repoPath);\n\t\tif (record != null) {\n\t\t\tthis.updateWatchMode(record);\n\t\t}\n\t}\n\n\t/** Polls the host's working-tree fingerprint, when it gives one, while the watcher isn't `reliable` */\n\tprivate updateWorkingTreePollTarget(entry: WorkingTreeWatcherEntry): void {\n\t\tconst poller = this.poller;\n\t\tconst getFingerprint = poller?.options.getWorkingTreeFingerprint;\n\t\tif (poller == null || getFingerprint == null) return;\n\n\t\tif (entry.reliability === 'reliable') {\n\t\t\tif (entry.pollTarget != null) {\n\t\t\t\tpoller.delete(entry.pollTarget);\n\t\t\t\tentry.pollTarget = undefined;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tif (entry.pollTarget != null) return;\n\n\t\tconst repoPath = entry.session.repoPath;\n\t\tlet last: string | undefined;\n\t\tconst target: PollTarget = {\n\t\t\tpoll: async (): Promise<void> => {\n\t\t\t\tlet fingerprint: string | undefined;\n\t\t\t\ttry {\n\t\t\t\t\tfingerprint = await getFingerprint(repoPath);\n\t\t\t\t} catch {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (fingerprint == null || entry.pollTarget !== target) return;\n\n\t\t\t\tconst moved = last != null && last !== fingerprint;\n\t\t\t\tlast = fingerprint;\n\t\t\t\t// A fingerprint says the tree moved, not which files did\n\t\t\t\tif (moved) {\n\t\t\t\t\tentry.session.pushWorkingTreeChanges([], true);\n\t\t\t\t}\n\t\t\t},\n\t\t};\n\t\tentry.pollTarget = target;\n\t\tpoller.add(target);\n\t}\n\n\tprivate canPruneIgnoredDirectories(): boolean {\n\t\treturn this.provider.supportsExcludes === true && this.getIgnoredDirectories != null;\n\t}\n\n\t/**\n\t * An event the gitignore post-filter dropped. When its directory is itself ignored, the watcher is watching an\n\t * ignored directory it could prune (one created after the last read), so the ignored set is read again. An ignored\n\t * file in a watched directory (a root `.env`, `src/debug.log`) triggers nothing.\n\t */\n\tprivate onIgnoredEvent(entry: WorkingTreeWatcherEntry, filter: GitIgnoreFilter, relativePath: string): void {\n\t\t// A refresh is already pending and reads the whole set; keep a flood of these events at two field checks\n\t\tif (entry.refreshTimer != null || entry.rereadQueued) return;\n\n\t\t// The outermost ignored directory above the path — the one a read would prune. Keyed on it (not the immediate\n\t\t// parent), so a directory git won't prune costs one read however many subdirectories it has\n\t\tlet ignoredDir: string | undefined;\n\t\tfor (let slash = relativePath.indexOf('/'); slash !== -1; slash = relativePath.indexOf('/', slash + 1)) {\n\t\t\tconst dir = relativePath.slice(0, slash);\n\t\t\tif (entry.unpruned?.has(dir) || entry.unprunable?.has(dir)) return;\n\t\t\tif (filter.isIgnored(`${dir}/`)) {\n\t\t\t\tignoredDir = dir;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (ignoredDir == null) return;\n\n\t\tentry.unpruned ??= new Set();\n\t\tentry.unpruned.add(ignoredDir);\n\n\t\t// Ignore rules didn't change, so a read in flight stays valid (no generation bump), but it may predate the\n\t\t// directory\n\t\tif (entry.reading) {\n\t\t\tentry.rereadQueued = true;\n\t\t\treturn;\n\t\t}\n\n\t\tthis.armIgnoredDirectoriesRefresh(entry);\n\t}\n\n\t/** Re-reads the git-ignored directories once ignore rules stop changing (trailing debounce, one timer per repo) */\n\tprivate scheduleIgnoredDirectoriesRefresh(entry: WorkingTreeWatcherEntry): void {\n\t\tif (!this.canPruneIgnoredDirectories()) return;\n\n\t\t// A read already in flight is now stale, and a directory git wouldn't prune may be prunable now\n\t\tentry.generation++;\n\t\tentry.unprunable = undefined;\n\t\tclearTimeout(entry.refreshTimer);\n\t\tthis.armIgnoredDirectoriesRefresh(entry);\n\t}\n\n\tprivate armIgnoredDirectoriesRefresh(entry: WorkingTreeWatcherEntry): void {\n\t\tentry.refreshTimer = setTimeout(() => {\n\t\t\tentry.refreshTimer = undefined;\n\t\t\tconst repoPath = entry.session.repoPath;\n\t\t\tif (this.wtWatchers.get(repoPath) !== entry) return;\n\n\t\t\tif (entry.reading) {\n\t\t\t\tentry.rereadQueued = true;\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tvoid this.refreshIgnoredDirectories(entry, repoPath);\n\t\t}, ignoredDirectoriesRefreshDelayMs);\n\t}\n\n\t/** Single-flight: reads the git-ignored directories and, when they changed, swaps in a watcher excluding them */\n\tprivate async refreshIgnoredDirectories(entry: WorkingTreeWatcherEntry, repoPath: string): Promise<void> {\n\t\tif (this.getIgnoredDirectories == null) return;\n\n\t\tentry.reading = true;\n\t\ttry {\n\t\t\tconst generation = entry.generation;\n\t\t\t// The unpruned directories seen so far; any seen during the read wait for the next one\n\t\t\tconst unpruned = entry.unpruned?.size ? [...entry.unpruned] : undefined;\n\n\t\t\tlet directories: readonly string[] | undefined;\n\t\t\ttry {\n\t\t\t\tdirectories = await this.getIgnoredDirectories(repoPath);\n\t\t\t} catch {\n\t\t\t\t// Keep what the watcher has; the next ignore-rule change reads again\n\t\t\t\tdirectories = undefined;\n\t\t\t}\n\n\t\t\tif (entry.generation === generation) {\n\t\t\t\tif (directories != null) {\n\t\t\t\t\tthis.swapWorkingTreeWatcher(entry, repoPath, directories);\n\t\t\t\t}\n\t\t\t\tif (unpruned != null) {\n\t\t\t\t\tthis.settleUnprunedDirectories(entry, unpruned, directories);\n\t\t\t\t}\n\t\t\t}\n\t\t} finally {\n\t\t\tentry.reading = false;\n\t\t}\n\n\t\tif (entry.rereadQueued) {\n\t\t\tentry.rereadQueued = false;\n\t\t\tif (this.wtWatchers.get(repoPath) === entry) {\n\t\t\t\tvoid this.refreshIgnoredDirectories(entry, repoPath);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * A directory the read left unexcluded is one git won't prune (or the read failed), so it never triggers a read\n\t * again until ignore rules change — otherwise each of its events would spawn git.\n\t */\n\tprivate settleUnprunedDirectories(\n\t\tentry: WorkingTreeWatcherEntry,\n\t\tunpruned: readonly string[],\n\t\tdirectories: readonly string[] | undefined,\n\t): void {\n\t\tfor (const dir of unpruned) {\n\t\t\tentry.unpruned?.delete(dir);\n\t\t\tif (directories?.some(d => d === dir || dir.startsWith(`${d}/`))) continue;\n\n\t\t\tentry.unprunable ??= new Set();\n\t\t\tentry.unprunable.add(dir);\n\t\t}\n\t}\n\n\tprivate swapWorkingTreeWatcher(\n\t\tentry: WorkingTreeWatcherEntry,\n\t\trepoPath: string,\n\t\tdirectories: readonly string[],\n\t): void {\n\t\tconst previous = entry.ignoredDirectories;\n\t\tif (previous.length === directories.length && previous.every(d => directories.includes(d))) return;\n\n\t\t// New first, then the old one: the overlap can repeat an event (the session coalesces them), but never loses one\n\t\tlet created: { watcher: FileWatcher; reliability: FileWatcherReliability };\n\t\ttry {\n\t\t\tcreated = this.createWorkingTreeWatcher(repoPath, entry.onEvent, [...workingTreeExcludes, ...directories]);\n\t\t} catch {\n\t\t\treturn;\n\t\t}\n\n\t\tconst previousWatcher = entry.watcher;\n\t\tentry.watcher = created.watcher;\n\t\tentry.ignoredDirectories = directories;\n\t\tpreviousWatcher.dispose();\n\t\tthis.setWorkingTreeReliability(entry, created.reliability);\n\n\t\t// Files in a directory that is watched again changed while nothing was looking. A directory still covered by\n\t\t// a surviving ancestor entry isn't watched again.\n\t\tif (previous.some(d => !directories.some(n => n === d || d.startsWith(`${n}/`)))) {\n\t\t\tentry.session.pushWorkingTreeChanges([], true);\n\t\t}\n\t}\n\n\tprivate refreshWorkingTreeFilter(repoPath: string): void {\n\t\tconst entry = this.wtWatchers.get(repoPath);\n\t\tif (entry == null) return;\n\n\t\tif (entry.filter != null) {\n\t\t\tvoid entry.filter.refresh();\n\t\t}\n\t\tthis.scheduleIgnoredDirectoriesRefresh(entry);\n\t}\n\n\tprivate disposeSessionRecord(record: SessionRecord): void {\n\t\t// The working tree first: with it still up, leaving the group would read as a move back to `'events'` and\n\t\t// announce one for a repository that is going away\n\t\tthis.stopWorkingTreeWatcher(record.session.repoPath);\n\t\tthis.disconnectFromWatchGroup(record);\n\t\tthis.updateWatchMode(record);\n\t\trecord.session.dispose();\n\t}\n}\n\nfunction filterWorkingTreePaths(absolutePaths: string[], repoPath: string, filter: GitIgnoreFilter): string[] {\n\tconst result: string[] = [];\n\tfor (const p of absolutePaths) {\n\t\tif (!isDescendant(p, repoPath)) continue;\n\n\t\tconst relativePath = relative(repoPath, p);\n\t\tif (!filter.isIgnored(relativePath)) {\n\t\t\tresult.push(p);\n\t\t}\n\t}\n\treturn result;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"watchService.js","names":[],"sources":["../../../../git/src/watching/watchService.ts"],"sourcesContent":["import type { UnifiedDisposable } from '@gitlens/utils/disposable.js';\nimport type { Event } from '@gitlens/utils/event.js';\nimport { Emitter } from '@gitlens/utils/event.js';\nimport { isDescendant, relative } from '@gitlens/utils/path.js';\nimport type { ResourceUsage } from '@gitlens/utils/resourceUsage.js';\nimport { getRepositoryKey } from '@gitlens/utils/uri.js';\nimport type { GitDir, RepositoryChange } from '../models/repository.js';\nimport type { WatcherRepoChangeEvent } from './changeEvent.js';\nimport type { PollTarget } from './gitDirPoller.js';\nimport { GitDirPoller } from './gitDirPoller.js';\nimport type { GitIgnoreFilter } from './gitIgnoreFilter.js';\nimport type {\n\tFileWatcher,\n\tFileWatcherOptions,\n\tFileWatcherReliability,\n\tFileWatchEvent,\n\tFileWatchingProvider,\n} from './provider.js';\nimport { shouldIgnoreWorkingTreePath, workingTreeExcludes } from './watcherPatterns.js';\nimport type { WatchGroupHooks } from './watchGroup.js';\nimport { WatchGroup } from './watchGroup.js';\nimport { RepositoryWatchSession } from './watchSession.js';\n\n/**\n * Cap on paths held while the gitignore filter loads. Higher than the session's own cap because these are\n * pre-filter — most of a build's output is typically gitignored and would be discarded on replay, so a\n * tighter bound here would mark batches incomplete when they filter down to almost nothing.\n */\nconst maxBufferedWorkingTreePaths = 5000;\n\n/** Trailing debounce before the git-ignored directory set is re-read after an ignore rule changes */\nconst ignoredDirectoriesRefreshDelayMs = 500;\n\ninterface WorkingTreeWatcherEntry {\n\treadonly session: RepositoryWatchSession;\n\t/** The event handler shared by every watcher this entry has had — a re-created watcher reuses it */\n\treadonly onEvent: (event: FileWatchEvent) => void;\n\treadonly filter?: GitIgnoreFilter;\n\twatcher: FileWatcher;\n\t/** The git-ignored directories the live watcher was created excluding (on top of the static set) */\n\tignoredDirectories: readonly string[];\n\t/** Bumped by every ignore-rule change and by stopping; a read that lands under a different value is stale */\n\tgeneration: number;\n\treading: boolean;\n\t/** A refresh came due while a read was in flight */\n\trereadQueued: boolean;\n\trefreshTimer?: ReturnType<typeof setTimeout>;\n\t/** What the live watcher last reported; a re-created watcher starts over from its own report */\n\treliability: FileWatcherReliability;\n\t/** The live watcher has yet to call the `onDidStart` its provider promised (see `callsOnDidStart`) */\n\tstarting: boolean;\n\t/** Compares the host's working-tree fingerprint while the watcher isn't `reliable` */\n\tpollTarget?: PollTarget;\n\t/**\n\t * Git-ignored directories (repo-relative) whose events reached the post-filter, so the watcher isn't pruning them —\n\t * typically created after the last read (a first build's `dist/`). Each waits for a read that started after it.\n\t */\n\tunpruned?: Set<string>;\n\t/**\n\t * Directories a read showed git won't prune (they hold tracked files, or glob metacharacters in their names);\n\t * cleared when ignore rules change\n\t */\n\tunprunable?: Set<string>;\n}\n\n/**\n * A host whose watchers can report they can't be trusted (see `FileWatcherOptions.onDidChangeReliability`) passes\n * these to have core poll in their place. A `.git` directory is polled by statting the files its watchers read and\n * feeding each one that moved through the same interpretation as a watcher event; a working tree is never walked.\n */\nexport interface WatchPollingOptions {\n\t/**\n\t * Reads a path's modification time (ms) and size; `undefined` when it doesn't exist. A rejection means unknown:\n\t * the last reading stands, so it never counts as a change.\n\t */\n\treadonly stat: (path: string) => Promise<{ readonly mtime: number; readonly size: number } | undefined>;\n\t/** Whether a poll is worth running now. Default: the service isn't suspended (`suspendAll`/`resumeAll`) */\n\treadonly isActive?: () => boolean;\n\t/** How often each untrusted watcher is polled. Default: 60000ms */\n\treadonly intervalMs?: number;\n\t/**\n\t * How often `isActive` is looked at, which bounds how late the first poll after it turns true can be. Only with\n\t * `isActive`: the default gate wakes only for a poll. Default: 5000ms\n\t */\n\treadonly activeCheckMs?: number;\n\t/**\n\t * Refs to poll for a repository beyond its subscribers' `relevantRefs`: one with none (a host listing every branch)\n\t * still needs its checked-out branch's upstream, which a push moves without writing `FETCH_HEAD`\n\t */\n\treadonly getRefs?: (repoPath: string) => Iterable<string>;\n\t/**\n\t * Something that changes whenever the repository's working tree does (e.g. a digest of its status and the\n\t * changed files' stats), compared at each poll while working-tree subscribers are watching a working tree whose\n\t * watcher can't be trusted; a change is reported with no paths. `undefined` or a rejection skips that poll. With or\n\t * without it, a working tree whose watcher is `unwatchable` is reported changed (no paths) whenever its index or\n\t * `HEAD` changes.\n\t */\n\treadonly getWorkingTreeFingerprint?: (repoPath: string) => Promise<string | undefined>;\n}\n\nexport interface WatchServiceOptions {\n\treadonly watchingProvider: FileWatchingProvider;\n\t/** Default debounce for repo changes when subscriber doesn't specify. Default: 250ms */\n\treadonly defaultRepoDelayMs?: number;\n\t/** Default debounce for working tree changes when subscriber doesn't specify. Default: 2500ms */\n\treadonly defaultWorkingTreeDelayMs?: number;\n\t/** Default cap on how long continuous repo activity can defer a flush. `Infinity` disables it. Default: 2000ms */\n\treadonly defaultRepoMaxWaitMs?: number;\n\t/** Default cap on how long continuous working tree activity can defer a flush. `Infinity` disables it. Default: 10000ms */\n\treadonly defaultWorkingTreeMaxWaitMs?: number;\n\t/** Returns a GitIgnoreFilter for a given repo. When absent or returns undefined, gitignore filtering is skipped. */\n\treadonly getIgnoreFilter?: (repoPath: string, gitDirPath: string) => GitIgnoreFilter | undefined;\n\t/**\n\t * Returns the repo-relative directories an ignore rule matches, for a watching provider that prunes\n\t * `excludes` natively (`supportsExcludes`). Never called for one that doesn't. A rejection keeps the static set.\n\t */\n\treadonly getIgnoredDirectories?: (repoPath: string) => Promise<readonly string[]>;\n\t/**\n\t * Returns the absolute, normalized folders whose worktrees are ignored (see `GitProvider.getIgnoredWorktreeRoots`):\n\t * with a `readFile` and a `readDirectory`, the watcher's reports of their admin entries reach no session. Asked\n\t * once per common git dir, when its first session starts watching, and never without both. When absent, empty or\n\t * rejected, no admin entry is read.\n\t */\n\treadonly getIgnoredWorktreeRoots?: (repoPath: string) => Promise<readonly string[]>;\n\t/**\n\t * Returns the folders whose worktrees are ignored, relative to a repository's common git dir (see\n\t * `GitProvider.getIgnoredWorktreeFolders`): the `.git` watchers whose tree holds the common git dir leave\n\t * them unwatched. Asked once per common git dir, when its first session starts watching.\n\t */\n\treadonly getIgnoredWorktreeFolders?: (repoPath: string) => readonly string[];\n\t/** Opts into polling the watchers that report they can't be trusted. When absent, no watcher is asked. */\n\treadonly polling?: WatchPollingOptions;\n\t/**\n\t * Reads a file's bytes; a rejection means it couldn't be read. With it, each report of a repository's shared\n\t * `.git/config` is classified by what changed in the file: a change confined to some branches' `branch.<name>`\n\t * sections reaches only the subscribers following those branches, and carries no `remotes`. Without it, every\n\t * config change concerns everyone, as before.\n\t */\n\treadonly readFile?: (path: string) => Promise<Uint8Array>;\n\t/**\n\t * Returns a directory's entries with their types, as the host file system's `readDirectory` does; only the\n\t * directories count as admin entries (a stray `.DS_Store` is no worktree's). A rejection means it couldn't be\n\t * listed, and one whose `code` is `ENOENT` or `FileNotFound` that the directory isn't there. With it and a\n\t * `readFile`, the admin entries of a repository's worktrees are listed and read once as its first session starts\n\t * watching, so the reports of an ignored worktree's (see `getIgnoredWorktreeRoots`) reach no session, and the others\n\t * aren't held back.\n\t */\n\treadonly readDirectory?: (path: string) => Promise<readonly (readonly [string, number])[]>;\n}\n\nexport interface WatchHooks {\n\t/** Called when FETCH_HEAD changed (for last-fetched tracking) */\n\treadonly onFetchHeadChanged?: (repoPath: string) => void;\n\t/** Called when .git/info/exclude changed */\n\treadonly onIgnoresChanged?: (repoPath: string) => void;\n\t/** Called when .gitignore changed in the working tree (extension watches this separately) */\n\treadonly onGitIgnoreChanged?: (repoPath: string) => void;\n}\n\nexport interface WatchHandle {\n\treadonly session: RepositoryWatchSession;\n\tdispose(): void;\n}\n\ninterface SessionRecord {\n\treadonly session: RepositoryWatchSession;\n\treadonly gitDir: GitDir;\n\t/**\n\t * Hooks contributed by all current `watch()` callers for this repo path. Earlier\n\t * implementations stored just the first caller's hooks (`WatchHooks | undefined`) and\n\t * silently dropped subsequent callers' hooks — that broke the case where a low-level\n\t * subscriber (e.g. the Graph's secondary-worktree WIP watcher) opened the session\n\t * first without hooks, and a later `Repository.watch()` for the same path lost\n\t * its `markFetched` / `onIgnoresChanged` wiring. Each `watch()` call adds its hooks\n\t * to this set and removes them on dispose; fire sites iterate the set.\n\t */\n\treadonly watchHooks: Set<WatchHooks>;\n\trefCount: number;\n\t/** The mode {@link RepositoryWatchService.onDidChangeWatchMode} last announced; `'events'` while unwatched */\n\twatchMode: 'events' | 'poll';\n}\n\n/**\n * Global service that multiplexes repository watching across all repos.\n *\n * Manages:\n * - Session map: one `RepositoryWatchSession` per repo path (ref-counted)\n * - Watch groups: shared `.git` watchers across worktrees\n * - Working tree watchers: per-repo filesystem watchers with gitignore filtering\n *\n * Sessions and watch groups are created lazily on first `subscribe()`\n * within a session, and destroyed when the last subscriber leaves.\n */\nexport class RepositoryWatchService implements UnifiedDisposable {\n\tprivate readonly _onDidChangeRepository = new Emitter<WatcherRepoChangeEvent>();\n\t/** Global multiplexed event for ALL watched repos */\n\tget onDidChangeRepository(): Event<WatcherRepoChangeEvent> {\n\t\treturn this._onDidChangeRepository.event;\n\t}\n\n\tprivate readonly _onDidChangeWorkingTree = new Emitter<string>();\n\t/** Global multiplexed working-tree change event (repo path) for ALL watched repos. Fires once per\n\t * debounced flush per session — the single point that drives the cache's status clock for working-tree edits. */\n\tget onDidChangeWorkingTree(): Event<string> {\n\t\treturn this._onDidChangeWorkingTree.event;\n\t}\n\n\tprivate readonly _onDidChangeFetchHead = new Emitter<string>();\n\t/**\n\t * Global FETCH_HEAD change event (repo path) for ALL watched repos, fired as each change is seen (no debounce).\n\t * FETCH_HEAD maps to no {@link RepositoryChange} on the session, so this is how a host invalidates its cached\n\t * last-fetched time — the standalone service bridges it to the cache's `lastFetched`.\n\t */\n\tget onDidChangeFetchHead(): Event<string> {\n\t\treturn this._onDidChangeFetchHead.event;\n\t}\n\n\tprivate readonly _onDidChangeWatchMode = new Emitter<{ repoPath: string; mode: 'events' | 'poll' }>();\n\t/**\n\t * Fires as a watched repository starts being polled in place of a watcher (see {@link getWatchMode}), and with\n\t * `'events'` as it is watched natively again. Never fires as the repository stops being watched; one watched again\n\t * later starts from `'events'`.\n\t */\n\tget onDidChangeWatchMode(): Event<{ repoPath: string; mode: 'events' | 'poll' }> {\n\t\treturn this._onDidChangeWatchMode.event;\n\t}\n\n\tprivate readonly defaultRepoDelayMs?: number;\n\tprivate readonly defaultWorkingTreeDelayMs?: number;\n\tprivate readonly defaultRepoMaxWaitMs?: number;\n\tprivate readonly defaultWorkingTreeMaxWaitMs?: number;\n\tprivate readonly getIgnoreFilter?: (repoPath: string, gitDirPath: string) => GitIgnoreFilter | undefined;\n\tprivate readonly getIgnoredDirectories?: (repoPath: string) => Promise<readonly string[]>;\n\tprivate readonly getIgnoredWorktreeRoots?: (repoPath: string) => Promise<readonly string[]>;\n\tprivate readonly getIgnoredWorktreeFolders?: (repoPath: string) => readonly string[];\n\tprivate readonly provider: FileWatchingProvider;\n\tprivate readonly poller: GitDirPoller | undefined;\n\tprivate readonly readFile: ((path: string) => Promise<Uint8Array>) | undefined;\n\tprivate readonly readDirectory: ((path: string) => Promise<readonly (readonly [string, number])[]>) | undefined;\n\n\tprivate readonly sessionMap = new Map<string, SessionRecord>();\n\tprivate readonly watchGroups = new Map<string, WatchGroup>();\n\tprivate readonly wtWatchers = new Map<string, WorkingTreeWatcherEntry>();\n\t/**\n\t * The last git-ignored directory read under each filter's current rules, kept across a working-tree watcher's\n\t * stop and start so a restart can skip the read; dropped with the filter\n\t */\n\tprivate readonly keptIgnoredDirectories = new WeakMap<GitIgnoreFilter, readonly string[]>();\n\tprivate _disposed = false;\n\t/** Global suspend state (driven by window focus); inherited by sessions created via {@link watch}. */\n\tprivate _suspended = false;\n\n\tconstructor(options: WatchServiceOptions) {\n\t\tthis.provider = options.watchingProvider;\n\t\tthis.defaultRepoDelayMs = options.defaultRepoDelayMs;\n\t\tthis.defaultWorkingTreeDelayMs = options.defaultWorkingTreeDelayMs;\n\t\tthis.defaultRepoMaxWaitMs = options.defaultRepoMaxWaitMs;\n\t\tthis.defaultWorkingTreeMaxWaitMs = options.defaultWorkingTreeMaxWaitMs;\n\t\tthis.getIgnoreFilter = options.getIgnoreFilter;\n\t\tthis.getIgnoredDirectories = options.getIgnoredDirectories;\n\t\tthis.getIgnoredWorktreeRoots = options.getIgnoredWorktreeRoots;\n\t\tthis.getIgnoredWorktreeFolders = options.getIgnoredWorktreeFolders;\n\t\tthis.readFile = options.readFile;\n\t\tthis.readDirectory = options.readDirectory;\n\t\tif (options.polling != null) {\n\t\t\tthis.poller = new GitDirPoller(options.polling);\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\tfor (const record of this.sessionMap.values()) {\n\t\t\tthis.disposeSessionRecord(record);\n\t\t}\n\t\tthis.sessionMap.clear();\n\n\t\tfor (const group of this.watchGroups.values()) {\n\t\t\tgroup.dispose();\n\t\t}\n\t\tthis.watchGroups.clear();\n\n\t\tfor (const repoPath of [...this.wtWatchers.keys()]) {\n\t\t\tthis.stopWorkingTreeWatcher(repoPath);\n\t\t}\n\n\t\tthis.poller?.dispose();\n\n\t\tthis._onDidChangeRepository.dispose();\n\t\tthis._onDidChangeWorkingTree.dispose();\n\t\tthis._onDidChangeFetchHead.dispose();\n\t\tthis._onDidChangeWatchMode.dispose();\n\t}\n\n\t/**\n\t * Start watching a repository. Reference-counted:\n\t * same repoPath → same session (refCount++).\n\t * Worktrees sharing a .git dir share the WatchGroup.\n\t *\n\t * Returns a handle with the session and a dispose function.\n\t * Disposing the handle decrements the ref count.\n\t *\n\t * `repoPath` is keyed by {@link getRepositoryKey}, and every session, event and hook carries that key — the\n\t * same one the cache fences status reads on — so a host passing `C:\\repo\\` and one passing `c:/repo` share\n\t * a session and advance the same status clock.\n\t */\n\twatch(repoPath: string, gitDir: GitDir, watchHooks?: WatchHooks): WatchHandle | undefined {\n\t\tif (this._disposed) return undefined;\n\n\t\trepoPath = getRepositoryKey(repoPath);\n\n\t\tlet record = this.sessionMap.get(repoPath);\n\t\tif (record != null) {\n\t\t\trecord.refCount++;\n\t\t\tif (watchHooks != null) {\n\t\t\t\trecord.watchHooks.add(watchHooks);\n\t\t\t}\n\t\t} else {\n\t\t\tconst session = new RepositoryWatchSession({\n\t\t\t\trepoPath: repoPath,\n\t\t\t\tdefaultRepoDelayMs: this.defaultRepoDelayMs,\n\t\t\t\tdefaultWorkingTreeDelayMs: this.defaultWorkingTreeDelayMs,\n\t\t\t\tdefaultRepoMaxWaitMs: this.defaultRepoMaxWaitMs,\n\t\t\t\tdefaultWorkingTreeMaxWaitMs: this.defaultWorkingTreeMaxWaitMs,\n\t\t\t\tsuspended: this._suspended,\n\t\t\t\tlifecycle: {\n\t\t\t\t\tonFirstRepoSubscriber: (): void => {\n\t\t\t\t\t\tconst rec = this.sessionMap.get(repoPath);\n\t\t\t\t\t\tif (rec != null) {\n\t\t\t\t\t\t\tthis.connectToWatchGroup(rec);\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\tonLastRepoSubscriber: (): void => {\n\t\t\t\t\t\tconst rec = this.sessionMap.get(repoPath);\n\t\t\t\t\t\tif (rec != null) {\n\t\t\t\t\t\t\tthis.disconnectFromWatchGroup(rec);\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\tonFirstWorkingTreeSubscriber: (): void => {\n\t\t\t\t\t\tconst rec = this.sessionMap.get(repoPath);\n\t\t\t\t\t\tif (rec != null) {\n\t\t\t\t\t\t\tthis.startWorkingTreeWatcher(rec);\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\tonLastWorkingTreeSubscriber: (): void => {\n\t\t\t\t\t\tthis.stopWorkingTreeWatcher(repoPath);\n\t\t\t\t\t\tconst rec = this.sessionMap.get(repoPath);\n\t\t\t\t\t\tif (rec != null) {\n\t\t\t\t\t\t\tthis.updateWatchMode(rec);\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tonDidFireRepoChange: (event): void => {\n\t\t\t\t\tthis._onDidChangeRepository.fire(event);\n\t\t\t\t},\n\t\t\t\tonDidFireWorkingTreeChange: (repoPath): void => {\n\t\t\t\t\tthis._onDidChangeWorkingTree.fire(repoPath);\n\t\t\t\t},\n\t\t\t});\n\n\t\t\tconst watchHooksSet = new Set<WatchHooks>();\n\t\t\tif (watchHooks != null) {\n\t\t\t\twatchHooksSet.add(watchHooks);\n\t\t\t}\n\n\t\t\trecord = {\n\t\t\t\tsession: session,\n\t\t\t\tgitDir: gitDir,\n\t\t\t\twatchHooks: watchHooksSet,\n\t\t\t\trefCount: 1,\n\t\t\t\twatchMode: 'events',\n\t\t\t};\n\t\t\tthis.sessionMap.set(repoPath, record);\n\t\t}\n\n\t\tconst sessionRef = record.session;\n\t\tlet handleDisposed = false;\n\n\t\treturn {\n\t\t\tsession: sessionRef,\n\t\t\tdispose: (): void => {\n\t\t\t\tif (handleDisposed) return;\n\n\t\t\t\thandleDisposed = true;\n\n\t\t\t\tconst rec = this.sessionMap.get(repoPath);\n\t\t\t\tif (rec == null) return;\n\n\t\t\t\trec.refCount--;\n\t\t\t\tif (watchHooks != null) {\n\t\t\t\t\trec.watchHooks.delete(watchHooks);\n\t\t\t\t}\n\t\t\t\tif (rec.refCount <= 0) {\n\t\t\t\t\tthis.sessionMap.delete(repoPath);\n\t\t\t\t\tthis.disposeSessionRecord(rec);\n\t\t\t\t}\n\t\t\t},\n\t\t};\n\t}\n\n\t/**\n\t * Core announced one of its own writes for `repoPath`; the session watching it fires the announcement in place of\n\t * the write's own `.git` events (see {@link RepositoryWatchSession.noteSelfWrite}); `refs` are the full ref names it\n\t * moved, when it knows them, and `options.configConfinedToRefs` confines its `config` change to the sections of the\n\t * local branches among them (see `GitServiceHooks.repository.onChanged`). A path no session watches is ignored.\n\t */\n\tnoteSelfWrite(\n\t\trepoPath: string,\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\tthis.sessionMap.get(getRepositoryKey(repoPath))?.session.noteSelfWrite(changes, refs, options);\n\t}\n\n\t/** Get an existing session by repo path */\n\tgetSession(repoPath: string): RepositoryWatchSession | undefined {\n\t\treturn this.sessionMap.get(getRepositoryKey(repoPath))?.session;\n\t}\n\n\t/**\n\t * Whether the working tree's watch has yet to go live, owing a rescan once it does (see\n\t * `FileWatchingProvider.callsOnDidStart`). Not while it's `unwatchable`: it rescans when it comes back.\n\t */\n\tisWorkingTreeStarting(repoPath: string): boolean {\n\t\tconst entry = this.wtWatchers.get(getRepositoryKey(repoPath));\n\t\treturn entry?.starting === true && entry.reliability !== 'unwatchable';\n\t}\n\n\t/**\n\t * `'poll'` while a watcher the repository is watched through — its `.git` watchers or its working tree's — can't\n\t * watch, and is polled instead; `'events'` while it is watched natively (polled beside an `unreliable` watcher\n\t * included); `undefined` when nothing watches it, which {@link onDidChangeWatchMode} doesn't announce.\n\t */\n\tgetWatchMode(repoPath: string): 'events' | 'poll' | undefined {\n\t\tconst record = this.sessionMap.get(getRepositoryKey(repoPath));\n\t\treturn record != null ? this.computeWatchMode(record) : undefined;\n\t}\n\n\t/** Resource usage retained by repository watchers. */\n\tgetResourceUsage(): ResourceUsage {\n\t\tconst usage: ResourceUsage = {\n\t\t\t'watch.sessions.count': this.sessionMap.size,\n\t\t\t'watch.groups.count': this.watchGroups.size,\n\t\t\t'watch.workingTreeWatchers.count': this.wtWatchers.size,\n\t\t};\n\t\tif (this.poller != null) {\n\t\t\tusage['watch.pollTargets.count'] = this.poller.size;\n\t\t}\n\t\treturn usage;\n\t}\n\n\t/** Suspend all sessions and mark the service suspended so late-joining sessions start suspended */\n\tsuspendAll(): void {\n\t\tthis._suspended = true;\n\t\tfor (const record of this.sessionMap.values()) {\n\t\t\trecord.session.suspend();\n\t\t}\n\t\tthis.poller?.suspend();\n\t}\n\n\t/** Resume all sessions (clearing the global suspend state), with optional per-session delay */\n\tresumeAll(getDelay?: (session: RepositoryWatchSession) => number): void {\n\t\tthis._suspended = false;\n\t\tfor (const record of this.sessionMap.values()) {\n\t\t\tconst delayMs = getDelay?.(record.session);\n\t\t\trecord.session.resume(delayMs);\n\t\t}\n\t\tthis.poller?.resume();\n\t}\n\n\tprivate getOrCreateWatchGroup(commonGitDir: string, repoPath: string): WatchGroup {\n\t\tlet group = this.watchGroups.get(commonGitDir);\n\t\tif (group == null) {\n\t\t\tgroup = new WatchGroup(\n\t\t\t\tcommonGitDir,\n\t\t\t\tthis.provider,\n\t\t\t\tthis.poller,\n\t\t\t\tthis.readFile,\n\t\t\t\tthis.readDirectory,\n\t\t\t\t// Only a group that can read the admin entries waits on the folders; asking otherwise reads for nothing,\n\t\t\t\t// and leaves a rejection unhandled\n\t\t\t\tthis.readFile != null && this.readDirectory != null\n\t\t\t\t\t? this.getIgnoredWorktreeRoots?.(repoPath)\n\t\t\t\t\t: undefined,\n\t\t\t\tthis.getIgnoredWorktreeFolders?.(repoPath),\n\t\t\t);\n\t\t\tthis.watchGroups.set(commonGitDir, group);\n\t\t}\n\t\treturn group;\n\t}\n\n\tprivate maybeDisposeWatchGroup(commonGitDir: string): void {\n\t\tconst group = this.watchGroups.get(commonGitDir);\n\t\tif (group == null) return;\n\n\t\tif (group.sessions.size === 0) {\n\t\t\tgroup.dispose();\n\t\t\tthis.watchGroups.delete(commonGitDir);\n\t\t}\n\t}\n\n\tprivate connectToWatchGroup(record: SessionRecord): void {\n\t\tconst { session, gitDir, watchHooks } = record;\n\t\tconst commonGitDir = (gitDir.commonUri ?? gitDir.uri).fsPath;\n\t\tconst group = this.getOrCreateWatchGroup(commonGitDir, session.repoPath);\n\n\t\tconst hooks: WatchGroupHooks = {\n\t\t\tonRepoChanged: (\n\t\t\t\t_repoPath: string,\n\t\t\t\tchanges: RepositoryChange[],\n\t\t\t\tref?: string | false,\n\t\t\t\toverflow?: boolean,\n\t\t\t\tconfigBranches?: ReadonlySet<string>,\n\t\t\t): void => {\n\t\t\t\tif (overflow) {\n\t\t\t\t\tsession.pushUnfencedRepoChanges(changes);\n\t\t\t\t} else {\n\t\t\t\t\tsession.pushRepoChanges(changes, ref, configBranches);\n\t\t\t\t}\n\n\t\t\t\t// A working tree nothing watches is read again whenever the index or HEAD says it may have moved\n\t\t\t\tif (\n\t\t\t\t\tthis.poller != null &&\n\t\t\t\t\t(changes.includes('index') || changes.includes('head')) &&\n\t\t\t\t\tthis.wtWatchers.get(session.repoPath)?.reliability === 'unwatchable'\n\t\t\t\t) {\n\t\t\t\t\tsession.pushWorkingTreeChanges([], true);\n\t\t\t\t}\n\t\t\t},\n\t\t\tonFetchHeadChanged: (repoPath: string): void => {\n\t\t\t\t// Global first, so a host's cache drops its last-fetched time before any hook re-reads it\n\t\t\t\tthis._onDidChangeFetchHead.fire(repoPath);\n\t\t\t\t// Iterate at fire time so callers that joined after the group was wired up\n\t\t\t\t// still receive events (e.g. a `Repository.watch()` whose hooks landed\n\t\t\t\t// after a hookless `GitRepositoryService.watch` registered first).\n\t\t\t\tfor (const h of watchHooks) {\n\t\t\t\t\th.onFetchHeadChanged?.(repoPath);\n\t\t\t\t}\n\t\t\t},\n\t\t\tonIgnoresChanged: (repoPath: string): void => {\n\t\t\t\tthis.refreshWorkingTreeFilter(repoPath);\n\t\t\t\tfor (const h of watchHooks) {\n\t\t\t\t\th.onIgnoresChanged?.(repoPath);\n\t\t\t\t}\n\t\t\t},\n\t\t\tgetProbeRefs: (): Iterable<string> => {\n\t\t\t\tconst refs = session.getRelevantRefs();\n\t\t\t\tfor (const ref of this.poller?.options.getRefs?.(session.repoPath) ?? []) {\n\t\t\t\t\trefs.add(ref);\n\t\t\t\t}\n\t\t\t\treturn refs;\n\t\t\t},\n\t\t\tonUnwatchableChanged: (): void => this.updateWatchMode(record),\n\t\t};\n\n\t\tgroup.addSession(session.repoPath, gitDir, hooks);\n\t\tthis.updateWatchMode(record);\n\t}\n\n\tprivate disconnectFromWatchGroup(record: SessionRecord): void {\n\t\tconst { session, gitDir } = record;\n\t\tconst commonGitDir = (gitDir.commonUri ?? gitDir.uri).fsPath;\n\t\tconst group = this.watchGroups.get(commonGitDir);\n\t\tif (group == null) return;\n\n\t\tgroup.removeSession(session.repoPath);\n\t\tthis.maybeDisposeWatchGroup(commonGitDir);\n\t\tthis.updateWatchMode(record);\n\t}\n\n\tprivate computeWatchMode(record: SessionRecord): 'events' | 'poll' | undefined {\n\t\tconst repoPath = record.session.repoPath;\n\t\tconst group = this.watchGroups.get((record.gitDir.commonUri ?? record.gitDir.uri).fsPath);\n\t\tconst connected = group?.sessions.has(repoPath) ?? false;\n\t\tconst wt = this.wtWatchers.get(repoPath);\n\t\tif (!connected && wt == null) return undefined;\n\n\t\treturn group?.isUnwatchable(repoPath) || wt?.reliability === 'unwatchable' ? 'poll' : 'events';\n\t}\n\n\tprivate updateWatchMode(record: SessionRecord): void {\n\t\tif (this._disposed) return;\n\n\t\tconst mode = this.computeWatchMode(record);\n\t\tif (mode == null) {\n\t\t\trecord.watchMode = 'events';\n\t\t\treturn;\n\t\t}\n\t\tif (mode === record.watchMode) return;\n\n\t\trecord.watchMode = mode;\n\t\tthis._onDidChangeWatchMode.fire({ repoPath: record.session.repoPath, mode: mode });\n\t}\n\n\tprivate startWorkingTreeWatcher(record: SessionRecord): void {\n\t\tconst { session, gitDir, watchHooks } = record;\n\t\tconst repoPath = session.repoPath;\n\t\tif (this.wtWatchers.has(repoPath)) return;\n\n\t\tconst canPrune = this.canPruneIgnoredDirectories();\n\t\t// Assigned once the first watcher exists; read by the handlers below instead of a map lookup per event\n\t\tlet entry: WorkingTreeWatcherEntry | undefined;\n\n\t\tlet filter: GitIgnoreFilter | undefined;\n\t\tif (this.getIgnoreFilter != null) {\n\t\t\tfilter = this.getIgnoreFilter(repoPath, gitDir.uri.fsPath);\n\t\t}\n\n\t\t// Buffer events while the gitignore filter loads, then replay\n\t\tlet filterReady = filter == null;\n\t\tlet buffered: string[] | undefined = filter != null ? [] : undefined;\n\t\tlet bufferIncomplete = false;\n\n\t\t// Ahead of `ready()`, so the replay below waits for rules that may have changed while nothing watched them\n\t\tconst revalidated = filter?.revalidate();\n\t\tif (filter != null) {\n\t\t\tvoid filter\n\t\t\t\t.ready()\n\t\t\t\t.then(() => {\n\t\t\t\t\tfilterReady = true;\n\t\t\t\t\tif (buffered?.length) {\n\t\t\t\t\t\tconst paths = filterWorkingTreePaths(buffered, repoPath, filter);\n\t\t\t\t\t\tif (paths.length > 0 || bufferIncomplete) {\n\t\t\t\t\t\t\tsession.pushWorkingTreeChanges(paths, bufferIncomplete);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbuffered = undefined;\n\t\t\t\t\tbufferIncomplete = false;\n\t\t\t\t})\n\t\t\t\t.catch(() => {\n\t\t\t\t\tfilterReady = true;\n\t\t\t\t\tbuffered = undefined;\n\t\t\t\t});\n\t\t}\n\n\t\t// An ignore rule changed (or may have): refresh the filter (unless it already reloaded) and the pruned set, and\n\t\t// notify the host\n\t\tconst onIgnoreRulesChanged = (refreshFilter: boolean): void => {\n\t\t\tif (refreshFilter && filter != null) {\n\t\t\t\tvoid filter.refresh();\n\t\t\t}\n\t\t\tif (entry != null) {\n\t\t\t\tthis.scheduleIgnoredDirectoriesRefresh(entry);\n\t\t\t}\n\t\t\t// See `connectToWatchGroup` for the iterate-at-fire-time rationale.\n\t\t\tfor (const h of watchHooks) {\n\t\t\t\th.onGitIgnoreChanged?.(repoPath);\n\t\t\t}\n\t\t\t// Route the ignore-rule change through the repo channel as an `ignores` change (like `info/exclude`),\n\t\t\t// so it reaches EVERY session subscriber — including a closed secondary worktree's `watch()` `repoSub`,\n\t\t\t// which has no `onGitIgnoreChanged` hook and would otherwise keep a stale untracked-file list. Coalesces\n\t\t\t// with any hook-fired `ignores` above, so open repos don't double. Unfenced: it comes from the working tree,\n\t\t\t// which a self-write never holds back.\n\t\t\tsession.pushUnfencedRepoChanges(['ignores']);\n\t\t};\n\n\t\tconst onEvent = (event: FileWatchEvent): void => {\n\t\t\t// Dropped events: nothing to filter or buffer, the whole tree needs a re-read. A `.gitignore` edit may be\n\t\t\t// among them, and a filter left on the old rules would keep dropping changes they used to ignore\n\t\t\tif (event.reason === 'overflow') {\n\t\t\t\tsession.pushWorkingTreeChanges([], true);\n\t\t\t\tonIgnoreRulesChanged(true);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Fast noise check (node_modules, .git, .watchman-cookie-)\n\t\t\tif (shouldIgnoreWorkingTreePath(event.path)) return;\n\n\t\t\t// Detect .gitignore changes (nested ones too, unless git skips them for sitting in an ignored directory).\n\t\t\t// The root .gitignore is only an ignore change; a nested one is also an ordinary working-tree change.\n\t\t\tconst relativePath = isDescendant(event.path, repoPath) ? relative(repoPath, event.path) : undefined;\n\t\t\tif (relativePath === '.gitignore') {\n\t\t\t\tonIgnoreRulesChanged(true);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (relativePath?.endsWith('/.gitignore') && !(filterReady && filter?.isIgnored(relativePath))) {\n\t\t\t\tonIgnoreRulesChanged(true);\n\t\t\t}\n\n\t\t\tif (!filterReady) {\n\t\t\t\t// Buffer until the gitignore filter is ready. Capped: `ready()` awaits a `core.excludesFile`\n\t\t\t\t// read, which queues behind every other git command, so this window can stay open for a long\n\t\t\t\t// time — and these paths haven't been gitignore-filtered yet, so a build's output all lands\n\t\t\t\t// here. Past the cap, stop retaining and mark the batch partial rather than growing.\n\t\t\t\tif (buffered != null && buffered.length >= maxBufferedWorkingTreePaths) {\n\t\t\t\t\tbufferIncomplete = true;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tbuffered?.push(event.path);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Gitignore filter (sync after ready)\n\t\t\tif (filter != null && relativePath != null && filter.isIgnored(relativePath)) {\n\t\t\t\tif (canPrune && entry != null) {\n\t\t\t\t\tthis.onIgnoredEvent(entry, filter, relativePath);\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tsession.pushWorkingTreeChanges([event.path]);\n\t\t};\n\n\t\t// A read from before the watch last stopped; should the rules have moved since, `revalidate` brings a re-read\n\t\tconst kept = canPrune && filter != null ? this.keptIgnoredDirectories.get(filter) : undefined;\n\t\tconst created = this.createWorkingTreeWatcher(\n\t\t\trepoPath,\n\t\t\tonEvent,\n\t\t\tkept != null ? [...workingTreeExcludes, ...kept] : workingTreeExcludes,\n\t\t);\n\t\tentry = {\n\t\t\tsession: session,\n\t\t\tonEvent: onEvent,\n\t\t\tfilter: filter,\n\t\t\twatcher: created.watcher,\n\t\t\tignoredDirectories: kept ?? [],\n\t\t\tgeneration: 0,\n\t\t\treading: false,\n\t\t\trereadQueued: false,\n\t\t\treliability: 'reliable',\n\t\t\tstarting: created.starting,\n\t\t};\n\t\tthis.wtWatchers.set(repoPath, entry);\n\t\tthis.setWorkingTreeReliability(entry, created.reliability, true);\n\n\t\tif (canPrune && kept == null) {\n\t\t\tvoid this.refreshIgnoredDirectories(entry, repoPath);\n\t\t}\n\n\t\t// The kept read predates the rules either outcome lands on, so it goes even when the watch has stopped meanwhile\n\t\tvoid revalidated?.then(\n\t\t\tchanged => {\n\t\t\t\tif (!changed) return;\n\n\t\t\t\tif (filter != null) {\n\t\t\t\t\tthis.keptIgnoredDirectories.delete(filter);\n\t\t\t\t}\n\t\t\t\tif (this.wtWatchers.get(repoPath) === entry) {\n\t\t\t\t\tonIgnoreRulesChanged(false);\n\t\t\t\t}\n\t\t\t},\n\t\t\t() => {\n\t\t\t\tif (filter != null) {\n\t\t\t\t\tthis.keptIgnoredDirectories.delete(filter);\n\t\t\t\t}\n\t\t\t\tif (this.wtWatchers.get(repoPath) === entry) {\n\t\t\t\t\tonIgnoreRulesChanged(true);\n\t\t\t\t}\n\t\t\t},\n\t\t);\n\t}\n\n\tprivate stopWorkingTreeWatcher(repoPath: string): void {\n\t\tconst entry = this.wtWatchers.get(repoPath);\n\t\tif (entry == null) return;\n\n\t\t// Drops any read still in flight, and any refresh waiting on its debounce\n\t\tentry.generation++;\n\t\tclearTimeout(entry.refreshTimer);\n\t\tentry.refreshTimer = undefined;\n\t\tentry.watcher.dispose();\n\t\tif (entry.pollTarget != null) {\n\t\t\tthis.poller?.delete(entry.pollTarget);\n\t\t\tentry.pollTarget = undefined;\n\t\t}\n\t\tthis.wtWatchers.delete(repoPath);\n\t}\n\n\t/**\n\t * Creates a working-tree watcher, with the reliability it reported before returning, and whether it has yet to call\n\t * the `onDidStart` its provider promised\n\t */\n\tprivate createWorkingTreeWatcher(\n\t\trepoPath: string,\n\t\tonEvent: (event: FileWatchEvent) => void,\n\t\texcludes: readonly string[],\n\t): { watcher: FileWatcher; reliability: FileWatcherReliability; starting: boolean } {\n\t\tlet watcher: FileWatcher | undefined;\n\t\tlet started = false;\n\t\t// Not given to `.git` watchers: a host places one for every checkout at startup, and each would cost a rescan\n\t\tconst onDidStart = (): void => {\n\t\t\tif (started) return;\n\n\t\t\tstarted = true;\n\t\t\t// A watch that was live before `createWatcher` returned missed nothing\n\t\t\tif (watcher == null) return;\n\n\t\t\t// The same rescan as a watch coming back, and only for the live watcher, never one already replaced or stopped\n\t\t\tconst entry = this.wtWatchers.get(repoPath);\n\t\t\tif (entry?.watcher === watcher) {\n\t\t\t\tentry.starting = false;\n\t\t\t\tentry.session.pushWorkingTreeChanges([], true);\n\t\t\t}\n\t\t};\n\n\t\tif (this.poller == null) {\n\t\t\twatcher = this.provider.createWatcher(repoPath, '**', onEvent, {\n\t\t\t\texcludes: excludes,\n\t\t\t\tonDidStart: onDidStart,\n\t\t\t});\n\t\t\treturn {\n\t\t\t\twatcher: watcher,\n\t\t\t\treliability: 'reliable',\n\t\t\t\tstarting: this.provider.callsOnDidStart === true && !started,\n\t\t\t};\n\t\t}\n\n\t\tlet reliability: FileWatcherReliability = 'reliable';\n\t\tconst options: FileWatcherOptions = {\n\t\t\texcludes: excludes,\n\t\t\tonDidStart: onDidStart,\n\t\t\tonDidChangeReliability: state => {\n\t\t\t\tif (watcher == null) {\n\t\t\t\t\treliability = state;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Only the live watcher speaks for the working tree, never one already replaced\n\t\t\t\tconst entry = this.wtWatchers.get(repoPath);\n\t\t\t\tif (entry?.watcher === watcher) {\n\t\t\t\t\tthis.setWorkingTreeReliability(entry, state);\n\t\t\t\t}\n\t\t\t},\n\t\t};\n\t\twatcher = this.provider.createWatcher(repoPath, '**', onEvent, options);\n\t\treturn {\n\t\t\twatcher: watcher,\n\t\t\treliability: reliability,\n\t\t\tstarting: this.provider.callsOnDidStart === true && !started,\n\t\t};\n\t}\n\n\tprivate setWorkingTreeReliability(\n\t\tentry: WorkingTreeWatcherEntry,\n\t\tstate: FileWatcherReliability,\n\t\tinitial?: boolean,\n\t): void {\n\t\tconst previous = entry.reliability;\n\t\tif (previous === state) return;\n\n\t\tentry.reliability = state;\n\t\tthis.updateWorkingTreePollTarget(entry);\n\n\t\tif ((state === 'unwatchable') === (previous === 'unwatchable')) return;\n\n\t\t// The watch went down or came back: whatever changed meanwhile went unseen. This rescan stands in for a start still\n\t\t// owed, which a provider that lost the watch may never report\n\t\tif (!initial) {\n\t\t\tentry.starting = false;\n\t\t\tentry.session.pushWorkingTreeChanges([], true);\n\t\t}\n\t\tconst record = this.sessionMap.get(entry.session.repoPath);\n\t\tif (record != null) {\n\t\t\tthis.updateWatchMode(record);\n\t\t}\n\t}\n\n\t/** Polls the host's working-tree fingerprint, when it gives one, while the watcher isn't `reliable` */\n\tprivate updateWorkingTreePollTarget(entry: WorkingTreeWatcherEntry): void {\n\t\tconst poller = this.poller;\n\t\tconst getFingerprint = poller?.options.getWorkingTreeFingerprint;\n\t\tif (poller == null || getFingerprint == null) return;\n\n\t\tif (entry.reliability === 'reliable') {\n\t\t\tif (entry.pollTarget != null) {\n\t\t\t\tpoller.delete(entry.pollTarget);\n\t\t\t\tentry.pollTarget = undefined;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tif (entry.pollTarget != null) return;\n\n\t\tconst repoPath = entry.session.repoPath;\n\t\tlet last: string | undefined;\n\t\tconst target: PollTarget = {\n\t\t\tpoll: async (): Promise<void> => {\n\t\t\t\tlet fingerprint: string | undefined;\n\t\t\t\ttry {\n\t\t\t\t\tfingerprint = await getFingerprint(repoPath);\n\t\t\t\t} catch {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (fingerprint == null || entry.pollTarget !== target) return;\n\n\t\t\t\tconst moved = last != null && last !== fingerprint;\n\t\t\t\tlast = fingerprint;\n\t\t\t\t// A fingerprint says the tree moved, not which files did\n\t\t\t\tif (moved) {\n\t\t\t\t\tentry.session.pushWorkingTreeChanges([], true);\n\t\t\t\t}\n\t\t\t},\n\t\t};\n\t\tentry.pollTarget = target;\n\t\tpoller.add(target);\n\t}\n\n\tprivate canPruneIgnoredDirectories(): boolean {\n\t\treturn this.provider.supportsExcludes === true && this.getIgnoredDirectories != null;\n\t}\n\n\t/**\n\t * An event the gitignore post-filter dropped. When its directory is itself ignored, the watcher is watching an\n\t * ignored directory it could prune (one created after the last read), so the ignored set is read again. An ignored\n\t * file in a watched directory (a root `.env`, `src/debug.log`) triggers nothing.\n\t */\n\tprivate onIgnoredEvent(entry: WorkingTreeWatcherEntry, filter: GitIgnoreFilter, relativePath: string): void {\n\t\t// A refresh is already pending and reads the whole set; keep a flood of these events at two field checks\n\t\tif (entry.refreshTimer != null || entry.rereadQueued) return;\n\n\t\t// The outermost ignored directory above the path — the one a read would prune. Keyed on it (not the immediate\n\t\t// parent), so a directory git won't prune costs one read however many subdirectories it has\n\t\tlet ignoredDir: string | undefined;\n\t\tfor (let slash = relativePath.indexOf('/'); slash !== -1; slash = relativePath.indexOf('/', slash + 1)) {\n\t\t\tconst dir = relativePath.slice(0, slash);\n\t\t\tif (entry.unpruned?.has(dir) || entry.unprunable?.has(dir)) return;\n\t\t\tif (filter.isIgnored(`${dir}/`)) {\n\t\t\t\tignoredDir = dir;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (ignoredDir == null) return;\n\n\t\tentry.unpruned ??= new Set();\n\t\tentry.unpruned.add(ignoredDir);\n\n\t\t// Ignore rules didn't change, so a read in flight stays valid (no generation bump), but it may predate the\n\t\t// directory\n\t\tif (entry.reading) {\n\t\t\tentry.rereadQueued = true;\n\t\t\treturn;\n\t\t}\n\n\t\tthis.armIgnoredDirectoriesRefresh(entry);\n\t}\n\n\t/** Re-reads the git-ignored directories once ignore rules stop changing (trailing debounce, one timer per repo) */\n\tprivate scheduleIgnoredDirectoriesRefresh(entry: WorkingTreeWatcherEntry): void {\n\t\t// Read under the old rules, so a restart must read again even if the watcher stops before this re-read lands\n\t\tif (entry.filter != null) {\n\t\t\tthis.keptIgnoredDirectories.delete(entry.filter);\n\t\t}\n\t\tif (!this.canPruneIgnoredDirectories()) return;\n\n\t\t// A read already in flight is now stale, and a directory git wouldn't prune may be prunable now\n\t\tentry.generation++;\n\t\tentry.unprunable = undefined;\n\t\tclearTimeout(entry.refreshTimer);\n\t\tthis.armIgnoredDirectoriesRefresh(entry);\n\t}\n\n\tprivate armIgnoredDirectoriesRefresh(entry: WorkingTreeWatcherEntry): void {\n\t\tentry.refreshTimer = setTimeout(() => {\n\t\t\tentry.refreshTimer = undefined;\n\t\t\tconst repoPath = entry.session.repoPath;\n\t\t\tif (this.wtWatchers.get(repoPath) !== entry) return;\n\n\t\t\tif (entry.reading) {\n\t\t\t\tentry.rereadQueued = true;\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tvoid this.refreshIgnoredDirectories(entry, repoPath);\n\t\t}, ignoredDirectoriesRefreshDelayMs);\n\t}\n\n\t/** Single-flight: reads the git-ignored directories and, when they changed, swaps in a watcher excluding them */\n\tprivate async refreshIgnoredDirectories(entry: WorkingTreeWatcherEntry, repoPath: string): Promise<void> {\n\t\tif (this.getIgnoredDirectories == null) return;\n\n\t\tentry.reading = true;\n\t\ttry {\n\t\t\tconst generation = entry.generation;\n\t\t\t// The unpruned directories seen so far; any seen during the read wait for the next one\n\t\t\tconst unpruned = entry.unpruned?.size ? [...entry.unpruned] : undefined;\n\n\t\t\tlet directories: readonly string[] | undefined;\n\t\t\ttry {\n\t\t\t\tdirectories = await this.getIgnoredDirectories(repoPath);\n\t\t\t} catch {\n\t\t\t\t// Keep what the watcher has; the next ignore-rule change reads again\n\t\t\t\tdirectories = undefined;\n\t\t\t}\n\n\t\t\tif (entry.generation === generation) {\n\t\t\t\tif (directories != null) {\n\t\t\t\t\tthis.swapWorkingTreeWatcher(entry, repoPath, directories);\n\t\t\t\t\tif (entry.filter != null) {\n\t\t\t\t\t\tthis.keptIgnoredDirectories.set(entry.filter, directories);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (unpruned != null) {\n\t\t\t\t\tthis.settleUnprunedDirectories(entry, unpruned, directories);\n\t\t\t\t}\n\t\t\t}\n\t\t} finally {\n\t\t\tentry.reading = false;\n\t\t}\n\n\t\tif (entry.rereadQueued) {\n\t\t\tentry.rereadQueued = false;\n\t\t\tif (this.wtWatchers.get(repoPath) === entry) {\n\t\t\t\tvoid this.refreshIgnoredDirectories(entry, repoPath);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * A directory the read left unexcluded is one git won't prune (or the read failed), so it never triggers a read\n\t * again until ignore rules change — otherwise each of its events would spawn git.\n\t */\n\tprivate settleUnprunedDirectories(\n\t\tentry: WorkingTreeWatcherEntry,\n\t\tunpruned: readonly string[],\n\t\tdirectories: readonly string[] | undefined,\n\t): void {\n\t\tfor (const dir of unpruned) {\n\t\t\tentry.unpruned?.delete(dir);\n\t\t\tif (directories?.some(d => d === dir || dir.startsWith(`${d}/`))) continue;\n\n\t\t\tentry.unprunable ??= new Set();\n\t\t\tentry.unprunable.add(dir);\n\t\t}\n\t}\n\n\tprivate swapWorkingTreeWatcher(\n\t\tentry: WorkingTreeWatcherEntry,\n\t\trepoPath: string,\n\t\tdirectories: readonly string[],\n\t): void {\n\t\tconst previous = entry.ignoredDirectories;\n\t\tif (previous.length === directories.length && previous.every(d => directories.includes(d))) return;\n\n\t\t// New first, then the old one: the overlap can repeat an event (the session coalesces them), but never loses one.\n\t\t// A provider that places its watch asynchronously leaves a gap until it goes live, which its `onDidStart` rescans\n\t\tlet created: { watcher: FileWatcher; reliability: FileWatcherReliability; starting: boolean };\n\t\ttry {\n\t\t\tcreated = this.createWorkingTreeWatcher(repoPath, entry.onEvent, [...workingTreeExcludes, ...directories]);\n\t\t} catch {\n\t\t\treturn;\n\t\t}\n\n\t\tconst previousWatcher = entry.watcher;\n\t\tentry.watcher = created.watcher;\n\t\t// The replacement owes its own start\n\t\tentry.starting = created.starting;\n\t\tentry.ignoredDirectories = directories;\n\t\tpreviousWatcher.dispose();\n\t\tthis.setWorkingTreeReliability(entry, created.reliability);\n\n\t\t// Files in a directory that is watched again changed while nothing was looking. A directory still covered by\n\t\t// a surviving ancestor entry isn't watched again.\n\t\tif (previous.some(d => !directories.some(n => n === d || d.startsWith(`${n}/`)))) {\n\t\t\tentry.session.pushWorkingTreeChanges([], true);\n\t\t}\n\t}\n\n\tprivate refreshWorkingTreeFilter(repoPath: string): void {\n\t\tconst entry = this.wtWatchers.get(repoPath);\n\t\tif (entry == null) return;\n\n\t\tif (entry.filter != null) {\n\t\t\tvoid entry.filter.refresh();\n\t\t}\n\t\tthis.scheduleIgnoredDirectoriesRefresh(entry);\n\t}\n\n\tprivate disposeSessionRecord(record: SessionRecord): void {\n\t\t// The working tree first: with it still up, leaving the group would read as a move back to `'events'` and\n\t\t// announce one for a repository that is going away\n\t\tthis.stopWorkingTreeWatcher(record.session.repoPath);\n\t\tthis.disconnectFromWatchGroup(record);\n\t\tthis.updateWatchMode(record);\n\t\trecord.session.dispose();\n\t}\n}\n\nfunction filterWorkingTreePaths(absolutePaths: string[], repoPath: string, filter: GitIgnoreFilter): string[] {\n\tconst result: string[] = [];\n\tfor (const p of absolutePaths) {\n\t\tif (!isDescendant(p, repoPath)) continue;\n\n\t\tconst relativePath = relative(repoPath, p);\n\t\tif (!filter.isIgnored(relativePath)) {\n\t\t\tresult.push(p);\n\t\t}\n\t}\n\treturn result;\n}\n"],"mappings":"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"}
@@ -79,6 +79,11 @@ export declare class RepositoryWatchSession implements UnifiedDisposable {
79
79
  * {@link noteSelfWrite})
80
80
  */
81
81
  private readonly pendingRepoAnnounced;
82
+ /**
83
+ * Refs core's writes named since the last flush, kept when an outside report makes their kind unannounced: each is
84
+ * a ref a write changed, never the echo of a ref directory
85
+ */
86
+ private readonly pendingRepoAnnouncedRefs;
82
87
  /**
83
88
  * For a pending `config` change, the branches (full ref names) whose sections are all that every report of it
84
89
  * changed; `undefined` once any report may have changed anything else
@@ -187,14 +192,14 @@ export declare class RepositoryWatchSession implements UnifiedDisposable {
187
192
  * `config` is confined to the `branch.<name>` sections of the local branches `refs` names when
188
193
  * `options.configConfinedToRefs` says so, and the event carries them (`configBranches`);
189
194
  * - a raw change of an announced kind is the write's own report of it only when it is provably within what the
190
- * write announced: a ref the write named, or a config change confined to branches the write's config was confined
191
- * to. Pending when the announcement arrives, it is replaced. Arriving later, it merges and the kind stays
192
- * announced only within the repo debounce delay of the announcement; a window can stay pending for minutes while
193
- * the session is suspended, and a later move of the same ref is as likely an outside one. Any other report of
194
- * the kind is an outside change: it stays, and the kind isn't `announced`, since the host has not handled the
195
- * outside part. A write never names `HEAD`, but a commit locks it to write its reflog, so under a write that
196
- * announced `head`, a report of the `HEAD` file within the same delay is the write's echo, its `heads` part
197
- * included;
195
+ * write announced: a ref the write named or a ref directory holding one, or a config change confined to branches
196
+ * the write's config was confined to. Pending when the announcement arrives, it is replaced. Arriving later, it
197
+ * merges and the kind stays announced only within the repo debounce delay of the announcement; a window can stay
198
+ * pending for minutes while the session is suspended, and a later move of the same ref is as likely an outside
199
+ * one. Any other report of the kind is an outside change: it stays, and the kind isn't `announced`, since the
200
+ * host has not handled the outside part. A write never names `HEAD`, but a commit locks it to write its reflog,
201
+ * so under a write that announced `head`, a report of the `HEAD` file within the same delay is the write's echo,
202
+ * its `heads` part included;
198
203
  * - a kind the write announced open (unnamed, or `config` unconfined) covers any change of the kind: raw changes
199
204
  * of it still pending are replaced, since the host evicts everything of the kind after the write. One arriving
200
205
  * within the repo debounce delay of the announcement, before its event fires, is taken for the write's own late
@@ -150,6 +150,11 @@ var RepositoryWatchSession = class RepositoryWatchSession {
150
150
  */
151
151
  pendingRepoAnnounced = /* @__PURE__ */ new Map();
152
152
  /**
153
+ * Refs core's writes named since the last flush, kept when an outside report makes their kind unannounced: each is
154
+ * a ref a write changed, never the echo of a ref directory
155
+ */
156
+ pendingRepoAnnouncedRefs = /* @__PURE__ */ new Set();
157
+ /**
153
158
  * For a pending `config` change, the branches (full ref names) whose sections are all that every report of it
154
159
  * changed; `undefined` once any report may have changed anything else
155
160
  */
@@ -374,14 +379,14 @@ var RepositoryWatchSession = class RepositoryWatchSession {
374
379
  * `config` is confined to the `branch.<name>` sections of the local branches `refs` names when
375
380
  * `options.configConfinedToRefs` says so, and the event carries them (`configBranches`);
376
381
  * - a raw change of an announced kind is the write's own report of it only when it is provably within what the
377
- * write announced: a ref the write named, or a config change confined to branches the write's config was confined
378
- * to. Pending when the announcement arrives, it is replaced. Arriving later, it merges and the kind stays
379
- * announced only within the repo debounce delay of the announcement; a window can stay pending for minutes while
380
- * the session is suspended, and a later move of the same ref is as likely an outside one. Any other report of
381
- * the kind is an outside change: it stays, and the kind isn't `announced`, since the host has not handled the
382
- * outside part. A write never names `HEAD`, but a commit locks it to write its reflog, so under a write that
383
- * announced `head`, a report of the `HEAD` file within the same delay is the write's echo, its `heads` part
384
- * included;
382
+ * write announced: a ref the write named or a ref directory holding one, or a config change confined to branches
383
+ * the write's config was confined to. Pending when the announcement arrives, it is replaced. Arriving later, it
384
+ * merges and the kind stays announced only within the repo debounce delay of the announcement; a window can stay
385
+ * pending for minutes while the session is suspended, and a later move of the same ref is as likely an outside
386
+ * one. Any other report of the kind is an outside change: it stays, and the kind isn't `announced`, since the
387
+ * host has not handled the outside part. A write never names `HEAD`, but a commit locks it to write its reflog,
388
+ * so under a write that announced `head`, a report of the `HEAD` file within the same delay is the write's echo,
389
+ * its `heads` part included;
385
390
  * - a kind the write announced open (unnamed, or `config` unconfined) covers any change of the kind: raw changes
386
391
  * of it still pending are replaced, since the host evicts everything of the kind after the write. One arriving
387
392
  * within the repo debounce delay of the announcement, before its event fires, is taken for the write's own late
@@ -411,6 +416,7 @@ var RepositoryWatchSession = class RepositoryWatchSession {
411
416
  const names = isRefChangeKind(change) ? named?.get(change) : void 0;
412
417
  if (names != null) {
413
418
  for (const name of names) this.pendingRepoRefs.delete(name);
419
+ for (const name of names) for (const dir of getRefDirectories(name)) if (!this.pendingRepoAnnouncedRefs.has(dir)) this.pendingRepoRefs.delete(dir);
414
420
  if (this.hasPendingRefOfKind(change)) continue;
415
421
  }
416
422
  this.pendingRepoChanges.delete(change);
@@ -422,7 +428,10 @@ var RepositoryWatchSession = class RepositoryWatchSession {
422
428
  }
423
429
  if (announced.length !== 0) {
424
430
  this.coalesceRepo(named != null ? announced.filter((c) => !isRefChangeKind(c) || !named.has(c)) : announced, void 0, "announced", configBranches);
425
- if (named != null) for (const [kind, names] of named) for (const name of names) this.coalesceRepo([kind], name, "announced");
431
+ if (named != null) for (const [kind, names] of named) for (const name of names) {
432
+ this.coalesceRepo([kind], name, "announced");
433
+ if (this.pendingRepoRefs.has(name)) this.pendingRepoAnnouncedRefs.add(name);
434
+ }
426
435
  } else if (this.pendingRepoChanges.size === 0) this.repoClock.cancel();
427
436
  }
428
437
  /** Push working tree file changes into the pipeline */
@@ -523,7 +532,7 @@ var RepositoryWatchSession = class RepositoryWatchSession {
523
532
  if (now - announcement.at > this.effectiveRepoMs) return false;
524
533
  if (announcement.names == null) return true;
525
534
  if (names == null) return false;
526
- if (isSubsetOf(names, announcement.names)) return true;
535
+ if (isRefChangeKind(change) ? isCoveredBy(names, announcement.names) : isSubsetOf(names, announcement.names)) return true;
527
536
  return this.isHeadEcho(change, names, now);
528
537
  }
529
538
  /** Whether a raw `heads` report confined to `HEAD` is the echo of a write that announced `head` in time */
@@ -545,6 +554,7 @@ var RepositoryWatchSession = class RepositoryWatchSession {
545
554
  this.pendingRepoUnnamedRefKinds.clear();
546
555
  this.pendingRepoUnfencedChanges.clear();
547
556
  this.pendingRepoAnnounced.clear();
557
+ this.pendingRepoAnnouncedRefs.clear();
548
558
  this.pendingRepoConfigBranches = void 0;
549
559
  }
550
560
  flushWorkingTree() {
@@ -602,6 +612,36 @@ function getBranchRefs(refs) {
602
612
  for (const ref of refs ?? []) if (ref.startsWith("refs/heads/")) (branches ??= /* @__PURE__ */ new Set()).add(ref);
603
613
  return branches;
604
614
  }
615
+ /**
616
+ * Whether `ref` is a ref directory holding one of `names` (compared on `/` boundaries). A namespace root
617
+ * (`refs/heads`) or a pseudo-ref is no such directory, so a report of it is never a write's own by this rule.
618
+ */
619
+ function isRefDirectoryOfAny(ref, names) {
620
+ if (!ref.startsWith("refs/") || getRefChangeKind(ref) == null) return false;
621
+ const prefix = `${ref}/`;
622
+ for (const name of names) if (name.startsWith(prefix)) return true;
623
+ return false;
624
+ }
625
+ /**
626
+ * The ref directories holding `ref`, innermost first, each of `ref`'s own kind; never a namespace root (`refs/heads`),
627
+ * as {@link isRefDirectoryOfAny} counts them
628
+ */
629
+ function* getRefDirectories(ref) {
630
+ for (let index = ref.lastIndexOf("/"); index > 0; index = ref.lastIndexOf("/", index - 1)) {
631
+ const dir = ref.substring(0, index);
632
+ if (!dir.startsWith("refs/") || getRefChangeKind(dir) == null) return;
633
+ yield dir;
634
+ }
635
+ }
636
+ /**
637
+ * Whether every name in `names` is one in `of` or a ref directory holding one; never for `undefined`, which stands for
638
+ * any name
639
+ */
640
+ function isCoveredBy(names, of) {
641
+ if (names == null) return false;
642
+ for (const name of names) if (!of.has(name) && !isRefDirectoryOfAny(name, of)) return false;
643
+ return true;
644
+ }
605
645
  /** Whether every name in `names` is in `of`; never for `undefined`, which stands for any name */
606
646
  function isSubsetOf(names, of) {
607
647
  if (names == null) return false;