@gitkraken/core-gitlens 0.15.4 → 0.15.6
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 +25 -1
- package/dist/git/cache.d.ts +9 -12
- package/dist/git/cache.js +34 -16
- package/dist/git/cache.js.map +1 -1
- package/dist/git/context.d.ts +19 -2
- package/dist/git/context.js.map +1 -1
- package/dist/git/providers/provider.d.ts +7 -0
- package/dist/git/service.d.ts +5 -1
- package/dist/git/service.js +8 -4
- package/dist/git/service.js.map +1 -1
- package/dist/git/watching/changeEvent.d.ts +25 -1
- package/dist/git/watching/changeEvent.js +36 -2
- package/dist/git/watching/changeEvent.js.map +1 -1
- package/dist/git/watching/configSections.d.ts +35 -0
- package/dist/git/watching/configSections.js +210 -0
- package/dist/git/watching/configSections.js.map +1 -0
- package/dist/git/watching/watchGroup.d.ts +36 -3
- package/dist/git/watching/watchGroup.js +107 -2
- package/dist/git/watching/watchGroup.js.map +1 -1
- package/dist/git/watching/watchService.d.ts +14 -3
- package/dist/git/watching/watchService.js +10 -7
- package/dist/git/watching/watchService.js.map +1 -1
- package/dist/git/watching/watchSession.d.ts +52 -21
- package/dist/git/watching/watchSession.js +164 -42
- package/dist/git/watching/watchSession.js.map +1 -1
- package/dist/git/watching/worktreeSummary.d.ts +9 -7
- package/dist/git/watching/worktreeSummary.js +21 -10
- package/dist/git/watching/worktreeSummary.js.map +1 -1
- package/dist/git-cli/cliGitProvider.d.ts +6 -1
- package/dist/git-cli/cliGitProvider.js +45 -5
- package/dist/git-cli/cliGitProvider.js.map +1 -1
- package/dist/git-cli/providers/branches.d.ts +2 -2
- package/dist/git-cli/providers/branches.js +17 -15
- package/dist/git-cli/providers/branches.js.map +1 -1
- package/dist/git-cli/providers/config.d.ts +13 -0
- package/dist/git-cli/providers/config.js +38 -1
- package/dist/git-cli/providers/config.js.map +1 -1
- package/dist/git-cli/providers/operations.d.ts +35 -1
- package/dist/git-cli/providers/operations.js +175 -16
- package/dist/git-cli/providers/operations.js.map +1 -1
- package/dist/git-cli/providers/stash.d.ts +1 -0
- package/dist/git-cli/providers/stash.js +9 -3
- package/dist/git-cli/providers/stash.js.map +1 -1
- package/dist/git-cli/providers/worktrees.js +11 -4
- package/dist/git-cli/providers/worktrees.js.map +1 -1
- package/dist/git-cli/service.js +5 -1
- package/dist/git-cli/service.js.map +1 -1
- package/dist/plus/integrations/collectionMetadata.js +4 -2
- package/dist/plus/integrations/collectionMetadata.js.map +1 -1
- package/dist/plus/integrations/providers/issueSorts.d.ts +0 -1
- package/dist/plus/integrations/providers/providerErrors.d.ts +22 -0
- package/dist/plus/integrations/providers/providerErrors.js +43 -2
- package/dist/plus/integrations/providers/providerErrors.js.map +1 -1
- package/dist/plus/integrations/providers/providersApi.js +12 -8
- package/dist/plus/integrations/providers/providersApi.js.map +1 -1
- package/dist/plus/integrations/reads/drains.d.ts +0 -1
- package/dist/plus/integrations/results.d.ts +13 -3
- package/dist/plus/integrations/results.js +12 -2
- package/dist/plus/integrations/results.js.map +1 -1
- package/docs/integrations.md +14 -0
- package/package.json +1 -1
|
@@ -63,6 +63,7 @@ var RepositoryWatchService = class {
|
|
|
63
63
|
getIgnoredDirectories;
|
|
64
64
|
provider;
|
|
65
65
|
poller;
|
|
66
|
+
readFile;
|
|
66
67
|
sessionMap = /* @__PURE__ */ new Map();
|
|
67
68
|
watchGroups = /* @__PURE__ */ new Map();
|
|
68
69
|
wtWatchers = /* @__PURE__ */ new Map();
|
|
@@ -77,6 +78,7 @@ var RepositoryWatchService = class {
|
|
|
77
78
|
this.defaultWorkingTreeMaxWaitMs = options.defaultWorkingTreeMaxWaitMs;
|
|
78
79
|
this.getIgnoreFilter = options.getIgnoreFilter;
|
|
79
80
|
this.getIgnoredDirectories = options.getIgnoredDirectories;
|
|
81
|
+
this.readFile = options.readFile;
|
|
80
82
|
if (options.polling != null) this.poller = new GitDirPoller(options.polling);
|
|
81
83
|
}
|
|
82
84
|
[Symbol.dispose]() {
|
|
@@ -180,12 +182,13 @@ var RepositoryWatchService = class {
|
|
|
180
182
|
}
|
|
181
183
|
/**
|
|
182
184
|
* Core announced one of its own writes for `repoPath`; the session watching it fires the announcement in place of
|
|
183
|
-
* the write's own `.git` events (see {@link RepositoryWatchSession.noteSelfWrite})
|
|
184
|
-
*
|
|
185
|
+
* the write's own `.git` events (see {@link RepositoryWatchSession.noteSelfWrite}); `refs` are the full ref names it
|
|
186
|
+
* moved, when it knows them, and `options.configConfinedToRefs` confines its `config` change to the sections of the
|
|
187
|
+
* local branches among them (see `GitServiceHooks.repository.onChanged`). A path no session watches is ignored.
|
|
185
188
|
*/
|
|
186
|
-
noteSelfWrite(repoPath, changes) {
|
|
189
|
+
noteSelfWrite(repoPath, changes, refs, options) {
|
|
187
190
|
if (this._disposed) return;
|
|
188
|
-
this.sessionMap.get(getRepositoryKey(repoPath))?.session.noteSelfWrite(changes);
|
|
191
|
+
this.sessionMap.get(getRepositoryKey(repoPath))?.session.noteSelfWrite(changes, refs, options);
|
|
189
192
|
}
|
|
190
193
|
/** Get an existing session by repo path */
|
|
191
194
|
getSession(repoPath) {
|
|
@@ -228,7 +231,7 @@ var RepositoryWatchService = class {
|
|
|
228
231
|
getOrCreateWatchGroup(commonGitDir) {
|
|
229
232
|
let group = this.watchGroups.get(commonGitDir);
|
|
230
233
|
if (group == null) {
|
|
231
|
-
group = new WatchGroup(commonGitDir, this.provider, this.poller);
|
|
234
|
+
group = new WatchGroup(commonGitDir, this.provider, this.poller, this.readFile);
|
|
232
235
|
this.watchGroups.set(commonGitDir, group);
|
|
233
236
|
}
|
|
234
237
|
return group;
|
|
@@ -245,9 +248,9 @@ var RepositoryWatchService = class {
|
|
|
245
248
|
const { session, gitDir, watchHooks } = record;
|
|
246
249
|
const commonGitDir = (gitDir.commonUri ?? gitDir.uri).fsPath;
|
|
247
250
|
this.getOrCreateWatchGroup(commonGitDir).addSession(session.repoPath, gitDir, {
|
|
248
|
-
onRepoChanged: (_repoPath, changes, ref, overflow) => {
|
|
251
|
+
onRepoChanged: (_repoPath, changes, ref, overflow, configBranches) => {
|
|
249
252
|
if (overflow) session.pushUnfencedRepoChanges(changes);
|
|
250
|
-
else session.pushRepoChanges(changes, ref);
|
|
253
|
+
else session.pushRepoChanges(changes, ref, configBranches);
|
|
251
254
|
if (this.poller != null && (changes.includes("index") || changes.includes("head")) && this.wtWatchers.get(session.repoPath)?.reliability === "unwatchable") session.pushWorkingTreeChanges([], true);
|
|
252
255
|
},
|
|
253
256
|
onFetchHeadChanged: (repoPath) => {
|
|
@@ -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: (\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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"}
|
|
1
|
+
{"version":3,"file":"watchService.js","names":[],"sources":["../../../../git/src/watching/watchService.ts"],"sourcesContent":["import type { UnifiedDisposable } from '@gitlens/utils/disposable.js';\nimport type { Event } from '@gitlens/utils/event.js';\nimport { Emitter } from '@gitlens/utils/event.js';\nimport { isDescendant, relative } from '@gitlens/utils/path.js';\nimport type { ResourceUsage } from '@gitlens/utils/resourceUsage.js';\nimport { getRepositoryKey } from '@gitlens/utils/uri.js';\nimport type { GitDir, RepositoryChange } from '../models/repository.js';\nimport type { WatcherRepoChangeEvent } from './changeEvent.js';\nimport type { PollTarget } from './gitDirPoller.js';\nimport { GitDirPoller } from './gitDirPoller.js';\nimport type { GitIgnoreFilter } from './gitIgnoreFilter.js';\nimport type {\n\tFileWatcher,\n\tFileWatcherOptions,\n\tFileWatcherReliability,\n\tFileWatchEvent,\n\tFileWatchingProvider,\n} from './provider.js';\nimport { shouldIgnoreWorkingTreePath, workingTreeExcludes } from './watcherPatterns.js';\nimport type { WatchGroupHooks } from './watchGroup.js';\nimport { WatchGroup } from './watchGroup.js';\nimport { RepositoryWatchSession } from './watchSession.js';\n\n/**\n * Cap on paths held while the gitignore filter loads. Higher than the session's own cap because these are\n * pre-filter — most of a build's output is typically gitignored and would be discarded on replay, so a\n * tighter bound here would mark batches incomplete when they filter down to almost nothing.\n */\nconst maxBufferedWorkingTreePaths = 5000;\n\n/** Trailing debounce before the git-ignored directory set is re-read after an ignore rule changes */\nconst ignoredDirectoriesRefreshDelayMs = 500;\n\ninterface WorkingTreeWatcherEntry {\n\treadonly session: RepositoryWatchSession;\n\t/** The event handler shared by every watcher this entry has had — a re-created watcher reuses it */\n\treadonly onEvent: (event: FileWatchEvent) => void;\n\treadonly filter?: GitIgnoreFilter;\n\twatcher: FileWatcher;\n\t/** The git-ignored directories the live watcher was created excluding (on top of the static set) */\n\tignoredDirectories: readonly string[];\n\t/** Bumped by every ignore-rule change and by stopping; a read that lands under a different value is stale */\n\tgeneration: number;\n\treading: boolean;\n\t/** A refresh came due while a read was in flight */\n\trereadQueued: boolean;\n\trefreshTimer?: ReturnType<typeof setTimeout>;\n\t/** What the live watcher last reported; a re-created watcher starts over from its own report */\n\treliability: FileWatcherReliability;\n\t/** Compares the host's working-tree fingerprint while the watcher isn't `reliable` */\n\tpollTarget?: PollTarget;\n\t/**\n\t * Git-ignored directories (repo-relative) whose events reached the post-filter, so the watcher isn't pruning them —\n\t * typically created after the last read (a first build's `dist/`). Each waits for a read that started after it.\n\t */\n\tunpruned?: Set<string>;\n\t/**\n\t * Directories a read showed git won't prune (they hold tracked files, or glob metacharacters in their names);\n\t * cleared when ignore rules change\n\t */\n\tunprunable?: Set<string>;\n}\n\n/**\n * A host whose watchers can report they can't be trusted (see `FileWatcherOptions.onDidChangeReliability`) passes\n * these to have core poll in their place. A `.git` directory is polled by statting the files its watchers read and\n * feeding each one that moved through the same interpretation as a watcher event; a working tree is never walked.\n */\nexport interface WatchPollingOptions {\n\t/**\n\t * Reads a path's modification time (ms) and size; `undefined` when it doesn't exist. A rejection means unknown:\n\t * the last reading stands, so it never counts as a change.\n\t */\n\treadonly stat: (path: string) => Promise<{ readonly mtime: number; readonly size: number } | undefined>;\n\t/** Whether a poll is worth running now. Default: the service isn't suspended (`suspendAll`/`resumeAll`) */\n\treadonly isActive?: () => boolean;\n\t/** How often each untrusted watcher is polled. Default: 60000ms */\n\treadonly intervalMs?: number;\n\t/**\n\t * How often `isActive` is looked at, which bounds how late the first poll after it turns true can be. Only with\n\t * `isActive`: the default gate wakes only for a poll. Default: 5000ms\n\t */\n\treadonly activeCheckMs?: number;\n\t/**\n\t * Refs to poll for a repository beyond its subscribers' `relevantRefs`: one with none (a host listing every branch)\n\t * still needs its checked-out branch's upstream, which a push moves without writing `FETCH_HEAD`\n\t */\n\treadonly getRefs?: (repoPath: string) => Iterable<string>;\n\t/**\n\t * Something that changes whenever the repository's working tree does (e.g. a digest of its status and the\n\t * changed files' stats), compared at each poll while working-tree subscribers are watching a working tree whose\n\t * watcher can't be trusted; a change is reported with no paths. `undefined` or a rejection skips that poll. With or\n\t * without it, a working tree whose watcher is `unwatchable` is reported changed (no paths) whenever its index or\n\t * `HEAD` changes.\n\t */\n\treadonly getWorkingTreeFingerprint?: (repoPath: string) => Promise<string | undefined>;\n}\n\nexport interface WatchServiceOptions {\n\treadonly watchingProvider: FileWatchingProvider;\n\t/** Default debounce for repo changes when subscriber doesn't specify. Default: 250ms */\n\treadonly defaultRepoDelayMs?: number;\n\t/** Default debounce for working tree changes when subscriber doesn't specify. Default: 2500ms */\n\treadonly defaultWorkingTreeDelayMs?: number;\n\t/** Default cap on how long continuous repo activity can defer a flush. `Infinity` disables it. Default: 2000ms */\n\treadonly defaultRepoMaxWaitMs?: number;\n\t/** Default cap on how long continuous working tree activity can defer a flush. `Infinity` disables it. Default: 10000ms */\n\treadonly defaultWorkingTreeMaxWaitMs?: number;\n\t/** Returns a GitIgnoreFilter for a given repo. When absent or returns undefined, gitignore filtering is skipped. */\n\treadonly getIgnoreFilter?: (repoPath: string, gitDirPath: string) => GitIgnoreFilter | undefined;\n\t/**\n\t * Returns the repo-relative directories an ignore rule matches, for a watching provider that prunes\n\t * `excludes` natively (`supportsExcludes`). Never called for one that doesn't. A rejection keeps the static set.\n\t */\n\treadonly getIgnoredDirectories?: (repoPath: string) => Promise<readonly string[]>;\n\t/** Opts into polling the watchers that report they can't be trusted. When absent, no watcher is asked. */\n\treadonly polling?: WatchPollingOptions;\n\t/**\n\t * Reads a file's bytes; a rejection means it couldn't be read. With it, each report of a repository's shared\n\t * `.git/config` is classified by what changed in the file: a change confined to some branches' `branch.<name>`\n\t * sections reaches only the subscribers following those branches, and carries no `remotes`. Without it, every\n\t * config change concerns everyone, as before.\n\t */\n\treadonly readFile?: (path: string) => Promise<Uint8Array>;\n}\n\nexport interface WatchHooks {\n\t/** Called when FETCH_HEAD changed (for last-fetched tracking) */\n\treadonly onFetchHeadChanged?: (repoPath: string) => void;\n\t/** Called when .git/info/exclude changed */\n\treadonly onIgnoresChanged?: (repoPath: string) => void;\n\t/** Called when .gitignore changed in the working tree (extension watches this separately) */\n\treadonly onGitIgnoreChanged?: (repoPath: string) => void;\n}\n\nexport interface WatchHandle {\n\treadonly session: RepositoryWatchSession;\n\tdispose(): void;\n}\n\ninterface SessionRecord {\n\treadonly session: RepositoryWatchSession;\n\treadonly gitDir: GitDir;\n\t/**\n\t * Hooks contributed by all current `watch()` callers for this repo path. Earlier\n\t * implementations stored just the first caller's hooks (`WatchHooks | undefined`) and\n\t * silently dropped subsequent callers' hooks — that broke the case where a low-level\n\t * subscriber (e.g. the Graph's secondary-worktree WIP watcher) opened the session\n\t * first without hooks, and a later `Repository.watch()` for the same path lost\n\t * its `markFetched` / `onIgnoresChanged` wiring. Each `watch()` call adds its hooks\n\t * to this set and removes them on dispose; fire sites iterate the set.\n\t */\n\treadonly watchHooks: Set<WatchHooks>;\n\trefCount: number;\n\t/** The mode {@link RepositoryWatchService.onDidChangeWatchMode} last announced; `'events'` while unwatched */\n\twatchMode: 'events' | 'poll';\n}\n\n/**\n * Global service that multiplexes repository watching across all repos.\n *\n * Manages:\n * - Session map: one `RepositoryWatchSession` per repo path (ref-counted)\n * - Watch groups: shared `.git` watchers across worktrees\n * - Working tree watchers: per-repo filesystem watchers with gitignore filtering\n *\n * Sessions and watch groups are created lazily on first `subscribe()`\n * within a session, and destroyed when the last subscriber leaves.\n */\nexport class RepositoryWatchService implements UnifiedDisposable {\n\tprivate readonly _onDidChangeRepository = new Emitter<WatcherRepoChangeEvent>();\n\t/** Global multiplexed event for ALL watched repos */\n\tget onDidChangeRepository(): Event<WatcherRepoChangeEvent> {\n\t\treturn this._onDidChangeRepository.event;\n\t}\n\n\tprivate readonly _onDidChangeWorkingTree = new Emitter<string>();\n\t/** Global multiplexed working-tree change event (repo path) for ALL watched repos. Fires once per\n\t * debounced flush per session — the single point that drives the cache's status clock for working-tree edits. */\n\tget onDidChangeWorkingTree(): Event<string> {\n\t\treturn this._onDidChangeWorkingTree.event;\n\t}\n\n\tprivate readonly _onDidChangeFetchHead = new Emitter<string>();\n\t/**\n\t * Global FETCH_HEAD change event (repo path) for ALL watched repos, fired as each change is seen (no debounce).\n\t * FETCH_HEAD maps to no {@link RepositoryChange} on the session, so this is how a host invalidates its cached\n\t * last-fetched time — the standalone service bridges it to the cache's `lastFetched`.\n\t */\n\tget onDidChangeFetchHead(): Event<string> {\n\t\treturn this._onDidChangeFetchHead.event;\n\t}\n\n\tprivate readonly _onDidChangeWatchMode = new Emitter<{ repoPath: string; mode: 'events' | 'poll' }>();\n\t/**\n\t * Fires as a watched repository starts being polled in place of a watcher (see {@link getWatchMode}), and with\n\t * `'events'` as it is watched natively again. Never fires as the repository stops being watched; one watched again\n\t * later starts from `'events'`.\n\t */\n\tget onDidChangeWatchMode(): Event<{ repoPath: string; mode: 'events' | 'poll' }> {\n\t\treturn this._onDidChangeWatchMode.event;\n\t}\n\n\tprivate readonly defaultRepoDelayMs?: number;\n\tprivate readonly defaultWorkingTreeDelayMs?: number;\n\tprivate readonly defaultRepoMaxWaitMs?: number;\n\tprivate readonly defaultWorkingTreeMaxWaitMs?: number;\n\tprivate readonly getIgnoreFilter?: (repoPath: string, gitDirPath: string) => GitIgnoreFilter | undefined;\n\tprivate readonly getIgnoredDirectories?: (repoPath: string) => Promise<readonly string[]>;\n\tprivate readonly provider: FileWatchingProvider;\n\tprivate readonly poller: GitDirPoller | undefined;\n\tprivate readonly readFile: ((path: string) => Promise<Uint8Array>) | undefined;\n\n\tprivate readonly sessionMap = new Map<string, SessionRecord>();\n\tprivate readonly watchGroups = new Map<string, WatchGroup>();\n\tprivate readonly wtWatchers = new Map<string, WorkingTreeWatcherEntry>();\n\tprivate _disposed = false;\n\t/** Global suspend state (driven by window focus); inherited by sessions created via {@link watch}. */\n\tprivate _suspended = false;\n\n\tconstructor(options: WatchServiceOptions) {\n\t\tthis.provider = options.watchingProvider;\n\t\tthis.defaultRepoDelayMs = options.defaultRepoDelayMs;\n\t\tthis.defaultWorkingTreeDelayMs = options.defaultWorkingTreeDelayMs;\n\t\tthis.defaultRepoMaxWaitMs = options.defaultRepoMaxWaitMs;\n\t\tthis.defaultWorkingTreeMaxWaitMs = options.defaultWorkingTreeMaxWaitMs;\n\t\tthis.getIgnoreFilter = options.getIgnoreFilter;\n\t\tthis.getIgnoredDirectories = options.getIgnoredDirectories;\n\t\tthis.readFile = options.readFile;\n\t\tif (options.polling != null) {\n\t\t\tthis.poller = new GitDirPoller(options.polling);\n\t\t}\n\t}\n\n\t[Symbol.dispose](): void {\n\t\tthis.dispose();\n\t}\n\n\tdispose(): void {\n\t\tif (this._disposed) return;\n\n\t\tthis._disposed = true;\n\n\t\tfor (const record of this.sessionMap.values()) {\n\t\t\tthis.disposeSessionRecord(record);\n\t\t}\n\t\tthis.sessionMap.clear();\n\n\t\tfor (const group of this.watchGroups.values()) {\n\t\t\tgroup.dispose();\n\t\t}\n\t\tthis.watchGroups.clear();\n\n\t\tfor (const repoPath of [...this.wtWatchers.keys()]) {\n\t\t\tthis.stopWorkingTreeWatcher(repoPath);\n\t\t}\n\n\t\tthis.poller?.dispose();\n\n\t\tthis._onDidChangeRepository.dispose();\n\t\tthis._onDidChangeWorkingTree.dispose();\n\t\tthis._onDidChangeFetchHead.dispose();\n\t\tthis._onDidChangeWatchMode.dispose();\n\t}\n\n\t/**\n\t * Start watching a repository. Reference-counted:\n\t * same repoPath → same session (refCount++).\n\t * Worktrees sharing a .git dir share the WatchGroup.\n\t *\n\t * Returns a handle with the session and a dispose function.\n\t * Disposing the handle decrements the ref count.\n\t *\n\t * `repoPath` is keyed by {@link getRepositoryKey}, and every session, event and hook carries that key — the\n\t * same one the cache fences status reads on — so a host passing `C:\\repo\\` and one passing `c:/repo` share\n\t * a session and advance the same status clock.\n\t */\n\twatch(repoPath: string, gitDir: GitDir, watchHooks?: WatchHooks): WatchHandle | undefined {\n\t\tif (this._disposed) return undefined;\n\n\t\trepoPath = getRepositoryKey(repoPath);\n\n\t\tlet record = this.sessionMap.get(repoPath);\n\t\tif (record != null) {\n\t\t\trecord.refCount++;\n\t\t\tif (watchHooks != null) {\n\t\t\t\trecord.watchHooks.add(watchHooks);\n\t\t\t}\n\t\t} else {\n\t\t\tconst session = new RepositoryWatchSession({\n\t\t\t\trepoPath: repoPath,\n\t\t\t\tdefaultRepoDelayMs: this.defaultRepoDelayMs,\n\t\t\t\tdefaultWorkingTreeDelayMs: this.defaultWorkingTreeDelayMs,\n\t\t\t\tdefaultRepoMaxWaitMs: this.defaultRepoMaxWaitMs,\n\t\t\t\tdefaultWorkingTreeMaxWaitMs: this.defaultWorkingTreeMaxWaitMs,\n\t\t\t\tsuspended: this._suspended,\n\t\t\t\tlifecycle: {\n\t\t\t\t\tonFirstRepoSubscriber: (): void => {\n\t\t\t\t\t\tconst rec = this.sessionMap.get(repoPath);\n\t\t\t\t\t\tif (rec != null) {\n\t\t\t\t\t\t\tthis.connectToWatchGroup(rec);\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\tonLastRepoSubscriber: (): void => {\n\t\t\t\t\t\tconst rec = this.sessionMap.get(repoPath);\n\t\t\t\t\t\tif (rec != null) {\n\t\t\t\t\t\t\tthis.disconnectFromWatchGroup(rec);\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\tonFirstWorkingTreeSubscriber: (): void => {\n\t\t\t\t\t\tconst rec = this.sessionMap.get(repoPath);\n\t\t\t\t\t\tif (rec != null) {\n\t\t\t\t\t\t\tthis.startWorkingTreeWatcher(rec);\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\tonLastWorkingTreeSubscriber: (): void => {\n\t\t\t\t\t\tthis.stopWorkingTreeWatcher(repoPath);\n\t\t\t\t\t\tconst rec = this.sessionMap.get(repoPath);\n\t\t\t\t\t\tif (rec != null) {\n\t\t\t\t\t\t\tthis.updateWatchMode(rec);\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tonDidFireRepoChange: (event): void => {\n\t\t\t\t\tthis._onDidChangeRepository.fire(event);\n\t\t\t\t},\n\t\t\t\tonDidFireWorkingTreeChange: (repoPath): void => {\n\t\t\t\t\tthis._onDidChangeWorkingTree.fire(repoPath);\n\t\t\t\t},\n\t\t\t});\n\n\t\t\tconst watchHooksSet = new Set<WatchHooks>();\n\t\t\tif (watchHooks != null) {\n\t\t\t\twatchHooksSet.add(watchHooks);\n\t\t\t}\n\n\t\t\trecord = {\n\t\t\t\tsession: session,\n\t\t\t\tgitDir: gitDir,\n\t\t\t\twatchHooks: watchHooksSet,\n\t\t\t\trefCount: 1,\n\t\t\t\twatchMode: 'events',\n\t\t\t};\n\t\t\tthis.sessionMap.set(repoPath, record);\n\t\t}\n\n\t\tconst sessionRef = record.session;\n\t\tlet handleDisposed = false;\n\n\t\treturn {\n\t\t\tsession: sessionRef,\n\t\t\tdispose: (): void => {\n\t\t\t\tif (handleDisposed) return;\n\n\t\t\t\thandleDisposed = true;\n\n\t\t\t\tconst rec = this.sessionMap.get(repoPath);\n\t\t\t\tif (rec == null) return;\n\n\t\t\t\trec.refCount--;\n\t\t\t\tif (watchHooks != null) {\n\t\t\t\t\trec.watchHooks.delete(watchHooks);\n\t\t\t\t}\n\t\t\t\tif (rec.refCount <= 0) {\n\t\t\t\t\tthis.sessionMap.delete(repoPath);\n\t\t\t\t\tthis.disposeSessionRecord(rec);\n\t\t\t\t}\n\t\t\t},\n\t\t};\n\t}\n\n\t/**\n\t * Core announced one of its own writes for `repoPath`; the session watching it fires the announcement in place of\n\t * the write's own `.git` events (see {@link RepositoryWatchSession.noteSelfWrite}); `refs` are the full ref names it\n\t * moved, when it knows them, and `options.configConfinedToRefs` confines its `config` change to the sections of the\n\t * local branches among them (see `GitServiceHooks.repository.onChanged`). A path no session watches is ignored.\n\t */\n\tnoteSelfWrite(\n\t\trepoPath: string,\n\t\tchanges: readonly RepositoryChange[],\n\t\trefs?: readonly string[],\n\t\toptions?: { readonly configConfinedToRefs?: boolean },\n\t): void {\n\t\tif (this._disposed) return;\n\n\t\tthis.sessionMap.get(getRepositoryKey(repoPath))?.session.noteSelfWrite(changes, refs, options);\n\t}\n\n\t/** Get an existing session by repo path */\n\tgetSession(repoPath: string): RepositoryWatchSession | undefined {\n\t\treturn this.sessionMap.get(getRepositoryKey(repoPath))?.session;\n\t}\n\n\t/**\n\t * `'poll'` while a watcher the repository is watched through — its `.git` watchers or its working tree's — can't\n\t * watch, and is polled instead; `'events'` while it is watched natively (polled beside an `unreliable` watcher\n\t * included); `undefined` when nothing watches it, which {@link onDidChangeWatchMode} doesn't announce.\n\t */\n\tgetWatchMode(repoPath: string): 'events' | 'poll' | undefined {\n\t\tconst record = this.sessionMap.get(getRepositoryKey(repoPath));\n\t\treturn record != null ? this.computeWatchMode(record) : undefined;\n\t}\n\n\t/** Resource usage retained by repository watchers. */\n\tgetResourceUsage(): ResourceUsage {\n\t\tconst usage: ResourceUsage = {\n\t\t\t'watch.sessions.count': this.sessionMap.size,\n\t\t\t'watch.groups.count': this.watchGroups.size,\n\t\t\t'watch.workingTreeWatchers.count': this.wtWatchers.size,\n\t\t};\n\t\tif (this.poller != null) {\n\t\t\tusage['watch.pollTargets.count'] = this.poller.size;\n\t\t}\n\t\treturn usage;\n\t}\n\n\t/** Suspend all sessions and mark the service suspended so late-joining sessions start suspended */\n\tsuspendAll(): void {\n\t\tthis._suspended = true;\n\t\tfor (const record of this.sessionMap.values()) {\n\t\t\trecord.session.suspend();\n\t\t}\n\t\tthis.poller?.suspend();\n\t}\n\n\t/** Resume all sessions (clearing the global suspend state), with optional per-session delay */\n\tresumeAll(getDelay?: (session: RepositoryWatchSession) => number): void {\n\t\tthis._suspended = false;\n\t\tfor (const record of this.sessionMap.values()) {\n\t\t\tconst delayMs = getDelay?.(record.session);\n\t\t\trecord.session.resume(delayMs);\n\t\t}\n\t\tthis.poller?.resume();\n\t}\n\n\tprivate getOrCreateWatchGroup(commonGitDir: string): WatchGroup {\n\t\tlet group = this.watchGroups.get(commonGitDir);\n\t\tif (group == null) {\n\t\t\tgroup = new WatchGroup(commonGitDir, this.provider, this.poller, this.readFile);\n\t\t\tthis.watchGroups.set(commonGitDir, group);\n\t\t}\n\t\treturn group;\n\t}\n\n\tprivate maybeDisposeWatchGroup(commonGitDir: string): void {\n\t\tconst group = this.watchGroups.get(commonGitDir);\n\t\tif (group == null) return;\n\n\t\tif (group.sessions.size === 0) {\n\t\t\tgroup.dispose();\n\t\t\tthis.watchGroups.delete(commonGitDir);\n\t\t}\n\t}\n\n\tprivate connectToWatchGroup(record: SessionRecord): void {\n\t\tconst { session, gitDir, watchHooks } = record;\n\t\tconst commonGitDir = (gitDir.commonUri ?? gitDir.uri).fsPath;\n\t\tconst group = this.getOrCreateWatchGroup(commonGitDir);\n\n\t\tconst hooks: WatchGroupHooks = {\n\t\t\tonRepoChanged: (\n\t\t\t\t_repoPath: string,\n\t\t\t\tchanges: RepositoryChange[],\n\t\t\t\tref?: string | false,\n\t\t\t\toverflow?: boolean,\n\t\t\t\tconfigBranches?: ReadonlySet<string>,\n\t\t\t): void => {\n\t\t\t\tif (overflow) {\n\t\t\t\t\tsession.pushUnfencedRepoChanges(changes);\n\t\t\t\t} else {\n\t\t\t\t\tsession.pushRepoChanges(changes, ref, configBranches);\n\t\t\t\t}\n\n\t\t\t\t// A working tree nothing watches is read again whenever the index or HEAD says it may have moved\n\t\t\t\tif (\n\t\t\t\t\tthis.poller != null &&\n\t\t\t\t\t(changes.includes('index') || changes.includes('head')) &&\n\t\t\t\t\tthis.wtWatchers.get(session.repoPath)?.reliability === 'unwatchable'\n\t\t\t\t) {\n\t\t\t\t\tsession.pushWorkingTreeChanges([], true);\n\t\t\t\t}\n\t\t\t},\n\t\t\tonFetchHeadChanged: (repoPath: string): void => {\n\t\t\t\t// Global first, so a host's cache drops its last-fetched time before any hook re-reads it\n\t\t\t\tthis._onDidChangeFetchHead.fire(repoPath);\n\t\t\t\t// Iterate at fire time so callers that joined after the group was wired up\n\t\t\t\t// still receive events (e.g. a `Repository.watch()` whose hooks landed\n\t\t\t\t// after a hookless `GitRepositoryService.watch` registered first).\n\t\t\t\tfor (const h of watchHooks) {\n\t\t\t\t\th.onFetchHeadChanged?.(repoPath);\n\t\t\t\t}\n\t\t\t},\n\t\t\tonIgnoresChanged: (repoPath: string): void => {\n\t\t\t\tthis.refreshWorkingTreeFilter(repoPath);\n\t\t\t\tfor (const h of watchHooks) {\n\t\t\t\t\th.onIgnoresChanged?.(repoPath);\n\t\t\t\t}\n\t\t\t},\n\t\t\tgetProbeRefs: (): Iterable<string> => {\n\t\t\t\tconst refs = session.getRelevantRefs();\n\t\t\t\tfor (const ref of this.poller?.options.getRefs?.(session.repoPath) ?? []) {\n\t\t\t\t\trefs.add(ref);\n\t\t\t\t}\n\t\t\t\treturn refs;\n\t\t\t},\n\t\t\tonUnwatchableChanged: (): void => this.updateWatchMode(record),\n\t\t};\n\n\t\tgroup.addSession(session.repoPath, gitDir, hooks);\n\t\tthis.updateWatchMode(record);\n\t}\n\n\tprivate disconnectFromWatchGroup(record: SessionRecord): void {\n\t\tconst { session, gitDir } = record;\n\t\tconst commonGitDir = (gitDir.commonUri ?? gitDir.uri).fsPath;\n\t\tconst group = this.watchGroups.get(commonGitDir);\n\t\tif (group == null) return;\n\n\t\tgroup.removeSession(session.repoPath);\n\t\tthis.maybeDisposeWatchGroup(commonGitDir);\n\t\tthis.updateWatchMode(record);\n\t}\n\n\tprivate computeWatchMode(record: SessionRecord): 'events' | 'poll' | undefined {\n\t\tconst repoPath = record.session.repoPath;\n\t\tconst group = this.watchGroups.get((record.gitDir.commonUri ?? record.gitDir.uri).fsPath);\n\t\tconst connected = group?.sessions.has(repoPath) ?? false;\n\t\tconst wt = this.wtWatchers.get(repoPath);\n\t\tif (!connected && wt == null) return undefined;\n\n\t\treturn group?.isUnwatchable(repoPath) || wt?.reliability === 'unwatchable' ? 'poll' : 'events';\n\t}\n\n\tprivate updateWatchMode(record: SessionRecord): void {\n\t\tif (this._disposed) return;\n\n\t\tconst mode = this.computeWatchMode(record);\n\t\tif (mode == null) {\n\t\t\trecord.watchMode = 'events';\n\t\t\treturn;\n\t\t}\n\t\tif (mode === record.watchMode) return;\n\n\t\trecord.watchMode = mode;\n\t\tthis._onDidChangeWatchMode.fire({ repoPath: record.session.repoPath, mode: mode });\n\t}\n\n\tprivate startWorkingTreeWatcher(record: SessionRecord): void {\n\t\tconst { session, gitDir, watchHooks } = record;\n\t\tconst repoPath = session.repoPath;\n\t\tif (this.wtWatchers.has(repoPath)) return;\n\n\t\tconst canPrune = this.canPruneIgnoredDirectories();\n\t\t// Assigned once the first watcher exists; read by the handlers below instead of a map lookup per event\n\t\tlet entry: WorkingTreeWatcherEntry | undefined;\n\n\t\tlet filter: GitIgnoreFilter | undefined;\n\t\tif (this.getIgnoreFilter != null) {\n\t\t\tfilter = this.getIgnoreFilter(repoPath, gitDir.uri.fsPath);\n\t\t}\n\n\t\t// Buffer events while the gitignore filter loads, then replay\n\t\tlet filterReady = filter == null;\n\t\tlet buffered: string[] | undefined = filter != null ? [] : undefined;\n\t\tlet bufferIncomplete = false;\n\n\t\tif (filter != null) {\n\t\t\tvoid filter\n\t\t\t\t.ready()\n\t\t\t\t.then(() => {\n\t\t\t\t\tfilterReady = true;\n\t\t\t\t\tif (buffered?.length) {\n\t\t\t\t\t\tconst paths = filterWorkingTreePaths(buffered, repoPath, filter);\n\t\t\t\t\t\tif (paths.length > 0 || bufferIncomplete) {\n\t\t\t\t\t\t\tsession.pushWorkingTreeChanges(paths, bufferIncomplete);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbuffered = undefined;\n\t\t\t\t\tbufferIncomplete = false;\n\t\t\t\t})\n\t\t\t\t.catch(() => {\n\t\t\t\t\tfilterReady = true;\n\t\t\t\t\tbuffered = undefined;\n\t\t\t\t});\n\t\t}\n\n\t\t// An ignore rule changed (or may have): refresh the filter and the pruned set, and notify the host\n\t\tconst onIgnoreRulesChanged = (): void => {\n\t\t\tif (filter != null) {\n\t\t\t\tvoid filter.refresh();\n\t\t\t}\n\t\t\tif (entry != null) {\n\t\t\t\tthis.scheduleIgnoredDirectoriesRefresh(entry);\n\t\t\t}\n\t\t\t// See `connectToWatchGroup` for the iterate-at-fire-time rationale.\n\t\t\tfor (const h of watchHooks) {\n\t\t\t\th.onGitIgnoreChanged?.(repoPath);\n\t\t\t}\n\t\t\t// Route the ignore-rule change through the repo channel as an `ignores` change (like `info/exclude`),\n\t\t\t// so it reaches EVERY session subscriber — including a closed secondary worktree's `watch()` `repoSub`,\n\t\t\t// which has no `onGitIgnoreChanged` hook and would otherwise keep a stale untracked-file list. Coalesces\n\t\t\t// with any hook-fired `ignores` above, so open repos don't double. Unfenced: it comes from the working tree,\n\t\t\t// which a self-write never holds back.\n\t\t\tsession.pushUnfencedRepoChanges(['ignores']);\n\t\t};\n\n\t\tconst onEvent = (event: FileWatchEvent): void => {\n\t\t\t// Dropped events: nothing to filter or buffer, the whole tree needs a re-read. A `.gitignore` edit may be\n\t\t\t// among them, and a filter left on the old rules would keep dropping changes they used to ignore\n\t\t\tif (event.reason === 'overflow') {\n\t\t\t\tsession.pushWorkingTreeChanges([], true);\n\t\t\t\tonIgnoreRulesChanged();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Fast noise check (node_modules, .git, .watchman-cookie-)\n\t\t\tif (shouldIgnoreWorkingTreePath(event.path)) return;\n\n\t\t\t// Detect .gitignore changes (nested ones too, unless git skips them for sitting in an ignored directory).\n\t\t\t// The root .gitignore is only an ignore change; a nested one is also an ordinary working-tree change.\n\t\t\tconst relativePath = isDescendant(event.path, repoPath) ? relative(repoPath, event.path) : undefined;\n\t\t\tif (relativePath === '.gitignore') {\n\t\t\t\tonIgnoreRulesChanged();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (relativePath?.endsWith('/.gitignore') && !(filterReady && filter?.isIgnored(relativePath))) {\n\t\t\t\tonIgnoreRulesChanged();\n\t\t\t}\n\n\t\t\tif (!filterReady) {\n\t\t\t\t// Buffer until the gitignore filter is ready. Capped: `ready()` awaits a `core.excludesFile`\n\t\t\t\t// read, which queues behind every other git command, so this window can stay open for a long\n\t\t\t\t// time — and these paths haven't been gitignore-filtered yet, so a build's output all lands\n\t\t\t\t// here. Past the cap, stop retaining and mark the batch partial rather than growing.\n\t\t\t\tif (buffered != null && buffered.length >= maxBufferedWorkingTreePaths) {\n\t\t\t\t\tbufferIncomplete = true;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tbuffered?.push(event.path);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Gitignore filter (sync after ready)\n\t\t\tif (filter != null && relativePath != null && filter.isIgnored(relativePath)) {\n\t\t\t\tif (canPrune && entry != null) {\n\t\t\t\t\tthis.onIgnoredEvent(entry, filter, relativePath);\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tsession.pushWorkingTreeChanges([event.path]);\n\t\t};\n\n\t\tconst created = this.createWorkingTreeWatcher(repoPath, onEvent, workingTreeExcludes);\n\t\tentry = {\n\t\t\tsession: session,\n\t\t\tonEvent: onEvent,\n\t\t\tfilter: filter,\n\t\t\twatcher: created.watcher,\n\t\t\tignoredDirectories: [],\n\t\t\tgeneration: 0,\n\t\t\treading: false,\n\t\t\trereadQueued: false,\n\t\t\treliability: 'reliable',\n\t\t};\n\t\tthis.wtWatchers.set(repoPath, entry);\n\t\tthis.setWorkingTreeReliability(entry, created.reliability, true);\n\n\t\tif (canPrune) {\n\t\t\tvoid this.refreshIgnoredDirectories(entry, repoPath);\n\t\t}\n\t}\n\n\tprivate stopWorkingTreeWatcher(repoPath: string): void {\n\t\tconst entry = this.wtWatchers.get(repoPath);\n\t\tif (entry == null) return;\n\n\t\t// Drops any read still in flight, and any refresh waiting on its debounce\n\t\tentry.generation++;\n\t\tclearTimeout(entry.refreshTimer);\n\t\tentry.refreshTimer = undefined;\n\t\tentry.watcher.dispose();\n\t\tif (entry.pollTarget != null) {\n\t\t\tthis.poller?.delete(entry.pollTarget);\n\t\t\tentry.pollTarget = undefined;\n\t\t}\n\t\tthis.wtWatchers.delete(repoPath);\n\t}\n\n\t/** Creates a working-tree watcher, with the reliability it reported before returning */\n\tprivate createWorkingTreeWatcher(\n\t\trepoPath: string,\n\t\tonEvent: (event: FileWatchEvent) => void,\n\t\texcludes: readonly string[],\n\t): { watcher: FileWatcher; reliability: FileWatcherReliability } {\n\t\tif (this.poller == null) {\n\t\t\treturn {\n\t\t\t\twatcher: this.provider.createWatcher(repoPath, '**', onEvent, { excludes: excludes }),\n\t\t\t\treliability: 'reliable',\n\t\t\t};\n\t\t}\n\n\t\tlet watcher: FileWatcher | undefined;\n\t\tlet reliability: FileWatcherReliability = 'reliable';\n\t\tconst options: FileWatcherOptions = {\n\t\t\texcludes: excludes,\n\t\t\tonDidChangeReliability: state => {\n\t\t\t\tif (watcher == null) {\n\t\t\t\t\treliability = state;\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// Only the live watcher speaks for the working tree, never one already replaced\n\t\t\t\tconst entry = this.wtWatchers.get(repoPath);\n\t\t\t\tif (entry?.watcher === watcher) {\n\t\t\t\t\tthis.setWorkingTreeReliability(entry, state);\n\t\t\t\t}\n\t\t\t},\n\t\t};\n\t\twatcher = this.provider.createWatcher(repoPath, '**', onEvent, options);\n\t\treturn { watcher: watcher, reliability: reliability };\n\t}\n\n\tprivate setWorkingTreeReliability(\n\t\tentry: WorkingTreeWatcherEntry,\n\t\tstate: FileWatcherReliability,\n\t\tinitial?: boolean,\n\t): void {\n\t\tconst previous = entry.reliability;\n\t\tif (previous === state) return;\n\n\t\tentry.reliability = state;\n\t\tthis.updateWorkingTreePollTarget(entry);\n\n\t\tif ((state === 'unwatchable') === (previous === 'unwatchable')) return;\n\n\t\t// The watch went down or came back: whatever changed meanwhile went unseen\n\t\tif (!initial) {\n\t\t\tentry.session.pushWorkingTreeChanges([], true);\n\t\t}\n\t\tconst record = this.sessionMap.get(entry.session.repoPath);\n\t\tif (record != null) {\n\t\t\tthis.updateWatchMode(record);\n\t\t}\n\t}\n\n\t/** Polls the host's working-tree fingerprint, when it gives one, while the watcher isn't `reliable` */\n\tprivate updateWorkingTreePollTarget(entry: WorkingTreeWatcherEntry): void {\n\t\tconst poller = this.poller;\n\t\tconst getFingerprint = poller?.options.getWorkingTreeFingerprint;\n\t\tif (poller == null || getFingerprint == null) return;\n\n\t\tif (entry.reliability === 'reliable') {\n\t\t\tif (entry.pollTarget != null) {\n\t\t\t\tpoller.delete(entry.pollTarget);\n\t\t\t\tentry.pollTarget = undefined;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t\tif (entry.pollTarget != null) return;\n\n\t\tconst repoPath = entry.session.repoPath;\n\t\tlet last: string | undefined;\n\t\tconst target: PollTarget = {\n\t\t\tpoll: async (): Promise<void> => {\n\t\t\t\tlet fingerprint: string | undefined;\n\t\t\t\ttry {\n\t\t\t\t\tfingerprint = await getFingerprint(repoPath);\n\t\t\t\t} catch {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (fingerprint == null || entry.pollTarget !== target) return;\n\n\t\t\t\tconst moved = last != null && last !== fingerprint;\n\t\t\t\tlast = fingerprint;\n\t\t\t\t// A fingerprint says the tree moved, not which files did\n\t\t\t\tif (moved) {\n\t\t\t\t\tentry.session.pushWorkingTreeChanges([], true);\n\t\t\t\t}\n\t\t\t},\n\t\t};\n\t\tentry.pollTarget = target;\n\t\tpoller.add(target);\n\t}\n\n\tprivate canPruneIgnoredDirectories(): boolean {\n\t\treturn this.provider.supportsExcludes === true && this.getIgnoredDirectories != null;\n\t}\n\n\t/**\n\t * An event the gitignore post-filter dropped. When its directory is itself ignored, the watcher is watching an\n\t * ignored directory it could prune (one created after the last read), so the ignored set is read again. An ignored\n\t * file in a watched directory (a root `.env`, `src/debug.log`) triggers nothing.\n\t */\n\tprivate onIgnoredEvent(entry: WorkingTreeWatcherEntry, filter: GitIgnoreFilter, relativePath: string): void {\n\t\t// A refresh is already pending and reads the whole set; keep a flood of these events at two field checks\n\t\tif (entry.refreshTimer != null || entry.rereadQueued) return;\n\n\t\t// The outermost ignored directory above the path — the one a read would prune. Keyed on it (not the immediate\n\t\t// parent), so a directory git won't prune costs one read however many subdirectories it has\n\t\tlet ignoredDir: string | undefined;\n\t\tfor (let slash = relativePath.indexOf('/'); slash !== -1; slash = relativePath.indexOf('/', slash + 1)) {\n\t\t\tconst dir = relativePath.slice(0, slash);\n\t\t\tif (entry.unpruned?.has(dir) || entry.unprunable?.has(dir)) return;\n\t\t\tif (filter.isIgnored(`${dir}/`)) {\n\t\t\t\tignoredDir = dir;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (ignoredDir == null) return;\n\n\t\tentry.unpruned ??= new Set();\n\t\tentry.unpruned.add(ignoredDir);\n\n\t\t// Ignore rules didn't change, so a read in flight stays valid (no generation bump), but it may predate the\n\t\t// directory\n\t\tif (entry.reading) {\n\t\t\tentry.rereadQueued = true;\n\t\t\treturn;\n\t\t}\n\n\t\tthis.armIgnoredDirectoriesRefresh(entry);\n\t}\n\n\t/** Re-reads the git-ignored directories once ignore rules stop changing (trailing debounce, one timer per repo) */\n\tprivate scheduleIgnoredDirectoriesRefresh(entry: WorkingTreeWatcherEntry): void {\n\t\tif (!this.canPruneIgnoredDirectories()) return;\n\n\t\t// A read already in flight is now stale, and a directory git wouldn't prune may be prunable now\n\t\tentry.generation++;\n\t\tentry.unprunable = undefined;\n\t\tclearTimeout(entry.refreshTimer);\n\t\tthis.armIgnoredDirectoriesRefresh(entry);\n\t}\n\n\tprivate armIgnoredDirectoriesRefresh(entry: WorkingTreeWatcherEntry): void {\n\t\tentry.refreshTimer = setTimeout(() => {\n\t\t\tentry.refreshTimer = undefined;\n\t\t\tconst repoPath = entry.session.repoPath;\n\t\t\tif (this.wtWatchers.get(repoPath) !== entry) return;\n\n\t\t\tif (entry.reading) {\n\t\t\t\tentry.rereadQueued = true;\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tvoid this.refreshIgnoredDirectories(entry, repoPath);\n\t\t}, ignoredDirectoriesRefreshDelayMs);\n\t}\n\n\t/** Single-flight: reads the git-ignored directories and, when they changed, swaps in a watcher excluding them */\n\tprivate async refreshIgnoredDirectories(entry: WorkingTreeWatcherEntry, repoPath: string): Promise<void> {\n\t\tif (this.getIgnoredDirectories == null) return;\n\n\t\tentry.reading = true;\n\t\ttry {\n\t\t\tconst generation = entry.generation;\n\t\t\t// The unpruned directories seen so far; any seen during the read wait for the next one\n\t\t\tconst unpruned = entry.unpruned?.size ? [...entry.unpruned] : undefined;\n\n\t\t\tlet directories: readonly string[] | undefined;\n\t\t\ttry {\n\t\t\t\tdirectories = await this.getIgnoredDirectories(repoPath);\n\t\t\t} catch {\n\t\t\t\t// Keep what the watcher has; the next ignore-rule change reads again\n\t\t\t\tdirectories = undefined;\n\t\t\t}\n\n\t\t\tif (entry.generation === generation) {\n\t\t\t\tif (directories != null) {\n\t\t\t\t\tthis.swapWorkingTreeWatcher(entry, repoPath, directories);\n\t\t\t\t}\n\t\t\t\tif (unpruned != null) {\n\t\t\t\t\tthis.settleUnprunedDirectories(entry, unpruned, directories);\n\t\t\t\t}\n\t\t\t}\n\t\t} finally {\n\t\t\tentry.reading = false;\n\t\t}\n\n\t\tif (entry.rereadQueued) {\n\t\t\tentry.rereadQueued = false;\n\t\t\tif (this.wtWatchers.get(repoPath) === entry) {\n\t\t\t\tvoid this.refreshIgnoredDirectories(entry, repoPath);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * A directory the read left unexcluded is one git won't prune (or the read failed), so it never triggers a read\n\t * again until ignore rules change — otherwise each of its events would spawn git.\n\t */\n\tprivate settleUnprunedDirectories(\n\t\tentry: WorkingTreeWatcherEntry,\n\t\tunpruned: readonly string[],\n\t\tdirectories: readonly string[] | undefined,\n\t): void {\n\t\tfor (const dir of unpruned) {\n\t\t\tentry.unpruned?.delete(dir);\n\t\t\tif (directories?.some(d => d === dir || dir.startsWith(`${d}/`))) continue;\n\n\t\t\tentry.unprunable ??= new Set();\n\t\t\tentry.unprunable.add(dir);\n\t\t}\n\t}\n\n\tprivate swapWorkingTreeWatcher(\n\t\tentry: WorkingTreeWatcherEntry,\n\t\trepoPath: string,\n\t\tdirectories: readonly string[],\n\t): void {\n\t\tconst previous = entry.ignoredDirectories;\n\t\tif (previous.length === directories.length && previous.every(d => directories.includes(d))) return;\n\n\t\t// New first, then the old one: the overlap can repeat an event (the session coalesces them), but never loses one\n\t\tlet created: { watcher: FileWatcher; reliability: FileWatcherReliability };\n\t\ttry {\n\t\t\tcreated = this.createWorkingTreeWatcher(repoPath, entry.onEvent, [...workingTreeExcludes, ...directories]);\n\t\t} catch {\n\t\t\treturn;\n\t\t}\n\n\t\tconst previousWatcher = entry.watcher;\n\t\tentry.watcher = created.watcher;\n\t\tentry.ignoredDirectories = directories;\n\t\tpreviousWatcher.dispose();\n\t\tthis.setWorkingTreeReliability(entry, created.reliability);\n\n\t\t// Files in a directory that is watched again changed while nothing was looking. A directory still covered by\n\t\t// a surviving ancestor entry isn't watched again.\n\t\tif (previous.some(d => !directories.some(n => n === d || d.startsWith(`${n}/`)))) {\n\t\t\tentry.session.pushWorkingTreeChanges([], true);\n\t\t}\n\t}\n\n\tprivate refreshWorkingTreeFilter(repoPath: string): void {\n\t\tconst entry = this.wtWatchers.get(repoPath);\n\t\tif (entry == null) return;\n\n\t\tif (entry.filter != null) {\n\t\t\tvoid entry.filter.refresh();\n\t\t}\n\t\tthis.scheduleIgnoredDirectoriesRefresh(entry);\n\t}\n\n\tprivate disposeSessionRecord(record: SessionRecord): void {\n\t\t// The working tree first: with it still up, leaving the group would read as a move back to `'events'` and\n\t\t// announce one for a repository that is going away\n\t\tthis.stopWorkingTreeWatcher(record.session.repoPath);\n\t\tthis.disconnectFromWatchGroup(record);\n\t\tthis.updateWatchMode(record);\n\t\trecord.session.dispose();\n\t}\n}\n\nfunction filterWorkingTreePaths(absolutePaths: string[], repoPath: string, filter: GitIgnoreFilter): string[] {\n\tconst result: string[] = [];\n\tfor (const p of absolutePaths) {\n\t\tif (!isDescendant(p, repoPath)) continue;\n\n\t\tconst relativePath = relative(repoPath, p);\n\t\tif (!filter.isIgnored(relativePath)) {\n\t\t\tresult.push(p);\n\t\t}\n\t}\n\treturn result;\n}\n"],"mappings":"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"}
|
|
@@ -75,10 +75,15 @@ export declare class RepositoryWatchSession implements UnifiedDisposable {
|
|
|
75
75
|
/** Kinds among {@link pendingRepoChanges} injected or pushed unfenced, which an announcement never replaces */
|
|
76
76
|
private readonly pendingRepoUnfencedChanges;
|
|
77
77
|
/**
|
|
78
|
-
* Kinds among {@link pendingRepoChanges} that only core's announced writes
|
|
79
|
-
*
|
|
78
|
+
* Kinds among {@link pendingRepoChanges} that only core's announced writes, and their own echoes, reported (see
|
|
79
|
+
* {@link noteSelfWrite})
|
|
80
80
|
*/
|
|
81
|
-
private readonly
|
|
81
|
+
private readonly pendingRepoAnnounced;
|
|
82
|
+
/**
|
|
83
|
+
* For a pending `config` change, the branches (full ref names) whose sections are all that every report of it
|
|
84
|
+
* changed; `undefined` once any report may have changed anything else
|
|
85
|
+
*/
|
|
86
|
+
private pendingRepoConfigBranches;
|
|
82
87
|
private readonly repoClock;
|
|
83
88
|
private effectiveRepoMs;
|
|
84
89
|
private effectiveRepoMaxWaitMs;
|
|
@@ -132,7 +137,9 @@ export declare class RepositoryWatchSession implements UnifiedDisposable {
|
|
|
132
137
|
* return the subscriber's current list. A `heads`/`remotes`/`tags`/`stash` change is delivered only when a listed
|
|
133
138
|
* ref of that kind may have moved, when the change isn't attributable to a name (e.g. a `packed-refs` rewrite),
|
|
134
139
|
* or, for `heads`, when a pseudo-ref (`HEAD`, `ORIG_HEAD`) moved — so `stash` and `tags` changes are dropped unless
|
|
135
|
-
* a ref of that kind is listed.
|
|
140
|
+
* a ref of that kind is listed. A `config` change confined to some branches' `branch.<name>` sections
|
|
141
|
+
* (`configBranches`) is delivered only when `HEAD` or one of those branches is listed. Other change kinds always
|
|
142
|
+
* pass, and an event left with no changes isn't delivered.
|
|
136
143
|
* The filter never affects other subscribers, the debounce, or the global `onDidFireRepoChange` forwarding.
|
|
137
144
|
*/
|
|
138
145
|
subscribe(opts?: {
|
|
@@ -159,9 +166,11 @@ export declare class RepositoryWatchSession implements UnifiedDisposable {
|
|
|
159
166
|
/**
|
|
160
167
|
* Push interpreted repo changes into the pipeline. `ref` is the full ref name (or ref directory, or pseudo-ref) whose
|
|
161
168
|
* 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.
|
|
169
|
+
* `false` preserves cache invalidations for a config edit without claiming any ref moved. `configBranches`, for a
|
|
170
|
+
* `config` among `changes`, are the local branches (full ref names) whose `branch.<name>` sections are all that
|
|
171
|
+
* changed in `.git/config`; without it, the config change may be anywhere.
|
|
163
172
|
*/
|
|
164
|
-
pushRepoChanges(changes: RepositoryChange[], ref?: string | false): void;
|
|
173
|
+
pushRepoChanges(changes: RepositoryChange[], ref?: string | false, configBranches?: ReadonlySet<string>): void;
|
|
165
174
|
/**
|
|
166
175
|
* Push watcher-observed repo changes that no write of core's can account for — an overflow's rescan, or an ignore
|
|
167
176
|
* rule changed in the working tree — so {@link noteSelfWrite} never replaces them or takes them for its echo. Ref
|
|
@@ -172,24 +181,36 @@ export declare class RepositoryWatchSession implements UnifiedDisposable {
|
|
|
172
181
|
* Core announced one of its own writes for this repository: its caches were evicted and its host notified, both
|
|
173
182
|
* after the write finished. The announcement becomes this session's one event for the write:
|
|
174
183
|
*
|
|
175
|
-
* -
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
* announced
|
|
182
|
-
*
|
|
184
|
+
* - the announced kinds are pushed through the debounce and suspend like {@link fireChange}, and the event carries
|
|
185
|
+
* them in `announced`. A ref kind is unnamed unless `refs` names refs of that kind (the full ref names the write
|
|
186
|
+
* moved), in which case it carries those names and no other ref of the kind counts as changed by the write.
|
|
187
|
+
* `config` is confined to the `branch.<name>` sections of the local branches `refs` names when
|
|
188
|
+
* `options.configConfinedToRefs` says so, and the event carries them (`configBranches`);
|
|
189
|
+
* - a raw change of an announced kind is the write's own report of it only when it is provably within what the
|
|
190
|
+
* write announced: a ref the write named, or a config change confined to branches the write's config was confined
|
|
191
|
+
* to. Pending when the announcement arrives, it is replaced. Arriving later, it merges and the kind stays
|
|
192
|
+
* announced only within the repo debounce delay of the announcement; a window can stay pending for minutes while
|
|
193
|
+
* the session is suspended, and a later move of the same ref is as likely an outside one. Any other report of
|
|
194
|
+
* the kind is an outside change: it stays, and the kind isn't `announced`, since the host has not handled the
|
|
195
|
+
* outside part. A write never names `HEAD`, but a commit locks it to write its reflog, so under a write that
|
|
196
|
+
* announced `head`, a report of the `HEAD` file within the same delay is the write's echo, its `heads` part
|
|
197
|
+
* included;
|
|
198
|
+
* - a kind the write announced open (unnamed, or `config` unconfined) covers any change of the kind: raw changes
|
|
199
|
+
* of it still pending are replaced, since the host evicts everything of the kind after the write. One arriving
|
|
200
|
+
* within the repo debounce delay of the announcement, before its event fires, is taken for the write's own late
|
|
201
|
+
* echo; any later report of the kind before the event fires clears the mark.
|
|
183
202
|
*
|
|
184
|
-
*
|
|
185
|
-
* (a duplicate refresh), rather than risk losing an outside change of the same kind.
|
|
203
|
+
* Once the event fires, nothing is special: an echo the watcher delivers after it causes one extra, unannounced
|
|
204
|
+
* event (a duplicate refresh, narrowed like any other), rather than risk losing an outside change of the same kind.
|
|
186
205
|
*
|
|
187
206
|
* Empty `changes`, or `'unknown'`, announces every kind a watcher can report (never `'unknown'` itself, which a
|
|
188
|
-
* cache reads as a close); `'closed'` and `'opened'` are ignored. Only raw `.git` changes
|
|
189
|
-
* announced: {@link fireChange}, {@link fireChangeImmediate}, {@link pushUnfencedRepoChanges}
|
|
190
|
-
* changes always pass.
|
|
207
|
+
* cache reads as a close), open whatever `refs` says; `'closed'` and `'opened'` are ignored. Only raw `.git` changes
|
|
208
|
+
* are replaced or kept announced: {@link fireChange}, {@link fireChangeImmediate}, {@link pushUnfencedRepoChanges}
|
|
209
|
+
* and working-tree changes always pass.
|
|
191
210
|
*/
|
|
192
|
-
noteSelfWrite(changes: readonly RepositoryChange[]
|
|
211
|
+
noteSelfWrite(changes: readonly RepositoryChange[], refs?: readonly string[], options?: {
|
|
212
|
+
readonly configConfinedToRefs?: boolean;
|
|
213
|
+
}): void;
|
|
193
214
|
/** Push working tree file changes into the pipeline */
|
|
194
215
|
/** `incomplete` marks the batch as partial when the caller already dropped paths of its own. */
|
|
195
216
|
pushWorkingTreeChanges(paths: Iterable<string>, incomplete?: boolean): void;
|
|
@@ -202,9 +223,19 @@ export declare class RepositoryWatchSession implements UnifiedDisposable {
|
|
|
202
223
|
/**
|
|
203
224
|
* `origin` is a raw `.git` event, a push no announcement replaces ({@link fireChange},
|
|
204
225
|
* {@link pushUnfencedRepoChanges}), or core's announced write. A kind stays announced only while nothing else
|
|
205
|
-
* reports it in the pending window, save the write's own
|
|
226
|
+
* reports it in the pending window, save the write's own report of it (see {@link noteSelfWrite}).
|
|
227
|
+
* `configBranches` confines a `config` among `changes` to those branches' sections; without it, it may be anywhere.
|
|
206
228
|
*/
|
|
207
229
|
private coalesceRepo;
|
|
230
|
+
/**
|
|
231
|
+
* Whether a raw report of `change`, confined to `names` (`undefined`: not confined), is the announced write's own.
|
|
232
|
+
* It must arrive within the repo debounce delay of the announcement, as it always had to. An open announcement
|
|
233
|
+
* asks nothing more; one confined to names also needs every name the report carries to be one the write announced,
|
|
234
|
+
* save the `heads` part of a `HEAD` report under a write that announced `head` in time, which is a commit's echo.
|
|
235
|
+
*/
|
|
236
|
+
private isOwnReport;
|
|
237
|
+
/** Whether a pending report of `kind` remains, unnamed or under a name */
|
|
238
|
+
private hasPendingRefOfKind;
|
|
208
239
|
private clearPendingRepoChanges;
|
|
209
240
|
private flushWorkingTree;
|
|
210
241
|
private coalesceWorkingTrees;
|