@gitkraken/core-gitlens 0.15.3 → 0.15.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -1
- package/dist/git/cache.d.ts +24 -11
- package/dist/git/cache.js +44 -16
- package/dist/git/cache.js.map +1 -1
- package/dist/git/models/pullRequest.d.ts +7 -5
- package/dist/git/models/pullRequest.js.map +1 -1
- package/dist/git/models/reference.d.ts +8 -0
- package/dist/git/providers/branches.d.ts +43 -0
- package/dist/git/watching/changeEvent.d.ts +4 -2
- package/dist/git/watching/changeEvent.js +3 -1
- package/dist/git/watching/changeEvent.js.map +1 -1
- package/dist/git/watching/watchGroup.d.ts +3 -3
- package/dist/git/watching/watchGroup.js +1 -0
- package/dist/git/watching/watchGroup.js.map +1 -1
- package/dist/git/watching/watchService.js.map +1 -1
- package/dist/git/watching/watchSession.d.ts +2 -1
- package/dist/git/watching/watchSession.js +3 -2
- package/dist/git/watching/watchSession.js.map +1 -1
- package/dist/git/watching/watcherPatterns.js +1 -0
- package/dist/git/watching/watcherPatterns.js.map +1 -1
- package/dist/git/watching/worktreeSummary.d.ts +19 -3
- package/dist/git/watching/worktreeSummary.js +107 -17
- package/dist/git/watching/worktreeSummary.js.map +1 -1
- package/dist/git-cli/parsers/refParser.js +2 -1
- package/dist/git-cli/parsers/refParser.js.map +1 -1
- package/dist/git-cli/providers/branches.d.ts +8 -1
- package/dist/git-cli/providers/branches.js +84 -12
- package/dist/git-cli/providers/branches.js.map +1 -1
- package/dist/git-cli/providers/config.d.ts +32 -9
- package/dist/git-cli/providers/config.js +113 -24
- package/dist/git-cli/providers/config.js.map +1 -1
- package/dist/plus/integrations/models/gitHostIntegration.d.ts +29 -14
- package/dist/plus/integrations/models/gitHostIntegration.js +41 -19
- package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
- package/dist/plus/integrations/providers/azure/azure.d.ts +16 -16
- package/dist/plus/integrations/providers/azure/azure.js +45 -25
- package/dist/plus/integrations/providers/azure/azure.js.map +1 -1
- package/dist/plus/integrations/providers/azure/azure.utils.d.ts +12 -0
- package/dist/plus/integrations/providers/azure/azure.utils.js +17 -1
- package/dist/plus/integrations/providers/azure/azure.utils.js.map +1 -1
- package/dist/plus/integrations/providers/azure/search.d.ts +4 -0
- package/dist/plus/integrations/providers/azure/search.js +46 -2
- package/dist/plus/integrations/providers/azure/search.js.map +1 -1
- package/dist/plus/integrations/providers/azure/workItemSearch.d.ts +11 -0
- package/dist/plus/integrations/providers/azure/workItemSearch.js +54 -0
- package/dist/plus/integrations/providers/azure/workItemSearch.js.map +1 -0
- package/dist/plus/integrations/providers/azure/workItemStateCategories.d.ts +2 -2
- package/dist/plus/integrations/providers/azure/workItemStateCategories.js +19 -15
- package/dist/plus/integrations/providers/azure/workItemStateCategories.js.map +1 -1
- package/dist/plus/integrations/providers/azureDevOps.d.ts +33 -20
- package/dist/plus/integrations/providers/azureDevOps.js +109 -48
- package/dist/plus/integrations/providers/azureDevOps.js.map +1 -1
- package/dist/plus/integrations/providers/azureDevOpsServer.d.ts +16 -47
- package/dist/plus/integrations/providers/azureDevOpsServer.js +52 -91
- package/dist/plus/integrations/providers/azureDevOpsServer.js.map +1 -1
- package/dist/plus/integrations/providers/models.d.ts +5 -0
- package/dist/plus/integrations/providers/models.js +14 -3
- package/dist/plus/integrations/providers/models.js.map +1 -1
- package/dist/plus/integrations/reads/drains.d.ts +2 -2
- package/dist/plus/integrations/reads/drains.js +4 -4
- package/dist/plus/integrations/reads/drains.js.map +1 -1
- package/dist/plus/integrations/reads/issues.js +7 -4
- package/dist/plus/integrations/reads/issues.js.map +1 -1
- package/dist/plus/integrations/reads/resolveRepository.js +5 -5
- package/dist/plus/integrations/reads/resolveRepository.js.map +1 -1
- 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}\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\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\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}). A path no session watches is\n\t * ignored.\n\t */\n\tnoteSelfWrite(repoPath: string, changes: readonly RepositoryChange[]): void {\n\t\tif (this._disposed) return;\n\n\t\tthis.sessionMap.get(getRepositoryKey(repoPath))?.session.noteSelfWrite(changes);\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);\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: (_repoPath: string, changes: RepositoryChange[], ref?: string, overflow?: boolean): 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);\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/** 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}\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\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\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}). A path no session watches is\n\t * ignored.\n\t */\n\tnoteSelfWrite(repoPath: string, changes: readonly RepositoryChange[]): void {\n\t\tif (this._disposed) return;\n\n\t\tthis.sessionMap.get(getRepositoryKey(repoPath))?.session.noteSelfWrite(changes);\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);\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): 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);\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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"}
|
|
@@ -159,8 +159,9 @@ export declare class RepositoryWatchSession implements UnifiedDisposable {
|
|
|
159
159
|
/**
|
|
160
160
|
* Push interpreted repo changes into the pipeline. `ref` is the full ref name (or ref directory, or pseudo-ref) whose
|
|
161
161
|
* file changed; a ref kind in `changes` is attributed to it when the kinds match, and is otherwise unnamed.
|
|
162
|
+
* `false` preserves cache invalidations for a config edit without claiming any ref moved.
|
|
162
163
|
*/
|
|
163
|
-
pushRepoChanges(changes: RepositoryChange[], ref?: string): void;
|
|
164
|
+
pushRepoChanges(changes: RepositoryChange[], ref?: string | false): void;
|
|
164
165
|
/**
|
|
165
166
|
* Push watcher-observed repo changes that no write of core's can account for — an overflow's rescan, or an ignore
|
|
166
167
|
* rule changed in the working tree — so {@link noteSelfWrite} never replaces them or takes them for its echo. Ref
|
|
@@ -339,6 +339,7 @@ var RepositoryWatchSession = class RepositoryWatchSession {
|
|
|
339
339
|
/**
|
|
340
340
|
* Push interpreted repo changes into the pipeline. `ref` is the full ref name (or ref directory, or pseudo-ref) whose
|
|
341
341
|
* file changed; a ref kind in `changes` is attributed to it when the kinds match, and is otherwise unnamed.
|
|
342
|
+
* `false` preserves cache invalidations for a config edit without claiming any ref moved.
|
|
342
343
|
*/
|
|
343
344
|
pushRepoChanges(changes, ref) {
|
|
344
345
|
if (this._disposed) return;
|
|
@@ -436,7 +437,7 @@ var RepositoryWatchSession = class RepositoryWatchSession {
|
|
|
436
437
|
coalesceRepo(changes, ref, origin) {
|
|
437
438
|
if (changes.length === 0) return;
|
|
438
439
|
const now = performance.now();
|
|
439
|
-
const refKind = ref
|
|
440
|
+
const refKind = typeof ref === "string" ? getRefChangeKind(ref) : void 0;
|
|
440
441
|
for (const change of changes) {
|
|
441
442
|
if (origin === "announced") {
|
|
442
443
|
if (!this.pendingRepoChanges.has(change)) this.pendingRepoAnnouncedAt.set(change, now);
|
|
@@ -446,7 +447,7 @@ var RepositoryWatchSession = class RepositoryWatchSession {
|
|
|
446
447
|
if (origin === "unfenced") this.pendingRepoUnfencedChanges.add(change);
|
|
447
448
|
}
|
|
448
449
|
this.pendingRepoChanges.add(change);
|
|
449
|
-
if (!isRefChangeKind(change)) continue;
|
|
450
|
+
if (ref === false || !isRefChangeKind(change)) continue;
|
|
450
451
|
if (ref != null && refKind === change && (this.pendingRepoRefs.size < maxPendingRefNames || this.pendingRepoRefs.has(ref))) this.pendingRepoRefs.add(ref);
|
|
451
452
|
else this.pendingRepoUnnamedRefKinds.add(change);
|
|
452
453
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"watchSession.js","names":[],"sources":["../../../../git/src/watching/watchSession.ts"],"sourcesContent":["import type { UnifiedDisposable } from '@gitlens/utils/disposable.js';\nimport { createDisposable } from '@gitlens/utils/disposable.js';\nimport type { Event } from '@gitlens/utils/event.js';\nimport { Emitter } from '@gitlens/utils/event.js';\nimport type { RepositoryChange } from '../models/repository.js';\nimport type { RefChangeKind, WorkingTreeChangeEvent } from './changeEvent.js';\nimport { findCoveringRefName, getRefChangeKind, isRefChangeKind, WatcherRepoChangeEvent } from './changeEvent.js';\nimport { allWatchedRepositoryChanges } from './classifyChange.js';\n\nexport interface RepositorySubscription extends UnifiedDisposable {\n\treadonly onDidChange: Event<WatcherRepoChangeEvent>;\n}\n\nexport interface WorkingTreeSubscription extends UnifiedDisposable {\n\treadonly onDidChangeWorkingTree: Event<WorkingTreeChangeEvent>;\n}\n\n/**\n * Lifecycle callbacks fired by the session when subscriber counts\n * cross the 0↔1 boundary. The host (WatchService) uses these to\n * lazily create/destroy watchers.\n */\nexport interface WatchSessionLifecycle {\n\t/** Called when the first repo-change subscriber joins */\n\tonFirstRepoSubscriber?(): void;\n\t/** Called when the last repo-change subscriber leaves */\n\tonLastRepoSubscriber?(): void;\n\t/** Called when the first working-tree subscriber joins */\n\tonFirstWorkingTreeSubscriber?(): void;\n\t/** Called when the last working-tree subscriber leaves */\n\tonLastWorkingTreeSubscriber?(): void;\n}\n\nexport interface WatchSessionOptions {\n\treadonly repoPath: string;\n\t/** Default debounce for repo changes. Default: 250ms */\n\treadonly defaultRepoDelayMs?: number;\n\t/** Default debounce for working tree changes. Default: 2500ms */\n\treadonly defaultWorkingTreeDelayMs?: number;\n\t/**\n\t * Default cap on how long repo changes can be held back by continuous activity — a flush happens at most this long\n\t * after the first pending change, however long the burst lasts. `Infinity` disables the cap. Default: 2000ms\n\t */\n\treadonly defaultRepoMaxWaitMs?: number;\n\t/** Default cap on how long working tree changes can be held back by continuous activity. `Infinity` disables the cap. Default: 10000ms */\n\treadonly defaultWorkingTreeMaxWaitMs?: number;\n\treadonly lifecycle?: WatchSessionLifecycle;\n\t/** Initial suspended state — new sessions inherit the service-global suspend state. Default: false */\n\treadonly suspended?: boolean;\n\t/**\n\t * Called whenever a debounced repo change event is dispatched to subscribers.\n\t * Does NOT count as a subscriber — used by WatchService for\n\t * global event forwarding without affecting lifecycle callbacks.\n\t */\n\treadonly onDidFireRepoChange?: (event: WatcherRepoChangeEvent) => void;\n\t/**\n\t * Called whenever a debounced working-tree change is dispatched to subscribers. Like\n\t * {@link onDidFireRepoChange}, does NOT count as a subscriber — used by WatchService for global\n\t * event forwarding (the single point that drives the cache's status clock for working-tree edits).\n\t */\n\treadonly onDidFireWorkingTreeChange?: (repoPath: string) => void;\n}\n\nconst defaultRepoDelay = 250;\nconst defaultWorkingTreeDelay = 2500;\nconst defaultRepoMaxWait = 2000;\nconst defaultWorkingTreeMaxWait = 10000;\n\n/** Largest delay `setTimeout` honors (a signed 32-bit int); anything longer fires immediately */\nconst maxTimerDelay = 0x7fffffff;\n\ninterface DebounceTiming {\n\treadonly delayMs: number;\n\treadonly maxWaitMs: number;\n}\n\n/**\n * Trailing-deadline debounce with a max-wait, shared by the repo and working-tree pipelines.\n *\n * Recording an event costs a `performance.now()` and, only when no timer is armed, one `setTimeout` — never a\n * `clearTimeout`/`setTimeout` pair per event, which is what made a busy repo allocate (and starve) on every raw\n * watcher event. The armed timer is a deadline, not a countdown: when it fires it recomputes the due time from the\n * latest timestamps and either flushes or re-arms for the remainder. The due time is\n * `min(lastEventAt + delay, firstPendingAt + maxWait)`, so continuous activity is flushed at the max-wait instead of\n * never. Monotonic time, so a wall-clock change can't push a deadline out.\n */\nclass DebounceClock {\n\tprivate active = false;\n\tprivate paused = false;\n\tprivate firstAt = 0;\n\tprivate lastAt = 0;\n\tprivate timer: ReturnType<typeof setTimeout> | undefined;\n\tprivate timerDueAt = 0;\n\t/** Absolute flush time set by {@link resume}; dropped by the next event, which returns to normal debounce */\n\tprivate resumeDueAt: number | undefined;\n\n\tconstructor(\n\t\tprivate readonly getDelayMs: () => number,\n\t\tprivate readonly getMaxWaitMs: () => number,\n\t\tprivate readonly onDue: () => void,\n\t) {}\n\n\t/** Records an event; arms the timer only if none is armed. Cheap enough to call per raw event. */\n\tnote(): void {\n\t\tconst now = performance.now();\n\t\tif (!this.active) {\n\t\t\tthis.active = true;\n\t\t\tthis.firstAt = now;\n\t\t}\n\t\tthis.lastAt = now;\n\n\t\tif (this.paused) return;\n\n\t\tif (this.resumeDueAt != null) {\n\t\t\t// The first event after a resume goes back to normal debounce — which may be due sooner than the resume deadline\n\t\t\tthis.resumeDueAt = undefined;\n\t\t\tthis.rearmIfEarlier();\n\t\t}\n\n\t\tif (this.timer == null) {\n\t\t\tthis.arm(this.getDueAt(), now);\n\t\t}\n\t}\n\n\t/** Moves the armed timer earlier when the due time shrank (a subscriber joined with a shorter delay or max-wait) */\n\trearmIfEarlier(): void {\n\t\tif (!this.active || this.paused || this.timer == null) return;\n\n\t\tconst dueAt = this.getDueAt();\n\t\tif (dueAt >= this.timerDueAt) return;\n\n\t\tclearTimeout(this.timer);\n\t\tthis.arm(dueAt, performance.now());\n\t}\n\n\t/** Cancels the timer; events keep accumulating but nothing is due until {@link resume} */\n\tsuspend(): void {\n\t\tthis.paused = true;\n\t\tthis.clearTimer();\n\t}\n\n\t/**\n\t * Restarts the max-wait window from now and schedules the flush for `delayMs` from now (the effective delay when\n\t * omitted). Does nothing if no events are pending.\n\t */\n\tresume(delayMs?: number): void {\n\t\tthis.paused = false;\n\t\tif (!this.active) return;\n\n\t\tconst now = performance.now();\n\t\tthis.firstAt = now;\n\t\tthis.resumeDueAt = now + (delayMs ?? this.getDelayMs());\n\t\tthis.clearTimer();\n\t\tthis.arm(this.resumeDueAt, now);\n\t}\n\n\t/** Drops the pending window and the timer */\n\tcancel(): void {\n\t\tthis.active = false;\n\t\tthis.resumeDueAt = undefined;\n\t\tthis.clearTimer();\n\t}\n\n\tprivate getDueAt(): number {\n\t\treturn this.resumeDueAt ?? Math.min(this.lastAt + this.getDelayMs(), this.firstAt + this.getMaxWaitMs());\n\t}\n\n\tprivate arm(dueAt: number, now: number): void {\n\t\tthis.timerDueAt = dueAt;\n\t\tthis.timer = setTimeout(() => this.onTimer(), Math.min(Math.max(0, dueAt - now), maxTimerDelay));\n\t}\n\n\tprivate onTimer(): void {\n\t\tthis.timer = undefined;\n\t\tif (!this.active || this.paused) return;\n\n\t\tconst now = performance.now();\n\t\tconst dueAt = this.getDueAt();\n\t\tif (dueAt > now) {\n\t\t\tthis.arm(dueAt, now);\n\t\t\treturn;\n\t\t}\n\n\t\t// Reset before firing so an event raised by a subscriber starts a fresh window\n\t\tthis.cancel();\n\t\tthis.onDue();\n\t}\n\n\tprivate clearTimer(): void {\n\t\tif (this.timer != null) {\n\t\t\tclearTimeout(this.timer);\n\t\t\tthis.timer = undefined;\n\t\t}\n\t}\n}\n\n/**\n * Cap on retained working-tree paths per flush. The set otherwise grows without bound — while the window\n * is unfocused the session is suspended, so a branch switch or build accumulates every touched path until\n * refocus. Set low deliberately: ordinary edits are a handful of paths, anything past this was a\n * refresh-everything event anyway, and dropping past it only costs the matching consumers a\n * conservative refresh.\n */\nconst maxWorkingTreePaths = 1000;\n\n/**\n * Cap on retained ref names per flush, for the same reason (a fetch of many refs while suspended). Past it, a ref kind\n * is recorded unnamed — any ref of that kind may have moved — which only makes `mayHaveChangedRef` more conservative.\n */\nconst maxPendingRefNames = 1000;\n\n/**\n * Per-repository session that owns the debounce/coalesce/suspend\n * pipeline for both repo (.git) and working-tree changes.\n *\n * Multiple subscribers share the same pipeline — the shortest\n * requested debounce delay wins, and so does the shortest max-wait.\n */\nexport class RepositoryWatchSession implements UnifiedDisposable {\n\treadonly repoPath: string;\n\n\tprivate readonly repoEmitter = new Emitter<WatcherRepoChangeEvent>();\n\tprivate readonly repoSubscribers = new Map<number, DebounceTiming>(); // subscriberId → timing\n\tprivate repoNextId = 0;\n\t/** Each repo subscriber's `relevantRefs`, for {@link getRelevantRefs} */\n\tprivate readonly relevantRefsSources = new Map<number, () => Iterable<string>>(); // subscriberId → relevantRefs\n\tprivate readonly pendingRepoChanges = new Set<RepositoryChange>();\n\t/** Full ref names, ref directories and pseudo-refs whose files changed, flushed with {@link pendingRepoChanges} */\n\tprivate readonly pendingRepoRefs = new Set<string>();\n\t/** Ref kinds that changed without a name, flushed with {@link pendingRepoChanges} */\n\tprivate readonly pendingRepoUnnamedRefKinds = new Set<RefChangeKind>();\n\t/** Kinds among {@link pendingRepoChanges} injected or pushed unfenced, which an announcement never replaces */\n\tprivate readonly pendingRepoUnfencedChanges = new Set<RepositoryChange>();\n\t/**\n\t * Kinds among {@link pendingRepoChanges} that only core's announced writes reported, with when each was announced\n\t * (see {@link noteSelfWrite})\n\t */\n\tprivate readonly pendingRepoAnnouncedAt = new Map<RepositoryChange, number>();\n\tprivate readonly repoClock: DebounceClock;\n\tprivate effectiveRepoMs: number;\n\tprivate effectiveRepoMaxWaitMs: number;\n\tprivate readonly defaultRepoMs: number;\n\tprivate readonly defaultRepoMaxWaitMs: number;\n\n\tprivate readonly workingTreeEmitter = new Emitter<WorkingTreeChangeEvent>();\n\tprivate readonly workingTreeSubscribers = new Map<number, DebounceTiming>();\n\tprivate workingTreeNextId = 0;\n\tprivate pendingWorkingTreePaths = new Set<string>();\n\tprivate pendingWorkingTreeIncomplete = false;\n\tprivate readonly workingTreeClock: DebounceClock;\n\tprivate effectiveWorkingTreeMs: number;\n\tprivate effectiveWorkingTreeMaxWaitMs: number;\n\tprivate readonly defaultWorkingTreeMs: number;\n\tprivate readonly defaultWorkingTreeMaxWaitMs: number;\n\n\tprivate _etagWorkingTree = 0;\n\tprivate _suspended: boolean;\n\tprivate _disposed = false;\n\n\tprivate readonly lifecycle?: WatchSessionLifecycle;\n\tprivate readonly onDidFireRepoChangeCallback?: (event: WatcherRepoChangeEvent) => void;\n\tprivate readonly onDidFireWorkingTreeChangeCallback?: (repoPath: string) => void;\n\n\tconstructor(options: WatchSessionOptions) {\n\t\tthis.repoPath = options.repoPath;\n\t\tthis.defaultRepoMs = options.defaultRepoDelayMs ?? defaultRepoDelay;\n\t\tthis.defaultWorkingTreeMs = options.defaultWorkingTreeDelayMs ?? defaultWorkingTreeDelay;\n\t\tthis.defaultRepoMaxWaitMs = options.defaultRepoMaxWaitMs ?? defaultRepoMaxWait;\n\t\tthis.defaultWorkingTreeMaxWaitMs = options.defaultWorkingTreeMaxWaitMs ?? defaultWorkingTreeMaxWait;\n\t\tthis.effectiveRepoMs = this.defaultRepoMs;\n\t\tthis.effectiveRepoMaxWaitMs = this.defaultRepoMaxWaitMs;\n\t\tthis.effectiveWorkingTreeMs = this.defaultWorkingTreeMs;\n\t\tthis.effectiveWorkingTreeMaxWaitMs = this.defaultWorkingTreeMaxWaitMs;\n\t\tthis.repoClock = new DebounceClock(\n\t\t\t() => this.effectiveRepoMs,\n\t\t\t() => this.effectiveRepoMaxWaitMs,\n\t\t\t() => this.flushRepo(),\n\t\t);\n\t\tthis.workingTreeClock = new DebounceClock(\n\t\t\t() => this.effectiveWorkingTreeMs,\n\t\t\t() => this.effectiveWorkingTreeMaxWaitMs,\n\t\t\t() => this.flushWorkingTree(),\n\t\t);\n\t\tthis.lifecycle = options.lifecycle;\n\t\tthis.onDidFireRepoChangeCallback = options.onDidFireRepoChange;\n\t\tthis.onDidFireWorkingTreeChangeCallback = options.onDidFireWorkingTreeChange;\n\t\tthis._suspended = options.suspended ?? false;\n\t\tif (this._suspended) {\n\t\t\tthis.repoClock.suspend();\n\t\t\tthis.workingTreeClock.suspend();\n\t\t}\n\t}\n\n\t[Symbol.dispose](): void {\n\t\tthis.dispose();\n\t}\n\n\tdispose(): void {\n\t\tif (this._disposed) return;\n\n\t\tthis._disposed = true;\n\n\t\tthis.repoClock.cancel();\n\t\tthis.workingTreeClock.cancel();\n\n\t\tthis.clearPendingRepoChanges();\n\t\tthis.pendingWorkingTreePaths.clear();\n\t\tthis.pendingWorkingTreeIncomplete = false;\n\t\tthis.repoSubscribers.clear();\n\t\tthis.relevantRefsSources.clear();\n\t\tthis.workingTreeSubscribers.clear();\n\n\t\tthis.repoEmitter.dispose();\n\t\tthis.workingTreeEmitter.dispose();\n\t}\n\n\tget suspended(): boolean {\n\t\treturn this._suspended;\n\t}\n\n\tget hasPendingChanges(): boolean {\n\t\treturn this.pendingRepoChanges.size > 0 || this.hasPendingWorkingTreeChanges;\n\t}\n\n\t/**\n\t * An incomplete batch with no surviving paths still has to fire: the caller dropped paths we never saw, so\n\t * gating the pipeline on the retained set alone would swallow the change entirely.\n\t */\n\tprivate get hasPendingWorkingTreeChanges(): boolean {\n\t\treturn this.pendingWorkingTreePaths.size > 0 || this.pendingWorkingTreeIncomplete;\n\t}\n\n\tget repoSubscriberCount(): number {\n\t\treturn this.repoSubscribers.size;\n\t}\n\n\tget workingTreeSubscriberCount(): number {\n\t\treturn this.workingTreeSubscribers.size;\n\t}\n\n\t/**\n\t * Monotonic counter incremented immediately when working tree changes\n\t * are pushed (before debounce). Used by `GitCommit.hasFullDetails()`\n\t * to detect staleness of uncommitted commit data.\n\t */\n\tget etagWorkingTree(): number {\n\t\treturn this._etagWorkingTree;\n\t}\n\n\t/**\n\t * Subscribe to .git directory changes with configurable debounce.\n\t * First subscribe fires `onFirstRepoSubscriber`, last dispose\n\t * fires `onLastRepoSubscriber`.\n\t *\n\t * When multiple subscribers exist, the shortest debounce wins —\n\t * all subscribers fire on the same (fastest) cadence. `maxWaitMs` bounds how long continuous\n\t * activity can defer a flush (the shortest wins too); `Infinity` means no cap.\n\t *\n\t * `relevantRefs` narrows THIS subscription's events to the refs it cares about, given as full ref names\n\t * (`refs/heads/main`, `refs/remotes/origin/main`, `refs/tags/v1`, `refs/stash`). It is read at delivery, so it can\n\t * return the subscriber's current list. A `heads`/`remotes`/`tags`/`stash` change is delivered only when a listed\n\t * ref of that kind may have moved, when the change isn't attributable to a name (e.g. a `packed-refs` rewrite),\n\t * or, for `heads`, when a pseudo-ref (`HEAD`, `ORIG_HEAD`) moved — so `stash` and `tags` changes are dropped unless\n\t * a ref of that kind is listed. Other change kinds always pass, and an event left with no changes isn't delivered.\n\t * The filter never affects other subscribers, the debounce, or the global `onDidFireRepoChange` forwarding.\n\t */\n\tsubscribe(opts?: {\n\t\tdelayMs?: number;\n\t\tmaxWaitMs?: number;\n\t\trelevantRefs?: () => Iterable<string>;\n\t}): RepositorySubscription {\n\t\tif (this._disposed) {\n\t\t\treturn Object.assign(\n\t\t\t\tcreateDisposable(() => {}),\n\t\t\t\t{\n\t\t\t\t\tonDidChange: this.repoEmitter.event,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\n\t\tconst id = this.repoNextId++;\n\t\tconst wasEmpty = this.repoSubscribers.size === 0;\n\n\t\tthis.repoSubscribers.set(id, {\n\t\t\tdelayMs: opts?.delayMs ?? this.defaultRepoMs,\n\t\t\tmaxWaitMs: opts?.maxWaitMs ?? this.defaultRepoMaxWaitMs,\n\t\t});\n\t\tthis.recalcRepoTiming();\n\n\t\tconst relevantRefs = opts?.relevantRefs;\n\t\tif (relevantRefs != null) {\n\t\t\tthis.relevantRefsSources.set(id, relevantRefs);\n\t\t}\n\n\t\tif (wasEmpty) {\n\t\t\tthis.lifecycle?.onFirstRepoSubscriber?.();\n\t\t}\n\n\t\t// If the effective delay or max-wait got shorter while a flush is pending, move it sooner\n\t\tthis.repoClock.rearmIfEarlier();\n\n\t\tlet subDisposed = false;\n\t\treturn Object.assign(\n\t\t\tcreateDisposable(() => {\n\t\t\t\tif (subDisposed) return;\n\n\t\t\t\tsubDisposed = true;\n\n\t\t\t\tthis.repoSubscribers.delete(id);\n\t\t\t\tthis.relevantRefsSources.delete(id);\n\t\t\t\tthis.recalcRepoTiming();\n\n\t\t\t\tif (this.repoSubscribers.size === 0) {\n\t\t\t\t\tthis.repoClock.cancel();\n\t\t\t\t\tthis.clearPendingRepoChanges();\n\t\t\t\t\tthis.lifecycle?.onLastRepoSubscriber?.();\n\t\t\t\t}\n\t\t\t}),\n\t\t\t{\n\t\t\t\tonDidChange:\n\t\t\t\t\trelevantRefs != null\n\t\t\t\t\t\t? createRelevantRefsEvent(this.repoEmitter.event, relevantRefs)\n\t\t\t\t\t\t: this.repoEmitter.event,\n\t\t\t},\n\t\t);\n\t}\n\n\t/** Every repo subscriber's current `relevantRefs` (see {@link subscribe}), unioned */\n\tgetRelevantRefs(): Set<string> {\n\t\tconst refs = new Set<string>();\n\t\tfor (const relevantRefs of this.relevantRefsSources.values()) {\n\t\t\tfor (const ref of relevantRefs()) {\n\t\t\t\trefs.add(ref);\n\t\t\t}\n\t\t}\n\t\treturn refs;\n\t}\n\n\t/** Subscribe to working tree changes; `delayMs` and `maxWaitMs` behave as in {@link subscribe}. */\n\tsubscribeToWorkingTree(opts?: { delayMs?: number; maxWaitMs?: number }): WorkingTreeSubscription {\n\t\tif (this._disposed) {\n\t\t\treturn Object.assign(\n\t\t\t\tcreateDisposable(() => {}),\n\t\t\t\t{\n\t\t\t\t\tonDidChangeWorkingTree: this.workingTreeEmitter.event,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\n\t\tconst id = this.workingTreeNextId++;\n\t\tconst wasEmpty = this.workingTreeSubscribers.size === 0;\n\n\t\tthis.workingTreeSubscribers.set(id, {\n\t\t\tdelayMs: opts?.delayMs ?? this.defaultWorkingTreeMs,\n\t\t\tmaxWaitMs: opts?.maxWaitMs ?? this.defaultWorkingTreeMaxWaitMs,\n\t\t});\n\t\tthis.recalcWorkingTreeTiming();\n\n\t\tif (wasEmpty) {\n\t\t\tthis.lifecycle?.onFirstWorkingTreeSubscriber?.();\n\t\t}\n\n\t\t// If the effective delay or max-wait got shorter while a flush is pending, move it sooner\n\t\tthis.workingTreeClock.rearmIfEarlier();\n\n\t\tlet subDisposed = false;\n\t\treturn Object.assign(\n\t\t\tcreateDisposable(() => {\n\t\t\t\tif (subDisposed) return;\n\n\t\t\t\tsubDisposed = true;\n\n\t\t\t\tthis.workingTreeSubscribers.delete(id);\n\t\t\t\tthis.recalcWorkingTreeTiming();\n\n\t\t\t\tif (this.workingTreeSubscribers.size === 0) {\n\t\t\t\t\tthis.workingTreeClock.cancel();\n\t\t\t\t\tthis.pendingWorkingTreePaths.clear();\n\t\t\t\t\t// Dropped with the paths it describes — a stale flag would make the next subscriber's\n\t\t\t\t\t// first ordinary flush claim a partial set and force needless full refreshes.\n\t\t\t\t\tthis.pendingWorkingTreeIncomplete = false;\n\t\t\t\t\tthis.lifecycle?.onLastWorkingTreeSubscriber?.();\n\t\t\t\t}\n\t\t\t}),\n\t\t\t{ onDidChangeWorkingTree: this.workingTreeEmitter.event },\n\t\t);\n\t}\n\n\tsuspend(): void {\n\t\tthis._suspended = true;\n\t\t// Cancel active timers — events continue to coalesce but don't fire\n\t\tthis.repoClock.suspend();\n\t\tthis.workingTreeClock.suspend();\n\t}\n\n\tresume(delayMs?: number): void {\n\t\tif (!this._suspended) return;\n\n\t\tthis._suspended = false;\n\n\t\t// Flush accumulated changes (a clock with nothing pending stays idle); the max-wait window restarts from now\n\t\tthis.repoClock.resume(delayMs);\n\t\tthis.workingTreeClock.resume(delayMs);\n\t}\n\n\t/**\n\t * Inject changes (e.g., after a git operation the caller performed). Goes through debounce + suspend. Ref kinds\n\t * among them are unnamed — any ref of that kind may have moved.\n\t */\n\tfireChange(...changes: RepositoryChange[]): void {\n\t\tif (this._disposed || changes.length === 0) return;\n\n\t\tthis.coalesceRepo(changes, undefined, 'unfenced');\n\t}\n\n\t/** Immediate fire, bypassing debounce + suspend (for Closed/Opened). Ref kinds among them are unnamed. */\n\tfireChangeImmediate(...changes: RepositoryChange[]): void {\n\t\tif (this._disposed || changes.length === 0) return;\n\n\t\tconst event = new WatcherRepoChangeEvent(this.repoPath, changes);\n\t\t// Same contract as `flushRepo`: notify the global forwarder (which advances the cache's status clock) BEFORE\n\t\t// subscribers. Skipping it here would route changes to subscribers that no one advanced the clock for —\n\t\t// subscribers see them as session-routed and (correctly) don't re-advance, so the cache would go stale.\n\t\tthis.onDidFireRepoChangeCallback?.(event);\n\t\tthis.repoEmitter.fire(event);\n\t}\n\n\t/**\n\t * Push interpreted repo changes into the pipeline. `ref` is the full ref name (or ref directory, or pseudo-ref) whose\n\t * file changed; a ref kind in `changes` is attributed to it when the kinds match, and is otherwise unnamed.\n\t */\n\tpushRepoChanges(changes: RepositoryChange[], ref?: string): void {\n\t\tif (this._disposed) return;\n\n\t\tthis.coalesceRepo(changes, ref, 'raw');\n\t}\n\n\t/**\n\t * Push watcher-observed repo changes that no write of core's can account for — an overflow's rescan, or an ignore\n\t * rule changed in the working tree — so {@link noteSelfWrite} never replaces them or takes them for its echo. Ref\n\t * kinds are unnamed.\n\t */\n\tpushUnfencedRepoChanges(changes: RepositoryChange[]): void {\n\t\tif (this._disposed) return;\n\n\t\tthis.coalesceRepo(changes, undefined, 'unfenced');\n\t}\n\n\t/**\n\t * Core announced one of its own writes for this repository: its caches were evicted and its host notified, both\n\t * after the write finished. The announcement becomes this session's one event for the write:\n\t *\n\t * - raw changes of an announced kind still pending are replaced by it — they happened before the announcement,\n\t * which already covers them, whether they came from the write or from an outside change;\n\t * - the announced kinds are pushed through the debounce and suspend like {@link fireChange}, ref kinds unnamed,\n\t * and the event carries them in `announced`;\n\t * - raw changes of an announced kind that arrive later are never dropped. One arriving within the repo debounce\n\t * delay of the announcement, before its event fires, is taken for the write's own late echo and keeps the kind\n\t * announced; any other report of the kind before the event fires clears the mark. Once it fires, nothing is\n\t * special.\n\t *\n\t * The trade-off: an echo the watcher delivers after the announced event fired causes one extra, unannounced event\n\t * (a duplicate refresh), rather than risk losing an outside change of the same kind.\n\t *\n\t * Empty `changes`, or `'unknown'`, announces every kind a watcher can report (never `'unknown'` itself, which a\n\t * cache reads as a close); `'closed'` and `'opened'` are ignored. Only raw `.git` changes are replaced or kept\n\t * announced: {@link fireChange}, {@link fireChangeImmediate}, {@link pushUnfencedRepoChanges} and working-tree\n\t * changes always pass.\n\t */\n\tnoteSelfWrite(changes: readonly RepositoryChange[]): void {\n\t\tif (this._disposed) return;\n\n\t\tconst all = changes.length === 0 || changes.includes('unknown');\n\t\tconst announced = all ? allWatchedRepositoryChanges : changes.filter(c => isAnnounceable(c));\n\n\t\tfor (const change of this.pendingRepoChanges) {\n\t\t\tif (this.pendingRepoUnfencedChanges.has(change) || !isAnnounceable(change)) continue;\n\t\t\tif (!all && !changes.includes(change)) continue;\n\n\t\t\tthis.pendingRepoChanges.delete(change);\n\t\t\tthis.pendingRepoAnnouncedAt.delete(change);\n\t\t\tif (!isRefChangeKind(change)) continue;\n\n\t\t\t// Announcements carry no ref names, so the replaced kind takes its named refs with it\n\t\t\tthis.pendingRepoUnnamedRefKinds.delete(change);\n\t\t\tfor (const ref of this.pendingRepoRefs) {\n\t\t\t\tif (getRefChangeKind(ref) === change) {\n\t\t\t\t\tthis.pendingRepoRefs.delete(ref);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (announced.length !== 0) {\n\t\t\tthis.coalesceRepo(announced, undefined, 'announced');\n\t\t} else if (this.pendingRepoChanges.size === 0) {\n\t\t\t// Nothing left to flush: idle the clock rather than let it fire an empty window\n\t\t\tthis.repoClock.cancel();\n\t\t}\n\t}\n\n\t/** Push working tree file changes into the pipeline */\n\t/** `incomplete` marks the batch as partial when the caller already dropped paths of its own. */\n\tpushWorkingTreeChanges(paths: Iterable<string>, incomplete?: boolean): void {\n\t\tif (this._disposed) return;\n\n\t\tthis._etagWorkingTree++;\n\t\tif (incomplete) {\n\t\t\tthis.pendingWorkingTreeIncomplete = true;\n\t\t}\n\t\tthis.coalesceWorkingTrees(paths);\n\t}\n\n\tprivate static minDelay(subscribers: Map<number, DebounceTiming>, fallback: number): number {\n\t\tlet min = fallback;\n\t\tfor (const { delayMs } of subscribers.values()) {\n\t\t\tif (delayMs < min) {\n\t\t\t\tmin = delayMs;\n\t\t\t}\n\t\t}\n\t\treturn min;\n\t}\n\n\t/** Unlike the delay, the default isn't a ceiling — a subscriber can opt out of the cap with `Infinity` */\n\tprivate static minMaxWait(subscribers: Map<number, DebounceTiming>, fallback: number): number {\n\t\tif (subscribers.size === 0) return fallback;\n\n\t\tlet min = Infinity;\n\t\tfor (const { maxWaitMs } of subscribers.values()) {\n\t\t\tif (maxWaitMs < min) {\n\t\t\t\tmin = maxWaitMs;\n\t\t\t}\n\t\t}\n\t\treturn min;\n\t}\n\n\tprivate recalcRepoTiming(): void {\n\t\tthis.effectiveRepoMs = RepositoryWatchSession.minDelay(this.repoSubscribers, this.defaultRepoMs);\n\t\tthis.effectiveRepoMaxWaitMs = RepositoryWatchSession.minMaxWait(\n\t\t\tthis.repoSubscribers,\n\t\t\tthis.defaultRepoMaxWaitMs,\n\t\t);\n\t}\n\n\tprivate recalcWorkingTreeTiming(): void {\n\t\tthis.effectiveWorkingTreeMs = RepositoryWatchSession.minDelay(\n\t\t\tthis.workingTreeSubscribers,\n\t\t\tthis.defaultWorkingTreeMs,\n\t\t);\n\t\tthis.effectiveWorkingTreeMaxWaitMs = RepositoryWatchSession.minMaxWait(\n\t\t\tthis.workingTreeSubscribers,\n\t\t\tthis.defaultWorkingTreeMaxWaitMs,\n\t\t);\n\t}\n\n\tprivate flushRepo(): void {\n\t\tif (this.pendingRepoChanges.size === 0) return;\n\n\t\t// The event is built only here — raw events just add to the pending sets (which the event copies)\n\t\tconst event = new WatcherRepoChangeEvent(\n\t\t\tthis.repoPath,\n\t\t\t[...this.pendingRepoChanges],\n\t\t\t{ names: this.pendingRepoRefs, unnamed: this.pendingRepoUnnamedRefKinds },\n\t\t\tthis.pendingRepoAnnouncedAt.keys(),\n\t\t);\n\t\tthis.clearPendingRepoChanges();\n\t\t// Advance any cache clock BEFORE notifying subscribers (same rationale as `flushWorkingTree`): a subscriber\n\t\t// that synchronously reads status must see the post-change generation, not a joinable pre-change run.\n\t\tthis.onDidFireRepoChangeCallback?.(event);\n\t\tthis.repoEmitter.fire(event);\n\t}\n\n\t/**\n\t * `origin` is a raw `.git` event, a push no announcement replaces ({@link fireChange},\n\t * {@link pushUnfencedRepoChanges}), or core's announced write. A kind stays announced only while nothing else\n\t * reports it in the pending window, save the write's own late echo (see {@link noteSelfWrite}).\n\t */\n\tprivate coalesceRepo(\n\t\tchanges: readonly RepositoryChange[],\n\t\tref: string | undefined,\n\t\torigin: 'raw' | 'unfenced' | 'announced',\n\t): void {\n\t\tif (changes.length === 0) return;\n\n\t\tconst now = performance.now();\n\t\tconst refKind = ref != null ? getRefChangeKind(ref) : undefined;\n\t\tfor (const change of changes) {\n\t\t\tif (origin === 'announced') {\n\t\t\t\tif (!this.pendingRepoChanges.has(change)) {\n\t\t\t\t\tthis.pendingRepoAnnouncedAt.set(change, now);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tconst announcedAt = origin === 'raw' ? this.pendingRepoAnnouncedAt.get(change) : undefined;\n\t\t\t\tif (announcedAt == null || now - announcedAt > this.effectiveRepoMs) {\n\t\t\t\t\tthis.pendingRepoAnnouncedAt.delete(change);\n\t\t\t\t}\n\t\t\t\tif (origin === 'unfenced') {\n\t\t\t\t\tthis.pendingRepoUnfencedChanges.add(change);\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.pendingRepoChanges.add(change);\n\t\t\tif (!isRefChangeKind(change)) continue;\n\n\t\t\tif (\n\t\t\t\tref != null &&\n\t\t\t\trefKind === change &&\n\t\t\t\t(this.pendingRepoRefs.size < maxPendingRefNames || this.pendingRepoRefs.has(ref))\n\t\t\t) {\n\t\t\t\tthis.pendingRepoRefs.add(ref);\n\t\t\t} else {\n\t\t\t\tthis.pendingRepoUnnamedRefKinds.add(change);\n\t\t\t}\n\t\t}\n\t\tthis.repoClock.note();\n\t}\n\n\tprivate clearPendingRepoChanges(): void {\n\t\tthis.pendingRepoChanges.clear();\n\t\tthis.pendingRepoRefs.clear();\n\t\tthis.pendingRepoUnnamedRefKinds.clear();\n\t\tthis.pendingRepoUnfencedChanges.clear();\n\t\tthis.pendingRepoAnnouncedAt.clear();\n\t}\n\n\tprivate flushWorkingTree(): void {\n\t\tif (!this.hasPendingWorkingTreeChanges) return;\n\n\t\tconst paths = this.pendingWorkingTreePaths;\n\t\tconst incomplete = this.pendingWorkingTreeIncomplete;\n\t\tthis.pendingWorkingTreePaths = new Set();\n\t\tthis.pendingWorkingTreeIncomplete = false;\n\t\t// Advance the cache's status clock (via WatchService's global forwarding) BEFORE notifying subscribers,\n\t\t// so a subscriber that synchronously reads status sees the post-change generation, not a joinable pre-change run.\n\t\tthis.onDidFireWorkingTreeChangeCallback?.(this.repoPath);\n\t\tthis.workingTreeEmitter.fire({ repoPath: this.repoPath, paths: paths, incomplete: incomplete });\n\t}\n\n\tprivate coalesceWorkingTrees(paths: Iterable<string>): void {\n\t\tfor (const p of paths) {\n\t\t\t// Stop retaining paths past the cap, but keep draining the iterable — the flag is what makes the\n\t\t\t// partial set safe to consume, and dropping out early would leave later paths for the next window.\n\t\t\tif (this.pendingWorkingTreePaths.size >= maxWorkingTreePaths && !this.pendingWorkingTreePaths.has(p)) {\n\t\t\t\tthis.pendingWorkingTreeIncomplete = true;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tthis.pendingWorkingTreePaths.add(p);\n\t\t}\n\n\t\tif (this.hasPendingWorkingTreeChanges) {\n\t\t\tthis.workingTreeClock.note();\n\t\t}\n\t}\n}\n\n/** `'closed'` and `'opened'` are lifecycle, not a write's effect, so an announcement never replaces or names them */\nfunction isAnnounceable(change: RepositoryChange): boolean {\n\treturn change !== 'closed' && change !== 'opened';\n}\n\n/** Wraps a session's repo event so each delivery is narrowed to the subscriber's relevant refs (see `subscribe`) */\nfunction createRelevantRefsEvent(\n\tevent: Event<WatcherRepoChangeEvent>,\n\trelevantRefs: () => Iterable<string>,\n): Event<WatcherRepoChangeEvent> {\n\treturn (listener, thisArgs, disposables) =>\n\t\tevent(\n\t\t\te => {\n\t\t\t\tconst narrowed = narrowToRelevantRefs(e, relevantRefs);\n\t\t\t\tif (narrowed != null) {\n\t\t\t\t\tlistener.call(thisArgs, narrowed);\n\t\t\t\t}\n\t\t\t},\n\t\t\tundefined,\n\t\t\tdisposables,\n\t\t);\n}\n\n/**\n * Narrows an event to the refs a subscriber cares about. Returns the event itself when no change kind is removed (the\n * kept names then stay a superset, which only makes `mayHaveChangedRef` more conservative), `undefined` when every kind\n * is removed, and otherwise a new event with the kept kinds, names and unnamed kinds.\n */\nfunction narrowToRelevantRefs(\n\te: WatcherRepoChangeEvent,\n\trelevantRefs: () => Iterable<string>,\n): WatcherRepoChangeEvent | undefined {\n\t// Ref kinds that pass only if a relevant ref of that kind may have moved\n\tlet unresolved: Set<RefChangeKind> | undefined;\n\tfor (const change of e.changes) {\n\t\tif (!isRefChangeKind(change) || e.unnamedRefKinds.has(change)) continue;\n\n\t\t(unresolved ??= new Set()).add(change);\n\t}\n\tif (unresolved == null) return e;\n\n\tconst pseudoRefs: string[] = [];\n\tfor (const name of e.refs) {\n\t\tif (!name.startsWith('refs/')) {\n\t\t\tpseudoRefs.push(name);\n\t\t}\n\t}\n\tif (pseudoRefs.length !== 0) {\n\t\t// A pseudo-ref (`HEAD`, `ORIG_HEAD`) moving is a `heads` change for every subscriber\n\t\tunresolved.delete('heads');\n\t\tif (unresolved.size === 0) return e;\n\t}\n\n\t// Names that equal or cover a relevant ref — collected as we go, since `relevantRefs` may be a one-shot iterable\n\tconst keptNames = new Set<string>(pseudoRefs);\n\tfor (const ref of relevantRefs()) {\n\t\tconst kind = getRefChangeKind(ref);\n\t\tif (kind == null || !e.changes.has(kind)) continue;\n\n\t\t// `HEAD` is usually a symref to the checked-out branch, so any `heads` change may have moved it\n\t\tif (ref === 'HEAD') {\n\t\t\tfor (const name of e.refs) {\n\t\t\t\tif (getRefChangeKind(name) === 'heads') {\n\t\t\t\t\tkeptNames.add(name);\n\t\t\t\t}\n\t\t\t}\n\t\t\tunresolved.delete(kind);\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst covering = findCoveringRefName(e.refs, ref);\n\t\tif (covering == null) continue;\n\n\t\tkeptNames.add(covering);\n\t\tunresolved.delete(kind);\n\t}\n\tif (unresolved.size === 0) return e;\n\n\tconst keptChanges: RepositoryChange[] = [];\n\tfor (const change of e.changes) {\n\t\tif (!isRefChangeKind(change) || !unresolved.has(change)) {\n\t\t\tkeptChanges.push(change);\n\t\t}\n\t}\n\tif (keptChanges.length === 0) return undefined;\n\n\treturn new WatcherRepoChangeEvent(\n\t\te.repoPath,\n\t\tkeptChanges,\n\t\t{ names: keptNames, unnamed: e.unnamedRefKinds },\n\t\te.announced,\n\t);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"watchSession.js","names":[],"sources":["../../../../git/src/watching/watchSession.ts"],"sourcesContent":["import type { UnifiedDisposable } from '@gitlens/utils/disposable.js';\nimport { createDisposable } from '@gitlens/utils/disposable.js';\nimport type { Event } from '@gitlens/utils/event.js';\nimport { Emitter } from '@gitlens/utils/event.js';\nimport type { RepositoryChange } from '../models/repository.js';\nimport type { RefChangeKind, WorkingTreeChangeEvent } from './changeEvent.js';\nimport { findCoveringRefName, getRefChangeKind, isRefChangeKind, WatcherRepoChangeEvent } from './changeEvent.js';\nimport { allWatchedRepositoryChanges } from './classifyChange.js';\n\nexport interface RepositorySubscription extends UnifiedDisposable {\n\treadonly onDidChange: Event<WatcherRepoChangeEvent>;\n}\n\nexport interface WorkingTreeSubscription extends UnifiedDisposable {\n\treadonly onDidChangeWorkingTree: Event<WorkingTreeChangeEvent>;\n}\n\n/**\n * Lifecycle callbacks fired by the session when subscriber counts\n * cross the 0↔1 boundary. The host (WatchService) uses these to\n * lazily create/destroy watchers.\n */\nexport interface WatchSessionLifecycle {\n\t/** Called when the first repo-change subscriber joins */\n\tonFirstRepoSubscriber?(): void;\n\t/** Called when the last repo-change subscriber leaves */\n\tonLastRepoSubscriber?(): void;\n\t/** Called when the first working-tree subscriber joins */\n\tonFirstWorkingTreeSubscriber?(): void;\n\t/** Called when the last working-tree subscriber leaves */\n\tonLastWorkingTreeSubscriber?(): void;\n}\n\nexport interface WatchSessionOptions {\n\treadonly repoPath: string;\n\t/** Default debounce for repo changes. Default: 250ms */\n\treadonly defaultRepoDelayMs?: number;\n\t/** Default debounce for working tree changes. Default: 2500ms */\n\treadonly defaultWorkingTreeDelayMs?: number;\n\t/**\n\t * Default cap on how long repo changes can be held back by continuous activity — a flush happens at most this long\n\t * after the first pending change, however long the burst lasts. `Infinity` disables the cap. Default: 2000ms\n\t */\n\treadonly defaultRepoMaxWaitMs?: number;\n\t/** Default cap on how long working tree changes can be held back by continuous activity. `Infinity` disables the cap. Default: 10000ms */\n\treadonly defaultWorkingTreeMaxWaitMs?: number;\n\treadonly lifecycle?: WatchSessionLifecycle;\n\t/** Initial suspended state — new sessions inherit the service-global suspend state. Default: false */\n\treadonly suspended?: boolean;\n\t/**\n\t * Called whenever a debounced repo change event is dispatched to subscribers.\n\t * Does NOT count as a subscriber — used by WatchService for\n\t * global event forwarding without affecting lifecycle callbacks.\n\t */\n\treadonly onDidFireRepoChange?: (event: WatcherRepoChangeEvent) => void;\n\t/**\n\t * Called whenever a debounced working-tree change is dispatched to subscribers. Like\n\t * {@link onDidFireRepoChange}, does NOT count as a subscriber — used by WatchService for global\n\t * event forwarding (the single point that drives the cache's status clock for working-tree edits).\n\t */\n\treadonly onDidFireWorkingTreeChange?: (repoPath: string) => void;\n}\n\nconst defaultRepoDelay = 250;\nconst defaultWorkingTreeDelay = 2500;\nconst defaultRepoMaxWait = 2000;\nconst defaultWorkingTreeMaxWait = 10000;\n\n/** Largest delay `setTimeout` honors (a signed 32-bit int); anything longer fires immediately */\nconst maxTimerDelay = 0x7fffffff;\n\ninterface DebounceTiming {\n\treadonly delayMs: number;\n\treadonly maxWaitMs: number;\n}\n\n/**\n * Trailing-deadline debounce with a max-wait, shared by the repo and working-tree pipelines.\n *\n * Recording an event costs a `performance.now()` and, only when no timer is armed, one `setTimeout` — never a\n * `clearTimeout`/`setTimeout` pair per event, which is what made a busy repo allocate (and starve) on every raw\n * watcher event. The armed timer is a deadline, not a countdown: when it fires it recomputes the due time from the\n * latest timestamps and either flushes or re-arms for the remainder. The due time is\n * `min(lastEventAt + delay, firstPendingAt + maxWait)`, so continuous activity is flushed at the max-wait instead of\n * never. Monotonic time, so a wall-clock change can't push a deadline out.\n */\nclass DebounceClock {\n\tprivate active = false;\n\tprivate paused = false;\n\tprivate firstAt = 0;\n\tprivate lastAt = 0;\n\tprivate timer: ReturnType<typeof setTimeout> | undefined;\n\tprivate timerDueAt = 0;\n\t/** Absolute flush time set by {@link resume}; dropped by the next event, which returns to normal debounce */\n\tprivate resumeDueAt: number | undefined;\n\n\tconstructor(\n\t\tprivate readonly getDelayMs: () => number,\n\t\tprivate readonly getMaxWaitMs: () => number,\n\t\tprivate readonly onDue: () => void,\n\t) {}\n\n\t/** Records an event; arms the timer only if none is armed. Cheap enough to call per raw event. */\n\tnote(): void {\n\t\tconst now = performance.now();\n\t\tif (!this.active) {\n\t\t\tthis.active = true;\n\t\t\tthis.firstAt = now;\n\t\t}\n\t\tthis.lastAt = now;\n\n\t\tif (this.paused) return;\n\n\t\tif (this.resumeDueAt != null) {\n\t\t\t// The first event after a resume goes back to normal debounce — which may be due sooner than the resume deadline\n\t\t\tthis.resumeDueAt = undefined;\n\t\t\tthis.rearmIfEarlier();\n\t\t}\n\n\t\tif (this.timer == null) {\n\t\t\tthis.arm(this.getDueAt(), now);\n\t\t}\n\t}\n\n\t/** Moves the armed timer earlier when the due time shrank (a subscriber joined with a shorter delay or max-wait) */\n\trearmIfEarlier(): void {\n\t\tif (!this.active || this.paused || this.timer == null) return;\n\n\t\tconst dueAt = this.getDueAt();\n\t\tif (dueAt >= this.timerDueAt) return;\n\n\t\tclearTimeout(this.timer);\n\t\tthis.arm(dueAt, performance.now());\n\t}\n\n\t/** Cancels the timer; events keep accumulating but nothing is due until {@link resume} */\n\tsuspend(): void {\n\t\tthis.paused = true;\n\t\tthis.clearTimer();\n\t}\n\n\t/**\n\t * Restarts the max-wait window from now and schedules the flush for `delayMs` from now (the effective delay when\n\t * omitted). Does nothing if no events are pending.\n\t */\n\tresume(delayMs?: number): void {\n\t\tthis.paused = false;\n\t\tif (!this.active) return;\n\n\t\tconst now = performance.now();\n\t\tthis.firstAt = now;\n\t\tthis.resumeDueAt = now + (delayMs ?? this.getDelayMs());\n\t\tthis.clearTimer();\n\t\tthis.arm(this.resumeDueAt, now);\n\t}\n\n\t/** Drops the pending window and the timer */\n\tcancel(): void {\n\t\tthis.active = false;\n\t\tthis.resumeDueAt = undefined;\n\t\tthis.clearTimer();\n\t}\n\n\tprivate getDueAt(): number {\n\t\treturn this.resumeDueAt ?? Math.min(this.lastAt + this.getDelayMs(), this.firstAt + this.getMaxWaitMs());\n\t}\n\n\tprivate arm(dueAt: number, now: number): void {\n\t\tthis.timerDueAt = dueAt;\n\t\tthis.timer = setTimeout(() => this.onTimer(), Math.min(Math.max(0, dueAt - now), maxTimerDelay));\n\t}\n\n\tprivate onTimer(): void {\n\t\tthis.timer = undefined;\n\t\tif (!this.active || this.paused) return;\n\n\t\tconst now = performance.now();\n\t\tconst dueAt = this.getDueAt();\n\t\tif (dueAt > now) {\n\t\t\tthis.arm(dueAt, now);\n\t\t\treturn;\n\t\t}\n\n\t\t// Reset before firing so an event raised by a subscriber starts a fresh window\n\t\tthis.cancel();\n\t\tthis.onDue();\n\t}\n\n\tprivate clearTimer(): void {\n\t\tif (this.timer != null) {\n\t\t\tclearTimeout(this.timer);\n\t\t\tthis.timer = undefined;\n\t\t}\n\t}\n}\n\n/**\n * Cap on retained working-tree paths per flush. The set otherwise grows without bound — while the window\n * is unfocused the session is suspended, so a branch switch or build accumulates every touched path until\n * refocus. Set low deliberately: ordinary edits are a handful of paths, anything past this was a\n * refresh-everything event anyway, and dropping past it only costs the matching consumers a\n * conservative refresh.\n */\nconst maxWorkingTreePaths = 1000;\n\n/**\n * Cap on retained ref names per flush, for the same reason (a fetch of many refs while suspended). Past it, a ref kind\n * is recorded unnamed — any ref of that kind may have moved — which only makes `mayHaveChangedRef` more conservative.\n */\nconst maxPendingRefNames = 1000;\n\n/**\n * Per-repository session that owns the debounce/coalesce/suspend\n * pipeline for both repo (.git) and working-tree changes.\n *\n * Multiple subscribers share the same pipeline — the shortest\n * requested debounce delay wins, and so does the shortest max-wait.\n */\nexport class RepositoryWatchSession implements UnifiedDisposable {\n\treadonly repoPath: string;\n\n\tprivate readonly repoEmitter = new Emitter<WatcherRepoChangeEvent>();\n\tprivate readonly repoSubscribers = new Map<number, DebounceTiming>(); // subscriberId → timing\n\tprivate repoNextId = 0;\n\t/** Each repo subscriber's `relevantRefs`, for {@link getRelevantRefs} */\n\tprivate readonly relevantRefsSources = new Map<number, () => Iterable<string>>(); // subscriberId → relevantRefs\n\tprivate readonly pendingRepoChanges = new Set<RepositoryChange>();\n\t/** Full ref names, ref directories and pseudo-refs whose files changed, flushed with {@link pendingRepoChanges} */\n\tprivate readonly pendingRepoRefs = new Set<string>();\n\t/** Ref kinds that changed without a name, flushed with {@link pendingRepoChanges} */\n\tprivate readonly pendingRepoUnnamedRefKinds = new Set<RefChangeKind>();\n\t/** Kinds among {@link pendingRepoChanges} injected or pushed unfenced, which an announcement never replaces */\n\tprivate readonly pendingRepoUnfencedChanges = new Set<RepositoryChange>();\n\t/**\n\t * Kinds among {@link pendingRepoChanges} that only core's announced writes reported, with when each was announced\n\t * (see {@link noteSelfWrite})\n\t */\n\tprivate readonly pendingRepoAnnouncedAt = new Map<RepositoryChange, number>();\n\tprivate readonly repoClock: DebounceClock;\n\tprivate effectiveRepoMs: number;\n\tprivate effectiveRepoMaxWaitMs: number;\n\tprivate readonly defaultRepoMs: number;\n\tprivate readonly defaultRepoMaxWaitMs: number;\n\n\tprivate readonly workingTreeEmitter = new Emitter<WorkingTreeChangeEvent>();\n\tprivate readonly workingTreeSubscribers = new Map<number, DebounceTiming>();\n\tprivate workingTreeNextId = 0;\n\tprivate pendingWorkingTreePaths = new Set<string>();\n\tprivate pendingWorkingTreeIncomplete = false;\n\tprivate readonly workingTreeClock: DebounceClock;\n\tprivate effectiveWorkingTreeMs: number;\n\tprivate effectiveWorkingTreeMaxWaitMs: number;\n\tprivate readonly defaultWorkingTreeMs: number;\n\tprivate readonly defaultWorkingTreeMaxWaitMs: number;\n\n\tprivate _etagWorkingTree = 0;\n\tprivate _suspended: boolean;\n\tprivate _disposed = false;\n\n\tprivate readonly lifecycle?: WatchSessionLifecycle;\n\tprivate readonly onDidFireRepoChangeCallback?: (event: WatcherRepoChangeEvent) => void;\n\tprivate readonly onDidFireWorkingTreeChangeCallback?: (repoPath: string) => void;\n\n\tconstructor(options: WatchSessionOptions) {\n\t\tthis.repoPath = options.repoPath;\n\t\tthis.defaultRepoMs = options.defaultRepoDelayMs ?? defaultRepoDelay;\n\t\tthis.defaultWorkingTreeMs = options.defaultWorkingTreeDelayMs ?? defaultWorkingTreeDelay;\n\t\tthis.defaultRepoMaxWaitMs = options.defaultRepoMaxWaitMs ?? defaultRepoMaxWait;\n\t\tthis.defaultWorkingTreeMaxWaitMs = options.defaultWorkingTreeMaxWaitMs ?? defaultWorkingTreeMaxWait;\n\t\tthis.effectiveRepoMs = this.defaultRepoMs;\n\t\tthis.effectiveRepoMaxWaitMs = this.defaultRepoMaxWaitMs;\n\t\tthis.effectiveWorkingTreeMs = this.defaultWorkingTreeMs;\n\t\tthis.effectiveWorkingTreeMaxWaitMs = this.defaultWorkingTreeMaxWaitMs;\n\t\tthis.repoClock = new DebounceClock(\n\t\t\t() => this.effectiveRepoMs,\n\t\t\t() => this.effectiveRepoMaxWaitMs,\n\t\t\t() => this.flushRepo(),\n\t\t);\n\t\tthis.workingTreeClock = new DebounceClock(\n\t\t\t() => this.effectiveWorkingTreeMs,\n\t\t\t() => this.effectiveWorkingTreeMaxWaitMs,\n\t\t\t() => this.flushWorkingTree(),\n\t\t);\n\t\tthis.lifecycle = options.lifecycle;\n\t\tthis.onDidFireRepoChangeCallback = options.onDidFireRepoChange;\n\t\tthis.onDidFireWorkingTreeChangeCallback = options.onDidFireWorkingTreeChange;\n\t\tthis._suspended = options.suspended ?? false;\n\t\tif (this._suspended) {\n\t\t\tthis.repoClock.suspend();\n\t\t\tthis.workingTreeClock.suspend();\n\t\t}\n\t}\n\n\t[Symbol.dispose](): void {\n\t\tthis.dispose();\n\t}\n\n\tdispose(): void {\n\t\tif (this._disposed) return;\n\n\t\tthis._disposed = true;\n\n\t\tthis.repoClock.cancel();\n\t\tthis.workingTreeClock.cancel();\n\n\t\tthis.clearPendingRepoChanges();\n\t\tthis.pendingWorkingTreePaths.clear();\n\t\tthis.pendingWorkingTreeIncomplete = false;\n\t\tthis.repoSubscribers.clear();\n\t\tthis.relevantRefsSources.clear();\n\t\tthis.workingTreeSubscribers.clear();\n\n\t\tthis.repoEmitter.dispose();\n\t\tthis.workingTreeEmitter.dispose();\n\t}\n\n\tget suspended(): boolean {\n\t\treturn this._suspended;\n\t}\n\n\tget hasPendingChanges(): boolean {\n\t\treturn this.pendingRepoChanges.size > 0 || this.hasPendingWorkingTreeChanges;\n\t}\n\n\t/**\n\t * An incomplete batch with no surviving paths still has to fire: the caller dropped paths we never saw, so\n\t * gating the pipeline on the retained set alone would swallow the change entirely.\n\t */\n\tprivate get hasPendingWorkingTreeChanges(): boolean {\n\t\treturn this.pendingWorkingTreePaths.size > 0 || this.pendingWorkingTreeIncomplete;\n\t}\n\n\tget repoSubscriberCount(): number {\n\t\treturn this.repoSubscribers.size;\n\t}\n\n\tget workingTreeSubscriberCount(): number {\n\t\treturn this.workingTreeSubscribers.size;\n\t}\n\n\t/**\n\t * Monotonic counter incremented immediately when working tree changes\n\t * are pushed (before debounce). Used by `GitCommit.hasFullDetails()`\n\t * to detect staleness of uncommitted commit data.\n\t */\n\tget etagWorkingTree(): number {\n\t\treturn this._etagWorkingTree;\n\t}\n\n\t/**\n\t * Subscribe to .git directory changes with configurable debounce.\n\t * First subscribe fires `onFirstRepoSubscriber`, last dispose\n\t * fires `onLastRepoSubscriber`.\n\t *\n\t * When multiple subscribers exist, the shortest debounce wins —\n\t * all subscribers fire on the same (fastest) cadence. `maxWaitMs` bounds how long continuous\n\t * activity can defer a flush (the shortest wins too); `Infinity` means no cap.\n\t *\n\t * `relevantRefs` narrows THIS subscription's events to the refs it cares about, given as full ref names\n\t * (`refs/heads/main`, `refs/remotes/origin/main`, `refs/tags/v1`, `refs/stash`). It is read at delivery, so it can\n\t * return the subscriber's current list. A `heads`/`remotes`/`tags`/`stash` change is delivered only when a listed\n\t * ref of that kind may have moved, when the change isn't attributable to a name (e.g. a `packed-refs` rewrite),\n\t * or, for `heads`, when a pseudo-ref (`HEAD`, `ORIG_HEAD`) moved — so `stash` and `tags` changes are dropped unless\n\t * a ref of that kind is listed. Other change kinds always pass, and an event left with no changes isn't delivered.\n\t * The filter never affects other subscribers, the debounce, or the global `onDidFireRepoChange` forwarding.\n\t */\n\tsubscribe(opts?: {\n\t\tdelayMs?: number;\n\t\tmaxWaitMs?: number;\n\t\trelevantRefs?: () => Iterable<string>;\n\t}): RepositorySubscription {\n\t\tif (this._disposed) {\n\t\t\treturn Object.assign(\n\t\t\t\tcreateDisposable(() => {}),\n\t\t\t\t{\n\t\t\t\t\tonDidChange: this.repoEmitter.event,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\n\t\tconst id = this.repoNextId++;\n\t\tconst wasEmpty = this.repoSubscribers.size === 0;\n\n\t\tthis.repoSubscribers.set(id, {\n\t\t\tdelayMs: opts?.delayMs ?? this.defaultRepoMs,\n\t\t\tmaxWaitMs: opts?.maxWaitMs ?? this.defaultRepoMaxWaitMs,\n\t\t});\n\t\tthis.recalcRepoTiming();\n\n\t\tconst relevantRefs = opts?.relevantRefs;\n\t\tif (relevantRefs != null) {\n\t\t\tthis.relevantRefsSources.set(id, relevantRefs);\n\t\t}\n\n\t\tif (wasEmpty) {\n\t\t\tthis.lifecycle?.onFirstRepoSubscriber?.();\n\t\t}\n\n\t\t// If the effective delay or max-wait got shorter while a flush is pending, move it sooner\n\t\tthis.repoClock.rearmIfEarlier();\n\n\t\tlet subDisposed = false;\n\t\treturn Object.assign(\n\t\t\tcreateDisposable(() => {\n\t\t\t\tif (subDisposed) return;\n\n\t\t\t\tsubDisposed = true;\n\n\t\t\t\tthis.repoSubscribers.delete(id);\n\t\t\t\tthis.relevantRefsSources.delete(id);\n\t\t\t\tthis.recalcRepoTiming();\n\n\t\t\t\tif (this.repoSubscribers.size === 0) {\n\t\t\t\t\tthis.repoClock.cancel();\n\t\t\t\t\tthis.clearPendingRepoChanges();\n\t\t\t\t\tthis.lifecycle?.onLastRepoSubscriber?.();\n\t\t\t\t}\n\t\t\t}),\n\t\t\t{\n\t\t\t\tonDidChange:\n\t\t\t\t\trelevantRefs != null\n\t\t\t\t\t\t? createRelevantRefsEvent(this.repoEmitter.event, relevantRefs)\n\t\t\t\t\t\t: this.repoEmitter.event,\n\t\t\t},\n\t\t);\n\t}\n\n\t/** Every repo subscriber's current `relevantRefs` (see {@link subscribe}), unioned */\n\tgetRelevantRefs(): Set<string> {\n\t\tconst refs = new Set<string>();\n\t\tfor (const relevantRefs of this.relevantRefsSources.values()) {\n\t\t\tfor (const ref of relevantRefs()) {\n\t\t\t\trefs.add(ref);\n\t\t\t}\n\t\t}\n\t\treturn refs;\n\t}\n\n\t/** Subscribe to working tree changes; `delayMs` and `maxWaitMs` behave as in {@link subscribe}. */\n\tsubscribeToWorkingTree(opts?: { delayMs?: number; maxWaitMs?: number }): WorkingTreeSubscription {\n\t\tif (this._disposed) {\n\t\t\treturn Object.assign(\n\t\t\t\tcreateDisposable(() => {}),\n\t\t\t\t{\n\t\t\t\t\tonDidChangeWorkingTree: this.workingTreeEmitter.event,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\n\t\tconst id = this.workingTreeNextId++;\n\t\tconst wasEmpty = this.workingTreeSubscribers.size === 0;\n\n\t\tthis.workingTreeSubscribers.set(id, {\n\t\t\tdelayMs: opts?.delayMs ?? this.defaultWorkingTreeMs,\n\t\t\tmaxWaitMs: opts?.maxWaitMs ?? this.defaultWorkingTreeMaxWaitMs,\n\t\t});\n\t\tthis.recalcWorkingTreeTiming();\n\n\t\tif (wasEmpty) {\n\t\t\tthis.lifecycle?.onFirstWorkingTreeSubscriber?.();\n\t\t}\n\n\t\t// If the effective delay or max-wait got shorter while a flush is pending, move it sooner\n\t\tthis.workingTreeClock.rearmIfEarlier();\n\n\t\tlet subDisposed = false;\n\t\treturn Object.assign(\n\t\t\tcreateDisposable(() => {\n\t\t\t\tif (subDisposed) return;\n\n\t\t\t\tsubDisposed = true;\n\n\t\t\t\tthis.workingTreeSubscribers.delete(id);\n\t\t\t\tthis.recalcWorkingTreeTiming();\n\n\t\t\t\tif (this.workingTreeSubscribers.size === 0) {\n\t\t\t\t\tthis.workingTreeClock.cancel();\n\t\t\t\t\tthis.pendingWorkingTreePaths.clear();\n\t\t\t\t\t// Dropped with the paths it describes — a stale flag would make the next subscriber's\n\t\t\t\t\t// first ordinary flush claim a partial set and force needless full refreshes.\n\t\t\t\t\tthis.pendingWorkingTreeIncomplete = false;\n\t\t\t\t\tthis.lifecycle?.onLastWorkingTreeSubscriber?.();\n\t\t\t\t}\n\t\t\t}),\n\t\t\t{ onDidChangeWorkingTree: this.workingTreeEmitter.event },\n\t\t);\n\t}\n\n\tsuspend(): void {\n\t\tthis._suspended = true;\n\t\t// Cancel active timers — events continue to coalesce but don't fire\n\t\tthis.repoClock.suspend();\n\t\tthis.workingTreeClock.suspend();\n\t}\n\n\tresume(delayMs?: number): void {\n\t\tif (!this._suspended) return;\n\n\t\tthis._suspended = false;\n\n\t\t// Flush accumulated changes (a clock with nothing pending stays idle); the max-wait window restarts from now\n\t\tthis.repoClock.resume(delayMs);\n\t\tthis.workingTreeClock.resume(delayMs);\n\t}\n\n\t/**\n\t * Inject changes (e.g., after a git operation the caller performed). Goes through debounce + suspend. Ref kinds\n\t * among them are unnamed — any ref of that kind may have moved.\n\t */\n\tfireChange(...changes: RepositoryChange[]): void {\n\t\tif (this._disposed || changes.length === 0) return;\n\n\t\tthis.coalesceRepo(changes, undefined, 'unfenced');\n\t}\n\n\t/** Immediate fire, bypassing debounce + suspend (for Closed/Opened). Ref kinds among them are unnamed. */\n\tfireChangeImmediate(...changes: RepositoryChange[]): void {\n\t\tif (this._disposed || changes.length === 0) return;\n\n\t\tconst event = new WatcherRepoChangeEvent(this.repoPath, changes);\n\t\t// Same contract as `flushRepo`: notify the global forwarder (which advances the cache's status clock) BEFORE\n\t\t// subscribers. Skipping it here would route changes to subscribers that no one advanced the clock for —\n\t\t// subscribers see them as session-routed and (correctly) don't re-advance, so the cache would go stale.\n\t\tthis.onDidFireRepoChangeCallback?.(event);\n\t\tthis.repoEmitter.fire(event);\n\t}\n\n\t/**\n\t * Push interpreted repo changes into the pipeline. `ref` is the full ref name (or ref directory, or pseudo-ref) whose\n\t * file changed; a ref kind in `changes` is attributed to it when the kinds match, and is otherwise unnamed.\n\t * `false` preserves cache invalidations for a config edit without claiming any ref moved.\n\t */\n\tpushRepoChanges(changes: RepositoryChange[], ref?: string | false): void {\n\t\tif (this._disposed) return;\n\n\t\tthis.coalesceRepo(changes, ref, 'raw');\n\t}\n\n\t/**\n\t * Push watcher-observed repo changes that no write of core's can account for — an overflow's rescan, or an ignore\n\t * rule changed in the working tree — so {@link noteSelfWrite} never replaces them or takes them for its echo. Ref\n\t * kinds are unnamed.\n\t */\n\tpushUnfencedRepoChanges(changes: RepositoryChange[]): void {\n\t\tif (this._disposed) return;\n\n\t\tthis.coalesceRepo(changes, undefined, 'unfenced');\n\t}\n\n\t/**\n\t * Core announced one of its own writes for this repository: its caches were evicted and its host notified, both\n\t * after the write finished. The announcement becomes this session's one event for the write:\n\t *\n\t * - raw changes of an announced kind still pending are replaced by it — they happened before the announcement,\n\t * which already covers them, whether they came from the write or from an outside change;\n\t * - the announced kinds are pushed through the debounce and suspend like {@link fireChange}, ref kinds unnamed,\n\t * and the event carries them in `announced`;\n\t * - raw changes of an announced kind that arrive later are never dropped. One arriving within the repo debounce\n\t * delay of the announcement, before its event fires, is taken for the write's own late echo and keeps the kind\n\t * announced; any other report of the kind before the event fires clears the mark. Once it fires, nothing is\n\t * special.\n\t *\n\t * The trade-off: an echo the watcher delivers after the announced event fired causes one extra, unannounced event\n\t * (a duplicate refresh), rather than risk losing an outside change of the same kind.\n\t *\n\t * Empty `changes`, or `'unknown'`, announces every kind a watcher can report (never `'unknown'` itself, which a\n\t * cache reads as a close); `'closed'` and `'opened'` are ignored. Only raw `.git` changes are replaced or kept\n\t * announced: {@link fireChange}, {@link fireChangeImmediate}, {@link pushUnfencedRepoChanges} and working-tree\n\t * changes always pass.\n\t */\n\tnoteSelfWrite(changes: readonly RepositoryChange[]): void {\n\t\tif (this._disposed) return;\n\n\t\tconst all = changes.length === 0 || changes.includes('unknown');\n\t\tconst announced = all ? allWatchedRepositoryChanges : changes.filter(c => isAnnounceable(c));\n\n\t\tfor (const change of this.pendingRepoChanges) {\n\t\t\tif (this.pendingRepoUnfencedChanges.has(change) || !isAnnounceable(change)) continue;\n\t\t\tif (!all && !changes.includes(change)) continue;\n\n\t\t\tthis.pendingRepoChanges.delete(change);\n\t\t\tthis.pendingRepoAnnouncedAt.delete(change);\n\t\t\tif (!isRefChangeKind(change)) continue;\n\n\t\t\t// Announcements carry no ref names, so the replaced kind takes its named refs with it\n\t\t\tthis.pendingRepoUnnamedRefKinds.delete(change);\n\t\t\tfor (const ref of this.pendingRepoRefs) {\n\t\t\t\tif (getRefChangeKind(ref) === change) {\n\t\t\t\t\tthis.pendingRepoRefs.delete(ref);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (announced.length !== 0) {\n\t\t\tthis.coalesceRepo(announced, undefined, 'announced');\n\t\t} else if (this.pendingRepoChanges.size === 0) {\n\t\t\t// Nothing left to flush: idle the clock rather than let it fire an empty window\n\t\t\tthis.repoClock.cancel();\n\t\t}\n\t}\n\n\t/** Push working tree file changes into the pipeline */\n\t/** `incomplete` marks the batch as partial when the caller already dropped paths of its own. */\n\tpushWorkingTreeChanges(paths: Iterable<string>, incomplete?: boolean): void {\n\t\tif (this._disposed) return;\n\n\t\tthis._etagWorkingTree++;\n\t\tif (incomplete) {\n\t\t\tthis.pendingWorkingTreeIncomplete = true;\n\t\t}\n\t\tthis.coalesceWorkingTrees(paths);\n\t}\n\n\tprivate static minDelay(subscribers: Map<number, DebounceTiming>, fallback: number): number {\n\t\tlet min = fallback;\n\t\tfor (const { delayMs } of subscribers.values()) {\n\t\t\tif (delayMs < min) {\n\t\t\t\tmin = delayMs;\n\t\t\t}\n\t\t}\n\t\treturn min;\n\t}\n\n\t/** Unlike the delay, the default isn't a ceiling — a subscriber can opt out of the cap with `Infinity` */\n\tprivate static minMaxWait(subscribers: Map<number, DebounceTiming>, fallback: number): number {\n\t\tif (subscribers.size === 0) return fallback;\n\n\t\tlet min = Infinity;\n\t\tfor (const { maxWaitMs } of subscribers.values()) {\n\t\t\tif (maxWaitMs < min) {\n\t\t\t\tmin = maxWaitMs;\n\t\t\t}\n\t\t}\n\t\treturn min;\n\t}\n\n\tprivate recalcRepoTiming(): void {\n\t\tthis.effectiveRepoMs = RepositoryWatchSession.minDelay(this.repoSubscribers, this.defaultRepoMs);\n\t\tthis.effectiveRepoMaxWaitMs = RepositoryWatchSession.minMaxWait(\n\t\t\tthis.repoSubscribers,\n\t\t\tthis.defaultRepoMaxWaitMs,\n\t\t);\n\t}\n\n\tprivate recalcWorkingTreeTiming(): void {\n\t\tthis.effectiveWorkingTreeMs = RepositoryWatchSession.minDelay(\n\t\t\tthis.workingTreeSubscribers,\n\t\t\tthis.defaultWorkingTreeMs,\n\t\t);\n\t\tthis.effectiveWorkingTreeMaxWaitMs = RepositoryWatchSession.minMaxWait(\n\t\t\tthis.workingTreeSubscribers,\n\t\t\tthis.defaultWorkingTreeMaxWaitMs,\n\t\t);\n\t}\n\n\tprivate flushRepo(): void {\n\t\tif (this.pendingRepoChanges.size === 0) return;\n\n\t\t// The event is built only here — raw events just add to the pending sets (which the event copies)\n\t\tconst event = new WatcherRepoChangeEvent(\n\t\t\tthis.repoPath,\n\t\t\t[...this.pendingRepoChanges],\n\t\t\t{ names: this.pendingRepoRefs, unnamed: this.pendingRepoUnnamedRefKinds },\n\t\t\tthis.pendingRepoAnnouncedAt.keys(),\n\t\t);\n\t\tthis.clearPendingRepoChanges();\n\t\t// Advance any cache clock BEFORE notifying subscribers (same rationale as `flushWorkingTree`): a subscriber\n\t\t// that synchronously reads status must see the post-change generation, not a joinable pre-change run.\n\t\tthis.onDidFireRepoChangeCallback?.(event);\n\t\tthis.repoEmitter.fire(event);\n\t}\n\n\t/**\n\t * `origin` is a raw `.git` event, a push no announcement replaces ({@link fireChange},\n\t * {@link pushUnfencedRepoChanges}), or core's announced write. A kind stays announced only while nothing else\n\t * reports it in the pending window, save the write's own late echo (see {@link noteSelfWrite}).\n\t */\n\tprivate coalesceRepo(\n\t\tchanges: readonly RepositoryChange[],\n\t\tref: string | false | undefined,\n\t\torigin: 'raw' | 'unfenced' | 'announced',\n\t): void {\n\t\tif (changes.length === 0) return;\n\n\t\tconst now = performance.now();\n\t\tconst refKind = typeof ref === 'string' ? getRefChangeKind(ref) : undefined;\n\t\tfor (const change of changes) {\n\t\t\tif (origin === 'announced') {\n\t\t\t\tif (!this.pendingRepoChanges.has(change)) {\n\t\t\t\t\tthis.pendingRepoAnnouncedAt.set(change, now);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tconst announcedAt = origin === 'raw' ? this.pendingRepoAnnouncedAt.get(change) : undefined;\n\t\t\t\tif (announcedAt == null || now - announcedAt > this.effectiveRepoMs) {\n\t\t\t\t\tthis.pendingRepoAnnouncedAt.delete(change);\n\t\t\t\t}\n\t\t\t\tif (origin === 'unfenced') {\n\t\t\t\t\tthis.pendingRepoUnfencedChanges.add(change);\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.pendingRepoChanges.add(change);\n\t\t\tif (ref === false || !isRefChangeKind(change)) continue;\n\n\t\t\tif (\n\t\t\t\tref != null &&\n\t\t\t\trefKind === change &&\n\t\t\t\t(this.pendingRepoRefs.size < maxPendingRefNames || this.pendingRepoRefs.has(ref))\n\t\t\t) {\n\t\t\t\tthis.pendingRepoRefs.add(ref);\n\t\t\t} else {\n\t\t\t\tthis.pendingRepoUnnamedRefKinds.add(change);\n\t\t\t}\n\t\t}\n\t\tthis.repoClock.note();\n\t}\n\n\tprivate clearPendingRepoChanges(): void {\n\t\tthis.pendingRepoChanges.clear();\n\t\tthis.pendingRepoRefs.clear();\n\t\tthis.pendingRepoUnnamedRefKinds.clear();\n\t\tthis.pendingRepoUnfencedChanges.clear();\n\t\tthis.pendingRepoAnnouncedAt.clear();\n\t}\n\n\tprivate flushWorkingTree(): void {\n\t\tif (!this.hasPendingWorkingTreeChanges) return;\n\n\t\tconst paths = this.pendingWorkingTreePaths;\n\t\tconst incomplete = this.pendingWorkingTreeIncomplete;\n\t\tthis.pendingWorkingTreePaths = new Set();\n\t\tthis.pendingWorkingTreeIncomplete = false;\n\t\t// Advance the cache's status clock (via WatchService's global forwarding) BEFORE notifying subscribers,\n\t\t// so a subscriber that synchronously reads status sees the post-change generation, not a joinable pre-change run.\n\t\tthis.onDidFireWorkingTreeChangeCallback?.(this.repoPath);\n\t\tthis.workingTreeEmitter.fire({ repoPath: this.repoPath, paths: paths, incomplete: incomplete });\n\t}\n\n\tprivate coalesceWorkingTrees(paths: Iterable<string>): void {\n\t\tfor (const p of paths) {\n\t\t\t// Stop retaining paths past the cap, but keep draining the iterable — the flag is what makes the\n\t\t\t// partial set safe to consume, and dropping out early would leave later paths for the next window.\n\t\t\tif (this.pendingWorkingTreePaths.size >= maxWorkingTreePaths && !this.pendingWorkingTreePaths.has(p)) {\n\t\t\t\tthis.pendingWorkingTreeIncomplete = true;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tthis.pendingWorkingTreePaths.add(p);\n\t\t}\n\n\t\tif (this.hasPendingWorkingTreeChanges) {\n\t\t\tthis.workingTreeClock.note();\n\t\t}\n\t}\n}\n\n/** `'closed'` and `'opened'` are lifecycle, not a write's effect, so an announcement never replaces or names them */\nfunction isAnnounceable(change: RepositoryChange): boolean {\n\treturn change !== 'closed' && change !== 'opened';\n}\n\n/** Wraps a session's repo event so each delivery is narrowed to the subscriber's relevant refs (see `subscribe`) */\nfunction createRelevantRefsEvent(\n\tevent: Event<WatcherRepoChangeEvent>,\n\trelevantRefs: () => Iterable<string>,\n): Event<WatcherRepoChangeEvent> {\n\treturn (listener, thisArgs, disposables) =>\n\t\tevent(\n\t\t\te => {\n\t\t\t\tconst narrowed = narrowToRelevantRefs(e, relevantRefs);\n\t\t\t\tif (narrowed != null) {\n\t\t\t\t\tlistener.call(thisArgs, narrowed);\n\t\t\t\t}\n\t\t\t},\n\t\t\tundefined,\n\t\t\tdisposables,\n\t\t);\n}\n\n/**\n * Narrows an event to the refs a subscriber cares about. Returns the event itself when no change kind is removed (the\n * kept names then stay a superset, which only makes `mayHaveChangedRef` more conservative), `undefined` when every kind\n * is removed, and otherwise a new event with the kept kinds, names and unnamed kinds.\n */\nfunction narrowToRelevantRefs(\n\te: WatcherRepoChangeEvent,\n\trelevantRefs: () => Iterable<string>,\n): WatcherRepoChangeEvent | undefined {\n\t// Ref kinds that pass only if a relevant ref of that kind may have moved\n\tlet unresolved: Set<RefChangeKind> | undefined;\n\tfor (const change of e.changes) {\n\t\tif (!isRefChangeKind(change) || e.unnamedRefKinds.has(change)) continue;\n\n\t\t(unresolved ??= new Set()).add(change);\n\t}\n\tif (unresolved == null) return e;\n\n\tconst pseudoRefs: string[] = [];\n\tfor (const name of e.refs) {\n\t\tif (!name.startsWith('refs/')) {\n\t\t\tpseudoRefs.push(name);\n\t\t}\n\t}\n\tif (pseudoRefs.length !== 0) {\n\t\t// A pseudo-ref (`HEAD`, `ORIG_HEAD`) moving is a `heads` change for every subscriber\n\t\tunresolved.delete('heads');\n\t\tif (unresolved.size === 0) return e;\n\t}\n\n\t// Names that equal or cover a relevant ref — collected as we go, since `relevantRefs` may be a one-shot iterable\n\tconst keptNames = new Set<string>(pseudoRefs);\n\tfor (const ref of relevantRefs()) {\n\t\tconst kind = getRefChangeKind(ref);\n\t\tif (kind == null || !e.changes.has(kind)) continue;\n\n\t\t// `HEAD` is usually a symref to the checked-out branch, so any `heads` change may have moved it\n\t\tif (ref === 'HEAD') {\n\t\t\tfor (const name of e.refs) {\n\t\t\t\tif (getRefChangeKind(name) === 'heads') {\n\t\t\t\t\tkeptNames.add(name);\n\t\t\t\t}\n\t\t\t}\n\t\t\tunresolved.delete(kind);\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst covering = findCoveringRefName(e.refs, ref);\n\t\tif (covering == null) continue;\n\n\t\tkeptNames.add(covering);\n\t\tunresolved.delete(kind);\n\t}\n\tif (unresolved.size === 0) return e;\n\n\tconst keptChanges: RepositoryChange[] = [];\n\tfor (const change of e.changes) {\n\t\tif (!isRefChangeKind(change) || !unresolved.has(change)) {\n\t\t\tkeptChanges.push(change);\n\t\t}\n\t}\n\tif (keptChanges.length === 0) return undefined;\n\n\treturn new WatcherRepoChangeEvent(\n\t\te.repoPath,\n\t\tkeptChanges,\n\t\t{ names: keptNames, unnamed: e.unnamedRefKinds },\n\t\te.announced,\n\t);\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"watcherPatterns.js","names":[],"sources":["../../../../git/src/watching/watcherPatterns.ts"],"sourcesContent":["import type { GitDirProbe } from './gitDirPoller.js';\n\n// Glob patterns for watching files inside the .git directory.\n// They represent pure Git knowledge — the library owns these patterns and\n// provides them to the pluggable FileWatcherFactory.\n\nconst dotGitFiles =\n\t'index,HEAD,*_HEAD,MERGE_*,rebase-apply,rebase-apply/**,rebase-merge,rebase-merge/**,sequencer,sequencer/**';\n\n// `worktrees` (the bare entry, no `/*` suffix) is its own pattern: `worktree remove` deletes the whole\n// `worktrees/<id>` subtree in one syscall, and some platforms/watcher backends coalesce a same-instant\n// subtree delete into a single event for the PARENT directory — `worktrees/*` alone never matches that.\n// `classifyGitDirChange` classifies a bare `worktrees` path, so this is watch-coverage only.\n//\n// Only what `git worktree list` reports per worktree is watched here (its HEAD, lock and location). A\n// linked worktree's own index, `*_HEAD`, `MERGE_*`, rebase and sequencer state is watched by that\n// worktree's root watcher (`dotGitGlobRoot`) instead, so it never wakes sibling worktrees.\nconst dotGitWorktreeFiles = 'worktrees,worktrees/*,worktrees/*/HEAD,worktrees/*/locked,worktrees/*/gitdir';\n\nconst dotGitCommonFiles = `config,gk/config,refs/**,packed-refs,shallow,info/exclude,FETCH_HEAD,${dotGitWorktreeFiles}`;\n\n/** For standard repos (no worktrees): single watcher covering all .git files */\nexport const dotGitGlobCombined = `{${dotGitFiles},${dotGitCommonFiles}}`;\n\n/** For worktree repos: root watcher (worktree-specific files only) */\nexport const dotGitGlobRoot = `{${dotGitFiles}}`;\n\n/** For worktree repos: common watcher (shared config, refs, info, etc.) */\nexport const dotGitGlobCommon = `{${dotGitCommonFiles}}`;\n\n/**\n * Directories to leave unwatched under the `.git` directory (relative to it), for a provider that prunes natively.\n * Only directories nothing in the globs above reads: never anything under `refs`, `worktrees/*` itself, `gk`, `info`,\n * or the rebase/sequencer directories.\n */\nexport const gitDirExcludes: readonly string[] = [\n\t'objects',\n\t'logs',\n\t'hooks',\n\t'lfs',\n\t'modules',\n\t'rr-cache',\n\t'fsmonitor--daemon',\n\t'worktrees/*/logs',\n];\n\n/**\n * What a poll stats in a worktree's own `.git` directory when its watcher can't be trusted — the files\n * {@link dotGitGlobRoot} reads.\n */\nexport const gitDirRootProbes: readonly GitDirProbe[] = [\n\t{ path: 'HEAD' },\n\t{ path: 'index' },\n\t{ path: 'ORIG_HEAD' },\n\t{ path: 'MERGE_HEAD' },\n\t{ path: 'CHERRY_PICK_HEAD' },\n\t{ path: 'REVERT_HEAD' },\n\t{ path: 'REBASE_HEAD' },\n\t{ path: 'rebase-merge' },\n\t{ path: 'rebase-apply' },\n\t{ path: 'sequencer' },\n\t// Moves with every commit, reset or checkout on the checked-out branch, without the poll knowing its name\n\t{ path: 'logs/HEAD', emitAs: ['HEAD'] },\n];\n\n/**\n * What a poll stats in the common `.git` directory — the files {@link dotGitGlobCommon} reads, with the `refs/**` it\n * can't list narrowed to its namespace directories (whose mtime moves as a top-level ref is created or deleted) and\n * to the named refs of {@link getGitDirRefProbes}.\n */\nexport const gitDirCommonProbes: readonly GitDirProbe[] = [\n\t{ path: 'packed-refs' },\n\t{ path: 'config' },\n\t{ path: 'gk/config' },\n\t{ path: 'info/exclude' },\n\t{ path: 'refs/heads' },\n\t{ path: 'refs/tags' },\n\t{ path: 'refs/stash' },\n\t{ path: 'worktrees' },\n\t{ path: 'shallow' },\n\t// A fetch writes it with the remote-tracking refs it updated, which the poll can't list. Git rewrites it even on a\n\t// fetch that moves nothing, so each fetch is reported as a `remotes` change: the cost of seeing remote-tracking\n\t// refs no subscriber names\n\t{ path: 'FETCH_HEAD', emitAs: ['FETCH_HEAD', 'refs/remotes'] },\n];\n\n/** For each path a probe of {@link gitDirRootProbes} or {@link gitDirCommonProbes} is emitted as, the probes emitted as it */\nexport const gitDirProbesByEmittedPath: ReadonlyMap<string, readonly string[]> = getProbesByEmittedPath([\n\t...gitDirRootProbes,\n\t...gitDirCommonProbes,\n]);\n\nfunction getProbesByEmittedPath(probes: readonly GitDirProbe[]): Map<string, string[]> {\n\tconst map = new Map<string, string[]>();\n\tfor (const probe of probes) {\n\t\tfor (const path of probe.emitAs ?? []) {\n\t\t\tconst paths = map.get(path);\n\t\t\tif (paths == null) {\n\t\t\t\tmap.set(path, [probe.path]);\n\t\t\t} else {\n\t\t\t\tpaths.push(probe.path);\n\t\t\t}\n\t\t}\n\t}\n\treturn map;\n}\n\n/**\n * Probes for named branches and remote-tracking refs: each loose ref file, and each directory between it and its\n * namespace (`refs/heads/feature` for `refs/heads/feature/x`, `refs/remotes/origin`), whose mtime moves as a sibling\n * ref is created or deleted. A ref created in a directory no named ref sits in is left to `packed-refs`, `logs/HEAD`\n * and `FETCH_HEAD`.\n */\nexport function getGitDirRefProbes(refs: Iterable<string>): GitDirProbe[] {\n\tconst paths = new Set<string>();\n\tfor (const ref of refs) {\n\t\tconst namespaceEnd = ref.startsWith('refs/heads/') ? 10 : ref.startsWith('refs/remotes/') ? 12 : -1;\n\t\tif (namespaceEnd === -1) continue;\n\n\t\tfor (let path = ref; path.length > namespaceEnd; path = path.substring(0, path.lastIndexOf('/'))) {\n\t\t\tpaths.add(path);\n\t\t}\n\t}\n\treturn Array.from(paths, path => ({ path: path }));\n}\n\n/**\n * Directories to leave unwatched in the working tree (relative to it), for a provider that prunes natively —\n * the same ones {@link shouldIgnoreWorkingTreePath} drops from events.\n */\nexport const workingTreeExcludes: readonly string[] = ['**/node_modules', '**/.git'];\n\n/** Glob for watching .gitignore files in the working tree */\nexport const gitIgnoreGlob = '.gitignore';\n\n/** Glob for detecting .git directory creation (repository init/clone) */\nexport const gitInitGlob = '**/.git';\n\n// Regex-based filters that complement the globs above. The globs tell\n// the watcher *what* to observe; these filters tell it what to *skip*\n// at runtime (transient lock files, daemon artifacts, etc.).\n\nconst ignoredRepoPathRegex = /(?:\\/|\\\\)fsmonitor--daemon(?:\\/|\\\\)|(?:^|\\/)index\\.lock$/;\n\n/**\n * Returns true if the `.git`-relative path should be ignored\n * (transient filesystem artifacts, not meaningful git state changes).\n */\nexport function shouldIgnoreRepoPath(relativePath: string): boolean {\n\treturn ignoredRepoPathRegex.test(relativePath);\n}\n\nconst ignoredWorkingTreePathRegex =\n\t/(?:(?:\\/|\\\\)node_modules(?:\\/|\\\\|$)|\\.git(?:\\/index\\.lock)?(?:\\/|\\\\|$)|\\.watchman-cookie-)/;\n\n/**\n * Returns true if the working tree path should be ignored\n * (node_modules, .git internals, watchman cookies).\n */\nexport function shouldIgnoreWorkingTreePath(absolutePath: string): boolean {\n\treturn ignoredWorkingTreePathRegex.test(absolutePath);\n}\n"],"mappings":";AAMA,MAAM,cACL;AAYD,MAAM,oBAAoB;;AAG1B,MAAa,qBAAqB,IAAI,YAAY,GAAG,kBAAkB;;AAGvE,MAAa,iBAAiB,IAAI,YAAY;;AAG9C,MAAa,mBAAmB,IAAI,kBAAkB;;;;;;AAOtD,MAAa,iBAAoC;CAChD;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACD;;;;;AAMA,MAAa,mBAA2C;CACvD,EAAE,MAAM,OAAO;CACf,EAAE,MAAM,QAAQ;CAChB,EAAE,MAAM,YAAY;CACpB,EAAE,MAAM,aAAa;CACrB,EAAE,MAAM,mBAAmB;CAC3B,EAAE,MAAM,cAAc;CACtB,EAAE,MAAM,cAAc;CACtB,EAAE,MAAM,eAAe;CACvB,EAAE,MAAM,eAAe;CACvB,EAAE,MAAM,YAAY;CAEpB;EAAE,MAAM;EAAa,QAAQ,CAAC,MAAM;CAAE;AACvC;;;;;;AAOA,MAAa,qBAA6C;CACzD,EAAE,MAAM,cAAc;CACtB,EAAE,MAAM,SAAS;CACjB,EAAE,MAAM,YAAY;CACpB,EAAE,MAAM,eAAe;CACvB,EAAE,MAAM,aAAa;
|
|
1
|
+
{"version":3,"file":"watcherPatterns.js","names":[],"sources":["../../../../git/src/watching/watcherPatterns.ts"],"sourcesContent":["import type { GitDirProbe } from './gitDirPoller.js';\n\n// Glob patterns for watching files inside the .git directory.\n// They represent pure Git knowledge — the library owns these patterns and\n// provides them to the pluggable FileWatcherFactory.\n\nconst dotGitFiles =\n\t'index,HEAD,*_HEAD,MERGE_*,rebase-apply,rebase-apply/**,rebase-merge,rebase-merge/**,sequencer,sequencer/**';\n\n// `worktrees` (the bare entry, no `/*` suffix) is its own pattern: `worktree remove` deletes the whole\n// `worktrees/<id>` subtree in one syscall, and some platforms/watcher backends coalesce a same-instant\n// subtree delete into a single event for the PARENT directory — `worktrees/*` alone never matches that.\n// `classifyGitDirChange` classifies a bare `worktrees` path, so this is watch-coverage only.\n//\n// Only what `git worktree list` reports per worktree is watched here (its HEAD, lock and location). A\n// linked worktree's own index, `*_HEAD`, `MERGE_*`, rebase and sequencer state is watched by that\n// worktree's root watcher (`dotGitGlobRoot`) instead, so it never wakes sibling worktrees.\nconst dotGitWorktreeFiles = 'worktrees,worktrees/*,worktrees/*/HEAD,worktrees/*/locked,worktrees/*/gitdir';\n\nconst dotGitCommonFiles = `config,gk/config,refs/**,packed-refs,shallow,info/exclude,FETCH_HEAD,${dotGitWorktreeFiles}`;\n\n/** For standard repos (no worktrees): single watcher covering all .git files */\nexport const dotGitGlobCombined = `{${dotGitFiles},${dotGitCommonFiles}}`;\n\n/** For worktree repos: root watcher (worktree-specific files only) */\nexport const dotGitGlobRoot = `{${dotGitFiles}}`;\n\n/** For worktree repos: common watcher (shared config, refs, info, etc.) */\nexport const dotGitGlobCommon = `{${dotGitCommonFiles}}`;\n\n/**\n * Directories to leave unwatched under the `.git` directory (relative to it), for a provider that prunes natively.\n * Only directories nothing in the globs above reads: never anything under `refs`, `worktrees/*` itself, `gk`, `info`,\n * or the rebase/sequencer directories.\n */\nexport const gitDirExcludes: readonly string[] = [\n\t'objects',\n\t'logs',\n\t'hooks',\n\t'lfs',\n\t'modules',\n\t'rr-cache',\n\t'fsmonitor--daemon',\n\t'worktrees/*/logs',\n];\n\n/**\n * What a poll stats in a worktree's own `.git` directory when its watcher can't be trusted — the files\n * {@link dotGitGlobRoot} reads.\n */\nexport const gitDirRootProbes: readonly GitDirProbe[] = [\n\t{ path: 'HEAD' },\n\t{ path: 'index' },\n\t{ path: 'ORIG_HEAD' },\n\t{ path: 'MERGE_HEAD' },\n\t{ path: 'CHERRY_PICK_HEAD' },\n\t{ path: 'REVERT_HEAD' },\n\t{ path: 'REBASE_HEAD' },\n\t{ path: 'rebase-merge' },\n\t{ path: 'rebase-apply' },\n\t{ path: 'sequencer' },\n\t// Moves with every commit, reset or checkout on the checked-out branch, without the poll knowing its name\n\t{ path: 'logs/HEAD', emitAs: ['HEAD'] },\n];\n\n/**\n * What a poll stats in the common `.git` directory — the files {@link dotGitGlobCommon} reads, with the `refs/**` it\n * can't list narrowed to its namespace directories (whose mtime moves as a top-level ref is created or deleted) and\n * to the named refs of {@link getGitDirRefProbes}.\n */\nexport const gitDirCommonProbes: readonly GitDirProbe[] = [\n\t{ path: 'packed-refs' },\n\t{ path: 'config' },\n\t{ path: 'gk/config' },\n\t{ path: 'info/exclude' },\n\t{ path: 'refs/heads' },\n\t// Moves as a remote's directory appears or goes: `git remote remove` and `git remote rename` delete or move each of\n\t// its loose refs (a packed one is locked through a loose path too), and git removes the emptied directory. Reported\n\t// as every remote-tracking ref possibly moved, since the poll can't tell which remote it was. A remote whose name\n\t// nests under a directory another remote still uses (`foo/bar` beside `foo/baz`) leaves it unmoved, so only a named\n\t// ref's probe sees that one go\n\t{ path: 'refs/remotes' },\n\t{ path: 'refs/tags' },\n\t{ path: 'refs/stash' },\n\t{ path: 'worktrees' },\n\t{ path: 'shallow' },\n\t// A fetch writes it with the remote-tracking refs it updated, which the poll can't list. Git rewrites it even on a\n\t// fetch that moves nothing, so each fetch is reported as a `remotes` change: the cost of seeing remote-tracking\n\t// refs no subscriber names\n\t{ path: 'FETCH_HEAD', emitAs: ['FETCH_HEAD', 'refs/remotes'] },\n];\n\n/** For each path a probe of {@link gitDirRootProbes} or {@link gitDirCommonProbes} is emitted as, the probes emitted as it */\nexport const gitDirProbesByEmittedPath: ReadonlyMap<string, readonly string[]> = getProbesByEmittedPath([\n\t...gitDirRootProbes,\n\t...gitDirCommonProbes,\n]);\n\nfunction getProbesByEmittedPath(probes: readonly GitDirProbe[]): Map<string, string[]> {\n\tconst map = new Map<string, string[]>();\n\tfor (const probe of probes) {\n\t\tfor (const path of probe.emitAs ?? []) {\n\t\t\tconst paths = map.get(path);\n\t\t\tif (paths == null) {\n\t\t\t\tmap.set(path, [probe.path]);\n\t\t\t} else {\n\t\t\t\tpaths.push(probe.path);\n\t\t\t}\n\t\t}\n\t}\n\treturn map;\n}\n\n/**\n * Probes for named branches and remote-tracking refs: each loose ref file, and each directory between it and its\n * namespace (`refs/heads/feature` for `refs/heads/feature/x`, `refs/remotes/origin`), whose mtime moves as a sibling\n * ref is created or deleted. A ref created in a directory no named ref sits in is left to `packed-refs`, `logs/HEAD`\n * and `FETCH_HEAD`.\n */\nexport function getGitDirRefProbes(refs: Iterable<string>): GitDirProbe[] {\n\tconst paths = new Set<string>();\n\tfor (const ref of refs) {\n\t\tconst namespaceEnd = ref.startsWith('refs/heads/') ? 10 : ref.startsWith('refs/remotes/') ? 12 : -1;\n\t\tif (namespaceEnd === -1) continue;\n\n\t\tfor (let path = ref; path.length > namespaceEnd; path = path.substring(0, path.lastIndexOf('/'))) {\n\t\t\tpaths.add(path);\n\t\t}\n\t}\n\treturn Array.from(paths, path => ({ path: path }));\n}\n\n/**\n * Directories to leave unwatched in the working tree (relative to it), for a provider that prunes natively —\n * the same ones {@link shouldIgnoreWorkingTreePath} drops from events.\n */\nexport const workingTreeExcludes: readonly string[] = ['**/node_modules', '**/.git'];\n\n/** Glob for watching .gitignore files in the working tree */\nexport const gitIgnoreGlob = '.gitignore';\n\n/** Glob for detecting .git directory creation (repository init/clone) */\nexport const gitInitGlob = '**/.git';\n\n// Regex-based filters that complement the globs above. The globs tell\n// the watcher *what* to observe; these filters tell it what to *skip*\n// at runtime (transient lock files, daemon artifacts, etc.).\n\nconst ignoredRepoPathRegex = /(?:\\/|\\\\)fsmonitor--daemon(?:\\/|\\\\)|(?:^|\\/)index\\.lock$/;\n\n/**\n * Returns true if the `.git`-relative path should be ignored\n * (transient filesystem artifacts, not meaningful git state changes).\n */\nexport function shouldIgnoreRepoPath(relativePath: string): boolean {\n\treturn ignoredRepoPathRegex.test(relativePath);\n}\n\nconst ignoredWorkingTreePathRegex =\n\t/(?:(?:\\/|\\\\)node_modules(?:\\/|\\\\|$)|\\.git(?:\\/index\\.lock)?(?:\\/|\\\\|$)|\\.watchman-cookie-)/;\n\n/**\n * Returns true if the working tree path should be ignored\n * (node_modules, .git internals, watchman cookies).\n */\nexport function shouldIgnoreWorkingTreePath(absolutePath: string): boolean {\n\treturn ignoredWorkingTreePathRegex.test(absolutePath);\n}\n"],"mappings":";AAMA,MAAM,cACL;AAYD,MAAM,oBAAoB;;AAG1B,MAAa,qBAAqB,IAAI,YAAY,GAAG,kBAAkB;;AAGvE,MAAa,iBAAiB,IAAI,YAAY;;AAG9C,MAAa,mBAAmB,IAAI,kBAAkB;;;;;;AAOtD,MAAa,iBAAoC;CAChD;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACD;;;;;AAMA,MAAa,mBAA2C;CACvD,EAAE,MAAM,OAAO;CACf,EAAE,MAAM,QAAQ;CAChB,EAAE,MAAM,YAAY;CACpB,EAAE,MAAM,aAAa;CACrB,EAAE,MAAM,mBAAmB;CAC3B,EAAE,MAAM,cAAc;CACtB,EAAE,MAAM,cAAc;CACtB,EAAE,MAAM,eAAe;CACvB,EAAE,MAAM,eAAe;CACvB,EAAE,MAAM,YAAY;CAEpB;EAAE,MAAM;EAAa,QAAQ,CAAC,MAAM;CAAE;AACvC;;;;;;AAOA,MAAa,qBAA6C;CACzD,EAAE,MAAM,cAAc;CACtB,EAAE,MAAM,SAAS;CACjB,EAAE,MAAM,YAAY;CACpB,EAAE,MAAM,eAAe;CACvB,EAAE,MAAM,aAAa;CAMrB,EAAE,MAAM,eAAe;CACvB,EAAE,MAAM,YAAY;CACpB,EAAE,MAAM,aAAa;CACrB,EAAE,MAAM,YAAY;CACpB,EAAE,MAAM,UAAU;CAIlB;EAAE,MAAM;EAAc,QAAQ,CAAC,cAAc,cAAc;CAAE;AAC9D;;AAGA,MAAa,4BAAoE,uBAAuB,CACvG,GAAG,kBACH,GAAG,kBACJ,CAAC;AAED,SAAS,uBAAuB,QAAuD;CACtF,MAAM,sBAAM,IAAI,IAAsB;CACtC,KAAK,MAAM,SAAS,QACnB,KAAK,MAAM,QAAQ,MAAM,UAAU,CAAC,GAAG;EACtC,MAAM,QAAQ,IAAI,IAAI,IAAI;EAC1B,IAAI,SAAS,MACZ,IAAI,IAAI,MAAM,CAAC,MAAM,IAAI,CAAC;OAE1B,MAAM,KAAK,MAAM,IAAI;CAEvB;CAED,OAAO;AACR;;;;;;;AAQA,SAAgB,mBAAmB,MAAuC;CACzE,MAAM,wBAAQ,IAAI,IAAY;CAC9B,KAAK,MAAM,OAAO,MAAM;EACvB,MAAM,eAAe,IAAI,WAAW,aAAa,IAAI,KAAK,IAAI,WAAW,eAAe,IAAI,KAAK;EACjG,IAAI,iBAAiB,IAAI;EAEzB,KAAK,IAAI,OAAO,KAAK,KAAK,SAAS,cAAc,OAAO,KAAK,UAAU,GAAG,KAAK,YAAY,GAAG,CAAC,GAC9F,MAAM,IAAI,IAAI;CAEhB;CACA,OAAO,MAAM,KAAK,QAAO,UAAS,EAAQ,KAAK,EAAE;AAClD;;;;;AAMA,MAAa,sBAAyC,CAAC,mBAAmB,SAAS;;AAGnF,MAAa,gBAAgB;;AAG7B,MAAa,cAAc;AAM3B,MAAM,uBAAuB;;;;;AAM7B,SAAgB,qBAAqB,cAA+B;CACnE,OAAO,qBAAqB,KAAK,YAAY;AAC9C;AAEA,MAAM,8BACL;;;;;AAMD,SAAgB,4BAA4B,cAA+B;CAC1E,OAAO,4BAA4B,KAAK,YAAY;AACrD"}
|
|
@@ -112,9 +112,13 @@ export interface WorktreeSummaryServiceOptions {
|
|
|
112
112
|
* `dirty`; the upstream moving reads only `branch`; a ref the base depends on reads only `base`; a `gk` config change
|
|
113
113
|
* (mostly bookkeeping writes to the shared `.git/gk/config`), or core's own write of the branch's merge target or
|
|
114
114
|
* base, re-reads the branch's stored merge targets and recorded base, and reads `base` only when one of them changed;
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
* run
|
|
115
|
+
* a `.git/config` change reads `branch` and the provider's config inputs to the base (one read per repository), and
|
|
116
|
+
* reads `base` only when they, the stored values, HEAD's commit or the upstream changed; the checked-out branch
|
|
117
|
+
* moving, or a HEAD change, reads all of them. Stash, unrelated tags and branches, and the like read nothing. One run
|
|
118
|
+
* is in flight per worktree at a time, at most four per repository (the worktrees sharing a common git directory), and
|
|
119
|
+
* at most eight across the service. Runs take turns first come, first served, except that one whose repository is at
|
|
120
|
+
* its cap lets later runs of other repositories go first, so a burst in one repository never delays another's reads:
|
|
121
|
+
* events while a run waits for its turn join it, and events during a run are merged into a single follow-up run.
|
|
118
122
|
*
|
|
119
123
|
* **Value-change-only notification.** {@link WorktreeSummarySubscription.onDidChange} and {@link onDidChangeSummary}
|
|
120
124
|
* fire only when some field's value differs from the previous summary — a recompute that reads the same answer is
|
|
@@ -126,6 +130,11 @@ export declare class WorktreeSummaryService implements UnifiedDisposable {
|
|
|
126
130
|
/** Fires when the summary of any subscribed worktree changes value */
|
|
127
131
|
get onDidChangeSummary(): Event<WorktreeSummary>;
|
|
128
132
|
private readonly entries;
|
|
133
|
+
/** Reads running for each repository, by its slots key; a repository with none has no entry */
|
|
134
|
+
private readonly activeReadsByRepository;
|
|
135
|
+
private activeReads;
|
|
136
|
+
/** Entries waiting for a read slot, oldest first */
|
|
137
|
+
private readonly readWaiters;
|
|
129
138
|
private readonly watchService;
|
|
130
139
|
private readonly getRepository;
|
|
131
140
|
private readonly getCommonPath;
|
|
@@ -153,6 +162,13 @@ export declare class WorktreeSummaryService implements UnifiedDisposable {
|
|
|
153
162
|
private queue;
|
|
154
163
|
/** Runs until nothing is pending. Never rejects — every failure becomes entry state. */
|
|
155
164
|
private drain;
|
|
165
|
+
/**
|
|
166
|
+
* A read takes one of its repository's slots and one of the service's. Each release hands what it frees to the
|
|
167
|
+
* waiters first, so a new read takes a slot only when no waiter can use it.
|
|
168
|
+
*/
|
|
169
|
+
private acquireRead;
|
|
170
|
+
private releaseRead;
|
|
171
|
+
private tryTakeReadSlots;
|
|
156
172
|
private run;
|
|
157
173
|
private fail;
|
|
158
174
|
private fireChange;
|