@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":"watchSession.js","names":[],"sources":["../../../../git/src/watching/watchSession.ts"],"sourcesContent":["import type { UnifiedDisposable } from '@gitlens/utils/disposable.js';\nimport { createDisposable } from '@gitlens/utils/disposable.js';\nimport type { Event } from '@gitlens/utils/event.js';\nimport { Emitter } from '@gitlens/utils/event.js';\nimport type { RepositoryChange } from '../models/repository.js';\nimport type { RefChangeKind, WorkingTreeChangeEvent } from './changeEvent.js';\nimport { findCoveringRefName, getRefChangeKind, isRefChangeKind, WatcherRepoChangeEvent } from './changeEvent.js';\n\nexport interface RepositorySubscription extends UnifiedDisposable {\n\treadonly onDidChange: Event<WatcherRepoChangeEvent>;\n}\n\nexport interface WorkingTreeSubscription extends UnifiedDisposable {\n\treadonly onDidChangeWorkingTree: Event<WorkingTreeChangeEvent>;\n}\n\n/**\n * Lifecycle callbacks fired by the session when subscriber counts\n * cross the 0↔1 boundary. The host (WatchService) uses these to\n * lazily create/destroy watchers.\n */\nexport interface WatchSessionLifecycle {\n\t/** Called when the first repo-change subscriber joins */\n\tonFirstRepoSubscriber?(): void;\n\t/** Called when the last repo-change subscriber leaves */\n\tonLastRepoSubscriber?(): void;\n\t/** Called when the first working-tree subscriber joins */\n\tonFirstWorkingTreeSubscriber?(): void;\n\t/** Called when the last working-tree subscriber leaves */\n\tonLastWorkingTreeSubscriber?(): void;\n}\n\nexport interface WatchSessionOptions {\n\treadonly repoPath: string;\n\t/** Default debounce for repo changes. Default: 250ms */\n\treadonly defaultRepoDelayMs?: number;\n\t/** Default debounce for working tree changes. Default: 2500ms */\n\treadonly defaultWorkingTreeDelayMs?: number;\n\t/**\n\t * Default cap on how long repo changes can be held back by continuous activity — a flush happens at most this long\n\t * after the first pending change, however long the burst lasts. `Infinity` disables the cap. Default: 2000ms\n\t */\n\treadonly defaultRepoMaxWaitMs?: number;\n\t/** Default cap on how long working tree changes can be held back by continuous activity. `Infinity` disables the cap. Default: 10000ms */\n\treadonly defaultWorkingTreeMaxWaitMs?: number;\n\treadonly lifecycle?: WatchSessionLifecycle;\n\t/** Initial suspended state — new sessions inherit the service-global suspend state. Default: false */\n\treadonly suspended?: boolean;\n\t/**\n\t * Called whenever a debounced repo change event is dispatched to subscribers.\n\t * Does NOT count as a subscriber — used by WatchService for\n\t * global event forwarding without affecting lifecycle callbacks.\n\t */\n\treadonly onDidFireRepoChange?: (event: WatcherRepoChangeEvent) => void;\n\t/**\n\t * Called whenever a debounced working-tree change is dispatched to subscribers. Like\n\t * {@link onDidFireRepoChange}, does NOT count as a subscriber — used by WatchService for global\n\t * event forwarding (the single point that drives the cache's status clock for working-tree edits).\n\t */\n\treadonly onDidFireWorkingTreeChange?: (repoPath: string) => void;\n}\n\nconst defaultRepoDelay = 250;\nconst defaultWorkingTreeDelay = 2500;\nconst defaultRepoMaxWait = 2000;\nconst defaultWorkingTreeMaxWait = 10000;\n\n/** Largest delay `setTimeout` honors (a signed 32-bit int); anything longer fires immediately */\nconst maxTimerDelay = 0x7fffffff;\n\ninterface DebounceTiming {\n\treadonly delayMs: number;\n\treadonly maxWaitMs: number;\n}\n\n/**\n * Trailing-deadline debounce with a max-wait, shared by the repo and working-tree pipelines.\n *\n * Recording an event costs a `performance.now()` and, only when no timer is armed, one `setTimeout` — never a\n * `clearTimeout`/`setTimeout` pair per event, which is what made a busy repo allocate (and starve) on every raw\n * watcher event. The armed timer is a deadline, not a countdown: when it fires it recomputes the due time from the\n * latest timestamps and either flushes or re-arms for the remainder. The due time is\n * `min(lastEventAt + delay, firstPendingAt + maxWait)`, so continuous activity is flushed at the max-wait instead of\n * never. Monotonic time, so a wall-clock change can't push a deadline out.\n */\nclass DebounceClock {\n\tprivate active = false;\n\tprivate paused = false;\n\tprivate firstAt = 0;\n\tprivate lastAt = 0;\n\tprivate timer: ReturnType<typeof setTimeout> | undefined;\n\tprivate timerDueAt = 0;\n\t/** Absolute flush time set by {@link resume}; dropped by the next event, which returns to normal debounce */\n\tprivate resumeDueAt: number | undefined;\n\n\tconstructor(\n\t\tprivate readonly getDelayMs: () => number,\n\t\tprivate readonly getMaxWaitMs: () => number,\n\t\tprivate readonly onDue: () => void,\n\t) {}\n\n\t/** Records an event; arms the timer only if none is armed. Cheap enough to call per raw event. */\n\tnote(): void {\n\t\tconst now = performance.now();\n\t\tif (!this.active) {\n\t\t\tthis.active = true;\n\t\t\tthis.firstAt = now;\n\t\t}\n\t\tthis.lastAt = now;\n\n\t\tif (this.paused) return;\n\n\t\tif (this.resumeDueAt != null) {\n\t\t\t// The first event after a resume goes back to normal debounce — which may be due sooner than the resume deadline\n\t\t\tthis.resumeDueAt = undefined;\n\t\t\tthis.rearmIfEarlier();\n\t\t}\n\n\t\tif (this.timer == null) {\n\t\t\tthis.arm(this.getDueAt(), now);\n\t\t}\n\t}\n\n\t/** Moves the armed timer earlier when the due time shrank (a subscriber joined with a shorter delay or max-wait) */\n\trearmIfEarlier(): void {\n\t\tif (!this.active || this.paused || this.timer == null) return;\n\n\t\tconst dueAt = this.getDueAt();\n\t\tif (dueAt >= this.timerDueAt) return;\n\n\t\tclearTimeout(this.timer);\n\t\tthis.arm(dueAt, performance.now());\n\t}\n\n\t/** Cancels the timer; events keep accumulating but nothing is due until {@link resume} */\n\tsuspend(): void {\n\t\tthis.paused = true;\n\t\tthis.clearTimer();\n\t}\n\n\t/**\n\t * Restarts the max-wait window from now and schedules the flush for `delayMs` from now (the effective delay when\n\t * omitted). Does nothing if no events are pending.\n\t */\n\tresume(delayMs?: number): void {\n\t\tthis.paused = false;\n\t\tif (!this.active) return;\n\n\t\tconst now = performance.now();\n\t\tthis.firstAt = now;\n\t\tthis.resumeDueAt = now + (delayMs ?? this.getDelayMs());\n\t\tthis.clearTimer();\n\t\tthis.arm(this.resumeDueAt, now);\n\t}\n\n\t/** Drops the pending window and the timer */\n\tcancel(): void {\n\t\tthis.active = false;\n\t\tthis.resumeDueAt = undefined;\n\t\tthis.clearTimer();\n\t}\n\n\tprivate getDueAt(): number {\n\t\treturn this.resumeDueAt ?? Math.min(this.lastAt + this.getDelayMs(), this.firstAt + this.getMaxWaitMs());\n\t}\n\n\tprivate arm(dueAt: number, now: number): void {\n\t\tthis.timerDueAt = dueAt;\n\t\tthis.timer = setTimeout(() => this.onTimer(), Math.min(Math.max(0, dueAt - now), maxTimerDelay));\n\t}\n\n\tprivate onTimer(): void {\n\t\tthis.timer = undefined;\n\t\tif (!this.active || this.paused) return;\n\n\t\tconst now = performance.now();\n\t\tconst dueAt = this.getDueAt();\n\t\tif (dueAt > now) {\n\t\t\tthis.arm(dueAt, now);\n\t\t\treturn;\n\t\t}\n\n\t\t// Reset before firing so an event raised by a subscriber starts a fresh window\n\t\tthis.cancel();\n\t\tthis.onDue();\n\t}\n\n\tprivate clearTimer(): void {\n\t\tif (this.timer != null) {\n\t\t\tclearTimeout(this.timer);\n\t\t\tthis.timer = undefined;\n\t\t}\n\t}\n}\n\n/**\n * Cap on retained working-tree paths per flush. The set otherwise grows without bound — while the window\n * is unfocused the session is suspended, so a branch switch or build accumulates every touched path until\n * refocus. Set low deliberately: ordinary edits are a handful of paths, anything past this was a\n * refresh-everything event anyway, and dropping past it only costs the matching consumers a\n * conservative refresh.\n */\nconst maxWorkingTreePaths = 1000;\n\n/**\n * Cap on retained ref names per flush, for the same reason (a fetch of many refs while suspended). Past it, a ref kind\n * is recorded unnamed — any ref of that kind may have moved — which only makes `mayHaveChangedRef` more conservative.\n */\nconst maxPendingRefNames = 1000;\n\n/**\n * Per-repository session that owns the debounce/coalesce/suspend\n * pipeline for both repo (.git) and working-tree changes.\n *\n * Multiple subscribers share the same pipeline — the shortest\n * requested debounce delay wins, and so does the shortest max-wait.\n */\nexport class RepositoryWatchSession implements UnifiedDisposable {\n\treadonly repoPath: string;\n\n\tprivate readonly repoEmitter = new Emitter<WatcherRepoChangeEvent>();\n\tprivate readonly repoSubscribers = new Map<number, DebounceTiming>(); // subscriberId → timing\n\tprivate repoNextId = 0;\n\tprivate readonly pendingRepoChanges = new Set<RepositoryChange>();\n\t/** Full ref names, ref directories and pseudo-refs whose files changed, flushed with {@link pendingRepoChanges} */\n\tprivate readonly pendingRepoRefs = new Set<string>();\n\t/** Ref kinds that changed without a name, flushed with {@link pendingRepoChanges} */\n\tprivate readonly pendingRepoUnnamedRefKinds = new Set<RefChangeKind>();\n\tprivate readonly repoClock: DebounceClock;\n\tprivate effectiveRepoMs: number;\n\tprivate effectiveRepoMaxWaitMs: number;\n\tprivate readonly defaultRepoMs: number;\n\tprivate readonly defaultRepoMaxWaitMs: number;\n\n\tprivate readonly workingTreeEmitter = new Emitter<WorkingTreeChangeEvent>();\n\tprivate readonly workingTreeSubscribers = new Map<number, DebounceTiming>();\n\tprivate workingTreeNextId = 0;\n\tprivate pendingWorkingTreePaths = new Set<string>();\n\tprivate pendingWorkingTreeIncomplete = false;\n\tprivate readonly workingTreeClock: DebounceClock;\n\tprivate effectiveWorkingTreeMs: number;\n\tprivate effectiveWorkingTreeMaxWaitMs: number;\n\tprivate readonly defaultWorkingTreeMs: number;\n\tprivate readonly defaultWorkingTreeMaxWaitMs: number;\n\n\tprivate _etagWorkingTree = 0;\n\tprivate _suspended: boolean;\n\tprivate _disposed = false;\n\n\tprivate readonly lifecycle?: WatchSessionLifecycle;\n\tprivate readonly onDidFireRepoChangeCallback?: (event: WatcherRepoChangeEvent) => void;\n\tprivate readonly onDidFireWorkingTreeChangeCallback?: (repoPath: string) => void;\n\n\tconstructor(options: WatchSessionOptions) {\n\t\tthis.repoPath = options.repoPath;\n\t\tthis.defaultRepoMs = options.defaultRepoDelayMs ?? defaultRepoDelay;\n\t\tthis.defaultWorkingTreeMs = options.defaultWorkingTreeDelayMs ?? defaultWorkingTreeDelay;\n\t\tthis.defaultRepoMaxWaitMs = options.defaultRepoMaxWaitMs ?? defaultRepoMaxWait;\n\t\tthis.defaultWorkingTreeMaxWaitMs = options.defaultWorkingTreeMaxWaitMs ?? defaultWorkingTreeMaxWait;\n\t\tthis.effectiveRepoMs = this.defaultRepoMs;\n\t\tthis.effectiveRepoMaxWaitMs = this.defaultRepoMaxWaitMs;\n\t\tthis.effectiveWorkingTreeMs = this.defaultWorkingTreeMs;\n\t\tthis.effectiveWorkingTreeMaxWaitMs = this.defaultWorkingTreeMaxWaitMs;\n\t\tthis.repoClock = new DebounceClock(\n\t\t\t() => this.effectiveRepoMs,\n\t\t\t() => this.effectiveRepoMaxWaitMs,\n\t\t\t() => this.flushRepo(),\n\t\t);\n\t\tthis.workingTreeClock = new DebounceClock(\n\t\t\t() => this.effectiveWorkingTreeMs,\n\t\t\t() => this.effectiveWorkingTreeMaxWaitMs,\n\t\t\t() => this.flushWorkingTree(),\n\t\t);\n\t\tthis.lifecycle = options.lifecycle;\n\t\tthis.onDidFireRepoChangeCallback = options.onDidFireRepoChange;\n\t\tthis.onDidFireWorkingTreeChangeCallback = options.onDidFireWorkingTreeChange;\n\t\tthis._suspended = options.suspended ?? false;\n\t\tif (this._suspended) {\n\t\t\tthis.repoClock.suspend();\n\t\t\tthis.workingTreeClock.suspend();\n\t\t}\n\t}\n\n\t[Symbol.dispose](): void {\n\t\tthis.dispose();\n\t}\n\n\tdispose(): void {\n\t\tif (this._disposed) return;\n\n\t\tthis._disposed = true;\n\n\t\tthis.repoClock.cancel();\n\t\tthis.workingTreeClock.cancel();\n\n\t\tthis.clearPendingRepoChanges();\n\t\tthis.pendingWorkingTreePaths.clear();\n\t\tthis.pendingWorkingTreeIncomplete = false;\n\t\tthis.repoSubscribers.clear();\n\t\tthis.workingTreeSubscribers.clear();\n\n\t\tthis.repoEmitter.dispose();\n\t\tthis.workingTreeEmitter.dispose();\n\t}\n\n\tget suspended(): boolean {\n\t\treturn this._suspended;\n\t}\n\n\tget hasPendingChanges(): boolean {\n\t\treturn this.pendingRepoChanges.size > 0 || this.hasPendingWorkingTreeChanges;\n\t}\n\n\t/**\n\t * An incomplete batch with no surviving paths still has to fire: the caller dropped paths we never saw, so\n\t * gating the pipeline on the retained set alone would swallow the change entirely.\n\t */\n\tprivate get hasPendingWorkingTreeChanges(): boolean {\n\t\treturn this.pendingWorkingTreePaths.size > 0 || this.pendingWorkingTreeIncomplete;\n\t}\n\n\tget repoSubscriberCount(): number {\n\t\treturn this.repoSubscribers.size;\n\t}\n\n\tget workingTreeSubscriberCount(): number {\n\t\treturn this.workingTreeSubscribers.size;\n\t}\n\n\t/**\n\t * Monotonic counter incremented immediately when working tree changes\n\t * are pushed (before debounce). Used by `GitCommit.hasFullDetails()`\n\t * to detect staleness of uncommitted commit data.\n\t */\n\tget etagWorkingTree(): number {\n\t\treturn this._etagWorkingTree;\n\t}\n\n\t/**\n\t * Subscribe to .git directory changes with configurable debounce.\n\t * First subscribe fires `onFirstRepoSubscriber`, last dispose\n\t * fires `onLastRepoSubscriber`.\n\t *\n\t * When multiple subscribers exist, the shortest debounce wins —\n\t * all subscribers fire on the same (fastest) cadence. `maxWaitMs` bounds how long continuous\n\t * activity can defer a flush (the shortest wins too); `Infinity` means no cap.\n\t *\n\t * `relevantRefs` narrows THIS subscription's events to the refs it cares about, given as full ref names\n\t * (`refs/heads/main`, `refs/remotes/origin/main`, `refs/tags/v1`, `refs/stash`). It is read at delivery, so it can\n\t * return the subscriber's current list. A `heads`/`remotes`/`tags`/`stash` change is delivered only when a listed\n\t * ref of that kind may have moved, when the change isn't attributable to a name (e.g. a `packed-refs` rewrite),\n\t * or, for `heads`, when a pseudo-ref (`HEAD`, `ORIG_HEAD`) moved — so `stash` and `tags` changes are dropped unless\n\t * a ref of that kind is listed. Other change kinds always pass, and an event left with no changes isn't delivered.\n\t * The filter never affects other subscribers, the debounce, or the global `onDidFireRepoChange` forwarding.\n\t */\n\tsubscribe(opts?: {\n\t\tdelayMs?: number;\n\t\tmaxWaitMs?: number;\n\t\trelevantRefs?: () => Iterable<string>;\n\t}): RepositorySubscription {\n\t\tif (this._disposed) {\n\t\t\treturn Object.assign(\n\t\t\t\tcreateDisposable(() => {}),\n\t\t\t\t{\n\t\t\t\t\tonDidChange: this.repoEmitter.event,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\n\t\tconst id = this.repoNextId++;\n\t\tconst wasEmpty = this.repoSubscribers.size === 0;\n\n\t\tthis.repoSubscribers.set(id, {\n\t\t\tdelayMs: opts?.delayMs ?? this.defaultRepoMs,\n\t\t\tmaxWaitMs: opts?.maxWaitMs ?? this.defaultRepoMaxWaitMs,\n\t\t});\n\t\tthis.recalcRepoTiming();\n\n\t\tif (wasEmpty) {\n\t\t\tthis.lifecycle?.onFirstRepoSubscriber?.();\n\t\t}\n\n\t\t// If the effective delay or max-wait got shorter while a flush is pending, move it sooner\n\t\tthis.repoClock.rearmIfEarlier();\n\n\t\tlet subDisposed = false;\n\t\treturn Object.assign(\n\t\t\tcreateDisposable(() => {\n\t\t\t\tif (subDisposed) return;\n\n\t\t\t\tsubDisposed = true;\n\n\t\t\t\tthis.repoSubscribers.delete(id);\n\t\t\t\tthis.recalcRepoTiming();\n\n\t\t\t\tif (this.repoSubscribers.size === 0) {\n\t\t\t\t\tthis.repoClock.cancel();\n\t\t\t\t\tthis.clearPendingRepoChanges();\n\t\t\t\t\tthis.lifecycle?.onLastRepoSubscriber?.();\n\t\t\t\t}\n\t\t\t}),\n\t\t\t{\n\t\t\t\tonDidChange:\n\t\t\t\t\topts?.relevantRefs != null\n\t\t\t\t\t\t? createRelevantRefsEvent(this.repoEmitter.event, opts.relevantRefs)\n\t\t\t\t\t\t: this.repoEmitter.event,\n\t\t\t},\n\t\t);\n\t}\n\n\t/** Subscribe to working tree changes; `delayMs` and `maxWaitMs` behave as in {@link subscribe}. */\n\tsubscribeToWorkingTree(opts?: { delayMs?: number; maxWaitMs?: number }): WorkingTreeSubscription {\n\t\tif (this._disposed) {\n\t\t\treturn Object.assign(\n\t\t\t\tcreateDisposable(() => {}),\n\t\t\t\t{\n\t\t\t\t\tonDidChangeWorkingTree: this.workingTreeEmitter.event,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\n\t\tconst id = this.workingTreeNextId++;\n\t\tconst wasEmpty = this.workingTreeSubscribers.size === 0;\n\n\t\tthis.workingTreeSubscribers.set(id, {\n\t\t\tdelayMs: opts?.delayMs ?? this.defaultWorkingTreeMs,\n\t\t\tmaxWaitMs: opts?.maxWaitMs ?? this.defaultWorkingTreeMaxWaitMs,\n\t\t});\n\t\tthis.recalcWorkingTreeTiming();\n\n\t\tif (wasEmpty) {\n\t\t\tthis.lifecycle?.onFirstWorkingTreeSubscriber?.();\n\t\t}\n\n\t\t// If the effective delay or max-wait got shorter while a flush is pending, move it sooner\n\t\tthis.workingTreeClock.rearmIfEarlier();\n\n\t\tlet subDisposed = false;\n\t\treturn Object.assign(\n\t\t\tcreateDisposable(() => {\n\t\t\t\tif (subDisposed) return;\n\n\t\t\t\tsubDisposed = true;\n\n\t\t\t\tthis.workingTreeSubscribers.delete(id);\n\t\t\t\tthis.recalcWorkingTreeTiming();\n\n\t\t\t\tif (this.workingTreeSubscribers.size === 0) {\n\t\t\t\t\tthis.workingTreeClock.cancel();\n\t\t\t\t\tthis.pendingWorkingTreePaths.clear();\n\t\t\t\t\t// Dropped with the paths it describes — a stale flag would make the next subscriber's\n\t\t\t\t\t// first ordinary flush claim a partial set and force needless full refreshes.\n\t\t\t\t\tthis.pendingWorkingTreeIncomplete = false;\n\t\t\t\t\tthis.lifecycle?.onLastWorkingTreeSubscriber?.();\n\t\t\t\t}\n\t\t\t}),\n\t\t\t{ onDidChangeWorkingTree: this.workingTreeEmitter.event },\n\t\t);\n\t}\n\n\tsuspend(): void {\n\t\tthis._suspended = true;\n\t\t// Cancel active timers — events continue to coalesce but don't fire\n\t\tthis.repoClock.suspend();\n\t\tthis.workingTreeClock.suspend();\n\t}\n\n\tresume(delayMs?: number): void {\n\t\tif (!this._suspended) return;\n\n\t\tthis._suspended = false;\n\n\t\t// Flush accumulated changes (a clock with nothing pending stays idle); the max-wait window restarts from now\n\t\tthis.repoClock.resume(delayMs);\n\t\tthis.workingTreeClock.resume(delayMs);\n\t}\n\n\t/**\n\t * Inject changes (e.g., after a git operation the caller performed). Goes through debounce + suspend. Ref kinds\n\t * among them are unnamed — any ref of that kind may have moved.\n\t */\n\tfireChange(...changes: RepositoryChange[]): void {\n\t\tif (this._disposed || changes.length === 0) return;\n\n\t\tthis.coalesceRepo(changes);\n\t}\n\n\t/** Immediate fire, bypassing debounce + suspend (for Closed/Opened). Ref kinds among them are unnamed. */\n\tfireChangeImmediate(...changes: RepositoryChange[]): void {\n\t\tif (this._disposed || changes.length === 0) return;\n\n\t\tconst event = new WatcherRepoChangeEvent(this.repoPath, changes);\n\t\t// Same contract as `flushRepo`: notify the global forwarder (which advances the cache's status clock) BEFORE\n\t\t// subscribers. Skipping it here would route changes to subscribers that no one advanced the clock for —\n\t\t// subscribers see them as session-routed and (correctly) don't re-advance, so the cache would go stale.\n\t\tthis.onDidFireRepoChangeCallback?.(event);\n\t\tthis.repoEmitter.fire(event);\n\t}\n\n\t/**\n\t * Push interpreted repo changes into the pipeline. `ref` is the full ref name (or ref directory, or pseudo-ref) whose\n\t * file changed; a ref kind in `changes` is attributed to it when the kinds match, and is otherwise unnamed.\n\t */\n\tpushRepoChanges(changes: RepositoryChange[], ref?: string): void {\n\t\tif (this._disposed) return;\n\n\t\tthis.coalesceRepo(changes, ref);\n\t}\n\n\t/** Push working tree file changes into the pipeline */\n\t/** `incomplete` marks the batch as partial when the caller already dropped paths of its own. */\n\tpushWorkingTreeChanges(paths: Iterable<string>, incomplete?: boolean): void {\n\t\tif (this._disposed) return;\n\n\t\tthis._etagWorkingTree++;\n\t\tif (incomplete) {\n\t\t\tthis.pendingWorkingTreeIncomplete = true;\n\t\t}\n\t\tthis.coalesceWorkingTrees(paths);\n\t}\n\n\tprivate static minDelay(subscribers: Map<number, DebounceTiming>, fallback: number): number {\n\t\tlet min = fallback;\n\t\tfor (const { delayMs } of subscribers.values()) {\n\t\t\tif (delayMs < min) {\n\t\t\t\tmin = delayMs;\n\t\t\t}\n\t\t}\n\t\treturn min;\n\t}\n\n\t/** Unlike the delay, the default isn't a ceiling — a subscriber can opt out of the cap with `Infinity` */\n\tprivate static minMaxWait(subscribers: Map<number, DebounceTiming>, fallback: number): number {\n\t\tif (subscribers.size === 0) return fallback;\n\n\t\tlet min = Infinity;\n\t\tfor (const { maxWaitMs } of subscribers.values()) {\n\t\t\tif (maxWaitMs < min) {\n\t\t\t\tmin = maxWaitMs;\n\t\t\t}\n\t\t}\n\t\treturn min;\n\t}\n\n\tprivate recalcRepoTiming(): void {\n\t\tthis.effectiveRepoMs = RepositoryWatchSession.minDelay(this.repoSubscribers, this.defaultRepoMs);\n\t\tthis.effectiveRepoMaxWaitMs = RepositoryWatchSession.minMaxWait(\n\t\t\tthis.repoSubscribers,\n\t\t\tthis.defaultRepoMaxWaitMs,\n\t\t);\n\t}\n\n\tprivate recalcWorkingTreeTiming(): void {\n\t\tthis.effectiveWorkingTreeMs = RepositoryWatchSession.minDelay(\n\t\t\tthis.workingTreeSubscribers,\n\t\t\tthis.defaultWorkingTreeMs,\n\t\t);\n\t\tthis.effectiveWorkingTreeMaxWaitMs = RepositoryWatchSession.minMaxWait(\n\t\t\tthis.workingTreeSubscribers,\n\t\t\tthis.defaultWorkingTreeMaxWaitMs,\n\t\t);\n\t}\n\n\tprivate flushRepo(): void {\n\t\tif (this.pendingRepoChanges.size === 0) return;\n\n\t\t// The event is built only here — raw events just add to the pending sets (which the event copies)\n\t\tconst event = new WatcherRepoChangeEvent(this.repoPath, [...this.pendingRepoChanges], {\n\t\t\tnames: this.pendingRepoRefs,\n\t\t\tunnamed: this.pendingRepoUnnamedRefKinds,\n\t\t});\n\t\tthis.clearPendingRepoChanges();\n\t\t// Advance any cache clock BEFORE notifying subscribers (same rationale as `flushWorkingTree`): a subscriber\n\t\t// that synchronously reads status must see the post-change generation, not a joinable pre-change run.\n\t\tthis.onDidFireRepoChangeCallback?.(event);\n\t\tthis.repoEmitter.fire(event);\n\t}\n\n\tprivate coalesceRepo(changes: RepositoryChange[], ref?: string): void {\n\t\tif (changes.length === 0) return;\n\n\t\tconst refKind = ref != null ? getRefChangeKind(ref) : undefined;\n\t\tfor (const change of changes) {\n\t\t\tthis.pendingRepoChanges.add(change);\n\t\t\tif (!isRefChangeKind(change)) continue;\n\n\t\t\tif (\n\t\t\t\tref != null &&\n\t\t\t\trefKind === change &&\n\t\t\t\t(this.pendingRepoRefs.size < maxPendingRefNames || this.pendingRepoRefs.has(ref))\n\t\t\t) {\n\t\t\t\tthis.pendingRepoRefs.add(ref);\n\t\t\t} else {\n\t\t\t\tthis.pendingRepoUnnamedRefKinds.add(change);\n\t\t\t}\n\t\t}\n\t\tthis.repoClock.note();\n\t}\n\n\tprivate clearPendingRepoChanges(): void {\n\t\tthis.pendingRepoChanges.clear();\n\t\tthis.pendingRepoRefs.clear();\n\t\tthis.pendingRepoUnnamedRefKinds.clear();\n\t}\n\n\tprivate flushWorkingTree(): void {\n\t\tif (!this.hasPendingWorkingTreeChanges) return;\n\n\t\tconst paths = this.pendingWorkingTreePaths;\n\t\tconst incomplete = this.pendingWorkingTreeIncomplete;\n\t\tthis.pendingWorkingTreePaths = new Set();\n\t\tthis.pendingWorkingTreeIncomplete = false;\n\t\t// Advance the cache's status clock (via WatchService's global forwarding) BEFORE notifying subscribers,\n\t\t// so a subscriber that synchronously reads status sees the post-change generation, not a joinable pre-change run.\n\t\tthis.onDidFireWorkingTreeChangeCallback?.(this.repoPath);\n\t\tthis.workingTreeEmitter.fire({ repoPath: this.repoPath, paths: paths, incomplete: incomplete });\n\t}\n\n\tprivate coalesceWorkingTrees(paths: Iterable<string>): void {\n\t\tfor (const p of paths) {\n\t\t\t// Stop retaining paths past the cap, but keep draining the iterable — the flag is what makes the\n\t\t\t// partial set safe to consume, and dropping out early would leave later paths for the next window.\n\t\t\tif (this.pendingWorkingTreePaths.size >= maxWorkingTreePaths && !this.pendingWorkingTreePaths.has(p)) {\n\t\t\t\tthis.pendingWorkingTreeIncomplete = true;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tthis.pendingWorkingTreePaths.add(p);\n\t\t}\n\n\t\tif (this.hasPendingWorkingTreeChanges) {\n\t\t\tthis.workingTreeClock.note();\n\t\t}\n\t}\n}\n\n/** Wraps a session's repo event so each delivery is narrowed to the subscriber's relevant refs (see `subscribe`) */\nfunction createRelevantRefsEvent(\n\tevent: Event<WatcherRepoChangeEvent>,\n\trelevantRefs: () => Iterable<string>,\n): Event<WatcherRepoChangeEvent> {\n\treturn (listener, thisArgs, disposables) =>\n\t\tevent(\n\t\t\te => {\n\t\t\t\tconst narrowed = narrowToRelevantRefs(e, relevantRefs);\n\t\t\t\tif (narrowed != null) {\n\t\t\t\t\tlistener.call(thisArgs, narrowed);\n\t\t\t\t}\n\t\t\t},\n\t\t\tundefined,\n\t\t\tdisposables,\n\t\t);\n}\n\n/**\n * Narrows an event to the refs a subscriber cares about. Returns the event itself when no change kind is removed (the\n * kept names then stay a superset, which only makes `mayHaveChangedRef` more conservative), `undefined` when every kind\n * is removed, and otherwise a new event with the kept kinds, names and unnamed kinds.\n */\nfunction narrowToRelevantRefs(\n\te: WatcherRepoChangeEvent,\n\trelevantRefs: () => Iterable<string>,\n): WatcherRepoChangeEvent | undefined {\n\t// Ref kinds that pass only if a relevant ref of that kind may have moved\n\tlet unresolved: Set<RefChangeKind> | undefined;\n\tfor (const change of e.changes) {\n\t\tif (!isRefChangeKind(change) || e.unnamedRefKinds.has(change)) continue;\n\n\t\t(unresolved ??= new Set()).add(change);\n\t}\n\tif (unresolved == null) return e;\n\n\tconst pseudoRefs: string[] = [];\n\tfor (const name of e.refs) {\n\t\tif (!name.startsWith('refs/')) {\n\t\t\tpseudoRefs.push(name);\n\t\t}\n\t}\n\tif (pseudoRefs.length !== 0) {\n\t\t// A pseudo-ref (`HEAD`, `ORIG_HEAD`) moving is a `heads` change for every subscriber\n\t\tunresolved.delete('heads');\n\t\tif (unresolved.size === 0) return e;\n\t}\n\n\t// Names that equal or cover a relevant ref — collected as we go, since `relevantRefs` may be a one-shot iterable\n\tconst keptNames = new Set<string>(pseudoRefs);\n\tfor (const ref of relevantRefs()) {\n\t\tconst kind = getRefChangeKind(ref);\n\t\tif (kind == null || !e.changes.has(kind)) continue;\n\n\t\t// `HEAD` is usually a symref to the checked-out branch, so any `heads` change may have moved it\n\t\tif (ref === 'HEAD') {\n\t\t\tfor (const name of e.refs) {\n\t\t\t\tif (getRefChangeKind(name) === 'heads') {\n\t\t\t\t\tkeptNames.add(name);\n\t\t\t\t}\n\t\t\t}\n\t\t\tunresolved.delete(kind);\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst covering = findCoveringRefName(e.refs, ref);\n\t\tif (covering == null) continue;\n\n\t\tkeptNames.add(covering);\n\t\tunresolved.delete(kind);\n\t}\n\tif (unresolved.size === 0) return e;\n\n\tconst keptChanges: RepositoryChange[] = [];\n\tfor (const change of e.changes) {\n\t\tif (!isRefChangeKind(change) || !unresolved.has(change)) {\n\t\t\tkeptChanges.push(change);\n\t\t}\n\t}\n\tif (keptChanges.length === 0) return undefined;\n\n\treturn new WatcherRepoChangeEvent(e.repoPath, keptChanges, {\n\t\tnames: keptNames,\n\t\tunnamed: e.unnamedRefKinds,\n\t});\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"watchSession.js","names":[],"sources":["../../../../git/src/watching/watchSession.ts"],"sourcesContent":["import type { UnifiedDisposable } from '@gitlens/utils/disposable.js';\nimport { createDisposable } from '@gitlens/utils/disposable.js';\nimport type { Event } from '@gitlens/utils/event.js';\nimport { Emitter } from '@gitlens/utils/event.js';\nimport type { RepositoryChange } from '../models/repository.js';\nimport type { RefChangeKind, WorkingTreeChangeEvent } from './changeEvent.js';\nimport { findCoveringRefName, getRefChangeKind, isRefChangeKind, WatcherRepoChangeEvent } from './changeEvent.js';\nimport { allWatchedRepositoryChanges } from './classifyChange.js';\n\nexport interface RepositorySubscription extends UnifiedDisposable {\n\treadonly onDidChange: Event<WatcherRepoChangeEvent>;\n}\n\nexport interface WorkingTreeSubscription extends UnifiedDisposable {\n\treadonly onDidChangeWorkingTree: Event<WorkingTreeChangeEvent>;\n}\n\n/**\n * Lifecycle callbacks fired by the session when subscriber counts\n * cross the 0↔1 boundary. The host (WatchService) uses these to\n * lazily create/destroy watchers.\n */\nexport interface WatchSessionLifecycle {\n\t/** Called when the first repo-change subscriber joins */\n\tonFirstRepoSubscriber?(): void;\n\t/** Called when the last repo-change subscriber leaves */\n\tonLastRepoSubscriber?(): void;\n\t/** Called when the first working-tree subscriber joins */\n\tonFirstWorkingTreeSubscriber?(): void;\n\t/** Called when the last working-tree subscriber leaves */\n\tonLastWorkingTreeSubscriber?(): void;\n}\n\nexport interface WatchSessionOptions {\n\treadonly repoPath: string;\n\t/** Default debounce for repo changes. Default: 250ms */\n\treadonly defaultRepoDelayMs?: number;\n\t/** Default debounce for working tree changes. Default: 2500ms */\n\treadonly defaultWorkingTreeDelayMs?: number;\n\t/**\n\t * Default cap on how long repo changes can be held back by continuous activity — a flush happens at most this long\n\t * after the first pending change, however long the burst lasts. `Infinity` disables the cap. Default: 2000ms\n\t */\n\treadonly defaultRepoMaxWaitMs?: number;\n\t/** Default cap on how long working tree changes can be held back by continuous activity. `Infinity` disables the cap. Default: 10000ms */\n\treadonly defaultWorkingTreeMaxWaitMs?: number;\n\treadonly lifecycle?: WatchSessionLifecycle;\n\t/** Initial suspended state — new sessions inherit the service-global suspend state. Default: false */\n\treadonly suspended?: boolean;\n\t/**\n\t * Called whenever a debounced repo change event is dispatched to subscribers.\n\t * Does NOT count as a subscriber — used by WatchService for\n\t * global event forwarding without affecting lifecycle callbacks.\n\t */\n\treadonly onDidFireRepoChange?: (event: WatcherRepoChangeEvent) => void;\n\t/**\n\t * Called whenever a debounced working-tree change is dispatched to subscribers. Like\n\t * {@link onDidFireRepoChange}, does NOT count as a subscriber — used by WatchService for global\n\t * event forwarding (the single point that drives the cache's status clock for working-tree edits).\n\t */\n\treadonly onDidFireWorkingTreeChange?: (repoPath: string) => void;\n}\n\nconst defaultRepoDelay = 250;\nconst defaultWorkingTreeDelay = 2500;\nconst defaultRepoMaxWait = 2000;\nconst defaultWorkingTreeMaxWait = 10000;\n\n/** Largest delay `setTimeout` honors (a signed 32-bit int); anything longer fires immediately */\nconst maxTimerDelay = 0x7fffffff;\n\ninterface DebounceTiming {\n\treadonly delayMs: number;\n\treadonly maxWaitMs: number;\n}\n\n/**\n * Trailing-deadline debounce with a max-wait, shared by the repo and working-tree pipelines.\n *\n * Recording an event costs a `performance.now()` and, only when no timer is armed, one `setTimeout` — never a\n * `clearTimeout`/`setTimeout` pair per event, which is what made a busy repo allocate (and starve) on every raw\n * watcher event. The armed timer is a deadline, not a countdown: when it fires it recomputes the due time from the\n * latest timestamps and either flushes or re-arms for the remainder. The due time is\n * `min(lastEventAt + delay, firstPendingAt + maxWait)`, so continuous activity is flushed at the max-wait instead of\n * never. Monotonic time, so a wall-clock change can't push a deadline out.\n */\nclass DebounceClock {\n\tprivate active = false;\n\tprivate paused = false;\n\tprivate firstAt = 0;\n\tprivate lastAt = 0;\n\tprivate timer: ReturnType<typeof setTimeout> | undefined;\n\tprivate timerDueAt = 0;\n\t/** Absolute flush time set by {@link resume}; dropped by the next event, which returns to normal debounce */\n\tprivate resumeDueAt: number | undefined;\n\n\tconstructor(\n\t\tprivate readonly getDelayMs: () => number,\n\t\tprivate readonly getMaxWaitMs: () => number,\n\t\tprivate readonly onDue: () => void,\n\t) {}\n\n\t/** Records an event; arms the timer only if none is armed. Cheap enough to call per raw event. */\n\tnote(): void {\n\t\tconst now = performance.now();\n\t\tif (!this.active) {\n\t\t\tthis.active = true;\n\t\t\tthis.firstAt = now;\n\t\t}\n\t\tthis.lastAt = now;\n\n\t\tif (this.paused) return;\n\n\t\tif (this.resumeDueAt != null) {\n\t\t\t// The first event after a resume goes back to normal debounce — which may be due sooner than the resume deadline\n\t\t\tthis.resumeDueAt = undefined;\n\t\t\tthis.rearmIfEarlier();\n\t\t}\n\n\t\tif (this.timer == null) {\n\t\t\tthis.arm(this.getDueAt(), now);\n\t\t}\n\t}\n\n\t/** Moves the armed timer earlier when the due time shrank (a subscriber joined with a shorter delay or max-wait) */\n\trearmIfEarlier(): void {\n\t\tif (!this.active || this.paused || this.timer == null) return;\n\n\t\tconst dueAt = this.getDueAt();\n\t\tif (dueAt >= this.timerDueAt) return;\n\n\t\tclearTimeout(this.timer);\n\t\tthis.arm(dueAt, performance.now());\n\t}\n\n\t/** Cancels the timer; events keep accumulating but nothing is due until {@link resume} */\n\tsuspend(): void {\n\t\tthis.paused = true;\n\t\tthis.clearTimer();\n\t}\n\n\t/**\n\t * Restarts the max-wait window from now and schedules the flush for `delayMs` from now (the effective delay when\n\t * omitted). Does nothing if no events are pending.\n\t */\n\tresume(delayMs?: number): void {\n\t\tthis.paused = false;\n\t\tif (!this.active) return;\n\n\t\tconst now = performance.now();\n\t\tthis.firstAt = now;\n\t\tthis.resumeDueAt = now + (delayMs ?? this.getDelayMs());\n\t\tthis.clearTimer();\n\t\tthis.arm(this.resumeDueAt, now);\n\t}\n\n\t/** Drops the pending window and the timer */\n\tcancel(): void {\n\t\tthis.active = false;\n\t\tthis.resumeDueAt = undefined;\n\t\tthis.clearTimer();\n\t}\n\n\tprivate getDueAt(): number {\n\t\treturn this.resumeDueAt ?? Math.min(this.lastAt + this.getDelayMs(), this.firstAt + this.getMaxWaitMs());\n\t}\n\n\tprivate arm(dueAt: number, now: number): void {\n\t\tthis.timerDueAt = dueAt;\n\t\tthis.timer = setTimeout(() => this.onTimer(), Math.min(Math.max(0, dueAt - now), maxTimerDelay));\n\t}\n\n\tprivate onTimer(): void {\n\t\tthis.timer = undefined;\n\t\tif (!this.active || this.paused) return;\n\n\t\tconst now = performance.now();\n\t\tconst dueAt = this.getDueAt();\n\t\tif (dueAt > now) {\n\t\t\tthis.arm(dueAt, now);\n\t\t\treturn;\n\t\t}\n\n\t\t// Reset before firing so an event raised by a subscriber starts a fresh window\n\t\tthis.cancel();\n\t\tthis.onDue();\n\t}\n\n\tprivate clearTimer(): void {\n\t\tif (this.timer != null) {\n\t\t\tclearTimeout(this.timer);\n\t\t\tthis.timer = undefined;\n\t\t}\n\t}\n}\n\n/**\n * Cap on retained working-tree paths per flush. The set otherwise grows without bound — while the window\n * is unfocused the session is suspended, so a branch switch or build accumulates every touched path until\n * refocus. Set low deliberately: ordinary edits are a handful of paths, anything past this was a\n * refresh-everything event anyway, and dropping past it only costs the matching consumers a\n * conservative refresh.\n */\nconst maxWorkingTreePaths = 1000;\n\n/**\n * Cap on retained ref names per flush, for the same reason (a fetch of many refs while suspended). Past it, a ref kind\n * is recorded unnamed — any ref of that kind may have moved — which only makes `mayHaveChangedRef` more conservative.\n */\nconst maxPendingRefNames = 1000;\n\n/**\n * Per-repository session that owns the debounce/coalesce/suspend\n * pipeline for both repo (.git) and working-tree changes.\n *\n * Multiple subscribers share the same pipeline — the shortest\n * requested debounce delay wins, and so does the shortest max-wait.\n */\nexport class RepositoryWatchSession implements UnifiedDisposable {\n\treadonly repoPath: string;\n\n\tprivate readonly repoEmitter = new Emitter<WatcherRepoChangeEvent>();\n\tprivate readonly repoSubscribers = new Map<number, DebounceTiming>(); // subscriberId → timing\n\tprivate repoNextId = 0;\n\t/** Each repo subscriber's `relevantRefs`, for {@link getRelevantRefs} */\n\tprivate readonly relevantRefsSources = new Map<number, () => Iterable<string>>(); // subscriberId → relevantRefs\n\tprivate readonly pendingRepoChanges = new Set<RepositoryChange>();\n\t/** Full ref names, ref directories and pseudo-refs whose files changed, flushed with {@link pendingRepoChanges} */\n\tprivate readonly pendingRepoRefs = new Set<string>();\n\t/** Ref kinds that changed without a name, flushed with {@link pendingRepoChanges} */\n\tprivate readonly pendingRepoUnnamedRefKinds = new Set<RefChangeKind>();\n\t/** Kinds among {@link pendingRepoChanges} injected or pushed unfenced, which an announcement never replaces */\n\tprivate readonly pendingRepoUnfencedChanges = new Set<RepositoryChange>();\n\t/**\n\t * Kinds among {@link pendingRepoChanges} that only core's announced writes reported, with when each was announced\n\t * (see {@link noteSelfWrite})\n\t */\n\tprivate readonly pendingRepoAnnouncedAt = new Map<RepositoryChange, number>();\n\tprivate readonly repoClock: DebounceClock;\n\tprivate effectiveRepoMs: number;\n\tprivate effectiveRepoMaxWaitMs: number;\n\tprivate readonly defaultRepoMs: number;\n\tprivate readonly defaultRepoMaxWaitMs: number;\n\n\tprivate readonly workingTreeEmitter = new Emitter<WorkingTreeChangeEvent>();\n\tprivate readonly workingTreeSubscribers = new Map<number, DebounceTiming>();\n\tprivate workingTreeNextId = 0;\n\tprivate pendingWorkingTreePaths = new Set<string>();\n\tprivate pendingWorkingTreeIncomplete = false;\n\tprivate readonly workingTreeClock: DebounceClock;\n\tprivate effectiveWorkingTreeMs: number;\n\tprivate effectiveWorkingTreeMaxWaitMs: number;\n\tprivate readonly defaultWorkingTreeMs: number;\n\tprivate readonly defaultWorkingTreeMaxWaitMs: number;\n\n\tprivate _etagWorkingTree = 0;\n\tprivate _suspended: boolean;\n\tprivate _disposed = false;\n\n\tprivate readonly lifecycle?: WatchSessionLifecycle;\n\tprivate readonly onDidFireRepoChangeCallback?: (event: WatcherRepoChangeEvent) => void;\n\tprivate readonly onDidFireWorkingTreeChangeCallback?: (repoPath: string) => void;\n\n\tconstructor(options: WatchSessionOptions) {\n\t\tthis.repoPath = options.repoPath;\n\t\tthis.defaultRepoMs = options.defaultRepoDelayMs ?? defaultRepoDelay;\n\t\tthis.defaultWorkingTreeMs = options.defaultWorkingTreeDelayMs ?? defaultWorkingTreeDelay;\n\t\tthis.defaultRepoMaxWaitMs = options.defaultRepoMaxWaitMs ?? defaultRepoMaxWait;\n\t\tthis.defaultWorkingTreeMaxWaitMs = options.defaultWorkingTreeMaxWaitMs ?? defaultWorkingTreeMaxWait;\n\t\tthis.effectiveRepoMs = this.defaultRepoMs;\n\t\tthis.effectiveRepoMaxWaitMs = this.defaultRepoMaxWaitMs;\n\t\tthis.effectiveWorkingTreeMs = this.defaultWorkingTreeMs;\n\t\tthis.effectiveWorkingTreeMaxWaitMs = this.defaultWorkingTreeMaxWaitMs;\n\t\tthis.repoClock = new DebounceClock(\n\t\t\t() => this.effectiveRepoMs,\n\t\t\t() => this.effectiveRepoMaxWaitMs,\n\t\t\t() => this.flushRepo(),\n\t\t);\n\t\tthis.workingTreeClock = new DebounceClock(\n\t\t\t() => this.effectiveWorkingTreeMs,\n\t\t\t() => this.effectiveWorkingTreeMaxWaitMs,\n\t\t\t() => this.flushWorkingTree(),\n\t\t);\n\t\tthis.lifecycle = options.lifecycle;\n\t\tthis.onDidFireRepoChangeCallback = options.onDidFireRepoChange;\n\t\tthis.onDidFireWorkingTreeChangeCallback = options.onDidFireWorkingTreeChange;\n\t\tthis._suspended = options.suspended ?? false;\n\t\tif (this._suspended) {\n\t\t\tthis.repoClock.suspend();\n\t\t\tthis.workingTreeClock.suspend();\n\t\t}\n\t}\n\n\t[Symbol.dispose](): void {\n\t\tthis.dispose();\n\t}\n\n\tdispose(): void {\n\t\tif (this._disposed) return;\n\n\t\tthis._disposed = true;\n\n\t\tthis.repoClock.cancel();\n\t\tthis.workingTreeClock.cancel();\n\n\t\tthis.clearPendingRepoChanges();\n\t\tthis.pendingWorkingTreePaths.clear();\n\t\tthis.pendingWorkingTreeIncomplete = false;\n\t\tthis.repoSubscribers.clear();\n\t\tthis.relevantRefsSources.clear();\n\t\tthis.workingTreeSubscribers.clear();\n\n\t\tthis.repoEmitter.dispose();\n\t\tthis.workingTreeEmitter.dispose();\n\t}\n\n\tget suspended(): boolean {\n\t\treturn this._suspended;\n\t}\n\n\tget hasPendingChanges(): boolean {\n\t\treturn this.pendingRepoChanges.size > 0 || this.hasPendingWorkingTreeChanges;\n\t}\n\n\t/**\n\t * An incomplete batch with no surviving paths still has to fire: the caller dropped paths we never saw, so\n\t * gating the pipeline on the retained set alone would swallow the change entirely.\n\t */\n\tprivate get hasPendingWorkingTreeChanges(): boolean {\n\t\treturn this.pendingWorkingTreePaths.size > 0 || this.pendingWorkingTreeIncomplete;\n\t}\n\n\tget repoSubscriberCount(): number {\n\t\treturn this.repoSubscribers.size;\n\t}\n\n\tget workingTreeSubscriberCount(): number {\n\t\treturn this.workingTreeSubscribers.size;\n\t}\n\n\t/**\n\t * Monotonic counter incremented immediately when working tree changes\n\t * are pushed (before debounce). Used by `GitCommit.hasFullDetails()`\n\t * to detect staleness of uncommitted commit data.\n\t */\n\tget etagWorkingTree(): number {\n\t\treturn this._etagWorkingTree;\n\t}\n\n\t/**\n\t * Subscribe to .git directory changes with configurable debounce.\n\t * First subscribe fires `onFirstRepoSubscriber`, last dispose\n\t * fires `onLastRepoSubscriber`.\n\t *\n\t * When multiple subscribers exist, the shortest debounce wins —\n\t * all subscribers fire on the same (fastest) cadence. `maxWaitMs` bounds how long continuous\n\t * activity can defer a flush (the shortest wins too); `Infinity` means no cap.\n\t *\n\t * `relevantRefs` narrows THIS subscription's events to the refs it cares about, given as full ref names\n\t * (`refs/heads/main`, `refs/remotes/origin/main`, `refs/tags/v1`, `refs/stash`). It is read at delivery, so it can\n\t * return the subscriber's current list. A `heads`/`remotes`/`tags`/`stash` change is delivered only when a listed\n\t * ref of that kind may have moved, when the change isn't attributable to a name (e.g. a `packed-refs` rewrite),\n\t * or, for `heads`, when a pseudo-ref (`HEAD`, `ORIG_HEAD`) moved — so `stash` and `tags` changes are dropped unless\n\t * a ref of that kind is listed. Other change kinds always pass, and an event left with no changes isn't delivered.\n\t * The filter never affects other subscribers, the debounce, or the global `onDidFireRepoChange` forwarding.\n\t */\n\tsubscribe(opts?: {\n\t\tdelayMs?: number;\n\t\tmaxWaitMs?: number;\n\t\trelevantRefs?: () => Iterable<string>;\n\t}): RepositorySubscription {\n\t\tif (this._disposed) {\n\t\t\treturn Object.assign(\n\t\t\t\tcreateDisposable(() => {}),\n\t\t\t\t{\n\t\t\t\t\tonDidChange: this.repoEmitter.event,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\n\t\tconst id = this.repoNextId++;\n\t\tconst wasEmpty = this.repoSubscribers.size === 0;\n\n\t\tthis.repoSubscribers.set(id, {\n\t\t\tdelayMs: opts?.delayMs ?? this.defaultRepoMs,\n\t\t\tmaxWaitMs: opts?.maxWaitMs ?? this.defaultRepoMaxWaitMs,\n\t\t});\n\t\tthis.recalcRepoTiming();\n\n\t\tconst relevantRefs = opts?.relevantRefs;\n\t\tif (relevantRefs != null) {\n\t\t\tthis.relevantRefsSources.set(id, relevantRefs);\n\t\t}\n\n\t\tif (wasEmpty) {\n\t\t\tthis.lifecycle?.onFirstRepoSubscriber?.();\n\t\t}\n\n\t\t// If the effective delay or max-wait got shorter while a flush is pending, move it sooner\n\t\tthis.repoClock.rearmIfEarlier();\n\n\t\tlet subDisposed = false;\n\t\treturn Object.assign(\n\t\t\tcreateDisposable(() => {\n\t\t\t\tif (subDisposed) return;\n\n\t\t\t\tsubDisposed = true;\n\n\t\t\t\tthis.repoSubscribers.delete(id);\n\t\t\t\tthis.relevantRefsSources.delete(id);\n\t\t\t\tthis.recalcRepoTiming();\n\n\t\t\t\tif (this.repoSubscribers.size === 0) {\n\t\t\t\t\tthis.repoClock.cancel();\n\t\t\t\t\tthis.clearPendingRepoChanges();\n\t\t\t\t\tthis.lifecycle?.onLastRepoSubscriber?.();\n\t\t\t\t}\n\t\t\t}),\n\t\t\t{\n\t\t\t\tonDidChange:\n\t\t\t\t\trelevantRefs != null\n\t\t\t\t\t\t? createRelevantRefsEvent(this.repoEmitter.event, relevantRefs)\n\t\t\t\t\t\t: this.repoEmitter.event,\n\t\t\t},\n\t\t);\n\t}\n\n\t/** Every repo subscriber's current `relevantRefs` (see {@link subscribe}), unioned */\n\tgetRelevantRefs(): Set<string> {\n\t\tconst refs = new Set<string>();\n\t\tfor (const relevantRefs of this.relevantRefsSources.values()) {\n\t\t\tfor (const ref of relevantRefs()) {\n\t\t\t\trefs.add(ref);\n\t\t\t}\n\t\t}\n\t\treturn refs;\n\t}\n\n\t/** Subscribe to working tree changes; `delayMs` and `maxWaitMs` behave as in {@link subscribe}. */\n\tsubscribeToWorkingTree(opts?: { delayMs?: number; maxWaitMs?: number }): WorkingTreeSubscription {\n\t\tif (this._disposed) {\n\t\t\treturn Object.assign(\n\t\t\t\tcreateDisposable(() => {}),\n\t\t\t\t{\n\t\t\t\t\tonDidChangeWorkingTree: this.workingTreeEmitter.event,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\n\t\tconst id = this.workingTreeNextId++;\n\t\tconst wasEmpty = this.workingTreeSubscribers.size === 0;\n\n\t\tthis.workingTreeSubscribers.set(id, {\n\t\t\tdelayMs: opts?.delayMs ?? this.defaultWorkingTreeMs,\n\t\t\tmaxWaitMs: opts?.maxWaitMs ?? this.defaultWorkingTreeMaxWaitMs,\n\t\t});\n\t\tthis.recalcWorkingTreeTiming();\n\n\t\tif (wasEmpty) {\n\t\t\tthis.lifecycle?.onFirstWorkingTreeSubscriber?.();\n\t\t}\n\n\t\t// If the effective delay or max-wait got shorter while a flush is pending, move it sooner\n\t\tthis.workingTreeClock.rearmIfEarlier();\n\n\t\tlet subDisposed = false;\n\t\treturn Object.assign(\n\t\t\tcreateDisposable(() => {\n\t\t\t\tif (subDisposed) return;\n\n\t\t\t\tsubDisposed = true;\n\n\t\t\t\tthis.workingTreeSubscribers.delete(id);\n\t\t\t\tthis.recalcWorkingTreeTiming();\n\n\t\t\t\tif (this.workingTreeSubscribers.size === 0) {\n\t\t\t\t\tthis.workingTreeClock.cancel();\n\t\t\t\t\tthis.pendingWorkingTreePaths.clear();\n\t\t\t\t\t// Dropped with the paths it describes — a stale flag would make the next subscriber's\n\t\t\t\t\t// first ordinary flush claim a partial set and force needless full refreshes.\n\t\t\t\t\tthis.pendingWorkingTreeIncomplete = false;\n\t\t\t\t\tthis.lifecycle?.onLastWorkingTreeSubscriber?.();\n\t\t\t\t}\n\t\t\t}),\n\t\t\t{ onDidChangeWorkingTree: this.workingTreeEmitter.event },\n\t\t);\n\t}\n\n\tsuspend(): void {\n\t\tthis._suspended = true;\n\t\t// Cancel active timers — events continue to coalesce but don't fire\n\t\tthis.repoClock.suspend();\n\t\tthis.workingTreeClock.suspend();\n\t}\n\n\tresume(delayMs?: number): void {\n\t\tif (!this._suspended) return;\n\n\t\tthis._suspended = false;\n\n\t\t// Flush accumulated changes (a clock with nothing pending stays idle); the max-wait window restarts from now\n\t\tthis.repoClock.resume(delayMs);\n\t\tthis.workingTreeClock.resume(delayMs);\n\t}\n\n\t/**\n\t * Inject changes (e.g., after a git operation the caller performed). Goes through debounce + suspend. Ref kinds\n\t * among them are unnamed — any ref of that kind may have moved.\n\t */\n\tfireChange(...changes: RepositoryChange[]): void {\n\t\tif (this._disposed || changes.length === 0) return;\n\n\t\tthis.coalesceRepo(changes, undefined, 'unfenced');\n\t}\n\n\t/** Immediate fire, bypassing debounce + suspend (for Closed/Opened). Ref kinds among them are unnamed. */\n\tfireChangeImmediate(...changes: RepositoryChange[]): void {\n\t\tif (this._disposed || changes.length === 0) return;\n\n\t\tconst event = new WatcherRepoChangeEvent(this.repoPath, changes);\n\t\t// Same contract as `flushRepo`: notify the global forwarder (which advances the cache's status clock) BEFORE\n\t\t// subscribers. Skipping it here would route changes to subscribers that no one advanced the clock for —\n\t\t// subscribers see them as session-routed and (correctly) don't re-advance, so the cache would go stale.\n\t\tthis.onDidFireRepoChangeCallback?.(event);\n\t\tthis.repoEmitter.fire(event);\n\t}\n\n\t/**\n\t * Push interpreted repo changes into the pipeline. `ref` is the full ref name (or ref directory, or pseudo-ref) whose\n\t * file changed; a ref kind in `changes` is attributed to it when the kinds match, and is otherwise unnamed.\n\t */\n\tpushRepoChanges(changes: RepositoryChange[], ref?: string): void {\n\t\tif (this._disposed) return;\n\n\t\tthis.coalesceRepo(changes, ref, 'raw');\n\t}\n\n\t/**\n\t * Push watcher-observed repo changes that no write of core's can account for — an overflow's rescan, or an ignore\n\t * rule changed in the working tree — so {@link noteSelfWrite} never replaces them or takes them for its echo. Ref\n\t * kinds are unnamed.\n\t */\n\tpushUnfencedRepoChanges(changes: RepositoryChange[]): void {\n\t\tif (this._disposed) return;\n\n\t\tthis.coalesceRepo(changes, undefined, 'unfenced');\n\t}\n\n\t/**\n\t * Core announced one of its own writes for this repository: its caches were evicted and its host notified, both\n\t * after the write finished. The announcement becomes this session's one event for the write:\n\t *\n\t * - raw changes of an announced kind still pending are replaced by it — they happened before the announcement,\n\t * which already covers them, whether they came from the write or from an outside change;\n\t * - the announced kinds are pushed through the debounce and suspend like {@link fireChange}, ref kinds unnamed,\n\t * and the event carries them in `announced`;\n\t * - raw changes of an announced kind that arrive later are never dropped. One arriving within the repo debounce\n\t * delay of the announcement, before its event fires, is taken for the write's own late echo and keeps the kind\n\t * announced; any other report of the kind before the event fires clears the mark. Once it fires, nothing is\n\t * special.\n\t *\n\t * The trade-off: an echo the watcher delivers after the announced event fired causes one extra, unannounced event\n\t * (a duplicate refresh), rather than risk losing an outside change of the same kind.\n\t *\n\t * Empty `changes`, or `'unknown'`, announces every kind a watcher can report (never `'unknown'` itself, which a\n\t * cache reads as a close); `'closed'` and `'opened'` are ignored. Only raw `.git` changes are replaced or kept\n\t * announced: {@link fireChange}, {@link fireChangeImmediate}, {@link pushUnfencedRepoChanges} and working-tree\n\t * changes always pass.\n\t */\n\tnoteSelfWrite(changes: readonly RepositoryChange[]): void {\n\t\tif (this._disposed) return;\n\n\t\tconst all = changes.length === 0 || changes.includes('unknown');\n\t\tconst announced = all ? allWatchedRepositoryChanges : changes.filter(c => isAnnounceable(c));\n\n\t\tfor (const change of this.pendingRepoChanges) {\n\t\t\tif (this.pendingRepoUnfencedChanges.has(change) || !isAnnounceable(change)) continue;\n\t\t\tif (!all && !changes.includes(change)) continue;\n\n\t\t\tthis.pendingRepoChanges.delete(change);\n\t\t\tthis.pendingRepoAnnouncedAt.delete(change);\n\t\t\tif (!isRefChangeKind(change)) continue;\n\n\t\t\t// Announcements carry no ref names, so the replaced kind takes its named refs with it\n\t\t\tthis.pendingRepoUnnamedRefKinds.delete(change);\n\t\t\tfor (const ref of this.pendingRepoRefs) {\n\t\t\t\tif (getRefChangeKind(ref) === change) {\n\t\t\t\t\tthis.pendingRepoRefs.delete(ref);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (announced.length !== 0) {\n\t\t\tthis.coalesceRepo(announced, undefined, 'announced');\n\t\t} else if (this.pendingRepoChanges.size === 0) {\n\t\t\t// Nothing left to flush: idle the clock rather than let it fire an empty window\n\t\t\tthis.repoClock.cancel();\n\t\t}\n\t}\n\n\t/** Push working tree file changes into the pipeline */\n\t/** `incomplete` marks the batch as partial when the caller already dropped paths of its own. */\n\tpushWorkingTreeChanges(paths: Iterable<string>, incomplete?: boolean): void {\n\t\tif (this._disposed) return;\n\n\t\tthis._etagWorkingTree++;\n\t\tif (incomplete) {\n\t\t\tthis.pendingWorkingTreeIncomplete = true;\n\t\t}\n\t\tthis.coalesceWorkingTrees(paths);\n\t}\n\n\tprivate static minDelay(subscribers: Map<number, DebounceTiming>, fallback: number): number {\n\t\tlet min = fallback;\n\t\tfor (const { delayMs } of subscribers.values()) {\n\t\t\tif (delayMs < min) {\n\t\t\t\tmin = delayMs;\n\t\t\t}\n\t\t}\n\t\treturn min;\n\t}\n\n\t/** Unlike the delay, the default isn't a ceiling — a subscriber can opt out of the cap with `Infinity` */\n\tprivate static minMaxWait(subscribers: Map<number, DebounceTiming>, fallback: number): number {\n\t\tif (subscribers.size === 0) return fallback;\n\n\t\tlet min = Infinity;\n\t\tfor (const { maxWaitMs } of subscribers.values()) {\n\t\t\tif (maxWaitMs < min) {\n\t\t\t\tmin = maxWaitMs;\n\t\t\t}\n\t\t}\n\t\treturn min;\n\t}\n\n\tprivate recalcRepoTiming(): void {\n\t\tthis.effectiveRepoMs = RepositoryWatchSession.minDelay(this.repoSubscribers, this.defaultRepoMs);\n\t\tthis.effectiveRepoMaxWaitMs = RepositoryWatchSession.minMaxWait(\n\t\t\tthis.repoSubscribers,\n\t\t\tthis.defaultRepoMaxWaitMs,\n\t\t);\n\t}\n\n\tprivate recalcWorkingTreeTiming(): void {\n\t\tthis.effectiveWorkingTreeMs = RepositoryWatchSession.minDelay(\n\t\t\tthis.workingTreeSubscribers,\n\t\t\tthis.defaultWorkingTreeMs,\n\t\t);\n\t\tthis.effectiveWorkingTreeMaxWaitMs = RepositoryWatchSession.minMaxWait(\n\t\t\tthis.workingTreeSubscribers,\n\t\t\tthis.defaultWorkingTreeMaxWaitMs,\n\t\t);\n\t}\n\n\tprivate flushRepo(): void {\n\t\tif (this.pendingRepoChanges.size === 0) return;\n\n\t\t// The event is built only here — raw events just add to the pending sets (which the event copies)\n\t\tconst event = new WatcherRepoChangeEvent(\n\t\t\tthis.repoPath,\n\t\t\t[...this.pendingRepoChanges],\n\t\t\t{ names: this.pendingRepoRefs, unnamed: this.pendingRepoUnnamedRefKinds },\n\t\t\tthis.pendingRepoAnnouncedAt.keys(),\n\t\t);\n\t\tthis.clearPendingRepoChanges();\n\t\t// Advance any cache clock BEFORE notifying subscribers (same rationale as `flushWorkingTree`): a subscriber\n\t\t// that synchronously reads status must see the post-change generation, not a joinable pre-change run.\n\t\tthis.onDidFireRepoChangeCallback?.(event);\n\t\tthis.repoEmitter.fire(event);\n\t}\n\n\t/**\n\t * `origin` is a raw `.git` event, a push no announcement replaces ({@link fireChange},\n\t * {@link pushUnfencedRepoChanges}), or core's announced write. A kind stays announced only while nothing else\n\t * reports it in the pending window, save the write's own late echo (see {@link noteSelfWrite}).\n\t */\n\tprivate coalesceRepo(\n\t\tchanges: readonly RepositoryChange[],\n\t\tref: string | undefined,\n\t\torigin: 'raw' | 'unfenced' | 'announced',\n\t): void {\n\t\tif (changes.length === 0) return;\n\n\t\tconst now = performance.now();\n\t\tconst refKind = ref != null ? getRefChangeKind(ref) : undefined;\n\t\tfor (const change of changes) {\n\t\t\tif (origin === 'announced') {\n\t\t\t\tif (!this.pendingRepoChanges.has(change)) {\n\t\t\t\t\tthis.pendingRepoAnnouncedAt.set(change, now);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tconst announcedAt = origin === 'raw' ? this.pendingRepoAnnouncedAt.get(change) : undefined;\n\t\t\t\tif (announcedAt == null || now - announcedAt > this.effectiveRepoMs) {\n\t\t\t\t\tthis.pendingRepoAnnouncedAt.delete(change);\n\t\t\t\t}\n\t\t\t\tif (origin === 'unfenced') {\n\t\t\t\t\tthis.pendingRepoUnfencedChanges.add(change);\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.pendingRepoChanges.add(change);\n\t\t\tif (!isRefChangeKind(change)) continue;\n\n\t\t\tif (\n\t\t\t\tref != null &&\n\t\t\t\trefKind === change &&\n\t\t\t\t(this.pendingRepoRefs.size < maxPendingRefNames || this.pendingRepoRefs.has(ref))\n\t\t\t) {\n\t\t\t\tthis.pendingRepoRefs.add(ref);\n\t\t\t} else {\n\t\t\t\tthis.pendingRepoUnnamedRefKinds.add(change);\n\t\t\t}\n\t\t}\n\t\tthis.repoClock.note();\n\t}\n\n\tprivate clearPendingRepoChanges(): void {\n\t\tthis.pendingRepoChanges.clear();\n\t\tthis.pendingRepoRefs.clear();\n\t\tthis.pendingRepoUnnamedRefKinds.clear();\n\t\tthis.pendingRepoUnfencedChanges.clear();\n\t\tthis.pendingRepoAnnouncedAt.clear();\n\t}\n\n\tprivate flushWorkingTree(): void {\n\t\tif (!this.hasPendingWorkingTreeChanges) return;\n\n\t\tconst paths = this.pendingWorkingTreePaths;\n\t\tconst incomplete = this.pendingWorkingTreeIncomplete;\n\t\tthis.pendingWorkingTreePaths = new Set();\n\t\tthis.pendingWorkingTreeIncomplete = false;\n\t\t// Advance the cache's status clock (via WatchService's global forwarding) BEFORE notifying subscribers,\n\t\t// so a subscriber that synchronously reads status sees the post-change generation, not a joinable pre-change run.\n\t\tthis.onDidFireWorkingTreeChangeCallback?.(this.repoPath);\n\t\tthis.workingTreeEmitter.fire({ repoPath: this.repoPath, paths: paths, incomplete: incomplete });\n\t}\n\n\tprivate coalesceWorkingTrees(paths: Iterable<string>): void {\n\t\tfor (const p of paths) {\n\t\t\t// Stop retaining paths past the cap, but keep draining the iterable — the flag is what makes the\n\t\t\t// partial set safe to consume, and dropping out early would leave later paths for the next window.\n\t\t\tif (this.pendingWorkingTreePaths.size >= maxWorkingTreePaths && !this.pendingWorkingTreePaths.has(p)) {\n\t\t\t\tthis.pendingWorkingTreeIncomplete = true;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tthis.pendingWorkingTreePaths.add(p);\n\t\t}\n\n\t\tif (this.hasPendingWorkingTreeChanges) {\n\t\t\tthis.workingTreeClock.note();\n\t\t}\n\t}\n}\n\n/** `'closed'` and `'opened'` are lifecycle, not a write's effect, so an announcement never replaces or names them */\nfunction isAnnounceable(change: RepositoryChange): boolean {\n\treturn change !== 'closed' && change !== 'opened';\n}\n\n/** Wraps a session's repo event so each delivery is narrowed to the subscriber's relevant refs (see `subscribe`) */\nfunction createRelevantRefsEvent(\n\tevent: Event<WatcherRepoChangeEvent>,\n\trelevantRefs: () => Iterable<string>,\n): Event<WatcherRepoChangeEvent> {\n\treturn (listener, thisArgs, disposables) =>\n\t\tevent(\n\t\t\te => {\n\t\t\t\tconst narrowed = narrowToRelevantRefs(e, relevantRefs);\n\t\t\t\tif (narrowed != null) {\n\t\t\t\t\tlistener.call(thisArgs, narrowed);\n\t\t\t\t}\n\t\t\t},\n\t\t\tundefined,\n\t\t\tdisposables,\n\t\t);\n}\n\n/**\n * Narrows an event to the refs a subscriber cares about. Returns the event itself when no change kind is removed (the\n * kept names then stay a superset, which only makes `mayHaveChangedRef` more conservative), `undefined` when every kind\n * is removed, and otherwise a new event with the kept kinds, names and unnamed kinds.\n */\nfunction narrowToRelevantRefs(\n\te: WatcherRepoChangeEvent,\n\trelevantRefs: () => Iterable<string>,\n): WatcherRepoChangeEvent | undefined {\n\t// Ref kinds that pass only if a relevant ref of that kind may have moved\n\tlet unresolved: Set<RefChangeKind> | undefined;\n\tfor (const change of e.changes) {\n\t\tif (!isRefChangeKind(change) || e.unnamedRefKinds.has(change)) continue;\n\n\t\t(unresolved ??= new Set()).add(change);\n\t}\n\tif (unresolved == null) return e;\n\n\tconst pseudoRefs: string[] = [];\n\tfor (const name of e.refs) {\n\t\tif (!name.startsWith('refs/')) {\n\t\t\tpseudoRefs.push(name);\n\t\t}\n\t}\n\tif (pseudoRefs.length !== 0) {\n\t\t// A pseudo-ref (`HEAD`, `ORIG_HEAD`) moving is a `heads` change for every subscriber\n\t\tunresolved.delete('heads');\n\t\tif (unresolved.size === 0) return e;\n\t}\n\n\t// Names that equal or cover a relevant ref — collected as we go, since `relevantRefs` may be a one-shot iterable\n\tconst keptNames = new Set<string>(pseudoRefs);\n\tfor (const ref of relevantRefs()) {\n\t\tconst kind = getRefChangeKind(ref);\n\t\tif (kind == null || !e.changes.has(kind)) continue;\n\n\t\t// `HEAD` is usually a symref to the checked-out branch, so any `heads` change may have moved it\n\t\tif (ref === 'HEAD') {\n\t\t\tfor (const name of e.refs) {\n\t\t\t\tif (getRefChangeKind(name) === 'heads') {\n\t\t\t\t\tkeptNames.add(name);\n\t\t\t\t}\n\t\t\t}\n\t\t\tunresolved.delete(kind);\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst covering = findCoveringRefName(e.refs, ref);\n\t\tif (covering == null) continue;\n\n\t\tkeptNames.add(covering);\n\t\tunresolved.delete(kind);\n\t}\n\tif (unresolved.size === 0) return e;\n\n\tconst keptChanges: RepositoryChange[] = [];\n\tfor (const change of e.changes) {\n\t\tif (!isRefChangeKind(change) || !unresolved.has(change)) {\n\t\t\tkeptChanges.push(change);\n\t\t}\n\t}\n\tif (keptChanges.length === 0) return undefined;\n\n\treturn new WatcherRepoChangeEvent(\n\t\te.repoPath,\n\t\tkeptChanges,\n\t\t{ names: keptNames, unnamed: e.unnamedRefKinds },\n\t\te.announced,\n\t);\n}\n"],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { GitDirProbe } from "./gitDirPoller.js";
|
|
1
2
|
//#region ../git/src/watching/watcherPatterns.d.ts
|
|
2
3
|
/** For standard repos (no worktrees): single watcher covering all .git files */
|
|
3
4
|
export declare const dotGitGlobCombined = "{index,HEAD,*_HEAD,MERGE_*,rebase-apply,rebase-apply/**,rebase-merge,rebase-merge/**,sequencer,sequencer/**,config,gk/config,refs/**,packed-refs,shallow,info/exclude,FETCH_HEAD,worktrees,worktrees/*,worktrees/*/HEAD,worktrees/*/locked,worktrees/*/gitdir}";
|
|
@@ -11,6 +12,26 @@ export declare const dotGitGlobCommon = "{config,gk/config,refs/**,packed-refs,s
|
|
|
11
12
|
* or the rebase/sequencer directories.
|
|
12
13
|
*/
|
|
13
14
|
export declare const gitDirExcludes: readonly string[];
|
|
15
|
+
/**
|
|
16
|
+
* What a poll stats in a worktree's own `.git` directory when its watcher can't be trusted — the files
|
|
17
|
+
* {@link dotGitGlobRoot} reads.
|
|
18
|
+
*/
|
|
19
|
+
export declare const gitDirRootProbes: readonly GitDirProbe[];
|
|
20
|
+
/**
|
|
21
|
+
* What a poll stats in the common `.git` directory — the files {@link dotGitGlobCommon} reads, with the `refs/**` it
|
|
22
|
+
* can't list narrowed to its namespace directories (whose mtime moves as a top-level ref is created or deleted) and
|
|
23
|
+
* to the named refs of {@link getGitDirRefProbes}.
|
|
24
|
+
*/
|
|
25
|
+
export declare const gitDirCommonProbes: readonly GitDirProbe[];
|
|
26
|
+
/** For each path a probe of {@link gitDirRootProbes} or {@link gitDirCommonProbes} is emitted as, the probes emitted as it */
|
|
27
|
+
export declare const gitDirProbesByEmittedPath: ReadonlyMap<string, readonly string[]>;
|
|
28
|
+
/**
|
|
29
|
+
* Probes for named branches and remote-tracking refs: each loose ref file, and each directory between it and its
|
|
30
|
+
* namespace (`refs/heads/feature` for `refs/heads/feature/x`, `refs/remotes/origin`), whose mtime moves as a sibling
|
|
31
|
+
* ref is created or deleted. A ref created in a directory no named ref sits in is left to `packed-refs`, `logs/HEAD`
|
|
32
|
+
* and `FETCH_HEAD`.
|
|
33
|
+
*/
|
|
34
|
+
export declare function getGitDirRefProbes(refs: Iterable<string>): GitDirProbe[];
|
|
14
35
|
/**
|
|
15
36
|
* Directories to leave unwatched in the working tree (relative to it), for a provider that prunes natively —
|
|
16
37
|
* the same ones {@link shouldIgnoreWorkingTreePath} drops from events.
|
|
@@ -23,6 +23,72 @@ const gitDirExcludes = [
|
|
|
23
23
|
"worktrees/*/logs"
|
|
24
24
|
];
|
|
25
25
|
/**
|
|
26
|
+
* What a poll stats in a worktree's own `.git` directory when its watcher can't be trusted — the files
|
|
27
|
+
* {@link dotGitGlobRoot} reads.
|
|
28
|
+
*/
|
|
29
|
+
const gitDirRootProbes = [
|
|
30
|
+
{ path: "HEAD" },
|
|
31
|
+
{ path: "index" },
|
|
32
|
+
{ path: "ORIG_HEAD" },
|
|
33
|
+
{ path: "MERGE_HEAD" },
|
|
34
|
+
{ path: "CHERRY_PICK_HEAD" },
|
|
35
|
+
{ path: "REVERT_HEAD" },
|
|
36
|
+
{ path: "REBASE_HEAD" },
|
|
37
|
+
{ path: "rebase-merge" },
|
|
38
|
+
{ path: "rebase-apply" },
|
|
39
|
+
{ path: "sequencer" },
|
|
40
|
+
{
|
|
41
|
+
path: "logs/HEAD",
|
|
42
|
+
emitAs: ["HEAD"]
|
|
43
|
+
}
|
|
44
|
+
];
|
|
45
|
+
/**
|
|
46
|
+
* What a poll stats in the common `.git` directory — the files {@link dotGitGlobCommon} reads, with the `refs/**` it
|
|
47
|
+
* can't list narrowed to its namespace directories (whose mtime moves as a top-level ref is created or deleted) and
|
|
48
|
+
* to the named refs of {@link getGitDirRefProbes}.
|
|
49
|
+
*/
|
|
50
|
+
const gitDirCommonProbes = [
|
|
51
|
+
{ path: "packed-refs" },
|
|
52
|
+
{ path: "config" },
|
|
53
|
+
{ path: "gk/config" },
|
|
54
|
+
{ path: "info/exclude" },
|
|
55
|
+
{ path: "refs/heads" },
|
|
56
|
+
{ path: "refs/tags" },
|
|
57
|
+
{ path: "refs/stash" },
|
|
58
|
+
{ path: "worktrees" },
|
|
59
|
+
{ path: "shallow" },
|
|
60
|
+
{
|
|
61
|
+
path: "FETCH_HEAD",
|
|
62
|
+
emitAs: ["FETCH_HEAD", "refs/remotes"]
|
|
63
|
+
}
|
|
64
|
+
];
|
|
65
|
+
/** For each path a probe of {@link gitDirRootProbes} or {@link gitDirCommonProbes} is emitted as, the probes emitted as it */
|
|
66
|
+
const gitDirProbesByEmittedPath = getProbesByEmittedPath([...gitDirRootProbes, ...gitDirCommonProbes]);
|
|
67
|
+
function getProbesByEmittedPath(probes) {
|
|
68
|
+
const map = /* @__PURE__ */ new Map();
|
|
69
|
+
for (const probe of probes) for (const path of probe.emitAs ?? []) {
|
|
70
|
+
const paths = map.get(path);
|
|
71
|
+
if (paths == null) map.set(path, [probe.path]);
|
|
72
|
+
else paths.push(probe.path);
|
|
73
|
+
}
|
|
74
|
+
return map;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Probes for named branches and remote-tracking refs: each loose ref file, and each directory between it and its
|
|
78
|
+
* namespace (`refs/heads/feature` for `refs/heads/feature/x`, `refs/remotes/origin`), whose mtime moves as a sibling
|
|
79
|
+
* ref is created or deleted. A ref created in a directory no named ref sits in is left to `packed-refs`, `logs/HEAD`
|
|
80
|
+
* and `FETCH_HEAD`.
|
|
81
|
+
*/
|
|
82
|
+
function getGitDirRefProbes(refs) {
|
|
83
|
+
const paths = /* @__PURE__ */ new Set();
|
|
84
|
+
for (const ref of refs) {
|
|
85
|
+
const namespaceEnd = ref.startsWith("refs/heads/") ? 10 : ref.startsWith("refs/remotes/") ? 12 : -1;
|
|
86
|
+
if (namespaceEnd === -1) continue;
|
|
87
|
+
for (let path = ref; path.length > namespaceEnd; path = path.substring(0, path.lastIndexOf("/"))) paths.add(path);
|
|
88
|
+
}
|
|
89
|
+
return Array.from(paths, (path) => ({ path }));
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
26
92
|
* Directories to leave unwatched in the working tree (relative to it), for a provider that prunes natively —
|
|
27
93
|
* the same ones {@link shouldIgnoreWorkingTreePath} drops from events.
|
|
28
94
|
*/
|
|
@@ -48,6 +114,6 @@ function shouldIgnoreWorkingTreePath(absolutePath) {
|
|
|
48
114
|
return ignoredWorkingTreePathRegex.test(absolutePath);
|
|
49
115
|
}
|
|
50
116
|
//#endregion
|
|
51
|
-
export { dotGitGlobCombined, dotGitGlobCommon, dotGitGlobRoot, gitDirExcludes, gitIgnoreGlob, gitInitGlob, shouldIgnoreRepoPath, shouldIgnoreWorkingTreePath, workingTreeExcludes };
|
|
117
|
+
export { dotGitGlobCombined, dotGitGlobCommon, dotGitGlobRoot, getGitDirRefProbes, gitDirCommonProbes, gitDirExcludes, gitDirProbesByEmittedPath, gitDirRootProbes, gitIgnoreGlob, gitInitGlob, shouldIgnoreRepoPath, shouldIgnoreWorkingTreePath, workingTreeExcludes };
|
|
52
118
|
|
|
53
119
|
//# sourceMappingURL=watcherPatterns.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"watcherPatterns.js","names":[],"sources":["../../../../git/src/watching/watcherPatterns.ts"],"sourcesContent":["// Glob patterns for watching files inside the .git directory.\n// They represent pure Git knowledge — the library owns these patterns and\n// provides them to the pluggable FileWatcherFactory.\n\nconst dotGitFiles =\n\t'index,HEAD,*_HEAD,MERGE_*,rebase-apply,rebase-apply/**,rebase-merge,rebase-merge/**,sequencer,sequencer/**';\n\n// `worktrees` (the bare entry, no `/*` suffix) is its own pattern: `worktree remove` deletes the whole\n// `worktrees/<id>` subtree in one syscall, and some platforms/watcher backends coalesce a same-instant\n// subtree delete into a single event for the PARENT directory — `worktrees/*` alone never matches that.\n// `classifyGitDirChange` classifies a bare `worktrees` path, so this is watch-coverage only.\n//\n// Only what `git worktree list` reports per worktree is watched here (its HEAD, lock and location). A\n// linked worktree's own index, `*_HEAD`, `MERGE_*`, rebase and sequencer state is watched by that\n// worktree's root watcher (`dotGitGlobRoot`) instead, so it never wakes sibling worktrees.\nconst dotGitWorktreeFiles = 'worktrees,worktrees/*,worktrees/*/HEAD,worktrees/*/locked,worktrees/*/gitdir';\n\nconst dotGitCommonFiles = `config,gk/config,refs/**,packed-refs,shallow,info/exclude,FETCH_HEAD,${dotGitWorktreeFiles}`;\n\n/** For standard repos (no worktrees): single watcher covering all .git files */\nexport const dotGitGlobCombined = `{${dotGitFiles},${dotGitCommonFiles}}`;\n\n/** For worktree repos: root watcher (worktree-specific files only) */\nexport const dotGitGlobRoot = `{${dotGitFiles}}`;\n\n/** For worktree repos: common watcher (shared config, refs, info, etc.) */\nexport const dotGitGlobCommon = `{${dotGitCommonFiles}}`;\n\n/**\n * Directories to leave unwatched under the `.git` directory (relative to it), for a provider that prunes natively.\n * Only directories nothing in the globs above reads: never anything under `refs`, `worktrees/*` itself, `gk`, `info`,\n * or the rebase/sequencer directories.\n */\nexport const gitDirExcludes: readonly string[] = [\n\t'objects',\n\t'logs',\n\t'hooks',\n\t'lfs',\n\t'modules',\n\t'rr-cache',\n\t'fsmonitor--daemon',\n\t'worktrees/*/logs',\n];\n\n/**\n * Directories to leave unwatched in the working tree (relative to it), for a provider that prunes natively —\n * the same ones {@link shouldIgnoreWorkingTreePath} drops from events.\n */\nexport const workingTreeExcludes: readonly string[] = ['**/node_modules', '**/.git'];\n\n/** Glob for watching .gitignore files in the working tree */\nexport const gitIgnoreGlob = '.gitignore';\n\n/** Glob for detecting .git directory creation (repository init/clone) */\nexport const gitInitGlob = '**/.git';\n\n// Regex-based filters that complement the globs above. The globs tell\n// the watcher *what* to observe; these filters tell it what to *skip*\n// at runtime (transient lock files, daemon artifacts, etc.).\n\nconst ignoredRepoPathRegex = /(?:\\/|\\\\)fsmonitor--daemon(?:\\/|\\\\)|(?:^|\\/)index\\.lock$/;\n\n/**\n * Returns true if the `.git`-relative path should be ignored\n * (transient filesystem artifacts, not meaningful git state changes).\n */\nexport function shouldIgnoreRepoPath(relativePath: string): boolean {\n\treturn ignoredRepoPathRegex.test(relativePath);\n}\n\nconst ignoredWorkingTreePathRegex =\n\t/(?:(?:\\/|\\\\)node_modules(?:\\/|\\\\|$)|\\.git(?:\\/index\\.lock)?(?:\\/|\\\\|$)|\\.watchman-cookie-)/;\n\n/**\n * Returns true if the working tree path should be ignored\n * (node_modules, .git internals, watchman cookies).\n */\nexport function shouldIgnoreWorkingTreePath(absolutePath: string): boolean {\n\treturn ignoredWorkingTreePathRegex.test(absolutePath);\n}\n"],"mappings":";
|
|
1
|
+
{"version":3,"file":"watcherPatterns.js","names":[],"sources":["../../../../git/src/watching/watcherPatterns.ts"],"sourcesContent":["import type { GitDirProbe } from './gitDirPoller.js';\n\n// Glob patterns for watching files inside the .git directory.\n// They represent pure Git knowledge — the library owns these patterns and\n// provides them to the pluggable FileWatcherFactory.\n\nconst dotGitFiles =\n\t'index,HEAD,*_HEAD,MERGE_*,rebase-apply,rebase-apply/**,rebase-merge,rebase-merge/**,sequencer,sequencer/**';\n\n// `worktrees` (the bare entry, no `/*` suffix) is its own pattern: `worktree remove` deletes the whole\n// `worktrees/<id>` subtree in one syscall, and some platforms/watcher backends coalesce a same-instant\n// subtree delete into a single event for the PARENT directory — `worktrees/*` alone never matches that.\n// `classifyGitDirChange` classifies a bare `worktrees` path, so this is watch-coverage only.\n//\n// Only what `git worktree list` reports per worktree is watched here (its HEAD, lock and location). A\n// linked worktree's own index, `*_HEAD`, `MERGE_*`, rebase and sequencer state is watched by that\n// worktree's root watcher (`dotGitGlobRoot`) instead, so it never wakes sibling worktrees.\nconst dotGitWorktreeFiles = 'worktrees,worktrees/*,worktrees/*/HEAD,worktrees/*/locked,worktrees/*/gitdir';\n\nconst dotGitCommonFiles = `config,gk/config,refs/**,packed-refs,shallow,info/exclude,FETCH_HEAD,${dotGitWorktreeFiles}`;\n\n/** For standard repos (no worktrees): single watcher covering all .git files */\nexport const dotGitGlobCombined = `{${dotGitFiles},${dotGitCommonFiles}}`;\n\n/** For worktree repos: root watcher (worktree-specific files only) */\nexport const dotGitGlobRoot = `{${dotGitFiles}}`;\n\n/** For worktree repos: common watcher (shared config, refs, info, etc.) */\nexport const dotGitGlobCommon = `{${dotGitCommonFiles}}`;\n\n/**\n * Directories to leave unwatched under the `.git` directory (relative to it), for a provider that prunes natively.\n * Only directories nothing in the globs above reads: never anything under `refs`, `worktrees/*` itself, `gk`, `info`,\n * or the rebase/sequencer directories.\n */\nexport const gitDirExcludes: readonly string[] = [\n\t'objects',\n\t'logs',\n\t'hooks',\n\t'lfs',\n\t'modules',\n\t'rr-cache',\n\t'fsmonitor--daemon',\n\t'worktrees/*/logs',\n];\n\n/**\n * What a poll stats in a worktree's own `.git` directory when its watcher can't be trusted — the files\n * {@link dotGitGlobRoot} reads.\n */\nexport const gitDirRootProbes: readonly GitDirProbe[] = [\n\t{ path: 'HEAD' },\n\t{ path: 'index' },\n\t{ path: 'ORIG_HEAD' },\n\t{ path: 'MERGE_HEAD' },\n\t{ path: 'CHERRY_PICK_HEAD' },\n\t{ path: 'REVERT_HEAD' },\n\t{ path: 'REBASE_HEAD' },\n\t{ path: 'rebase-merge' },\n\t{ path: 'rebase-apply' },\n\t{ path: 'sequencer' },\n\t// Moves with every commit, reset or checkout on the checked-out branch, without the poll knowing its name\n\t{ path: 'logs/HEAD', emitAs: ['HEAD'] },\n];\n\n/**\n * What a poll stats in the common `.git` directory — the files {@link dotGitGlobCommon} reads, with the `refs/**` it\n * can't list narrowed to its namespace directories (whose mtime moves as a top-level ref is created or deleted) and\n * to the named refs of {@link getGitDirRefProbes}.\n */\nexport const gitDirCommonProbes: readonly GitDirProbe[] = [\n\t{ path: 'packed-refs' },\n\t{ path: 'config' },\n\t{ path: 'gk/config' },\n\t{ path: 'info/exclude' },\n\t{ path: 'refs/heads' },\n\t{ path: 'refs/tags' },\n\t{ path: 'refs/stash' },\n\t{ path: 'worktrees' },\n\t{ path: 'shallow' },\n\t// A fetch writes it with the remote-tracking refs it updated, which the poll can't list. Git rewrites it even on a\n\t// fetch that moves nothing, so each fetch is reported as a `remotes` change: the cost of seeing remote-tracking\n\t// refs no subscriber names\n\t{ path: 'FETCH_HEAD', emitAs: ['FETCH_HEAD', 'refs/remotes'] },\n];\n\n/** For each path a probe of {@link gitDirRootProbes} or {@link gitDirCommonProbes} is emitted as, the probes emitted as it */\nexport const gitDirProbesByEmittedPath: ReadonlyMap<string, readonly string[]> = getProbesByEmittedPath([\n\t...gitDirRootProbes,\n\t...gitDirCommonProbes,\n]);\n\nfunction getProbesByEmittedPath(probes: readonly GitDirProbe[]): Map<string, string[]> {\n\tconst map = new Map<string, string[]>();\n\tfor (const probe of probes) {\n\t\tfor (const path of probe.emitAs ?? []) {\n\t\t\tconst paths = map.get(path);\n\t\t\tif (paths == null) {\n\t\t\t\tmap.set(path, [probe.path]);\n\t\t\t} else {\n\t\t\t\tpaths.push(probe.path);\n\t\t\t}\n\t\t}\n\t}\n\treturn map;\n}\n\n/**\n * Probes for named branches and remote-tracking refs: each loose ref file, and each directory between it and its\n * namespace (`refs/heads/feature` for `refs/heads/feature/x`, `refs/remotes/origin`), whose mtime moves as a sibling\n * ref is created or deleted. A ref created in a directory no named ref sits in is left to `packed-refs`, `logs/HEAD`\n * and `FETCH_HEAD`.\n */\nexport function getGitDirRefProbes(refs: Iterable<string>): GitDirProbe[] {\n\tconst paths = new Set<string>();\n\tfor (const ref of refs) {\n\t\tconst namespaceEnd = ref.startsWith('refs/heads/') ? 10 : ref.startsWith('refs/remotes/') ? 12 : -1;\n\t\tif (namespaceEnd === -1) continue;\n\n\t\tfor (let path = ref; path.length > namespaceEnd; path = path.substring(0, path.lastIndexOf('/'))) {\n\t\t\tpaths.add(path);\n\t\t}\n\t}\n\treturn Array.from(paths, path => ({ path: path }));\n}\n\n/**\n * Directories to leave unwatched in the working tree (relative to it), for a provider that prunes natively —\n * the same ones {@link shouldIgnoreWorkingTreePath} drops from events.\n */\nexport const workingTreeExcludes: readonly string[] = ['**/node_modules', '**/.git'];\n\n/** Glob for watching .gitignore files in the working tree */\nexport const gitIgnoreGlob = '.gitignore';\n\n/** Glob for detecting .git directory creation (repository init/clone) */\nexport const gitInitGlob = '**/.git';\n\n// Regex-based filters that complement the globs above. The globs tell\n// the watcher *what* to observe; these filters tell it what to *skip*\n// at runtime (transient lock files, daemon artifacts, etc.).\n\nconst ignoredRepoPathRegex = /(?:\\/|\\\\)fsmonitor--daemon(?:\\/|\\\\)|(?:^|\\/)index\\.lock$/;\n\n/**\n * Returns true if the `.git`-relative path should be ignored\n * (transient filesystem artifacts, not meaningful git state changes).\n */\nexport function shouldIgnoreRepoPath(relativePath: string): boolean {\n\treturn ignoredRepoPathRegex.test(relativePath);\n}\n\nconst ignoredWorkingTreePathRegex =\n\t/(?:(?:\\/|\\\\)node_modules(?:\\/|\\\\|$)|\\.git(?:\\/index\\.lock)?(?:\\/|\\\\|$)|\\.watchman-cookie-)/;\n\n/**\n * Returns true if the working tree path should be ignored\n * (node_modules, .git internals, watchman cookies).\n */\nexport function shouldIgnoreWorkingTreePath(absolutePath: string): boolean {\n\treturn ignoredWorkingTreePathRegex.test(absolutePath);\n}\n"],"mappings":";AAMA,MAAM,cACL;AAYD,MAAM,oBAAoB;;AAG1B,MAAa,qBAAqB,IAAI,YAAY,GAAG,kBAAkB;;AAGvE,MAAa,iBAAiB,IAAI,YAAY;;AAG9C,MAAa,mBAAmB,IAAI,kBAAkB;;;;;;AAOtD,MAAa,iBAAoC;CAChD;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACD;;;;;AAMA,MAAa,mBAA2C;CACvD,EAAE,MAAM,OAAO;CACf,EAAE,MAAM,QAAQ;CAChB,EAAE,MAAM,YAAY;CACpB,EAAE,MAAM,aAAa;CACrB,EAAE,MAAM,mBAAmB;CAC3B,EAAE,MAAM,cAAc;CACtB,EAAE,MAAM,cAAc;CACtB,EAAE,MAAM,eAAe;CACvB,EAAE,MAAM,eAAe;CACvB,EAAE,MAAM,YAAY;CAEpB;EAAE,MAAM;EAAa,QAAQ,CAAC,MAAM;CAAE;AACvC;;;;;;AAOA,MAAa,qBAA6C;CACzD,EAAE,MAAM,cAAc;CACtB,EAAE,MAAM,SAAS;CACjB,EAAE,MAAM,YAAY;CACpB,EAAE,MAAM,eAAe;CACvB,EAAE,MAAM,aAAa;CACrB,EAAE,MAAM,YAAY;CACpB,EAAE,MAAM,aAAa;CACrB,EAAE,MAAM,YAAY;CACpB,EAAE,MAAM,UAAU;CAIlB;EAAE,MAAM;EAAc,QAAQ,CAAC,cAAc,cAAc;CAAE;AAC9D;;AAGA,MAAa,4BAAoE,uBAAuB,CACvG,GAAG,kBACH,GAAG,kBACJ,CAAC;AAED,SAAS,uBAAuB,QAAuD;CACtF,MAAM,sBAAM,IAAI,IAAsB;CACtC,KAAK,MAAM,SAAS,QACnB,KAAK,MAAM,QAAQ,MAAM,UAAU,CAAC,GAAG;EACtC,MAAM,QAAQ,IAAI,IAAI,IAAI;EAC1B,IAAI,SAAS,MACZ,IAAI,IAAI,MAAM,CAAC,MAAM,IAAI,CAAC;OAE1B,MAAM,KAAK,MAAM,IAAI;CAEvB;CAED,OAAO;AACR;;;;;;;AAQA,SAAgB,mBAAmB,MAAuC;CACzE,MAAM,wBAAQ,IAAI,IAAY;CAC9B,KAAK,MAAM,OAAO,MAAM;EACvB,MAAM,eAAe,IAAI,WAAW,aAAa,IAAI,KAAK,IAAI,WAAW,eAAe,IAAI,KAAK;EACjG,IAAI,iBAAiB,IAAI;EAEzB,KAAK,IAAI,OAAO,KAAK,KAAK,SAAS,cAAc,OAAO,KAAK,UAAU,GAAG,KAAK,YAAY,GAAG,CAAC,GAC9F,MAAM,IAAI,IAAI;CAEhB;CACA,OAAO,MAAM,KAAK,QAAO,UAAS,EAAQ,KAAK,EAAE;AAClD;;;;;AAMA,MAAa,sBAAyC,CAAC,mBAAmB,SAAS;;AAGnF,MAAa,gBAAgB;;AAG7B,MAAa,cAAc;AAM3B,MAAM,uBAAuB;;;;;AAM7B,SAAgB,qBAAqB,cAA+B;CACnE,OAAO,qBAAqB,KAAK,YAAY;AAC9C;AAEA,MAAM,8BACL;;;;;AAMD,SAAgB,4BAA4B,cAA+B;CAC1E,OAAO,4BAA4B,KAAK,YAAY;AACrD"}
|
|
@@ -2,6 +2,8 @@ import { UnifiedDisposable } from "../../utils/disposable.js";
|
|
|
2
2
|
import { Event } from "../../utils/event.js";
|
|
3
3
|
import { ResourceUsage } from "../../utils/resourceUsage.js";
|
|
4
4
|
import { RepositoryWatchService } from "./watchService.js";
|
|
5
|
+
import { AheadOfBaseTier } from "../providers/branches.js";
|
|
6
|
+
import { MergeTargetsChangeEvent } from "../cache.js";
|
|
5
7
|
import { RepositoryService } from "../repositoryService.js";
|
|
6
8
|
//#region ../git/src/watching/worktreeSummary.d.ts
|
|
7
9
|
export interface WorktreeSummaryUpstream {
|
|
@@ -23,6 +25,10 @@ export interface WorktreeSummaryBase {
|
|
|
23
25
|
readonly ahead: number;
|
|
24
26
|
/** The commit {@link ref} pointed at when the count was read; `undefined` when it couldn't be resolved */
|
|
25
27
|
readonly sha: string | undefined;
|
|
28
|
+
/** Where HEAD forked from {@link ref} (see `AheadOfBase.base`) */
|
|
29
|
+
readonly forkPoint: string | undefined;
|
|
30
|
+
/** The source the base came from (see `AheadOfBase.base`) */
|
|
31
|
+
readonly tier: AheadOfBaseTier;
|
|
26
32
|
}
|
|
27
33
|
export interface WorktreeSummary {
|
|
28
34
|
/** The repository path in {@link getRepositoryKey} form */
|
|
@@ -39,13 +45,16 @@ export interface WorktreeSummary {
|
|
|
39
45
|
readonly behind: number;
|
|
40
46
|
/** Whether there is any staged, unstaged or untracked (non-ignored) change */
|
|
41
47
|
readonly dirty: boolean;
|
|
42
|
-
/**
|
|
48
|
+
/**
|
|
49
|
+
* How far the branch — or a detached HEAD, measured against the default branch — is past its base; `undefined` when
|
|
50
|
+
* unsupported or there is no base
|
|
51
|
+
*/
|
|
43
52
|
readonly base: WorktreeSummaryBase | undefined;
|
|
44
53
|
/**
|
|
45
54
|
* Full ref names whose movement can change {@link base}, as `getAheadOfBase` reports them (see
|
|
46
55
|
* `AheadOfBase.dependsOn`) — given with or without a base, so a watcher also learns when one appears. Empty when
|
|
47
|
-
* the base isn't read (
|
|
48
|
-
* earlier list for this
|
|
56
|
+
* the base isn't read (an unborn HEAD with no name, or a provider without `getAheadOfBase`), and when the base read
|
|
57
|
+
* failed with no earlier list for this HEAD (the first read, or the one after a checkout).
|
|
49
58
|
*/
|
|
50
59
|
readonly baseDependsOn: readonly string[];
|
|
51
60
|
/**
|
|
@@ -74,6 +83,16 @@ export interface WorktreeSummarySubscription extends UnifiedDisposable {
|
|
|
74
83
|
export interface WorktreeSummaryServiceOptions {
|
|
75
84
|
readonly watchService: RepositoryWatchService;
|
|
76
85
|
readonly getRepository: (repoPath: string) => RepositoryService | undefined;
|
|
86
|
+
/**
|
|
87
|
+
* Fires as core writes a branch's merge target or base, before the store's promise resolves; the summary re-reads
|
|
88
|
+
* that branch's stored values then, so a `getSummary` right after an awaited store sees the new base
|
|
89
|
+
*/
|
|
90
|
+
readonly onDidChangeMergeTargets?: Event<MergeTargetsChangeEvent>;
|
|
91
|
+
/**
|
|
92
|
+
* Maps a worktree path to the repository common path that {@link onDidChangeMergeTargets} events carry; only that
|
|
93
|
+
* repository's summaries react. Without it, or when it returns `undefined`, every summary reacts.
|
|
94
|
+
*/
|
|
95
|
+
readonly getCommonPath?: (repoPath: string) => string | undefined;
|
|
77
96
|
}
|
|
78
97
|
/**
|
|
79
98
|
* A cached, change-notifying summary of each subscribed worktree — its branch, upstream and ahead/behind, whether it
|
|
@@ -88,10 +107,11 @@ export interface WorktreeSummaryServiceOptions {
|
|
|
88
107
|
* HEAD's commit, upstream, ahead/behind) and `base` (ahead of base, the base's tip, and the refs it depends on) — and
|
|
89
108
|
* an event recomputes only the groups it can affect: a working-tree edit, an index or ignore-rule change reads only
|
|
90
109
|
* `dirty`; the upstream moving reads only `branch`; a ref the base depends on reads only `base`; a `gk` config change
|
|
91
|
-
* (mostly bookkeeping writes to the shared `.git/gk/config`)
|
|
92
|
-
* base, and reads `base` only when one of them changed;
|
|
93
|
-
* them. Stash, unrelated tags and branches, and the like
|
|
94
|
-
* events during a run are merged into a single follow-up
|
|
110
|
+
* (mostly bookkeeping writes to the shared `.git/gk/config`), or core's own write of the branch's merge target or
|
|
111
|
+
* base, re-reads the branch's stored merge targets and recorded base, and reads `base` only when one of them changed;
|
|
112
|
+
* the checked-out branch moving, or a HEAD change, reads all of them. Stash, unrelated tags and branches, and the like
|
|
113
|
+
* read nothing. One run is in flight per worktree at a time; events during a run are merged into a single follow-up
|
|
114
|
+
* run.
|
|
95
115
|
*
|
|
96
116
|
* **Value-change-only notification.** {@link WorktreeSummarySubscription.onDidChange} and {@link onDidChangeSummary}
|
|
97
117
|
* fire only when some field's value differs from the previous summary — a recompute that reads the same answer is
|
|
@@ -105,6 +125,8 @@ export declare class WorktreeSummaryService implements UnifiedDisposable {
|
|
|
105
125
|
private readonly entries;
|
|
106
126
|
private readonly watchService;
|
|
107
127
|
private readonly getRepository;
|
|
128
|
+
private readonly getCommonPath;
|
|
129
|
+
private readonly mergeTargetsListener;
|
|
108
130
|
private _disposed;
|
|
109
131
|
constructor(options: WorktreeSummaryServiceOptions);
|
|
110
132
|
[Symbol.dispose](): void;
|
|
@@ -120,6 +142,11 @@ export declare class WorktreeSummaryService implements UnifiedDisposable {
|
|
|
120
142
|
getResourceUsage(): ResourceUsage;
|
|
121
143
|
private start;
|
|
122
144
|
private onRepoChanged;
|
|
145
|
+
/**
|
|
146
|
+
* Queued synchronously, so a store awaited before `getSummary` finds the run in flight. The watcher's later
|
|
147
|
+
* `gkConfig` event for the same write then finds the keys unchanged and leaves the base alone.
|
|
148
|
+
*/
|
|
149
|
+
private onMergeTargetsChanged;
|
|
123
150
|
private queue;
|
|
124
151
|
/** Runs until nothing is pending. Never rejects — every failure becomes entry state. */
|
|
125
152
|
private drain;
|
|
@@ -13,6 +13,11 @@ const baseKeysField = 8;
|
|
|
13
13
|
/** Every group a full read covers — a base read refreshes the {@link BaseKeys} snapshot itself */
|
|
14
14
|
const allFields = 7;
|
|
15
15
|
const emptyRefs = Object.freeze([]);
|
|
16
|
+
const noBaseKeys = [
|
|
17
|
+
void 0,
|
|
18
|
+
void 0,
|
|
19
|
+
void 0
|
|
20
|
+
];
|
|
16
21
|
var SummarySubscription = class {
|
|
17
22
|
entry;
|
|
18
23
|
options;
|
|
@@ -72,10 +77,11 @@ var SummarySubscription = class {
|
|
|
72
77
|
* HEAD's commit, upstream, ahead/behind) and `base` (ahead of base, the base's tip, and the refs it depends on) — and
|
|
73
78
|
* an event recomputes only the groups it can affect: a working-tree edit, an index or ignore-rule change reads only
|
|
74
79
|
* `dirty`; the upstream moving reads only `branch`; a ref the base depends on reads only `base`; a `gk` config change
|
|
75
|
-
* (mostly bookkeeping writes to the shared `.git/gk/config`)
|
|
76
|
-
* base, and reads `base` only when one of them changed;
|
|
77
|
-
* them. Stash, unrelated tags and branches, and the like
|
|
78
|
-
* events during a run are merged into a single follow-up
|
|
80
|
+
* (mostly bookkeeping writes to the shared `.git/gk/config`), or core's own write of the branch's merge target or
|
|
81
|
+
* base, re-reads the branch's stored merge targets and recorded base, and reads `base` only when one of them changed;
|
|
82
|
+
* the checked-out branch moving, or a HEAD change, reads all of them. Stash, unrelated tags and branches, and the like
|
|
83
|
+
* read nothing. One run is in flight per worktree at a time; events during a run are merged into a single follow-up
|
|
84
|
+
* run.
|
|
79
85
|
*
|
|
80
86
|
* **Value-change-only notification.** {@link WorktreeSummarySubscription.onDidChange} and {@link onDidChangeSummary}
|
|
81
87
|
* fire only when some field's value differs from the previous summary — a recompute that reads the same answer is
|
|
@@ -91,10 +97,14 @@ var WorktreeSummaryService = class {
|
|
|
91
97
|
entries = /* @__PURE__ */ new Map();
|
|
92
98
|
watchService;
|
|
93
99
|
getRepository;
|
|
100
|
+
getCommonPath;
|
|
101
|
+
mergeTargetsListener;
|
|
94
102
|
_disposed = false;
|
|
95
103
|
constructor(options) {
|
|
96
104
|
this.watchService = options.watchService;
|
|
97
105
|
this.getRepository = options.getRepository;
|
|
106
|
+
this.getCommonPath = options.getCommonPath;
|
|
107
|
+
this.mergeTargetsListener = options.onDidChangeMergeTargets?.((e) => this.onMergeTargetsChanged(e));
|
|
98
108
|
}
|
|
99
109
|
[Symbol.dispose]() {
|
|
100
110
|
this.dispose();
|
|
@@ -102,6 +112,7 @@ var WorktreeSummaryService = class {
|
|
|
102
112
|
dispose() {
|
|
103
113
|
if (this._disposed) return;
|
|
104
114
|
this._disposed = true;
|
|
115
|
+
this.mergeTargetsListener?.dispose();
|
|
105
116
|
for (const entry of [...this.entries.values()]) for (const subscription of [...entry.subscribers]) subscription.dispose();
|
|
106
117
|
this.entries.clear();
|
|
107
118
|
this._onDidChangeSummary.dispose();
|
|
@@ -208,6 +219,20 @@ var WorktreeSummaryService = class {
|
|
|
208
219
|
}
|
|
209
220
|
this.queue(entry, fields);
|
|
210
221
|
}
|
|
222
|
+
/**
|
|
223
|
+
* Queued synchronously, so a store awaited before `getSummary` finds the run in flight. The watcher's later
|
|
224
|
+
* `gkConfig` event for the same write then finds the keys unchanged and leaves the base alone.
|
|
225
|
+
*/
|
|
226
|
+
onMergeTargetsChanged(e) {
|
|
227
|
+
for (const entry of this.entries.values()) {
|
|
228
|
+
if (entry.handle == null) continue;
|
|
229
|
+
const value = entry.value;
|
|
230
|
+
if (value != null && (value.branch == null || e.refs != null && !e.refs.has(value.branch))) continue;
|
|
231
|
+
const commonPath = this.getCommonPath?.(entry.key);
|
|
232
|
+
if (commonPath != null && commonPath !== e.repoPath) continue;
|
|
233
|
+
this.queue(entry, baseKeysField);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
211
236
|
queue(entry, fields) {
|
|
212
237
|
if (entry.disposed || fields === 0) return;
|
|
213
238
|
entry.pending |= entry.value == null || entry.value.stale ? allFields : fields;
|
|
@@ -330,21 +355,21 @@ async function readSummary(repo, key, fields, previous, previousBaseKeys, signal
|
|
|
330
355
|
let baseKeysRead = false;
|
|
331
356
|
let baseFailed = false;
|
|
332
357
|
let baseError;
|
|
333
|
-
const
|
|
334
|
-
if (fields & baseKeysField && !(fields & baseField) &&
|
|
335
|
-
baseKeys = await readBaseKeys(repo,
|
|
358
|
+
const baseReadable = repo.branches.getAheadOfBase != null && (detached || branch != null);
|
|
359
|
+
if (fields & baseKeysField && !(fields & baseField) && baseReadable) {
|
|
360
|
+
baseKeys = await readBaseKeys(repo, branch);
|
|
336
361
|
baseKeysRead = true;
|
|
337
362
|
if (baseKeys == null || previousBaseKeys == null || !baseKeysEqual(baseKeys, previousBaseKeys)) fields |= baseField;
|
|
338
363
|
}
|
|
339
364
|
if (fields & baseField) {
|
|
340
|
-
if (
|
|
365
|
+
if (!baseReadable) {
|
|
341
366
|
base = void 0;
|
|
342
367
|
dependsOn = emptyRefs;
|
|
343
368
|
baseKeys = void 0;
|
|
344
369
|
} else {
|
|
345
|
-
if (!baseKeysRead) baseKeys = await readBaseKeys(repo,
|
|
370
|
+
if (!baseKeysRead) baseKeys = await readBaseKeys(repo, branch);
|
|
346
371
|
try {
|
|
347
|
-
const aheadOfBase = await repo.branches.getAheadOfBase?.(
|
|
372
|
+
const aheadOfBase = await repo.branches.getAheadOfBase?.(branch, void 0, signal);
|
|
348
373
|
base = aheadOfBase?.base;
|
|
349
374
|
dependsOn = aheadOfBase?.dependsOn ?? emptyRefs;
|
|
350
375
|
} catch (ex) {
|
|
@@ -379,9 +404,11 @@ async function readSummary(repo, key, fields, previous, previousBaseKeys, signal
|
|
|
379
404
|
}
|
|
380
405
|
/**
|
|
381
406
|
* Reads {@link BaseKeys} for `branch`, normalized the way `getAheadOfBase` reads them. `undefined` when any read
|
|
382
|
-
* failed — an unknown snapshot, so the next `gk` config change reads the base rather than trusting it.
|
|
407
|
+
* failed — an unknown snapshot, so the next `gk` config change reads the base rather than trusting it. A detached
|
|
408
|
+
* HEAD (`branch` undefined) has no stored values to read.
|
|
383
409
|
*/
|
|
384
410
|
async function readBaseKeys(repo, branch) {
|
|
411
|
+
if (branch == null) return noBaseKeys;
|
|
385
412
|
try {
|
|
386
413
|
const [user, detected, recorded] = await Promise.all([
|
|
387
414
|
repo.branches.getStoredUserMergeTargetBranchName?.(branch),
|
|
@@ -421,7 +448,7 @@ async function toSummaryUpstream(repo, upstream, signal) {
|
|
|
421
448
|
};
|
|
422
449
|
}
|
|
423
450
|
function summariesEqual(a, b) {
|
|
424
|
-
return a.repoPath === b.repoPath && a.branch === b.branch && a.detached === b.detached && a.sha === b.sha && a.ahead === b.ahead && a.behind === b.behind && a.dirty === b.dirty && a.stale === b.stale && areEqual(a.baseDependsOn, b.baseDependsOn) && (a.upstream === b.upstream || a.upstream != null && b.upstream != null && a.upstream.name === b.upstream.name && a.upstream.remote === b.upstream.remote && a.upstream.branch === b.upstream.branch && a.upstream.missing === b.upstream.missing) && (a.base === b.base || a.base != null && b.base != null && a.base.ref === b.base.ref && a.base.ahead === b.base.ahead && a.base.sha === b.base.sha);
|
|
451
|
+
return a.repoPath === b.repoPath && a.branch === b.branch && a.detached === b.detached && a.sha === b.sha && a.ahead === b.ahead && a.behind === b.behind && a.dirty === b.dirty && a.stale === b.stale && areEqual(a.baseDependsOn, b.baseDependsOn) && (a.upstream === b.upstream || a.upstream != null && b.upstream != null && a.upstream.name === b.upstream.name && a.upstream.remote === b.upstream.remote && a.upstream.branch === b.upstream.branch && a.upstream.missing === b.upstream.missing) && (a.base === b.base || a.base != null && b.base != null && a.base.ref === b.base.ref && a.base.ahead === b.base.ahead && a.base.sha === b.base.sha && a.base.forkPoint === b.base.forkPoint && a.base.tier === b.base.tier);
|
|
425
452
|
}
|
|
426
453
|
//#endregion
|
|
427
454
|
export { WorktreeSummaryService };
|