@gitkraken/core-gitlens 0.12.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +78 -1
- package/dist/git/cache.d.ts +84 -29
- package/dist/git/cache.js +188 -54
- package/dist/git/cache.js.map +1 -1
- package/dist/git/features.d.ts +1 -1
- package/dist/git/features.js +5 -0
- package/dist/git/features.js.map +1 -1
- package/dist/git/models/branch.d.ts +20 -2
- package/dist/git/models/branch.js +28 -6
- package/dist/git/models/branch.js.map +1 -1
- package/dist/git/models/reference.d.ts +3 -0
- package/dist/git/models/status.js +2 -2
- package/dist/git/models/worktree.d.ts +4 -1
- package/dist/git/models/worktree.js +4 -2
- package/dist/git/models/worktree.js.map +1 -1
- package/dist/git/parsers/diffParser.js +1 -1
- package/dist/git/providers/branches.d.ts +73 -8
- package/dist/git/providers/operations.d.ts +3 -2
- package/dist/git/providers/pausedOperations.d.ts +6 -0
- package/dist/git/providers/provider.d.ts +45 -2
- package/dist/git/providers/revision.d.ts +17 -3
- package/dist/git/providers/status.d.ts +8 -2
- package/dist/git/remotes/azure-devops.js +61 -53
- package/dist/git/remotes/azure-devops.js.map +1 -1
- package/dist/git/run.types.d.ts +12 -0
- package/dist/git/service.d.ts +8 -5
- package/dist/git/service.js +16 -8
- package/dist/git/service.js.map +1 -1
- package/dist/git/utils/branch.utils.d.ts +12 -2
- package/dist/git/utils/branch.utils.js +49 -5
- package/dist/git/utils/branch.utils.js.map +1 -1
- package/dist/git/utils/reference.utils.d.ts +1 -0
- package/dist/git/utils/reference.utils.js.map +1 -1
- package/dist/git/watching/changeEvent.d.ts +13 -2
- package/dist/git/watching/changeEvent.js +17 -3
- package/dist/git/watching/changeEvent.js.map +1 -1
- package/dist/git/watching/gitDirPoller.d.ts +111 -0
- package/dist/git/watching/gitDirPoller.js +266 -0
- package/dist/git/watching/gitDirPoller.js.map +1 -0
- package/dist/git/watching/provider.d.ts +16 -0
- package/dist/git/watching/watchGroup.d.ts +40 -4
- package/dist/git/watching/watchGroup.js +143 -17
- package/dist/git/watching/watchGroup.js.map +1 -1
- package/dist/git/watching/watchService.d.ts +70 -1
- package/dist/git/watching/watchService.js +158 -13
- package/dist/git/watching/watchService.js.map +1 -1
- package/dist/git/watching/watchSession.d.ts +44 -0
- package/dist/git/watching/watchSession.js +91 -6
- package/dist/git/watching/watchSession.js.map +1 -1
- package/dist/git/watching/watcherPatterns.d.ts +21 -0
- package/dist/git/watching/watcherPatterns.js +67 -1
- package/dist/git/watching/watcherPatterns.js.map +1 -1
- package/dist/git/watching/worktreeSummary.d.ts +34 -7
- package/dist/git/watching/worktreeSummary.js +39 -12
- package/dist/git/watching/worktreeSummary.js.map +1 -1
- package/dist/git-cli/cliGitProvider.d.ts +40 -2
- package/dist/git-cli/cliGitProvider.js +182 -18
- package/dist/git-cli/cliGitProvider.js.map +1 -1
- package/dist/git-cli/exec/commandClassifier.d.ts +15 -0
- package/dist/git-cli/exec/commandClassifier.js +48 -2
- package/dist/git-cli/exec/commandClassifier.js.map +1 -1
- package/dist/git-cli/exec/git.d.ts +20 -3
- package/dist/git-cli/exec/git.js +63 -19
- package/dist/git-cli/exec/git.js.map +1 -1
- package/dist/git-cli/exec/gitQueue.d.ts +2 -0
- package/dist/git-cli/exec/gitQueue.js +2 -1
- package/dist/git-cli/exec/gitQueue.js.map +1 -1
- package/dist/git-cli/exec/indexLock.d.ts +39 -0
- package/dist/git-cli/exec/indexLock.js +160 -0
- package/dist/git-cli/exec/indexLock.js.map +1 -0
- package/dist/git-cli/parsers/blameParser.js +2 -2
- package/dist/git-cli/parsers/refParser.js +4 -2
- package/dist/git-cli/parsers/refParser.js.map +1 -1
- package/dist/git-cli/parsers/statusParser.d.ts +6 -0
- package/dist/git-cli/parsers/statusParser.js +33 -3
- package/dist/git-cli/parsers/statusParser.js.map +1 -1
- package/dist/git-cli/parsers/worktreeParser.js +42 -2
- package/dist/git-cli/parsers/worktreeParser.js.map +1 -1
- package/dist/git-cli/providers/branchRemote.d.ts +14 -0
- package/dist/git-cli/providers/branchRemote.js +36 -0
- package/dist/git-cli/providers/branchRemote.js.map +1 -0
- package/dist/git-cli/providers/branches.d.ts +109 -10
- package/dist/git-cli/providers/branches.js +630 -134
- package/dist/git-cli/providers/branches.js.map +1 -1
- package/dist/git-cli/providers/commits.js +2 -2
- package/dist/git-cli/providers/config.d.ts +9 -5
- package/dist/git-cli/providers/config.js +58 -25
- package/dist/git-cli/providers/config.js.map +1 -1
- package/dist/git-cli/providers/contributors.js +1 -1
- package/dist/git-cli/providers/graph.js +9 -6
- package/dist/git-cli/providers/graph.js.map +1 -1
- package/dist/git-cli/providers/operations.d.ts +17 -0
- package/dist/git-cli/providers/operations.js +172 -111
- package/dist/git-cli/providers/operations.js.map +1 -1
- package/dist/git-cli/providers/pausedOperations.d.ts +8 -0
- package/dist/git-cli/providers/pausedOperations.js +59 -39
- package/dist/git-cli/providers/pausedOperations.js.map +1 -1
- package/dist/git-cli/providers/refs.js +11 -8
- package/dist/git-cli/providers/refs.js.map +1 -1
- package/dist/git-cli/providers/remotes.js +19 -10
- package/dist/git-cli/providers/remotes.js.map +1 -1
- package/dist/git-cli/providers/revision.d.ts +6 -2
- package/dist/git-cli/providers/revision.js +38 -12
- package/dist/git-cli/providers/revision.js.map +1 -1
- package/dist/git-cli/providers/staging.js +39 -14
- package/dist/git-cli/providers/staging.js.map +1 -1
- package/dist/git-cli/providers/stash.d.ts +2 -0
- package/dist/git-cli/providers/stash.js +33 -18
- package/dist/git-cli/providers/stash.js.map +1 -1
- package/dist/git-cli/providers/status.d.ts +3 -1
- package/dist/git-cli/providers/status.js +40 -22
- package/dist/git-cli/providers/status.js.map +1 -1
- package/dist/git-cli/providers/tags.js +6 -4
- package/dist/git-cli/providers/tags.js.map +1 -1
- package/dist/git-cli/providers/worktrees.d.ts +1 -0
- package/dist/git-cli/providers/worktrees.js +60 -31
- package/dist/git-cli/providers/worktrees.js.map +1 -1
- package/dist/git-cli/service.d.ts +6 -0
- package/dist/git-cli/service.js +5 -2
- package/dist/git-cli/service.js.map +1 -1
- package/dist/plus/git-github/providers/github/revision.d.ts +6 -2
- package/dist/plus/git-github/providers/github/revision.js +15 -10
- package/dist/plus/git-github/providers/github/revision.js.map +1 -1
- package/dist/plus/git-github/providers/github/status.d.ts +3 -1
- package/dist/plus/git-github/providers/github/status.js +2 -2
- package/dist/plus/git-github/providers/github/status.js.map +1 -1
- package/dist/plus/integrations/collectionMetadata.d.ts +13 -2
- package/dist/plus/integrations/collectionMetadata.js +14 -2
- package/dist/plus/integrations/collectionMetadata.js.map +1 -1
- package/dist/plus/integrations/index.d.ts +2 -2
- package/dist/plus/integrations/index.js.map +1 -1
- package/dist/plus/integrations/integrationService.d.ts +1 -0
- package/dist/plus/integrations/integrationService.js +1 -1
- package/dist/plus/integrations/integrationService.js.map +1 -1
- package/dist/plus/integrations/manager.d.ts +1 -0
- package/dist/plus/integrations/models/gitHostIntegration.d.ts +41 -5
- package/dist/plus/integrations/models/gitHostIntegration.js +106 -29
- package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
- package/dist/plus/integrations/models/issueReads.d.ts +17 -4
- package/dist/plus/integrations/providers/apiClients.d.ts +2 -0
- package/dist/plus/integrations/providers/apiClients.js +7 -1
- package/dist/plus/integrations/providers/apiClients.js.map +1 -1
- package/dist/plus/integrations/providers/azure/azure.d.ts +5 -4
- package/dist/plus/integrations/providers/azure/azure.js +36 -76
- package/dist/plus/integrations/providers/azure/azure.js.map +1 -1
- package/dist/plus/integrations/providers/azure/azure.utils.js +20 -17
- package/dist/plus/integrations/providers/azure/azure.utils.js.map +1 -1
- package/dist/plus/integrations/providers/azure/models.d.ts +2 -2
- package/dist/plus/integrations/providers/azure/models.js +4 -4
- package/dist/plus/integrations/providers/azure/models.js.map +1 -1
- package/dist/plus/integrations/providers/azure/workItemStateCategories.d.ts +21 -12
- package/dist/plus/integrations/providers/azure/workItemStateCategories.js +74 -17
- package/dist/plus/integrations/providers/azure/workItemStateCategories.js.map +1 -1
- package/dist/plus/integrations/providers/azureDevOps.d.ts +44 -0
- package/dist/plus/integrations/providers/azureDevOps.js +108 -17
- package/dist/plus/integrations/providers/azureDevOps.js.map +1 -1
- package/dist/plus/integrations/providers/azureDevOpsServer.d.ts +45 -1
- package/dist/plus/integrations/providers/azureDevOpsServer.js +121 -6
- package/dist/plus/integrations/providers/azureDevOpsServer.js.map +1 -1
- package/dist/plus/integrations/providers/jira-server.js +18 -14
- package/dist/plus/integrations/providers/jira-server.js.map +1 -1
- package/dist/plus/integrations/providers/jira.js +18 -14
- package/dist/plus/integrations/providers/jira.js.map +1 -1
- package/dist/plus/integrations/providers/models.d.ts +1 -2
- package/dist/plus/integrations/providers/models.js +9 -2
- package/dist/plus/integrations/providers/models.js.map +1 -1
- package/dist/plus/integrations/providers/providersApi.d.ts +7 -17
- package/dist/plus/integrations/providers/providersApi.js +85 -78
- package/dist/plus/integrations/providers/providersApi.js.map +1 -1
- package/dist/plus/integrations/providers/utils/jiraIssueDrains.d.ts +22 -0
- package/dist/plus/integrations/providers/utils/jiraIssueDrains.js +69 -9
- package/dist/plus/integrations/providers/utils/jiraIssueDrains.js.map +1 -1
- package/dist/plus/integrations/providers/utils/providerPaging.js +8 -2
- package/dist/plus/integrations/providers/utils/providerPaging.js.map +1 -1
- package/dist/plus/integrations/reads/filters.d.ts +3 -3
- package/dist/plus/integrations/reads/filters.js.map +1 -1
- package/dist/plus/integrations/reads/issueBatch.d.ts +2 -1
- package/dist/plus/integrations/reads/issueBatch.js +1 -1
- package/dist/plus/integrations/reads/issueBatch.js.map +1 -1
- package/dist/plus/integrations/reads/issueTracker.js +23 -14
- package/dist/plus/integrations/reads/issueTracker.js.map +1 -1
- package/dist/plus/integrations/reads/issues.d.ts +2 -1
- package/dist/plus/integrations/reads/issues.js +18 -3
- package/dist/plus/integrations/reads/issues.js.map +1 -1
- package/dist/plus/integrations/reads/pullRequestBatch.js +1 -1
- package/dist/plus/integrations/reads/resolveRepository.js +12 -3
- package/dist/plus/integrations/reads/resolveRepository.js.map +1 -1
- package/dist/plus/integrations/reads/warnings.d.ts +11 -4
- package/dist/plus/integrations/reads/warnings.js +15 -5
- package/dist/plus/integrations/reads/warnings.js.map +1 -1
- package/dist/plus/integrations/results.d.ts +36 -2
- package/dist/plus/integrations/results.js +33 -13
- package/dist/plus/integrations/results.js.map +1 -1
- package/dist/utils/promiseCache.d.ts +10 -0
- package/dist/utils/promiseCache.js +17 -2
- package/dist/utils/promiseCache.js.map +1 -1
- package/docs/integrations.md +52 -20
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"watchGroup.js","names":[],"sources":["../../../../git/src/watching/watchGroup.ts"],"sourcesContent":["import type { UnifiedDisposable } from '@gitlens/utils/disposable.js';\nimport { some } from '@gitlens/utils/iterable.js';\nimport { isDescendant, relative } from '@gitlens/utils/path.js';\nimport type { GitDir, RepositoryChange } from '../models/repository.js';\nimport { allWatchedRepositoryChanges, classifyGitDirChange } from './classifyChange.js';\nimport type { FileWatcher, FileWatchEvent, FileWatchingProvider } from './provider.js';\nimport {\n\tdotGitGlobCombined,\n\tdotGitGlobCommon,\n\tdotGitGlobRoot,\n\tgitDirExcludes,\n\tshouldIgnoreRepoPath,\n} from './watcherPatterns.js';\n\n/**\n * Hooks that the WatchGroup fires when events are interpreted.\n * These are raw, un-debounced events — the session handles debouncing.\n */\nexport interface WatchGroupHooks {\n\t/**\n\t * `ref` is the full ref name (or ref directory) or pseudo-ref (`HEAD`, `ORIG_HEAD`) whose file changed, with a\n\t * trailing `.lock` stripped; `undefined` for any other `.git` file.\n\t */\n\treadonly onRepoChanged: (repoPath: string, changes: RepositoryChange[], ref?: string) => void;\n\treadonly onFetchHeadChanged?: (repoPath: string) => void;\n\treadonly onIgnoresChanged?: (repoPath: string) => void;\n}\n\n/**\n * Tracks one session's connection to a WatchGroup.\n * Each session has its own root watcher but shares the common watcher.\n */\ninterface SessionEntry {\n\treadonly repoPath: string;\n\treadonly gitDirPath: string;\n\treadonly rootWatcher: FileWatcher;\n\t/** True when the root watcher is on the common dir itself, so it already sees every common file */\n\treadonly coversCommon: boolean;\n\t/** For a linked session, its `<n>` in `<common>/worktrees/<n>`; otherwise `undefined` */\n\treadonly worktreeId: string | undefined;\n\treadonly hooks: WatchGroupHooks;\n}\n\n/**\n * Manages filesystem watchers for a single physical `.git` directory,\n * shared across all worktrees that reference it.\n *\n * Structure:\n * - 1 common watcher: config, refs/**, info/exclude, etc. Delivers to linked sessions only; a\n * session rooted at the common dir hears the same files from its own combined root watcher.\n * - N root watchers: one per worktree (index, HEAD, *_HEAD, etc.)\n *\n * For a standard (non-worktree) repo, the common and root directories\n * are the same, but we still create separate watchers for clean separation.\n */\nexport class WatchGroup implements UnifiedDisposable {\n\tprivate readonly _sessions = new Map<string, SessionEntry>();\n\tprivate _commonWatcher: FileWatcher | undefined;\n\tprivate _disposed = false;\n\n\tconstructor(\n\t\treadonly commonGitDir: string,\n\t\tprivate readonly provider: FileWatchingProvider,\n\t) {}\n\n\t[Symbol.dispose](): void {\n\t\tthis.dispose();\n\t}\n\n\t/** Dispose the entire group (common watcher + all root watchers). */\n\tdispose(): void {\n\t\tif (this._disposed) return;\n\n\t\tthis._disposed = true;\n\n\t\tfor (const entry of this._sessions.values()) {\n\t\t\tentry.rootWatcher.dispose();\n\t\t}\n\t\tthis._sessions.clear();\n\n\t\tthis._commonWatcher?.dispose();\n\t\tthis._commonWatcher = undefined;\n\t}\n\n\tget sessions(): ReadonlyMap<string, SessionEntry> {\n\t\treturn this._sessions;\n\t}\n\n\t/** Add a session to this group. Creates the root watcher for the session's gitDir. */\n\taddSession(repoPath: string, gitDir: GitDir, hooks: WatchGroupHooks): void {\n\t\tif (this._disposed) return;\n\t\tif (this._sessions.has(repoPath)) return; // Already tracked\n\n\t\t// Determine the glob pattern for the root watcher\n\t\tconst isStandard = gitDir.commonUri == null;\n\t\tconst rootPattern = isStandard ? dotGitGlobCombined : dotGitGlobRoot;\n\n\t\tconst rootWatcher = this.provider.createWatcher(\n\t\t\tgitDir.uri.fsPath,\n\t\t\trootPattern,\n\t\t\t(event: FileWatchEvent) => {\n\t\t\t\tconst entry = this._sessions.get(repoPath);\n\t\t\t\tif (entry != null) {\n\t\t\t\t\tthis.onRootEvent(event, entry);\n\t\t\t\t}\n\t\t\t},\n\t\t\t{ excludes: gitDirExcludes },\n\t\t);\n\n\t\tconst entryGitDirPath = gitDir.uri.fsPath;\n\t\tconst entry: SessionEntry = {\n\t\t\trepoPath: repoPath,\n\t\t\tgitDirPath: entryGitDirPath,\n\t\t\trootWatcher: rootWatcher,\n\t\t\tcoversCommon: isStandard,\n\t\t\tworktreeId: isStandard ? undefined : this.getWorktreeId(entryGitDirPath),\n\t\t\thooks: hooks,\n\t\t};\n\n\t\tthis._sessions.set(repoPath, entry);\n\n\t\t// Start the common watcher if this is a worktree repo and we haven't yet.\n\t\t// Standard repos don't need a separate common watcher because their root\n\t\t// watcher uses dotGitGlobCombined which already covers all patterns.\n\t\tif (!isStandard) {\n\t\t\tthis.ensureCommonWatcher();\n\t\t}\n\t}\n\n\t/** Remove a session from this group. Disposes its root watcher. */\n\tremoveSession(repoPath: string): void {\n\t\tconst entry = this._sessions.get(repoPath);\n\t\tif (entry == null) return;\n\n\t\tentry.rootWatcher.dispose();\n\t\tthis._sessions.delete(repoPath);\n\n\t\t// Once no linked session is left, dispose the common watcher — it delivers to linked sessions only\n\t\tif (this._commonWatcher != null && !some(this._sessions.values(), e => !e.coversCommon)) {\n\t\t\tthis._commonWatcher.dispose();\n\t\t\tthis._commonWatcher = undefined;\n\t\t}\n\t}\n\n\tprivate getWorktreeId(gitDirPath: string): string | undefined {\n\t\tif (!isDescendant(gitDirPath, this.commonGitDir)) return undefined;\n\n\t\tconst rel = relative(this.commonGitDir, gitDirPath);\n\t\tif (!rel.startsWith('worktrees/')) return undefined;\n\n\t\tconst id = rel.substring(10);\n\t\treturn id.length > 0 && !id.includes('/') ? id : undefined;\n\t}\n\n\tprivate ensureCommonWatcher(): void {\n\t\tif (this._commonWatcher != null || this._disposed) return;\n\n\t\tthis._commonWatcher = this.provider.createWatcher(\n\t\t\tthis.commonGitDir,\n\t\t\tdotGitGlobCommon,\n\t\t\t(event: FileWatchEvent) => {\n\t\t\t\tthis.onCommonEvent(event);\n\t\t\t},\n\t\t\t{ excludes: gitDirExcludes },\n\t\t);\n\t}\n\n\tprivate onCommonEvent(event: FileWatchEvent): void {\n\t\tif (event.reason === 'overflow') {\n\t\t\tfor (const entry of this._sessions.values()) {\n\t\t\t\tif (entry.coversCommon) continue;\n\n\t\t\t\tthis.deliverOverflow(entry);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Compute relative path from common git dir\n\t\tif (!isDescendant(event.path, this.commonGitDir)) return;\n\n\t\tconst relativePath = relative(this.commonGitDir, event.path);\n\t\tif (shouldIgnoreRepoPath(relativePath)) return;\n\n\t\tconst isFetchHead = relativePath === 'FETCH_HEAD';\n\t\tconst isExclude = relativePath === 'info/exclude';\n\t\tconst changes = isFetchHead ? undefined : classifyGitDirChange(relativePath);\n\t\tif (changes == null && !isFetchHead && !isExclude) return;\n\n\t\tconst ref = changes != null ? getChangedRef(relativePath) : undefined;\n\n\t\t// `worktrees/<n>/HEAD`: the owner hears its own HEAD from its root watcher. Its `locked` and `gitdir` aren't in\n\t\t// the root glob, so the owner hears those only from here\n\t\tlet ownerId: string | undefined;\n\t\tif (changes != null && relativePath.length > 10 && relativePath.startsWith('worktrees/')) {\n\t\t\tconst idEnd = relativePath.indexOf('/', 10);\n\t\t\tif (idEnd !== -1 && relativePath.substring(idEnd + 1) === 'HEAD') {\n\t\t\t\townerId = relativePath.substring(10, idEnd);\n\t\t\t}\n\t\t}\n\n\t\tfor (const entry of this._sessions.values()) {\n\t\t\t// A session rooted at the common dir sees every common file (hooks included) through its own\n\t\t\t// combined root watcher, so delivering here too would duplicate it\n\t\t\tif (entry.coversCommon) continue;\n\n\t\t\tif (isFetchHead) {\n\t\t\t\t// FETCH_HEAD lives in the main .git dir; each worktree updates its last-fetched state\n\t\t\t\tentry.hooks.onFetchHeadChanged?.(entry.repoPath);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (changes != null && (ownerId == null || entry.worktreeId !== ownerId)) {\n\t\t\t\tentry.hooks.onRepoChanged(entry.repoPath, changes, ref);\n\t\t\t}\n\n\t\t\tif (isExclude) {\n\t\t\t\tentry.hooks.onIgnoresChanged?.(entry.repoPath);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate deliverOverflow(entry: SessionEntry): void {\n\t\t// Every kind, no ref (ref kinds stay unnamed). Never `'unknown'` — the cache would unregister the path\n\t\tentry.hooks.onRepoChanged(entry.repoPath, [...allWatchedRepositoryChanges]);\n\t\tentry.hooks.onFetchHeadChanged?.(entry.repoPath);\n\t\tentry.hooks.onIgnoresChanged?.(entry.repoPath);\n\t}\n\n\tprivate onRootEvent(event: FileWatchEvent, entry: SessionEntry): void {\n\t\tif (event.reason === 'overflow') {\n\t\t\tthis.deliverOverflow(entry);\n\t\t\treturn;\n\t\t}\n\n\t\tif (!isDescendant(event.path, entry.gitDirPath)) return;\n\n\t\tconst relativePath = relative(entry.gitDirPath, event.path);\n\t\tif (shouldIgnoreRepoPath(relativePath)) return;\n\n\t\t// Check for FETCH_HEAD before interpretation\n\t\tif (relativePath === 'FETCH_HEAD') {\n\t\t\tentry.hooks.onFetchHeadChanged?.(entry.repoPath);\n\t\t\treturn;\n\t\t}\n\n\t\tconst changes = classifyGitDirChange(relativePath);\n\t\tif (changes != null) {\n\t\t\tentry.hooks.onRepoChanged(entry.repoPath, changes, getChangedRef(relativePath));\n\t\t}\n\n\t\tif (relativePath === 'info/exclude') {\n\t\t\tentry.hooks.onIgnoresChanged?.(entry.repoPath);\n\t\t}\n\t}\n}\n\n/**\n * The ref a `.git`-relative path names: a loose ref (or ref directory) under `refs/`, or the `HEAD`/`ORIG_HEAD`\n * pseudo-refs. Git writes a ref as `<name>.lock` and renames it over `<name>`, so the lock file names the same ref.\n */\nfunction getChangedRef(relativePath: string): string | undefined {\n\tconst ref = relativePath.endsWith('.lock') ? relativePath.substring(0, relativePath.length - 5) : relativePath;\n\treturn ref.startsWith('refs/') || ref === 'HEAD' || ref === 'ORIG_HEAD' ? ref : undefined;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAuDA,IAAa,aAAb,MAAqD;CAM1C;CACQ;CANlB,4BAA6B,IAAI,IAA0B;CAC3D;CACA,YAAoB;CAEpB,YACC,cACA,UACC;EAFQ,KAAA,eAAA;EACQ,KAAA,WAAA;CACf;CAEH,CAAC,OAAO,WAAiB;EACxB,KAAK,QAAQ;CACd;;CAGA,UAAgB;EACf,IAAI,KAAK,WAAW;EAEpB,KAAK,YAAY;EAEjB,KAAK,MAAM,SAAS,KAAK,UAAU,OAAO,GACzC,MAAM,YAAY,QAAQ;EAE3B,KAAK,UAAU,MAAM;EAErB,KAAK,gBAAgB,QAAQ;EAC7B,KAAK,iBAAiB,KAAA;CACvB;CAEA,IAAI,WAA8C;EACjD,OAAO,KAAK;CACb;;CAGA,WAAW,UAAkB,QAAgB,OAA8B;EAC1E,IAAI,KAAK,WAAW;EACpB,IAAI,KAAK,UAAU,IAAI,QAAQ,GAAG;EAGlC,MAAM,aAAa,OAAO,aAAa;EACvC,MAAM,cAAc,aAAa,qBAAqB;EAEtD,MAAM,cAAc,KAAK,SAAS,cACjC,OAAO,IAAI,QACX,cACC,UAA0B;GAC1B,MAAM,QAAQ,KAAK,UAAU,IAAI,QAAQ;GACzC,IAAI,SAAS,MACZ,KAAK,YAAY,OAAO,KAAK;EAE/B,GACA,EAAE,UAAU,eAAe,CAC5B;EAEA,MAAM,kBAAkB,OAAO,IAAI;EACnC,MAAM,QAAsB;GACjB;GACV,YAAY;GACC;GACb,cAAc;GACd,YAAY,aAAa,KAAA,IAAY,KAAK,cAAc,eAAe;GAChE;EACR;EAEA,KAAK,UAAU,IAAI,UAAU,KAAK;EAKlC,IAAI,CAAC,YACJ,KAAK,oBAAoB;CAE3B;;CAGA,cAAc,UAAwB;EACrC,MAAM,QAAQ,KAAK,UAAU,IAAI,QAAQ;EACzC,IAAI,SAAS,MAAM;EAEnB,MAAM,YAAY,QAAQ;EAC1B,KAAK,UAAU,OAAO,QAAQ;EAG9B,IAAI,KAAK,kBAAkB,QAAQ,CAAC,KAAK,KAAK,UAAU,OAAO,IAAG,MAAK,CAAC,EAAE,YAAY,GAAG;GACxF,KAAK,eAAe,QAAQ;GAC5B,KAAK,iBAAiB,KAAA;EACvB;CACD;CAEA,cAAsB,YAAwC;EAC7D,IAAI,CAAC,aAAa,YAAY,KAAK,YAAY,GAAG,OAAO,KAAA;EAEzD,MAAM,MAAM,SAAS,KAAK,cAAc,UAAU;EAClD,IAAI,CAAC,IAAI,WAAW,YAAY,GAAG,OAAO,KAAA;EAE1C,MAAM,KAAK,IAAI,UAAU,EAAE;EAC3B,OAAO,GAAG,SAAS,KAAK,CAAC,GAAG,SAAS,GAAG,IAAI,KAAK,KAAA;CAClD;CAEA,sBAAoC;EACnC,IAAI,KAAK,kBAAkB,QAAQ,KAAK,WAAW;EAEnD,KAAK,iBAAiB,KAAK,SAAS,cACnC,KAAK,cACL,mBACC,UAA0B;GAC1B,KAAK,cAAc,KAAK;EACzB,GACA,EAAE,UAAU,eAAe,CAC5B;CACD;CAEA,cAAsB,OAA6B;EAClD,IAAI,MAAM,WAAW,YAAY;GAChC,KAAK,MAAM,SAAS,KAAK,UAAU,OAAO,GAAG;IAC5C,IAAI,MAAM,cAAc;IAExB,KAAK,gBAAgB,KAAK;GAC3B;GACA;EACD;EAGA,IAAI,CAAC,aAAa,MAAM,MAAM,KAAK,YAAY,GAAG;EAElD,MAAM,eAAe,SAAS,KAAK,cAAc,MAAM,IAAI;EAC3D,IAAI,qBAAqB,YAAY,GAAG;EAExC,MAAM,cAAc,iBAAiB;EACrC,MAAM,YAAY,iBAAiB;EACnC,MAAM,UAAU,cAAc,KAAA,IAAY,qBAAqB,YAAY;EAC3E,IAAI,WAAW,QAAQ,CAAC,eAAe,CAAC,WAAW;EAEnD,MAAM,MAAM,WAAW,OAAO,cAAc,YAAY,IAAI,KAAA;EAI5D,IAAI;EACJ,IAAI,WAAW,QAAQ,aAAa,SAAS,MAAM,aAAa,WAAW,YAAY,GAAG;GACzF,MAAM,QAAQ,aAAa,QAAQ,KAAK,EAAE;GAC1C,IAAI,UAAU,MAAM,aAAa,UAAU,QAAQ,CAAC,MAAM,QACzD,UAAU,aAAa,UAAU,IAAI,KAAK;EAE5C;EAEA,KAAK,MAAM,SAAS,KAAK,UAAU,OAAO,GAAG;GAG5C,IAAI,MAAM,cAAc;GAExB,IAAI,aAAa;IAEhB,MAAM,MAAM,qBAAqB,MAAM,QAAQ;IAC/C;GACD;GAEA,IAAI,WAAW,SAAS,WAAW,QAAQ,MAAM,eAAe,UAC/D,MAAM,MAAM,cAAc,MAAM,UAAU,SAAS,GAAG;GAGvD,IAAI,WACH,MAAM,MAAM,mBAAmB,MAAM,QAAQ;EAE/C;CACD;CAEA,gBAAwB,OAA2B;EAElD,MAAM,MAAM,cAAc,MAAM,UAAU,CAAC,GAAG,2BAA2B,CAAC;EAC1E,MAAM,MAAM,qBAAqB,MAAM,QAAQ;EAC/C,MAAM,MAAM,mBAAmB,MAAM,QAAQ;CAC9C;CAEA,YAAoB,OAAuB,OAA2B;EACrE,IAAI,MAAM,WAAW,YAAY;GAChC,KAAK,gBAAgB,KAAK;GAC1B;EACD;EAEA,IAAI,CAAC,aAAa,MAAM,MAAM,MAAM,UAAU,GAAG;EAEjD,MAAM,eAAe,SAAS,MAAM,YAAY,MAAM,IAAI;EAC1D,IAAI,qBAAqB,YAAY,GAAG;EAGxC,IAAI,iBAAiB,cAAc;GAClC,MAAM,MAAM,qBAAqB,MAAM,QAAQ;GAC/C;EACD;EAEA,MAAM,UAAU,qBAAqB,YAAY;EACjD,IAAI,WAAW,MACd,MAAM,MAAM,cAAc,MAAM,UAAU,SAAS,cAAc,YAAY,CAAC;EAG/E,IAAI,iBAAiB,gBACpB,MAAM,MAAM,mBAAmB,MAAM,QAAQ;CAE/C;AACD;;;;;AAMA,SAAS,cAAc,cAA0C;CAChE,MAAM,MAAM,aAAa,SAAS,OAAO,IAAI,aAAa,UAAU,GAAG,aAAa,SAAS,CAAC,IAAI;CAClG,OAAO,IAAI,WAAW,OAAO,KAAK,QAAQ,UAAU,QAAQ,cAAc,MAAM,KAAA;AACjF"}
|
|
1
|
+
{"version":3,"file":"watchGroup.js","names":[],"sources":["../../../../git/src/watching/watchGroup.ts"],"sourcesContent":["import type { UnifiedDisposable } from '@gitlens/utils/disposable.js';\nimport { some } from '@gitlens/utils/iterable.js';\nimport { isDescendant, relative } from '@gitlens/utils/path.js';\nimport type { GitDir, RepositoryChange } from '../models/repository.js';\nimport { allWatchedRepositoryChanges, classifyGitDirChange } from './classifyChange.js';\nimport type { GitDirPoller, GitDirProbe } from './gitDirPoller.js';\nimport { GitDirProbeTarget } from './gitDirPoller.js';\nimport type {\n\tFileWatcher,\n\tFileWatcherOptions,\n\tFileWatcherReliability,\n\tFileWatchEvent,\n\tFileWatchingProvider,\n} from './provider.js';\nimport {\n\tdotGitGlobCombined,\n\tdotGitGlobCommon,\n\tdotGitGlobRoot,\n\tgetGitDirRefProbes,\n\tgitDirCommonProbes,\n\tgitDirExcludes,\n\tgitDirProbesByEmittedPath,\n\tgitDirRootProbes,\n\tshouldIgnoreRepoPath,\n} from './watcherPatterns.js';\n\n/**\n * Hooks that the WatchGroup fires when events are interpreted.\n * These are raw, un-debounced events — the session handles debouncing.\n */\nexport interface WatchGroupHooks {\n\t/**\n\t * `ref` is the full ref name (or ref directory) or pseudo-ref (`HEAD`, `ORIG_HEAD`) whose file changed, with a\n\t * trailing `.lock` stripped; `undefined` for any other `.git` file. `overflow` marks the every-kind rescan of a\n\t * watcher that dropped events, which no single write can account for.\n\t */\n\treadonly onRepoChanged: (repoPath: string, changes: RepositoryChange[], ref?: string, overflow?: boolean) => void;\n\treadonly onFetchHeadChanged?: (repoPath: string) => void;\n\treadonly onIgnoresChanged?: (repoPath: string) => void;\n\t/** The refs whose loose files a poll stats for this session, read at each poll */\n\treadonly getProbeRefs?: () => Iterable<string>;\n\t/** A watcher this session hears through went `unwatchable`, or stopped being it (see {@link WatchGroup.isUnwatchable}) */\n\treadonly onUnwatchableChanged?: (repoPath: string) => void;\n}\n\n/**\n * Tracks one session's connection to a WatchGroup.\n * Each session has its own root watcher but shares the common watcher.\n */\ninterface SessionEntry {\n\treadonly repoPath: string;\n\treadonly gitDirPath: string;\n\treadonly rootWatcher: FileWatcher;\n\t/** True when the root watcher is on the common dir itself, so it already sees every common file */\n\treadonly coversCommon: boolean;\n\t/** For a linked session, its `<n>` in `<common>/worktrees/<n>`; otherwise `undefined` */\n\treadonly worktreeId: string | undefined;\n\treadonly hooks: WatchGroupHooks;\n\treliability: FileWatcherReliability;\n\t/** Polls in place of (or beside) the root watcher while it isn't `reliable` */\n\ttarget?: GitDirProbeTarget;\n}\n\n/**\n * Manages filesystem watchers for a single physical `.git` directory,\n * shared across all worktrees that reference it.\n *\n * Structure:\n * - 1 common watcher: config, refs/**, info/exclude, etc. Delivers to linked sessions only; a\n * session rooted at the common dir hears the same files from its own combined root watcher.\n * - N root watchers: one per worktree (index, HEAD, *_HEAD, etc.)\n *\n * For a standard (non-worktree) repo, the common and root directories\n * are the same, but we still create separate watchers for clean separation.\n */\nexport class WatchGroup implements UnifiedDisposable {\n\tprivate readonly _sessions = new Map<string, SessionEntry>();\n\tprivate _commonWatcher: FileWatcher | undefined;\n\tprivate _commonReliability: FileWatcherReliability = 'reliable';\n\tprivate _commonTarget: GitDirProbeTarget | undefined;\n\tprivate _disposed = false;\n\n\tconstructor(\n\t\treadonly commonGitDir: string,\n\t\tprivate readonly provider: FileWatchingProvider,\n\t\t/** When given, a watcher that reports it can't be trusted is polled; otherwise none is asked */\n\t\tprivate readonly poller?: GitDirPoller,\n\t) {}\n\n\t[Symbol.dispose](): void {\n\t\tthis.dispose();\n\t}\n\n\t/** Dispose the entire group (common watcher + all root watchers). */\n\tdispose(): void {\n\t\tif (this._disposed) return;\n\n\t\tthis._disposed = true;\n\n\t\tfor (const entry of this._sessions.values()) {\n\t\t\tentry.rootWatcher.dispose();\n\t\t\tthis.stopPolling(entry);\n\t\t}\n\t\tthis._sessions.clear();\n\n\t\tthis.disposeCommonWatcher();\n\t}\n\n\tget sessions(): ReadonlyMap<string, SessionEntry> {\n\t\treturn this._sessions;\n\t}\n\n\t/** True when a watcher the session hears through — its root watcher, or a linked session's common one — can't watch */\n\tisUnwatchable(repoPath: string): boolean {\n\t\tconst entry = this._sessions.get(repoPath);\n\t\tif (entry == null) return false;\n\n\t\treturn (\n\t\t\tentry.reliability === 'unwatchable' || (!entry.coversCommon && this._commonReliability === 'unwatchable')\n\t\t);\n\t}\n\n\t/** Add a session to this group. Creates the root watcher for the session's gitDir. */\n\taddSession(repoPath: string, gitDir: GitDir, hooks: WatchGroupHooks): void {\n\t\tif (this._disposed) return;\n\t\tif (this._sessions.has(repoPath)) return; // Already tracked\n\n\t\t// Determine the glob pattern for the root watcher\n\t\tconst isStandard = gitDir.commonUri == null;\n\t\tconst rootPattern = isStandard ? dotGitGlobCombined : dotGitGlobRoot;\n\n\t\t// The watcher can report its reliability before `createWatcher` returns, so before the entry exists\n\t\tlet entry: SessionEntry | undefined;\n\t\tlet initialReliability: FileWatcherReliability = 'reliable';\n\n\t\tconst rootWatcher = this.provider.createWatcher(\n\t\t\tgitDir.uri.fsPath,\n\t\t\trootPattern,\n\t\t\t(event: FileWatchEvent) => {\n\t\t\t\tconst e = this._sessions.get(repoPath);\n\t\t\t\tif (e != null) {\n\t\t\t\t\tif (event.reason !== 'overflow') {\n\t\t\t\t\t\te.target?.forget(event.path);\n\t\t\t\t\t}\n\t\t\t\t\tthis.onRootEvent(event, e);\n\t\t\t\t}\n\t\t\t},\n\t\t\tthis.getWatcherOptions(state => {\n\t\t\t\tif (entry == null) {\n\t\t\t\t\tinitialReliability = state;\n\t\t\t\t} else if (this._sessions.get(repoPath) === entry) {\n\t\t\t\t\tthis.setRootReliability(entry, state);\n\t\t\t\t}\n\t\t\t}),\n\t\t);\n\n\t\tconst entryGitDirPath = gitDir.uri.fsPath;\n\t\tentry = {\n\t\t\trepoPath: repoPath,\n\t\t\tgitDirPath: entryGitDirPath,\n\t\t\trootWatcher: rootWatcher,\n\t\t\tcoversCommon: isStandard,\n\t\t\tworktreeId: isStandard ? undefined : this.getWorktreeId(entryGitDirPath),\n\t\t\thooks: hooks,\n\t\t\treliability: 'reliable',\n\t\t};\n\n\t\tthis._sessions.set(repoPath, entry);\n\t\tif (initialReliability !== 'reliable') {\n\t\t\tthis.setRootReliability(entry, initialReliability, true);\n\t\t}\n\n\t\t// Start the common watcher if this is a worktree repo and we haven't yet.\n\t\t// Standard repos don't need a separate common watcher because their root\n\t\t// watcher uses dotGitGlobCombined which already covers all patterns.\n\t\tif (!isStandard) {\n\t\t\tthis.ensureCommonWatcher();\n\t\t}\n\t}\n\n\t/** Remove a session from this group. Disposes its root watcher. */\n\tremoveSession(repoPath: string): void {\n\t\tconst entry = this._sessions.get(repoPath);\n\t\tif (entry == null) return;\n\n\t\tentry.rootWatcher.dispose();\n\t\tthis.stopPolling(entry);\n\t\tthis._sessions.delete(repoPath);\n\n\t\t// Once no linked session is left, dispose the common watcher — it delivers to linked sessions only\n\t\tif (this._commonWatcher != null && !some(this._sessions.values(), e => !e.coversCommon)) {\n\t\t\tthis.disposeCommonWatcher();\n\t\t}\n\t}\n\n\tprivate getWorktreeId(gitDirPath: string): string | undefined {\n\t\tif (!isDescendant(gitDirPath, this.commonGitDir)) return undefined;\n\n\t\tconst rel = relative(this.commonGitDir, gitDirPath);\n\t\tif (!rel.startsWith('worktrees/')) return undefined;\n\n\t\tconst id = rel.substring(10);\n\t\treturn id.length > 0 && !id.includes('/') ? id : undefined;\n\t}\n\n\tprivate ensureCommonWatcher(): void {\n\t\tif (this._commonWatcher != null || this._disposed) return;\n\n\t\t// The watcher can report its reliability before `createWatcher` returns\n\t\tlet watcher: FileWatcher | undefined;\n\t\tlet initialReliability: FileWatcherReliability = 'reliable';\n\t\tconst options = this.getWatcherOptions(state => {\n\t\t\tif (watcher == null) {\n\t\t\t\tinitialReliability = state;\n\t\t\t} else if (this._commonWatcher === watcher) {\n\t\t\t\tthis.setCommonReliability(state);\n\t\t\t}\n\t\t});\n\n\t\twatcher = this.provider.createWatcher(\n\t\t\tthis.commonGitDir,\n\t\t\tdotGitGlobCommon,\n\t\t\t(event: FileWatchEvent) => {\n\t\t\t\tif (event.reason !== 'overflow') {\n\t\t\t\t\tthis._commonTarget?.forget(event.path);\n\t\t\t\t}\n\t\t\t\tthis.onCommonEvent(event);\n\t\t\t},\n\t\t\toptions,\n\t\t);\n\t\tthis._commonWatcher = watcher;\n\t\tif (initialReliability !== 'reliable') {\n\t\t\tthis.setCommonReliability(initialReliability, true);\n\t\t}\n\t}\n\n\tprivate disposeCommonWatcher(): void {\n\t\tthis._commonWatcher?.dispose();\n\t\tthis._commonWatcher = undefined;\n\t\tthis._commonReliability = 'reliable';\n\t\tif (this._commonTarget != null) {\n\t\t\tthis.poller?.delete(this._commonTarget);\n\t\t\tthis._commonTarget = undefined;\n\t\t}\n\t}\n\n\t/** Hosts that didn't opt into polling get exactly the options they always had */\n\tprivate getWatcherOptions(onDidChangeReliability: (state: FileWatcherReliability) => void): FileWatcherOptions {\n\t\treturn this.poller != null\n\t\t\t? { excludes: gitDirExcludes, onDidChangeReliability: onDidChangeReliability }\n\t\t\t: { excludes: gitDirExcludes };\n\t}\n\n\tprivate setRootReliability(entry: SessionEntry, state: FileWatcherReliability, initial?: boolean): void {\n\t\tconst previous = entry.reliability;\n\t\tif (previous === state || this.poller == null) return;\n\n\t\tentry.reliability = state;\n\t\tif (state === 'reliable') {\n\t\t\tthis.stopPolling(entry);\n\t\t} else if (entry.target == null) {\n\t\t\tconst target = new GitDirProbeTarget(\n\t\t\t\tentry.gitDirPath,\n\t\t\t\tthis.poller,\n\t\t\t\t() => this.getRootProbes(entry),\n\t\t\t\tgitDirProbesByEmittedPath,\n\t\t\t\t(event, probe) => {\n\t\t\t\t\tif (entry.target !== target) return;\n\n\t\t\t\t\tthis.onRootEvent(event, entry);\n\t\t\t\t\tif (probe.path === 'logs/HEAD' && !entry.coversCommon) {\n\t\t\t\t\t\tthis.onLinkedHeadMoved();\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t);\n\t\t\tentry.target = target;\n\t\t\tthis.poller.add(target);\n\t\t}\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\tthis.deliverOverflow(entry);\n\t\t}\n\t\tentry.hooks.onUnwatchableChanged?.(entry.repoPath);\n\t}\n\n\tprivate setCommonReliability(state: FileWatcherReliability, initial?: boolean): void {\n\t\tconst previous = this._commonReliability;\n\t\tif (previous === state || this.poller == null) return;\n\n\t\tthis._commonReliability = state;\n\t\tif (state === 'reliable') {\n\t\t\tif (this._commonTarget != null) {\n\t\t\t\tthis.poller.delete(this._commonTarget);\n\t\t\t\tthis._commonTarget = undefined;\n\t\t\t}\n\t\t} else if (this._commonTarget == null) {\n\t\t\tconst target = new GitDirProbeTarget(\n\t\t\t\tthis.commonGitDir,\n\t\t\t\tthis.poller,\n\t\t\t\t() => this.getCommonProbes(),\n\t\t\t\tgitDirProbesByEmittedPath,\n\t\t\t\tevent => {\n\t\t\t\t\tif (this._commonTarget === target) {\n\t\t\t\t\t\tthis.onCommonEvent(event);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t);\n\t\t\tthis._commonTarget = target;\n\t\t\tthis.poller.add(target);\n\t\t}\n\n\t\tif ((state === 'unwatchable') === (previous === 'unwatchable')) return;\n\n\t\tfor (const entry of this._sessions.values()) {\n\t\t\tif (entry.coversCommon) continue;\n\n\t\t\tif (!initial) {\n\t\t\t\tthis.deliverOverflow(entry);\n\t\t\t}\n\t\t\tentry.hooks.onUnwatchableChanged?.(entry.repoPath);\n\t\t}\n\t}\n\n\tprivate stopPolling(entry: SessionEntry): void {\n\t\tif (entry.target == null) return;\n\n\t\tthis.poller?.delete(entry.target);\n\t\tentry.target = undefined;\n\t}\n\n\t/**\n\t * A session rooted at the common dir polls the common files too, with the refs of every session in the group: its\n\t * combined watcher would hear them all. While the common dir is polled too, the poller stats each shared file once\n\t * for both, and each keeps its own readings, so what one watcher reported is held back from its own sessions only\n\t */\n\tprivate getRootProbes(entry: SessionEntry): readonly GitDirProbe[] {\n\t\tif (!entry.coversCommon) return gitDirRootProbes;\n\n\t\tconst refs = new Set<string>();\n\t\tfor (const e of this._sessions.values()) {\n\t\t\tfor (const ref of e.hooks.getProbeRefs?.() ?? []) {\n\t\t\t\trefs.add(ref);\n\t\t\t}\n\t\t}\n\t\treturn [...gitDirRootProbes, ...gitDirCommonProbes, ...getGitDirRefProbes(refs)];\n\t}\n\n\t/**\n\t * A linked worktree's `logs/HEAD` moved — a commit or reset there, on a branch a polled session rooted at the common\n\t * dir may not probe — so each such session hears a `heads` change its combined watcher would have heard\n\t */\n\tprivate onLinkedHeadMoved(): void {\n\t\tfor (const entry of this._sessions.values()) {\n\t\t\tif (!entry.coversCommon || entry.target == null) continue;\n\n\t\t\tentry.hooks.onRepoChanged(entry.repoPath, ['heads']);\n\t\t}\n\t}\n\n\t/** The common files, with the refs of every linked session (the only ones the common watcher delivers to) */\n\tprivate getCommonProbes(): GitDirProbe[] {\n\t\tconst refs = new Set<string>();\n\t\tfor (const entry of this._sessions.values()) {\n\t\t\tif (entry.coversCommon) continue;\n\n\t\t\tfor (const ref of entry.hooks.getProbeRefs?.() ?? []) {\n\t\t\t\trefs.add(ref);\n\t\t\t}\n\t\t}\n\t\treturn [...gitDirCommonProbes, ...getGitDirRefProbes(refs)];\n\t}\n\n\tprivate onCommonEvent(event: FileWatchEvent): void {\n\t\tif (event.reason === 'overflow') {\n\t\t\tfor (const entry of this._sessions.values()) {\n\t\t\t\tif (entry.coversCommon) continue;\n\n\t\t\t\tthis.deliverOverflow(entry);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Compute relative path from common git dir\n\t\tif (!isDescendant(event.path, this.commonGitDir)) return;\n\n\t\tconst relativePath = relative(this.commonGitDir, event.path);\n\t\tif (shouldIgnoreRepoPath(relativePath)) return;\n\n\t\tconst isFetchHead = relativePath === 'FETCH_HEAD';\n\t\tconst isExclude = relativePath === 'info/exclude';\n\t\tconst changes = isFetchHead ? undefined : classifyGitDirChange(relativePath);\n\t\tif (changes == null && !isFetchHead && !isExclude) return;\n\n\t\tconst ref = changes != null ? getChangedRef(relativePath) : undefined;\n\n\t\t// `worktrees/<n>/HEAD`: the owner hears its own HEAD from its root watcher. Its `locked` and `gitdir` aren't in\n\t\t// the root glob, so the owner hears those only from here\n\t\tlet ownerId: string | undefined;\n\t\tif (changes != null && relativePath.length > 10 && relativePath.startsWith('worktrees/')) {\n\t\t\tconst idEnd = relativePath.indexOf('/', 10);\n\t\t\tif (idEnd !== -1 && relativePath.substring(idEnd + 1) === 'HEAD') {\n\t\t\t\townerId = relativePath.substring(10, idEnd);\n\t\t\t}\n\t\t}\n\n\t\tfor (const entry of this._sessions.values()) {\n\t\t\t// A session rooted at the common dir sees every common file (hooks included) through its own\n\t\t\t// combined root watcher, so delivering here too would duplicate it\n\t\t\tif (entry.coversCommon) continue;\n\n\t\t\tif (isFetchHead) {\n\t\t\t\t// FETCH_HEAD lives in the main .git dir; each worktree updates its last-fetched state\n\t\t\t\tentry.hooks.onFetchHeadChanged?.(entry.repoPath);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (changes != null && (ownerId == null || entry.worktreeId !== ownerId)) {\n\t\t\t\tentry.hooks.onRepoChanged(entry.repoPath, changes, ref);\n\t\t\t}\n\n\t\t\tif (isExclude) {\n\t\t\t\tentry.hooks.onIgnoresChanged?.(entry.repoPath);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate deliverOverflow(entry: SessionEntry): void {\n\t\t// Every kind, no ref (ref kinds stay unnamed). Never `'unknown'` — the cache would unregister the path\n\t\tentry.hooks.onRepoChanged(entry.repoPath, [...allWatchedRepositoryChanges], undefined, true);\n\t\tentry.hooks.onFetchHeadChanged?.(entry.repoPath);\n\t\tentry.hooks.onIgnoresChanged?.(entry.repoPath);\n\t}\n\n\tprivate onRootEvent(event: FileWatchEvent, entry: SessionEntry): void {\n\t\tif (event.reason === 'overflow') {\n\t\t\tthis.deliverOverflow(entry);\n\t\t\treturn;\n\t\t}\n\n\t\tif (!isDescendant(event.path, entry.gitDirPath)) return;\n\n\t\tconst relativePath = relative(entry.gitDirPath, event.path);\n\t\tif (shouldIgnoreRepoPath(relativePath)) return;\n\n\t\t// Check for FETCH_HEAD before interpretation\n\t\tif (relativePath === 'FETCH_HEAD') {\n\t\t\tentry.hooks.onFetchHeadChanged?.(entry.repoPath);\n\t\t\treturn;\n\t\t}\n\n\t\tconst changes = classifyGitDirChange(relativePath);\n\t\tif (changes != null) {\n\t\t\tentry.hooks.onRepoChanged(entry.repoPath, changes, getChangedRef(relativePath));\n\t\t}\n\n\t\tif (relativePath === 'info/exclude') {\n\t\t\tentry.hooks.onIgnoresChanged?.(entry.repoPath);\n\t\t}\n\t}\n}\n\n/**\n * The ref a `.git`-relative path names: a loose ref (or ref directory) under `refs/`, or the `HEAD`/`ORIG_HEAD`\n * pseudo-refs. Git writes a ref as `<name>.lock` and renames it over `<name>`, so the lock file names the same ref.\n */\nfunction getChangedRef(relativePath: string): string | undefined {\n\tconst ref = relativePath.endsWith('.lock') ? relativePath.substring(0, relativePath.length - 5) : relativePath;\n\treturn ref.startsWith('refs/') || ref === 'HEAD' || ref === 'ORIG_HEAD' ? ref : undefined;\n}\n"],"mappings":"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"}
|
|
@@ -5,8 +5,45 @@ import { WatcherRepoChangeEvent } from "./changeEvent.js";
|
|
|
5
5
|
import { GitIgnoreFilter } from "./gitIgnoreFilter.js";
|
|
6
6
|
import { FileWatchingProvider } from "./provider.js";
|
|
7
7
|
import { RepositoryWatchSession } from "./watchSession.js";
|
|
8
|
-
import { GitDir } from "../models/repository.js";
|
|
8
|
+
import { GitDir, RepositoryChange } from "../models/repository.js";
|
|
9
9
|
//#region ../git/src/watching/watchService.d.ts
|
|
10
|
+
/**
|
|
11
|
+
* A host whose watchers can report they can't be trusted (see `FileWatcherOptions.onDidChangeReliability`) passes
|
|
12
|
+
* these to have core poll in their place. A `.git` directory is polled by statting the files its watchers read and
|
|
13
|
+
* feeding each one that moved through the same interpretation as a watcher event; a working tree is never walked.
|
|
14
|
+
*/
|
|
15
|
+
export interface WatchPollingOptions {
|
|
16
|
+
/**
|
|
17
|
+
* Reads a path's modification time (ms) and size; `undefined` when it doesn't exist. A rejection means unknown:
|
|
18
|
+
* the last reading stands, so it never counts as a change.
|
|
19
|
+
*/
|
|
20
|
+
readonly stat: (path: string) => Promise<{
|
|
21
|
+
readonly mtime: number;
|
|
22
|
+
readonly size: number;
|
|
23
|
+
} | undefined>;
|
|
24
|
+
/** Whether a poll is worth running now. Default: the service isn't suspended (`suspendAll`/`resumeAll`) */
|
|
25
|
+
readonly isActive?: () => boolean;
|
|
26
|
+
/** How often each untrusted watcher is polled. Default: 60000ms */
|
|
27
|
+
readonly intervalMs?: number;
|
|
28
|
+
/**
|
|
29
|
+
* How often `isActive` is looked at, which bounds how late the first poll after it turns true can be. Only with
|
|
30
|
+
* `isActive`: the default gate wakes only for a poll. Default: 5000ms
|
|
31
|
+
*/
|
|
32
|
+
readonly activeCheckMs?: number;
|
|
33
|
+
/**
|
|
34
|
+
* Refs to poll for a repository beyond its subscribers' `relevantRefs`: one with none (a host listing every branch)
|
|
35
|
+
* still needs its checked-out branch's upstream, which a push moves without writing `FETCH_HEAD`
|
|
36
|
+
*/
|
|
37
|
+
readonly getRefs?: (repoPath: string) => Iterable<string>;
|
|
38
|
+
/**
|
|
39
|
+
* Something that changes whenever the repository's working tree does (e.g. a digest of its status and the
|
|
40
|
+
* changed files' stats), compared at each poll while working-tree subscribers are watching a working tree whose
|
|
41
|
+
* watcher can't be trusted; a change is reported with no paths. `undefined` or a rejection skips that poll. With or
|
|
42
|
+
* without it, a working tree whose watcher is `unwatchable` is reported changed (no paths) whenever its index or
|
|
43
|
+
* `HEAD` changes.
|
|
44
|
+
*/
|
|
45
|
+
readonly getWorkingTreeFingerprint?: (repoPath: string) => Promise<string | undefined>;
|
|
46
|
+
}
|
|
10
47
|
export interface WatchServiceOptions {
|
|
11
48
|
readonly watchingProvider: FileWatchingProvider;
|
|
12
49
|
/** Default debounce for repo changes when subscriber doesn't specify. Default: 250ms */
|
|
@@ -24,6 +61,8 @@ export interface WatchServiceOptions {
|
|
|
24
61
|
* `excludes` natively (`supportsExcludes`). Never called for one that doesn't. A rejection keeps the static set.
|
|
25
62
|
*/
|
|
26
63
|
readonly getIgnoredDirectories?: (repoPath: string) => Promise<readonly string[]>;
|
|
64
|
+
/** Opts into polling the watchers that report they can't be trusted. When absent, no watcher is asked. */
|
|
65
|
+
readonly polling?: WatchPollingOptions;
|
|
27
66
|
}
|
|
28
67
|
export interface WatchHooks {
|
|
29
68
|
/** Called when FETCH_HEAD changed (for last-fetched tracking) */
|
|
@@ -63,6 +102,16 @@ export declare class RepositoryWatchService implements UnifiedDisposable {
|
|
|
63
102
|
* last-fetched time — the standalone service bridges it to the cache's `lastFetched`.
|
|
64
103
|
*/
|
|
65
104
|
get onDidChangeFetchHead(): Event<string>;
|
|
105
|
+
private readonly _onDidChangeWatchMode;
|
|
106
|
+
/**
|
|
107
|
+
* Fires as a watched repository starts being polled in place of a watcher (see {@link getWatchMode}), and with
|
|
108
|
+
* `'events'` as it is watched natively again. Never fires as the repository stops being watched; one watched again
|
|
109
|
+
* later starts from `'events'`.
|
|
110
|
+
*/
|
|
111
|
+
get onDidChangeWatchMode(): Event<{
|
|
112
|
+
repoPath: string;
|
|
113
|
+
mode: 'events' | 'poll';
|
|
114
|
+
}>;
|
|
66
115
|
private readonly defaultRepoDelayMs?;
|
|
67
116
|
private readonly defaultWorkingTreeDelayMs?;
|
|
68
117
|
private readonly defaultRepoMaxWaitMs?;
|
|
@@ -70,6 +119,7 @@ export declare class RepositoryWatchService implements UnifiedDisposable {
|
|
|
70
119
|
private readonly getIgnoreFilter?;
|
|
71
120
|
private readonly getIgnoredDirectories?;
|
|
72
121
|
private readonly provider;
|
|
122
|
+
private readonly poller;
|
|
73
123
|
private readonly sessionMap;
|
|
74
124
|
private readonly watchGroups;
|
|
75
125
|
private readonly wtWatchers;
|
|
@@ -92,8 +142,20 @@ export declare class RepositoryWatchService implements UnifiedDisposable {
|
|
|
92
142
|
* a session and advance the same status clock.
|
|
93
143
|
*/
|
|
94
144
|
watch(repoPath: string, gitDir: GitDir, watchHooks?: WatchHooks): WatchHandle | undefined;
|
|
145
|
+
/**
|
|
146
|
+
* Core announced one of its own writes for `repoPath`; the session watching it fires the announcement in place of
|
|
147
|
+
* the write's own `.git` events (see {@link RepositoryWatchSession.noteSelfWrite}). A path no session watches is
|
|
148
|
+
* ignored.
|
|
149
|
+
*/
|
|
150
|
+
noteSelfWrite(repoPath: string, changes: readonly RepositoryChange[]): void;
|
|
95
151
|
/** Get an existing session by repo path */
|
|
96
152
|
getSession(repoPath: string): RepositoryWatchSession | undefined;
|
|
153
|
+
/**
|
|
154
|
+
* `'poll'` while a watcher the repository is watched through — its `.git` watchers or its working tree's — can't
|
|
155
|
+
* watch, and is polled instead; `'events'` while it is watched natively (polled beside an `unreliable` watcher
|
|
156
|
+
* included); `undefined` when nothing watches it, which {@link onDidChangeWatchMode} doesn't announce.
|
|
157
|
+
*/
|
|
158
|
+
getWatchMode(repoPath: string): 'events' | 'poll' | undefined;
|
|
97
159
|
/** Resource usage retained by repository watchers. */
|
|
98
160
|
getResourceUsage(): ResourceUsage;
|
|
99
161
|
/** Suspend all sessions and mark the service suspended so late-joining sessions start suspended */
|
|
@@ -104,8 +166,15 @@ export declare class RepositoryWatchService implements UnifiedDisposable {
|
|
|
104
166
|
private maybeDisposeWatchGroup;
|
|
105
167
|
private connectToWatchGroup;
|
|
106
168
|
private disconnectFromWatchGroup;
|
|
169
|
+
private computeWatchMode;
|
|
170
|
+
private updateWatchMode;
|
|
107
171
|
private startWorkingTreeWatcher;
|
|
108
172
|
private stopWorkingTreeWatcher;
|
|
173
|
+
/** Creates a working-tree watcher, with the reliability it reported before returning */
|
|
174
|
+
private createWorkingTreeWatcher;
|
|
175
|
+
private setWorkingTreeReliability;
|
|
176
|
+
/** Polls the host's working-tree fingerprint, when it gives one, while the watcher isn't `reliable` */
|
|
177
|
+
private updateWorkingTreePollTarget;
|
|
109
178
|
private canPruneIgnoredDirectories;
|
|
110
179
|
/**
|
|
111
180
|
* An event the gitignore post-filter dropped. When its directory is itself ignored, the watcher is watching an
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Emitter } from "../../utils/event.js";
|
|
2
2
|
import { getRepositoryKey } from "../../utils/uri.js";
|
|
3
3
|
import { isDescendant, relative } from "../../utils/path.js";
|
|
4
|
+
import { GitDirPoller } from "./gitDirPoller.js";
|
|
4
5
|
import { shouldIgnoreWorkingTreePath, workingTreeExcludes } from "./watcherPatterns.js";
|
|
5
6
|
import { WatchGroup } from "./watchGroup.js";
|
|
6
7
|
import { RepositoryWatchSession } from "./watchSession.js";
|
|
@@ -45,6 +46,15 @@ var RepositoryWatchService = class {
|
|
|
45
46
|
get onDidChangeFetchHead() {
|
|
46
47
|
return this._onDidChangeFetchHead.event;
|
|
47
48
|
}
|
|
49
|
+
_onDidChangeWatchMode = new Emitter();
|
|
50
|
+
/**
|
|
51
|
+
* Fires as a watched repository starts being polled in place of a watcher (see {@link getWatchMode}), and with
|
|
52
|
+
* `'events'` as it is watched natively again. Never fires as the repository stops being watched; one watched again
|
|
53
|
+
* later starts from `'events'`.
|
|
54
|
+
*/
|
|
55
|
+
get onDidChangeWatchMode() {
|
|
56
|
+
return this._onDidChangeWatchMode.event;
|
|
57
|
+
}
|
|
48
58
|
defaultRepoDelayMs;
|
|
49
59
|
defaultWorkingTreeDelayMs;
|
|
50
60
|
defaultRepoMaxWaitMs;
|
|
@@ -52,6 +62,7 @@ var RepositoryWatchService = class {
|
|
|
52
62
|
getIgnoreFilter;
|
|
53
63
|
getIgnoredDirectories;
|
|
54
64
|
provider;
|
|
65
|
+
poller;
|
|
55
66
|
sessionMap = /* @__PURE__ */ new Map();
|
|
56
67
|
watchGroups = /* @__PURE__ */ new Map();
|
|
57
68
|
wtWatchers = /* @__PURE__ */ new Map();
|
|
@@ -66,6 +77,7 @@ var RepositoryWatchService = class {
|
|
|
66
77
|
this.defaultWorkingTreeMaxWaitMs = options.defaultWorkingTreeMaxWaitMs;
|
|
67
78
|
this.getIgnoreFilter = options.getIgnoreFilter;
|
|
68
79
|
this.getIgnoredDirectories = options.getIgnoredDirectories;
|
|
80
|
+
if (options.polling != null) this.poller = new GitDirPoller(options.polling);
|
|
69
81
|
}
|
|
70
82
|
[Symbol.dispose]() {
|
|
71
83
|
this.dispose();
|
|
@@ -78,9 +90,11 @@ var RepositoryWatchService = class {
|
|
|
78
90
|
for (const group of this.watchGroups.values()) group.dispose();
|
|
79
91
|
this.watchGroups.clear();
|
|
80
92
|
for (const repoPath of [...this.wtWatchers.keys()]) this.stopWorkingTreeWatcher(repoPath);
|
|
93
|
+
this.poller?.dispose();
|
|
81
94
|
this._onDidChangeRepository.dispose();
|
|
82
95
|
this._onDidChangeWorkingTree.dispose();
|
|
83
96
|
this._onDidChangeFetchHead.dispose();
|
|
97
|
+
this._onDidChangeWatchMode.dispose();
|
|
84
98
|
}
|
|
85
99
|
/**
|
|
86
100
|
* Start watching a repository. Reference-counted:
|
|
@@ -124,6 +138,8 @@ var RepositoryWatchService = class {
|
|
|
124
138
|
},
|
|
125
139
|
onLastWorkingTreeSubscriber: () => {
|
|
126
140
|
this.stopWorkingTreeWatcher(repoPath);
|
|
141
|
+
const rec = this.sessionMap.get(repoPath);
|
|
142
|
+
if (rec != null) this.updateWatchMode(rec);
|
|
127
143
|
}
|
|
128
144
|
},
|
|
129
145
|
onDidFireRepoChange: (event) => {
|
|
@@ -139,7 +155,8 @@ var RepositoryWatchService = class {
|
|
|
139
155
|
session,
|
|
140
156
|
gitDir,
|
|
141
157
|
watchHooks: watchHooksSet,
|
|
142
|
-
refCount: 1
|
|
158
|
+
refCount: 1,
|
|
159
|
+
watchMode: "events"
|
|
143
160
|
};
|
|
144
161
|
this.sessionMap.set(repoPath, record);
|
|
145
162
|
}
|
|
@@ -161,22 +178,43 @@ var RepositoryWatchService = class {
|
|
|
161
178
|
}
|
|
162
179
|
};
|
|
163
180
|
}
|
|
181
|
+
/**
|
|
182
|
+
* 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}). A path no session watches is
|
|
184
|
+
* ignored.
|
|
185
|
+
*/
|
|
186
|
+
noteSelfWrite(repoPath, changes) {
|
|
187
|
+
if (this._disposed) return;
|
|
188
|
+
this.sessionMap.get(getRepositoryKey(repoPath))?.session.noteSelfWrite(changes);
|
|
189
|
+
}
|
|
164
190
|
/** Get an existing session by repo path */
|
|
165
191
|
getSession(repoPath) {
|
|
166
192
|
return this.sessionMap.get(getRepositoryKey(repoPath))?.session;
|
|
167
193
|
}
|
|
194
|
+
/**
|
|
195
|
+
* `'poll'` while a watcher the repository is watched through — its `.git` watchers or its working tree's — can't
|
|
196
|
+
* watch, and is polled instead; `'events'` while it is watched natively (polled beside an `unreliable` watcher
|
|
197
|
+
* included); `undefined` when nothing watches it, which {@link onDidChangeWatchMode} doesn't announce.
|
|
198
|
+
*/
|
|
199
|
+
getWatchMode(repoPath) {
|
|
200
|
+
const record = this.sessionMap.get(getRepositoryKey(repoPath));
|
|
201
|
+
return record != null ? this.computeWatchMode(record) : void 0;
|
|
202
|
+
}
|
|
168
203
|
/** Resource usage retained by repository watchers. */
|
|
169
204
|
getResourceUsage() {
|
|
170
|
-
|
|
205
|
+
const usage = {
|
|
171
206
|
"watch.sessions.count": this.sessionMap.size,
|
|
172
207
|
"watch.groups.count": this.watchGroups.size,
|
|
173
208
|
"watch.workingTreeWatchers.count": this.wtWatchers.size
|
|
174
209
|
};
|
|
210
|
+
if (this.poller != null) usage["watch.pollTargets.count"] = this.poller.size;
|
|
211
|
+
return usage;
|
|
175
212
|
}
|
|
176
213
|
/** Suspend all sessions and mark the service suspended so late-joining sessions start suspended */
|
|
177
214
|
suspendAll() {
|
|
178
215
|
this._suspended = true;
|
|
179
216
|
for (const record of this.sessionMap.values()) record.session.suspend();
|
|
217
|
+
this.poller?.suspend();
|
|
180
218
|
}
|
|
181
219
|
/** Resume all sessions (clearing the global suspend state), with optional per-session delay */
|
|
182
220
|
resumeAll(getDelay) {
|
|
@@ -185,11 +223,12 @@ var RepositoryWatchService = class {
|
|
|
185
223
|
const delayMs = getDelay?.(record.session);
|
|
186
224
|
record.session.resume(delayMs);
|
|
187
225
|
}
|
|
226
|
+
this.poller?.resume();
|
|
188
227
|
}
|
|
189
228
|
getOrCreateWatchGroup(commonGitDir) {
|
|
190
229
|
let group = this.watchGroups.get(commonGitDir);
|
|
191
230
|
if (group == null) {
|
|
192
|
-
group = new WatchGroup(commonGitDir, this.provider);
|
|
231
|
+
group = new WatchGroup(commonGitDir, this.provider, this.poller);
|
|
193
232
|
this.watchGroups.set(commonGitDir, group);
|
|
194
233
|
}
|
|
195
234
|
return group;
|
|
@@ -206,8 +245,10 @@ var RepositoryWatchService = class {
|
|
|
206
245
|
const { session, gitDir, watchHooks } = record;
|
|
207
246
|
const commonGitDir = (gitDir.commonUri ?? gitDir.uri).fsPath;
|
|
208
247
|
this.getOrCreateWatchGroup(commonGitDir).addSession(session.repoPath, gitDir, {
|
|
209
|
-
onRepoChanged:
|
|
210
|
-
session.
|
|
248
|
+
onRepoChanged: (_repoPath, changes, ref, overflow) => {
|
|
249
|
+
if (overflow) session.pushUnfencedRepoChanges(changes);
|
|
250
|
+
else session.pushRepoChanges(changes, ref);
|
|
251
|
+
if (this.poller != null && (changes.includes("index") || changes.includes("head")) && this.wtWatchers.get(session.repoPath)?.reliability === "unwatchable") session.pushWorkingTreeChanges([], true);
|
|
211
252
|
},
|
|
212
253
|
onFetchHeadChanged: (repoPath) => {
|
|
213
254
|
this._onDidChangeFetchHead.fire(repoPath);
|
|
@@ -216,8 +257,15 @@ var RepositoryWatchService = class {
|
|
|
216
257
|
onIgnoresChanged: (repoPath) => {
|
|
217
258
|
this.refreshWorkingTreeFilter(repoPath);
|
|
218
259
|
for (const h of watchHooks) h.onIgnoresChanged?.(repoPath);
|
|
219
|
-
}
|
|
260
|
+
},
|
|
261
|
+
getProbeRefs: () => {
|
|
262
|
+
const refs = session.getRelevantRefs();
|
|
263
|
+
for (const ref of this.poller?.options.getRefs?.(session.repoPath) ?? []) refs.add(ref);
|
|
264
|
+
return refs;
|
|
265
|
+
},
|
|
266
|
+
onUnwatchableChanged: () => this.updateWatchMode(record)
|
|
220
267
|
});
|
|
268
|
+
this.updateWatchMode(record);
|
|
221
269
|
}
|
|
222
270
|
disconnectFromWatchGroup(record) {
|
|
223
271
|
const { session, gitDir } = record;
|
|
@@ -226,6 +274,29 @@ var RepositoryWatchService = class {
|
|
|
226
274
|
if (group == null) return;
|
|
227
275
|
group.removeSession(session.repoPath);
|
|
228
276
|
this.maybeDisposeWatchGroup(commonGitDir);
|
|
277
|
+
this.updateWatchMode(record);
|
|
278
|
+
}
|
|
279
|
+
computeWatchMode(record) {
|
|
280
|
+
const repoPath = record.session.repoPath;
|
|
281
|
+
const group = this.watchGroups.get((record.gitDir.commonUri ?? record.gitDir.uri).fsPath);
|
|
282
|
+
const connected = group?.sessions.has(repoPath) ?? false;
|
|
283
|
+
const wt = this.wtWatchers.get(repoPath);
|
|
284
|
+
if (!connected && wt == null) return void 0;
|
|
285
|
+
return group?.isUnwatchable(repoPath) || wt?.reliability === "unwatchable" ? "poll" : "events";
|
|
286
|
+
}
|
|
287
|
+
updateWatchMode(record) {
|
|
288
|
+
if (this._disposed) return;
|
|
289
|
+
const mode = this.computeWatchMode(record);
|
|
290
|
+
if (mode == null) {
|
|
291
|
+
record.watchMode = "events";
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
if (mode === record.watchMode) return;
|
|
295
|
+
record.watchMode = mode;
|
|
296
|
+
this._onDidChangeWatchMode.fire({
|
|
297
|
+
repoPath: record.session.repoPath,
|
|
298
|
+
mode
|
|
299
|
+
});
|
|
229
300
|
}
|
|
230
301
|
startWorkingTreeWatcher(record) {
|
|
231
302
|
const { session, gitDir, watchHooks } = record;
|
|
@@ -254,7 +325,7 @@ var RepositoryWatchService = class {
|
|
|
254
325
|
if (filter != null) filter.refresh();
|
|
255
326
|
if (entry != null) this.scheduleIgnoredDirectoriesRefresh(entry);
|
|
256
327
|
for (const h of watchHooks) h.onGitIgnoreChanged?.(repoPath);
|
|
257
|
-
session.
|
|
328
|
+
session.pushUnfencedRepoChanges(["ignores"]);
|
|
258
329
|
};
|
|
259
330
|
const onEvent = (event) => {
|
|
260
331
|
if (event.reason === "overflow") {
|
|
@@ -283,17 +354,20 @@ var RepositoryWatchService = class {
|
|
|
283
354
|
}
|
|
284
355
|
session.pushWorkingTreeChanges([event.path]);
|
|
285
356
|
};
|
|
357
|
+
const created = this.createWorkingTreeWatcher(repoPath, onEvent, workingTreeExcludes);
|
|
286
358
|
entry = {
|
|
287
359
|
session,
|
|
288
360
|
onEvent,
|
|
289
361
|
filter,
|
|
290
|
-
watcher:
|
|
362
|
+
watcher: created.watcher,
|
|
291
363
|
ignoredDirectories: [],
|
|
292
364
|
generation: 0,
|
|
293
365
|
reading: false,
|
|
294
|
-
rereadQueued: false
|
|
366
|
+
rereadQueued: false,
|
|
367
|
+
reliability: "reliable"
|
|
295
368
|
};
|
|
296
369
|
this.wtWatchers.set(repoPath, entry);
|
|
370
|
+
this.setWorkingTreeReliability(entry, created.reliability, true);
|
|
297
371
|
if (canPrune) this.refreshIgnoredDirectories(entry, repoPath);
|
|
298
372
|
}
|
|
299
373
|
stopWorkingTreeWatcher(repoPath) {
|
|
@@ -303,8 +377,77 @@ var RepositoryWatchService = class {
|
|
|
303
377
|
clearTimeout(entry.refreshTimer);
|
|
304
378
|
entry.refreshTimer = void 0;
|
|
305
379
|
entry.watcher.dispose();
|
|
380
|
+
if (entry.pollTarget != null) {
|
|
381
|
+
this.poller?.delete(entry.pollTarget);
|
|
382
|
+
entry.pollTarget = void 0;
|
|
383
|
+
}
|
|
306
384
|
this.wtWatchers.delete(repoPath);
|
|
307
385
|
}
|
|
386
|
+
/** Creates a working-tree watcher, with the reliability it reported before returning */
|
|
387
|
+
createWorkingTreeWatcher(repoPath, onEvent, excludes) {
|
|
388
|
+
if (this.poller == null) return {
|
|
389
|
+
watcher: this.provider.createWatcher(repoPath, "**", onEvent, { excludes }),
|
|
390
|
+
reliability: "reliable"
|
|
391
|
+
};
|
|
392
|
+
let watcher;
|
|
393
|
+
let reliability = "reliable";
|
|
394
|
+
const options = {
|
|
395
|
+
excludes,
|
|
396
|
+
onDidChangeReliability: (state) => {
|
|
397
|
+
if (watcher == null) {
|
|
398
|
+
reliability = state;
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
const entry = this.wtWatchers.get(repoPath);
|
|
402
|
+
if (entry?.watcher === watcher) this.setWorkingTreeReliability(entry, state);
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
watcher = this.provider.createWatcher(repoPath, "**", onEvent, options);
|
|
406
|
+
return {
|
|
407
|
+
watcher,
|
|
408
|
+
reliability
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
setWorkingTreeReliability(entry, state, initial) {
|
|
412
|
+
const previous = entry.reliability;
|
|
413
|
+
if (previous === state) return;
|
|
414
|
+
entry.reliability = state;
|
|
415
|
+
this.updateWorkingTreePollTarget(entry);
|
|
416
|
+
if (state === "unwatchable" === (previous === "unwatchable")) return;
|
|
417
|
+
if (!initial) entry.session.pushWorkingTreeChanges([], true);
|
|
418
|
+
const record = this.sessionMap.get(entry.session.repoPath);
|
|
419
|
+
if (record != null) this.updateWatchMode(record);
|
|
420
|
+
}
|
|
421
|
+
/** Polls the host's working-tree fingerprint, when it gives one, while the watcher isn't `reliable` */
|
|
422
|
+
updateWorkingTreePollTarget(entry) {
|
|
423
|
+
const poller = this.poller;
|
|
424
|
+
const getFingerprint = poller?.options.getWorkingTreeFingerprint;
|
|
425
|
+
if (poller == null || getFingerprint == null) return;
|
|
426
|
+
if (entry.reliability === "reliable") {
|
|
427
|
+
if (entry.pollTarget != null) {
|
|
428
|
+
poller.delete(entry.pollTarget);
|
|
429
|
+
entry.pollTarget = void 0;
|
|
430
|
+
}
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
if (entry.pollTarget != null) return;
|
|
434
|
+
const repoPath = entry.session.repoPath;
|
|
435
|
+
let last;
|
|
436
|
+
const target = { poll: async () => {
|
|
437
|
+
let fingerprint;
|
|
438
|
+
try {
|
|
439
|
+
fingerprint = await getFingerprint(repoPath);
|
|
440
|
+
} catch {
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
if (fingerprint == null || entry.pollTarget !== target) return;
|
|
444
|
+
const moved = last != null && last !== fingerprint;
|
|
445
|
+
last = fingerprint;
|
|
446
|
+
if (moved) entry.session.pushWorkingTreeChanges([], true);
|
|
447
|
+
} };
|
|
448
|
+
entry.pollTarget = target;
|
|
449
|
+
poller.add(target);
|
|
450
|
+
}
|
|
308
451
|
canPruneIgnoredDirectories() {
|
|
309
452
|
return this.provider.supportsExcludes === true && this.getIgnoredDirectories != null;
|
|
310
453
|
}
|
|
@@ -393,16 +536,17 @@ var RepositoryWatchService = class {
|
|
|
393
536
|
swapWorkingTreeWatcher(entry, repoPath, directories) {
|
|
394
537
|
const previous = entry.ignoredDirectories;
|
|
395
538
|
if (previous.length === directories.length && previous.every((d) => directories.includes(d))) return;
|
|
396
|
-
let
|
|
539
|
+
let created;
|
|
397
540
|
try {
|
|
398
|
-
|
|
541
|
+
created = this.createWorkingTreeWatcher(repoPath, entry.onEvent, [...workingTreeExcludes, ...directories]);
|
|
399
542
|
} catch {
|
|
400
543
|
return;
|
|
401
544
|
}
|
|
402
545
|
const previousWatcher = entry.watcher;
|
|
403
|
-
entry.watcher = watcher;
|
|
546
|
+
entry.watcher = created.watcher;
|
|
404
547
|
entry.ignoredDirectories = directories;
|
|
405
548
|
previousWatcher.dispose();
|
|
549
|
+
this.setWorkingTreeReliability(entry, created.reliability);
|
|
406
550
|
if (previous.some((d) => !directories.some((n) => n === d || d.startsWith(`${n}/`)))) entry.session.pushWorkingTreeChanges([], true);
|
|
407
551
|
}
|
|
408
552
|
refreshWorkingTreeFilter(repoPath) {
|
|
@@ -412,8 +556,9 @@ var RepositoryWatchService = class {
|
|
|
412
556
|
this.scheduleIgnoredDirectoriesRefresh(entry);
|
|
413
557
|
}
|
|
414
558
|
disposeSessionRecord(record) {
|
|
415
|
-
this.disconnectFromWatchGroup(record);
|
|
416
559
|
this.stopWorkingTreeWatcher(record.session.repoPath);
|
|
560
|
+
this.disconnectFromWatchGroup(record);
|
|
561
|
+
this.updateWatchMode(record);
|
|
417
562
|
record.session.dispose();
|
|
418
563
|
}
|
|
419
564
|
};
|