@gitkraken/core-gitlens 0.12.0 → 0.13.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 +57 -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 +4 -0
- package/dist/git/features.js.map +1 -1
- package/dist/git/models/branch.d.ts +15 -2
- package/dist/git/models/branch.js +27 -5
- package/dist/git/models/branch.js.map +1 -1
- package/dist/git/models/status.js +1 -1
- 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/providers/branches.d.ts +73 -8
- package/dist/git/providers/pausedOperations.d.ts +6 -0
- package/dist/git/providers/provider.d.ts +34 -2
- 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/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 +8 -1
- package/dist/git/utils/branch.utils.js +30 -1
- package/dist/git/utils/branch.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 +34 -2
- package/dist/git-cli/cliGitProvider.js +119 -17
- 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 +15 -3
- package/dist/git-cli/exec/git.js +38 -17
- 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 +38 -0
- package/dist/git-cli/exec/indexLock.js +150 -0
- package/dist/git-cli/exec/indexLock.js.map +1 -0
- package/dist/git-cli/parsers/worktreeParser.js +42 -2
- package/dist/git-cli/parsers/worktreeParser.js.map +1 -1
- package/dist/git-cli/providers/branches.d.ts +109 -10
- package/dist/git-cli/providers/branches.js +624 -131
- package/dist/git-cli/providers/branches.js.map +1 -1
- package/dist/git-cli/providers/config.d.ts +9 -5
- package/dist/git-cli/providers/config.js +60 -27
- package/dist/git-cli/providers/config.js.map +1 -1
- package/dist/git-cli/providers/operations.d.ts +17 -0
- package/dist/git-cli/providers/operations.js +153 -96
- 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 +60 -40
- 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/staging.js +40 -15
- 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 +38 -20
- 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/ipc/discovery.js +1 -1
- package/dist/plus/agents/providers/claudeCodeTranscript.js +6 -6
- package/dist/plus/agents/providers/claudeCodeTranscript.js.map +1 -1
- package/dist/plus/ai/providers/openAICompatibleProviderBase.js +1 -1
- package/dist/plus/git-github/providers/github/status.d.ts +3 -1
- package/dist/plus/git-github/providers/github/status.js +1 -1
- 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 +37 -77
- 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 +18 -14
- package/dist/plus/integrations/providers/azure/workItemStateCategories.js +69 -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/env/node/exec.js +2 -2
- 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,6 +1,7 @@
|
|
|
1
1
|
import { UnifiedDisposable } from "../../utils/disposable.js";
|
|
2
|
-
import { FileWatcher, FileWatchingProvider } from "./provider.js";
|
|
2
|
+
import { FileWatcher, FileWatcherReliability, FileWatchingProvider } from "./provider.js";
|
|
3
3
|
import { GitDir, RepositoryChange } from "../models/repository.js";
|
|
4
|
+
import { GitDirPoller, GitDirProbeTarget } from "./gitDirPoller.js";
|
|
4
5
|
//#region ../git/src/watching/watchGroup.d.ts
|
|
5
6
|
/**
|
|
6
7
|
* Hooks that the WatchGroup fires when events are interpreted.
|
|
@@ -9,11 +10,16 @@ import { GitDir, RepositoryChange } from "../models/repository.js";
|
|
|
9
10
|
export interface WatchGroupHooks {
|
|
10
11
|
/**
|
|
11
12
|
* `ref` is the full ref name (or ref directory) or pseudo-ref (`HEAD`, `ORIG_HEAD`) whose file changed, with a
|
|
12
|
-
* trailing `.lock` stripped; `undefined` for any other `.git` file.
|
|
13
|
+
* trailing `.lock` stripped; `undefined` for any other `.git` file. `overflow` marks the every-kind rescan of a
|
|
14
|
+
* watcher that dropped events, which no single write can account for.
|
|
13
15
|
*/
|
|
14
|
-
readonly onRepoChanged: (repoPath: string, changes: RepositoryChange[], ref?: string) => void;
|
|
16
|
+
readonly onRepoChanged: (repoPath: string, changes: RepositoryChange[], ref?: string, overflow?: boolean) => void;
|
|
15
17
|
readonly onFetchHeadChanged?: (repoPath: string) => void;
|
|
16
18
|
readonly onIgnoresChanged?: (repoPath: string) => void;
|
|
19
|
+
/** The refs whose loose files a poll stats for this session, read at each poll */
|
|
20
|
+
readonly getProbeRefs?: () => Iterable<string>;
|
|
21
|
+
/** A watcher this session hears through went `unwatchable`, or stopped being it (see {@link WatchGroup.isUnwatchable}) */
|
|
22
|
+
readonly onUnwatchableChanged?: (repoPath: string) => void;
|
|
17
23
|
}
|
|
18
24
|
/**
|
|
19
25
|
* Tracks one session's connection to a WatchGroup.
|
|
@@ -28,6 +34,9 @@ interface SessionEntry {
|
|
|
28
34
|
/** For a linked session, its `<n>` in `<common>/worktrees/<n>`; otherwise `undefined` */
|
|
29
35
|
readonly worktreeId: string | undefined;
|
|
30
36
|
readonly hooks: WatchGroupHooks;
|
|
37
|
+
reliability: FileWatcherReliability;
|
|
38
|
+
/** Polls in place of (or beside) the root watcher while it isn't `reliable` */
|
|
39
|
+
target?: GitDirProbeTarget;
|
|
31
40
|
}
|
|
32
41
|
/**
|
|
33
42
|
* Manages filesystem watchers for a single physical `.git` directory,
|
|
@@ -44,20 +53,47 @@ interface SessionEntry {
|
|
|
44
53
|
export declare class WatchGroup implements UnifiedDisposable {
|
|
45
54
|
readonly commonGitDir: string;
|
|
46
55
|
private readonly provider;
|
|
56
|
+
/** When given, a watcher that reports it can't be trusted is polled; otherwise none is asked */
|
|
57
|
+
private readonly poller?;
|
|
47
58
|
private readonly _sessions;
|
|
48
59
|
private _commonWatcher;
|
|
60
|
+
private _commonReliability;
|
|
61
|
+
private _commonTarget;
|
|
49
62
|
private _disposed;
|
|
50
|
-
constructor(commonGitDir: string, provider: FileWatchingProvider
|
|
63
|
+
constructor(commonGitDir: string, provider: FileWatchingProvider,
|
|
64
|
+
/** When given, a watcher that reports it can't be trusted is polled; otherwise none is asked */
|
|
65
|
+
poller?: GitDirPoller | undefined);
|
|
51
66
|
[Symbol.dispose](): void;
|
|
52
67
|
/** Dispose the entire group (common watcher + all root watchers). */
|
|
53
68
|
dispose(): void;
|
|
54
69
|
get sessions(): ReadonlyMap<string, SessionEntry>;
|
|
70
|
+
/** True when a watcher the session hears through — its root watcher, or a linked session's common one — can't watch */
|
|
71
|
+
isUnwatchable(repoPath: string): boolean;
|
|
55
72
|
/** Add a session to this group. Creates the root watcher for the session's gitDir. */
|
|
56
73
|
addSession(repoPath: string, gitDir: GitDir, hooks: WatchGroupHooks): void;
|
|
57
74
|
/** Remove a session from this group. Disposes its root watcher. */
|
|
58
75
|
removeSession(repoPath: string): void;
|
|
59
76
|
private getWorktreeId;
|
|
60
77
|
private ensureCommonWatcher;
|
|
78
|
+
private disposeCommonWatcher;
|
|
79
|
+
/** Hosts that didn't opt into polling get exactly the options they always had */
|
|
80
|
+
private getWatcherOptions;
|
|
81
|
+
private setRootReliability;
|
|
82
|
+
private setCommonReliability;
|
|
83
|
+
private stopPolling;
|
|
84
|
+
/**
|
|
85
|
+
* A session rooted at the common dir polls the common files too, with the refs of every session in the group: its
|
|
86
|
+
* combined watcher would hear them all. While the common dir is polled too, the poller stats each shared file once
|
|
87
|
+
* for both, and each keeps its own readings, so what one watcher reported is held back from its own sessions only
|
|
88
|
+
*/
|
|
89
|
+
private getRootProbes;
|
|
90
|
+
/**
|
|
91
|
+
* A linked worktree's `logs/HEAD` moved — a commit or reset there, on a branch a polled session rooted at the common
|
|
92
|
+
* dir may not probe — so each such session hears a `heads` change its combined watcher would have heard
|
|
93
|
+
*/
|
|
94
|
+
private onLinkedHeadMoved;
|
|
95
|
+
/** The common files, with the refs of every linked session (the only ones the common watcher delivers to) */
|
|
96
|
+
private getCommonProbes;
|
|
61
97
|
private onCommonEvent;
|
|
62
98
|
private deliverOverflow;
|
|
63
99
|
private onRootEvent;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { some } from "../../utils/iterable.js";
|
|
2
2
|
import { isDescendant, relative } from "../../utils/path.js";
|
|
3
|
-
import {
|
|
3
|
+
import { GitDirProbeTarget } from "./gitDirPoller.js";
|
|
4
|
+
import { dotGitGlobCombined, dotGitGlobCommon, dotGitGlobRoot, getGitDirRefProbes, gitDirCommonProbes, gitDirExcludes, gitDirProbesByEmittedPath, gitDirRootProbes, shouldIgnoreRepoPath } from "./watcherPatterns.js";
|
|
4
5
|
import { allWatchedRepositoryChanges, classifyGitDirChange } from "./classifyChange.js";
|
|
5
6
|
//#region ../git/src/watching/watchGroup.ts
|
|
6
7
|
/**
|
|
@@ -18,12 +19,16 @@ import { allWatchedRepositoryChanges, classifyGitDirChange } from "./classifyCha
|
|
|
18
19
|
var WatchGroup = class {
|
|
19
20
|
commonGitDir;
|
|
20
21
|
provider;
|
|
22
|
+
poller;
|
|
21
23
|
_sessions = /* @__PURE__ */ new Map();
|
|
22
24
|
_commonWatcher;
|
|
25
|
+
_commonReliability = "reliable";
|
|
26
|
+
_commonTarget;
|
|
23
27
|
_disposed = false;
|
|
24
|
-
constructor(commonGitDir, provider) {
|
|
28
|
+
constructor(commonGitDir, provider, poller) {
|
|
25
29
|
this.commonGitDir = commonGitDir;
|
|
26
30
|
this.provider = provider;
|
|
31
|
+
this.poller = poller;
|
|
27
32
|
}
|
|
28
33
|
[Symbol.dispose]() {
|
|
29
34
|
this.dispose();
|
|
@@ -32,34 +37,52 @@ var WatchGroup = class {
|
|
|
32
37
|
dispose() {
|
|
33
38
|
if (this._disposed) return;
|
|
34
39
|
this._disposed = true;
|
|
35
|
-
for (const entry of this._sessions.values())
|
|
40
|
+
for (const entry of this._sessions.values()) {
|
|
41
|
+
entry.rootWatcher.dispose();
|
|
42
|
+
this.stopPolling(entry);
|
|
43
|
+
}
|
|
36
44
|
this._sessions.clear();
|
|
37
|
-
this.
|
|
38
|
-
this._commonWatcher = void 0;
|
|
45
|
+
this.disposeCommonWatcher();
|
|
39
46
|
}
|
|
40
47
|
get sessions() {
|
|
41
48
|
return this._sessions;
|
|
42
49
|
}
|
|
50
|
+
/** True when a watcher the session hears through — its root watcher, or a linked session's common one — can't watch */
|
|
51
|
+
isUnwatchable(repoPath) {
|
|
52
|
+
const entry = this._sessions.get(repoPath);
|
|
53
|
+
if (entry == null) return false;
|
|
54
|
+
return entry.reliability === "unwatchable" || !entry.coversCommon && this._commonReliability === "unwatchable";
|
|
55
|
+
}
|
|
43
56
|
/** Add a session to this group. Creates the root watcher for the session's gitDir. */
|
|
44
57
|
addSession(repoPath, gitDir, hooks) {
|
|
45
58
|
if (this._disposed) return;
|
|
46
59
|
if (this._sessions.has(repoPath)) return;
|
|
47
60
|
const isStandard = gitDir.commonUri == null;
|
|
48
61
|
const rootPattern = isStandard ? dotGitGlobCombined : dotGitGlobRoot;
|
|
62
|
+
let entry;
|
|
63
|
+
let initialReliability = "reliable";
|
|
49
64
|
const rootWatcher = this.provider.createWatcher(gitDir.uri.fsPath, rootPattern, (event) => {
|
|
50
|
-
const
|
|
51
|
-
if (
|
|
52
|
-
|
|
65
|
+
const e = this._sessions.get(repoPath);
|
|
66
|
+
if (e != null) {
|
|
67
|
+
if (event.reason !== "overflow") e.target?.forget(event.path);
|
|
68
|
+
this.onRootEvent(event, e);
|
|
69
|
+
}
|
|
70
|
+
}, this.getWatcherOptions((state) => {
|
|
71
|
+
if (entry == null) initialReliability = state;
|
|
72
|
+
else if (this._sessions.get(repoPath) === entry) this.setRootReliability(entry, state);
|
|
73
|
+
}));
|
|
53
74
|
const entryGitDirPath = gitDir.uri.fsPath;
|
|
54
|
-
|
|
75
|
+
entry = {
|
|
55
76
|
repoPath,
|
|
56
77
|
gitDirPath: entryGitDirPath,
|
|
57
78
|
rootWatcher,
|
|
58
79
|
coversCommon: isStandard,
|
|
59
80
|
worktreeId: isStandard ? void 0 : this.getWorktreeId(entryGitDirPath),
|
|
60
|
-
hooks
|
|
81
|
+
hooks,
|
|
82
|
+
reliability: "reliable"
|
|
61
83
|
};
|
|
62
84
|
this._sessions.set(repoPath, entry);
|
|
85
|
+
if (initialReliability !== "reliable") this.setRootReliability(entry, initialReliability, true);
|
|
63
86
|
if (!isStandard) this.ensureCommonWatcher();
|
|
64
87
|
}
|
|
65
88
|
/** Remove a session from this group. Disposes its root watcher. */
|
|
@@ -67,11 +90,9 @@ var WatchGroup = class {
|
|
|
67
90
|
const entry = this._sessions.get(repoPath);
|
|
68
91
|
if (entry == null) return;
|
|
69
92
|
entry.rootWatcher.dispose();
|
|
93
|
+
this.stopPolling(entry);
|
|
70
94
|
this._sessions.delete(repoPath);
|
|
71
|
-
if (this._commonWatcher != null && !some(this._sessions.values(), (e) => !e.coversCommon))
|
|
72
|
-
this._commonWatcher.dispose();
|
|
73
|
-
this._commonWatcher = void 0;
|
|
74
|
-
}
|
|
95
|
+
if (this._commonWatcher != null && !some(this._sessions.values(), (e) => !e.coversCommon)) this.disposeCommonWatcher();
|
|
75
96
|
}
|
|
76
97
|
getWorktreeId(gitDirPath) {
|
|
77
98
|
if (!isDescendant(gitDirPath, this.commonGitDir)) return void 0;
|
|
@@ -82,9 +103,114 @@ var WatchGroup = class {
|
|
|
82
103
|
}
|
|
83
104
|
ensureCommonWatcher() {
|
|
84
105
|
if (this._commonWatcher != null || this._disposed) return;
|
|
85
|
-
|
|
106
|
+
let watcher;
|
|
107
|
+
let initialReliability = "reliable";
|
|
108
|
+
const options = this.getWatcherOptions((state) => {
|
|
109
|
+
if (watcher == null) initialReliability = state;
|
|
110
|
+
else if (this._commonWatcher === watcher) this.setCommonReliability(state);
|
|
111
|
+
});
|
|
112
|
+
watcher = this.provider.createWatcher(this.commonGitDir, dotGitGlobCommon, (event) => {
|
|
113
|
+
if (event.reason !== "overflow") this._commonTarget?.forget(event.path);
|
|
86
114
|
this.onCommonEvent(event);
|
|
87
|
-
},
|
|
115
|
+
}, options);
|
|
116
|
+
this._commonWatcher = watcher;
|
|
117
|
+
if (initialReliability !== "reliable") this.setCommonReliability(initialReliability, true);
|
|
118
|
+
}
|
|
119
|
+
disposeCommonWatcher() {
|
|
120
|
+
this._commonWatcher?.dispose();
|
|
121
|
+
this._commonWatcher = void 0;
|
|
122
|
+
this._commonReliability = "reliable";
|
|
123
|
+
if (this._commonTarget != null) {
|
|
124
|
+
this.poller?.delete(this._commonTarget);
|
|
125
|
+
this._commonTarget = void 0;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/** Hosts that didn't opt into polling get exactly the options they always had */
|
|
129
|
+
getWatcherOptions(onDidChangeReliability) {
|
|
130
|
+
return this.poller != null ? {
|
|
131
|
+
excludes: gitDirExcludes,
|
|
132
|
+
onDidChangeReliability
|
|
133
|
+
} : { excludes: gitDirExcludes };
|
|
134
|
+
}
|
|
135
|
+
setRootReliability(entry, state, initial) {
|
|
136
|
+
const previous = entry.reliability;
|
|
137
|
+
if (previous === state || this.poller == null) return;
|
|
138
|
+
entry.reliability = state;
|
|
139
|
+
if (state === "reliable") this.stopPolling(entry);
|
|
140
|
+
else if (entry.target == null) {
|
|
141
|
+
const target = new GitDirProbeTarget(entry.gitDirPath, this.poller, () => this.getRootProbes(entry), gitDirProbesByEmittedPath, (event, probe) => {
|
|
142
|
+
if (entry.target !== target) return;
|
|
143
|
+
this.onRootEvent(event, entry);
|
|
144
|
+
if (probe.path === "logs/HEAD" && !entry.coversCommon) this.onLinkedHeadMoved();
|
|
145
|
+
});
|
|
146
|
+
entry.target = target;
|
|
147
|
+
this.poller.add(target);
|
|
148
|
+
}
|
|
149
|
+
if (state === "unwatchable" === (previous === "unwatchable")) return;
|
|
150
|
+
if (!initial) this.deliverOverflow(entry);
|
|
151
|
+
entry.hooks.onUnwatchableChanged?.(entry.repoPath);
|
|
152
|
+
}
|
|
153
|
+
setCommonReliability(state, initial) {
|
|
154
|
+
const previous = this._commonReliability;
|
|
155
|
+
if (previous === state || this.poller == null) return;
|
|
156
|
+
this._commonReliability = state;
|
|
157
|
+
if (state === "reliable") {
|
|
158
|
+
if (this._commonTarget != null) {
|
|
159
|
+
this.poller.delete(this._commonTarget);
|
|
160
|
+
this._commonTarget = void 0;
|
|
161
|
+
}
|
|
162
|
+
} else if (this._commonTarget == null) {
|
|
163
|
+
const target = new GitDirProbeTarget(this.commonGitDir, this.poller, () => this.getCommonProbes(), gitDirProbesByEmittedPath, (event) => {
|
|
164
|
+
if (this._commonTarget === target) this.onCommonEvent(event);
|
|
165
|
+
});
|
|
166
|
+
this._commonTarget = target;
|
|
167
|
+
this.poller.add(target);
|
|
168
|
+
}
|
|
169
|
+
if (state === "unwatchable" === (previous === "unwatchable")) return;
|
|
170
|
+
for (const entry of this._sessions.values()) {
|
|
171
|
+
if (entry.coversCommon) continue;
|
|
172
|
+
if (!initial) this.deliverOverflow(entry);
|
|
173
|
+
entry.hooks.onUnwatchableChanged?.(entry.repoPath);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
stopPolling(entry) {
|
|
177
|
+
if (entry.target == null) return;
|
|
178
|
+
this.poller?.delete(entry.target);
|
|
179
|
+
entry.target = void 0;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* A session rooted at the common dir polls the common files too, with the refs of every session in the group: its
|
|
183
|
+
* combined watcher would hear them all. While the common dir is polled too, the poller stats each shared file once
|
|
184
|
+
* for both, and each keeps its own readings, so what one watcher reported is held back from its own sessions only
|
|
185
|
+
*/
|
|
186
|
+
getRootProbes(entry) {
|
|
187
|
+
if (!entry.coversCommon) return gitDirRootProbes;
|
|
188
|
+
const refs = /* @__PURE__ */ new Set();
|
|
189
|
+
for (const e of this._sessions.values()) for (const ref of e.hooks.getProbeRefs?.() ?? []) refs.add(ref);
|
|
190
|
+
return [
|
|
191
|
+
...gitDirRootProbes,
|
|
192
|
+
...gitDirCommonProbes,
|
|
193
|
+
...getGitDirRefProbes(refs)
|
|
194
|
+
];
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* A linked worktree's `logs/HEAD` moved — a commit or reset there, on a branch a polled session rooted at the common
|
|
198
|
+
* dir may not probe — so each such session hears a `heads` change its combined watcher would have heard
|
|
199
|
+
*/
|
|
200
|
+
onLinkedHeadMoved() {
|
|
201
|
+
for (const entry of this._sessions.values()) {
|
|
202
|
+
if (!entry.coversCommon || entry.target == null) continue;
|
|
203
|
+
entry.hooks.onRepoChanged(entry.repoPath, ["heads"]);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
/** The common files, with the refs of every linked session (the only ones the common watcher delivers to) */
|
|
207
|
+
getCommonProbes() {
|
|
208
|
+
const refs = /* @__PURE__ */ new Set();
|
|
209
|
+
for (const entry of this._sessions.values()) {
|
|
210
|
+
if (entry.coversCommon) continue;
|
|
211
|
+
for (const ref of entry.hooks.getProbeRefs?.() ?? []) refs.add(ref);
|
|
212
|
+
}
|
|
213
|
+
return [...gitDirCommonProbes, ...getGitDirRefProbes(refs)];
|
|
88
214
|
}
|
|
89
215
|
onCommonEvent(event) {
|
|
90
216
|
if (event.reason === "overflow") {
|
|
@@ -118,7 +244,7 @@ var WatchGroup = class {
|
|
|
118
244
|
}
|
|
119
245
|
}
|
|
120
246
|
deliverOverflow(entry) {
|
|
121
|
-
entry.hooks.onRepoChanged(entry.repoPath, [...allWatchedRepositoryChanges]);
|
|
247
|
+
entry.hooks.onRepoChanged(entry.repoPath, [...allWatchedRepositoryChanges], void 0, true);
|
|
122
248
|
entry.hooks.onFetchHeadChanged?.(entry.repoPath);
|
|
123
249
|
entry.hooks.onIgnoresChanged?.(entry.repoPath);
|
|
124
250
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"watchGroup.js","names":[],"sources":["../../../../git/src/watching/watchGroup.ts"],"sourcesContent":["import type { UnifiedDisposable } from '@gitlens/utils/disposable.js';\nimport { some } from '@gitlens/utils/iterable.js';\nimport { isDescendant, relative } from '@gitlens/utils/path.js';\nimport type { GitDir, RepositoryChange } from '../models/repository.js';\nimport { allWatchedRepositoryChanges, classifyGitDirChange } from './classifyChange.js';\nimport type { FileWatcher, FileWatchEvent, FileWatchingProvider } from './provider.js';\nimport {\n\tdotGitGlobCombined,\n\tdotGitGlobCommon,\n\tdotGitGlobRoot,\n\tgitDirExcludes,\n\tshouldIgnoreRepoPath,\n} from './watcherPatterns.js';\n\n/**\n * Hooks that the WatchGroup fires when events are interpreted.\n * These are raw, un-debounced events — the session handles debouncing.\n */\nexport interface WatchGroupHooks {\n\t/**\n\t * `ref` is the full ref name (or ref directory) or pseudo-ref (`HEAD`, `ORIG_HEAD`) whose file changed, with a\n\t * trailing `.lock` stripped; `undefined` for any other `.git` file.\n\t */\n\treadonly onRepoChanged: (repoPath: string, changes: RepositoryChange[], ref?: string) => void;\n\treadonly onFetchHeadChanged?: (repoPath: string) => void;\n\treadonly onIgnoresChanged?: (repoPath: string) => void;\n}\n\n/**\n * Tracks one session's connection to a WatchGroup.\n * Each session has its own root watcher but shares the common watcher.\n */\ninterface SessionEntry {\n\treadonly repoPath: string;\n\treadonly gitDirPath: string;\n\treadonly rootWatcher: FileWatcher;\n\t/** True when the root watcher is on the common dir itself, so it already sees every common file */\n\treadonly coversCommon: boolean;\n\t/** For a linked session, its `<n>` in `<common>/worktrees/<n>`; otherwise `undefined` */\n\treadonly worktreeId: string | undefined;\n\treadonly hooks: WatchGroupHooks;\n}\n\n/**\n * Manages filesystem watchers for a single physical `.git` directory,\n * shared across all worktrees that reference it.\n *\n * Structure:\n * - 1 common watcher: config, refs/**, info/exclude, etc. Delivers to linked sessions only; a\n * session rooted at the common dir hears the same files from its own combined root watcher.\n * - N root watchers: one per worktree (index, HEAD, *_HEAD, etc.)\n *\n * For a standard (non-worktree) repo, the common and root directories\n * are the same, but we still create separate watchers for clean separation.\n */\nexport class WatchGroup implements UnifiedDisposable {\n\tprivate readonly _sessions = new Map<string, SessionEntry>();\n\tprivate _commonWatcher: FileWatcher | undefined;\n\tprivate _disposed = false;\n\n\tconstructor(\n\t\treadonly commonGitDir: string,\n\t\tprivate readonly provider: FileWatchingProvider,\n\t) {}\n\n\t[Symbol.dispose](): void {\n\t\tthis.dispose();\n\t}\n\n\t/** Dispose the entire group (common watcher + all root watchers). */\n\tdispose(): void {\n\t\tif (this._disposed) return;\n\n\t\tthis._disposed = true;\n\n\t\tfor (const entry of this._sessions.values()) {\n\t\t\tentry.rootWatcher.dispose();\n\t\t}\n\t\tthis._sessions.clear();\n\n\t\tthis._commonWatcher?.dispose();\n\t\tthis._commonWatcher = undefined;\n\t}\n\n\tget sessions(): ReadonlyMap<string, SessionEntry> {\n\t\treturn this._sessions;\n\t}\n\n\t/** Add a session to this group. Creates the root watcher for the session's gitDir. */\n\taddSession(repoPath: string, gitDir: GitDir, hooks: WatchGroupHooks): void {\n\t\tif (this._disposed) return;\n\t\tif (this._sessions.has(repoPath)) return; // Already tracked\n\n\t\t// Determine the glob pattern for the root watcher\n\t\tconst isStandard = gitDir.commonUri == null;\n\t\tconst rootPattern = isStandard ? dotGitGlobCombined : dotGitGlobRoot;\n\n\t\tconst rootWatcher = this.provider.createWatcher(\n\t\t\tgitDir.uri.fsPath,\n\t\t\trootPattern,\n\t\t\t(event: FileWatchEvent) => {\n\t\t\t\tconst entry = this._sessions.get(repoPath);\n\t\t\t\tif (entry != null) {\n\t\t\t\t\tthis.onRootEvent(event, entry);\n\t\t\t\t}\n\t\t\t},\n\t\t\t{ excludes: gitDirExcludes },\n\t\t);\n\n\t\tconst entryGitDirPath = gitDir.uri.fsPath;\n\t\tconst entry: SessionEntry = {\n\t\t\trepoPath: repoPath,\n\t\t\tgitDirPath: entryGitDirPath,\n\t\t\trootWatcher: rootWatcher,\n\t\t\tcoversCommon: isStandard,\n\t\t\tworktreeId: isStandard ? undefined : this.getWorktreeId(entryGitDirPath),\n\t\t\thooks: hooks,\n\t\t};\n\n\t\tthis._sessions.set(repoPath, entry);\n\n\t\t// Start the common watcher if this is a worktree repo and we haven't yet.\n\t\t// Standard repos don't need a separate common watcher because their root\n\t\t// watcher uses dotGitGlobCombined which already covers all patterns.\n\t\tif (!isStandard) {\n\t\t\tthis.ensureCommonWatcher();\n\t\t}\n\t}\n\n\t/** Remove a session from this group. Disposes its root watcher. */\n\tremoveSession(repoPath: string): void {\n\t\tconst entry = this._sessions.get(repoPath);\n\t\tif (entry == null) return;\n\n\t\tentry.rootWatcher.dispose();\n\t\tthis._sessions.delete(repoPath);\n\n\t\t// Once no linked session is left, dispose the common watcher — it delivers to linked sessions only\n\t\tif (this._commonWatcher != null && !some(this._sessions.values(), e => !e.coversCommon)) {\n\t\t\tthis._commonWatcher.dispose();\n\t\t\tthis._commonWatcher = undefined;\n\t\t}\n\t}\n\n\tprivate getWorktreeId(gitDirPath: string): string | undefined {\n\t\tif (!isDescendant(gitDirPath, this.commonGitDir)) return undefined;\n\n\t\tconst rel = relative(this.commonGitDir, gitDirPath);\n\t\tif (!rel.startsWith('worktrees/')) return undefined;\n\n\t\tconst id = rel.substring(10);\n\t\treturn id.length > 0 && !id.includes('/') ? id : undefined;\n\t}\n\n\tprivate ensureCommonWatcher(): void {\n\t\tif (this._commonWatcher != null || this._disposed) return;\n\n\t\tthis._commonWatcher = this.provider.createWatcher(\n\t\t\tthis.commonGitDir,\n\t\t\tdotGitGlobCommon,\n\t\t\t(event: FileWatchEvent) => {\n\t\t\t\tthis.onCommonEvent(event);\n\t\t\t},\n\t\t\t{ excludes: gitDirExcludes },\n\t\t);\n\t}\n\n\tprivate onCommonEvent(event: FileWatchEvent): void {\n\t\tif (event.reason === 'overflow') {\n\t\t\tfor (const entry of this._sessions.values()) {\n\t\t\t\tif (entry.coversCommon) continue;\n\n\t\t\t\tthis.deliverOverflow(entry);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Compute relative path from common git dir\n\t\tif (!isDescendant(event.path, this.commonGitDir)) return;\n\n\t\tconst relativePath = relative(this.commonGitDir, event.path);\n\t\tif (shouldIgnoreRepoPath(relativePath)) return;\n\n\t\tconst isFetchHead = relativePath === 'FETCH_HEAD';\n\t\tconst isExclude = relativePath === 'info/exclude';\n\t\tconst changes = isFetchHead ? undefined : classifyGitDirChange(relativePath);\n\t\tif (changes == null && !isFetchHead && !isExclude) return;\n\n\t\tconst ref = changes != null ? getChangedRef(relativePath) : undefined;\n\n\t\t// `worktrees/<n>/HEAD`: the owner hears its own HEAD from its root watcher. Its `locked` and `gitdir` aren't in\n\t\t// the root glob, so the owner hears those only from here\n\t\tlet ownerId: string | undefined;\n\t\tif (changes != null && relativePath.length > 10 && relativePath.startsWith('worktrees/')) {\n\t\t\tconst idEnd = relativePath.indexOf('/', 10);\n\t\t\tif (idEnd !== -1 && relativePath.substring(idEnd + 1) === 'HEAD') {\n\t\t\t\townerId = relativePath.substring(10, idEnd);\n\t\t\t}\n\t\t}\n\n\t\tfor (const entry of this._sessions.values()) {\n\t\t\t// A session rooted at the common dir sees every common file (hooks included) through its own\n\t\t\t// combined root watcher, so delivering here too would duplicate it\n\t\t\tif (entry.coversCommon) continue;\n\n\t\t\tif (isFetchHead) {\n\t\t\t\t// FETCH_HEAD lives in the main .git dir; each worktree updates its last-fetched state\n\t\t\t\tentry.hooks.onFetchHeadChanged?.(entry.repoPath);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (changes != null && (ownerId == null || entry.worktreeId !== ownerId)) {\n\t\t\t\tentry.hooks.onRepoChanged(entry.repoPath, changes, ref);\n\t\t\t}\n\n\t\t\tif (isExclude) {\n\t\t\t\tentry.hooks.onIgnoresChanged?.(entry.repoPath);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate deliverOverflow(entry: SessionEntry): void {\n\t\t// Every kind, no ref (ref kinds stay unnamed). Never `'unknown'` — the cache would unregister the path\n\t\tentry.hooks.onRepoChanged(entry.repoPath, [...allWatchedRepositoryChanges]);\n\t\tentry.hooks.onFetchHeadChanged?.(entry.repoPath);\n\t\tentry.hooks.onIgnoresChanged?.(entry.repoPath);\n\t}\n\n\tprivate onRootEvent(event: FileWatchEvent, entry: SessionEntry): void {\n\t\tif (event.reason === 'overflow') {\n\t\t\tthis.deliverOverflow(entry);\n\t\t\treturn;\n\t\t}\n\n\t\tif (!isDescendant(event.path, entry.gitDirPath)) return;\n\n\t\tconst relativePath = relative(entry.gitDirPath, event.path);\n\t\tif (shouldIgnoreRepoPath(relativePath)) return;\n\n\t\t// Check for FETCH_HEAD before interpretation\n\t\tif (relativePath === 'FETCH_HEAD') {\n\t\t\tentry.hooks.onFetchHeadChanged?.(entry.repoPath);\n\t\t\treturn;\n\t\t}\n\n\t\tconst changes = classifyGitDirChange(relativePath);\n\t\tif (changes != null) {\n\t\t\tentry.hooks.onRepoChanged(entry.repoPath, changes, getChangedRef(relativePath));\n\t\t}\n\n\t\tif (relativePath === 'info/exclude') {\n\t\t\tentry.hooks.onIgnoresChanged?.(entry.repoPath);\n\t\t}\n\t}\n}\n\n/**\n * The ref a `.git`-relative path names: a loose ref (or ref directory) under `refs/`, or the `HEAD`/`ORIG_HEAD`\n * pseudo-refs. Git writes a ref as `<name>.lock` and renames it over `<name>`, so the lock file names the same ref.\n */\nfunction getChangedRef(relativePath: string): string | undefined {\n\tconst ref = relativePath.endsWith('.lock') ? relativePath.substring(0, relativePath.length - 5) : relativePath;\n\treturn ref.startsWith('refs/') || ref === 'HEAD' || ref === 'ORIG_HEAD' ? ref : undefined;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"watchGroup.js","names":[],"sources":["../../../../git/src/watching/watchGroup.ts"],"sourcesContent":["import type { UnifiedDisposable } from '@gitlens/utils/disposable.js';\nimport { some } from '@gitlens/utils/iterable.js';\nimport { isDescendant, relative } from '@gitlens/utils/path.js';\nimport type { GitDir, RepositoryChange } from '../models/repository.js';\nimport { allWatchedRepositoryChanges, classifyGitDirChange } from './classifyChange.js';\nimport type { GitDirPoller, GitDirProbe } from './gitDirPoller.js';\nimport { GitDirProbeTarget } from './gitDirPoller.js';\nimport type {\n\tFileWatcher,\n\tFileWatcherOptions,\n\tFileWatcherReliability,\n\tFileWatchEvent,\n\tFileWatchingProvider,\n} from './provider.js';\nimport {\n\tdotGitGlobCombined,\n\tdotGitGlobCommon,\n\tdotGitGlobRoot,\n\tgetGitDirRefProbes,\n\tgitDirCommonProbes,\n\tgitDirExcludes,\n\tgitDirProbesByEmittedPath,\n\tgitDirRootProbes,\n\tshouldIgnoreRepoPath,\n} from './watcherPatterns.js';\n\n/**\n * Hooks that the WatchGroup fires when events are interpreted.\n * These are raw, un-debounced events — the session handles debouncing.\n */\nexport interface WatchGroupHooks {\n\t/**\n\t * `ref` is the full ref name (or ref directory) or pseudo-ref (`HEAD`, `ORIG_HEAD`) whose file changed, with a\n\t * trailing `.lock` stripped; `undefined` for any other `.git` file. `overflow` marks the every-kind rescan of a\n\t * watcher that dropped events, which no single write can account for.\n\t */\n\treadonly onRepoChanged: (repoPath: string, changes: RepositoryChange[], ref?: string, overflow?: boolean) => void;\n\treadonly onFetchHeadChanged?: (repoPath: string) => void;\n\treadonly onIgnoresChanged?: (repoPath: string) => void;\n\t/** The refs whose loose files a poll stats for this session, read at each poll */\n\treadonly getProbeRefs?: () => Iterable<string>;\n\t/** A watcher this session hears through went `unwatchable`, or stopped being it (see {@link WatchGroup.isUnwatchable}) */\n\treadonly onUnwatchableChanged?: (repoPath: string) => void;\n}\n\n/**\n * Tracks one session's connection to a WatchGroup.\n * Each session has its own root watcher but shares the common watcher.\n */\ninterface SessionEntry {\n\treadonly repoPath: string;\n\treadonly gitDirPath: string;\n\treadonly rootWatcher: FileWatcher;\n\t/** True when the root watcher is on the common dir itself, so it already sees every common file */\n\treadonly coversCommon: boolean;\n\t/** For a linked session, its `<n>` in `<common>/worktrees/<n>`; otherwise `undefined` */\n\treadonly worktreeId: string | undefined;\n\treadonly hooks: WatchGroupHooks;\n\treliability: FileWatcherReliability;\n\t/** Polls in place of (or beside) the root watcher while it isn't `reliable` */\n\ttarget?: GitDirProbeTarget;\n}\n\n/**\n * Manages filesystem watchers for a single physical `.git` directory,\n * shared across all worktrees that reference it.\n *\n * Structure:\n * - 1 common watcher: config, refs/**, info/exclude, etc. Delivers to linked sessions only; a\n * session rooted at the common dir hears the same files from its own combined root watcher.\n * - N root watchers: one per worktree (index, HEAD, *_HEAD, etc.)\n *\n * For a standard (non-worktree) repo, the common and root directories\n * are the same, but we still create separate watchers for clean separation.\n */\nexport class WatchGroup implements UnifiedDisposable {\n\tprivate readonly _sessions = new Map<string, SessionEntry>();\n\tprivate _commonWatcher: FileWatcher | undefined;\n\tprivate _commonReliability: FileWatcherReliability = 'reliable';\n\tprivate _commonTarget: GitDirProbeTarget | undefined;\n\tprivate _disposed = false;\n\n\tconstructor(\n\t\treadonly commonGitDir: string,\n\t\tprivate readonly provider: FileWatchingProvider,\n\t\t/** When given, a watcher that reports it can't be trusted is polled; otherwise none is asked */\n\t\tprivate readonly poller?: GitDirPoller,\n\t) {}\n\n\t[Symbol.dispose](): void {\n\t\tthis.dispose();\n\t}\n\n\t/** Dispose the entire group (common watcher + all root watchers). */\n\tdispose(): void {\n\t\tif (this._disposed) return;\n\n\t\tthis._disposed = true;\n\n\t\tfor (const entry of this._sessions.values()) {\n\t\t\tentry.rootWatcher.dispose();\n\t\t\tthis.stopPolling(entry);\n\t\t}\n\t\tthis._sessions.clear();\n\n\t\tthis.disposeCommonWatcher();\n\t}\n\n\tget sessions(): ReadonlyMap<string, SessionEntry> {\n\t\treturn this._sessions;\n\t}\n\n\t/** True when a watcher the session hears through — its root watcher, or a linked session's common one — can't watch */\n\tisUnwatchable(repoPath: string): boolean {\n\t\tconst entry = this._sessions.get(repoPath);\n\t\tif (entry == null) return false;\n\n\t\treturn (\n\t\t\tentry.reliability === 'unwatchable' || (!entry.coversCommon && this._commonReliability === 'unwatchable')\n\t\t);\n\t}\n\n\t/** Add a session to this group. Creates the root watcher for the session's gitDir. */\n\taddSession(repoPath: string, gitDir: GitDir, hooks: WatchGroupHooks): void {\n\t\tif (this._disposed) return;\n\t\tif (this._sessions.has(repoPath)) return; // Already tracked\n\n\t\t// Determine the glob pattern for the root watcher\n\t\tconst isStandard = gitDir.commonUri == null;\n\t\tconst rootPattern = isStandard ? dotGitGlobCombined : dotGitGlobRoot;\n\n\t\t// The watcher can report its reliability before `createWatcher` returns, so before the entry exists\n\t\tlet entry: SessionEntry | undefined;\n\t\tlet initialReliability: FileWatcherReliability = 'reliable';\n\n\t\tconst rootWatcher = this.provider.createWatcher(\n\t\t\tgitDir.uri.fsPath,\n\t\t\trootPattern,\n\t\t\t(event: FileWatchEvent) => {\n\t\t\t\tconst e = this._sessions.get(repoPath);\n\t\t\t\tif (e != null) {\n\t\t\t\t\tif (event.reason !== 'overflow') {\n\t\t\t\t\t\te.target?.forget(event.path);\n\t\t\t\t\t}\n\t\t\t\t\tthis.onRootEvent(event, e);\n\t\t\t\t}\n\t\t\t},\n\t\t\tthis.getWatcherOptions(state => {\n\t\t\t\tif (entry == null) {\n\t\t\t\t\tinitialReliability = state;\n\t\t\t\t} else if (this._sessions.get(repoPath) === entry) {\n\t\t\t\t\tthis.setRootReliability(entry, state);\n\t\t\t\t}\n\t\t\t}),\n\t\t);\n\n\t\tconst entryGitDirPath = gitDir.uri.fsPath;\n\t\tentry = {\n\t\t\trepoPath: repoPath,\n\t\t\tgitDirPath: entryGitDirPath,\n\t\t\trootWatcher: rootWatcher,\n\t\t\tcoversCommon: isStandard,\n\t\t\tworktreeId: isStandard ? undefined : this.getWorktreeId(entryGitDirPath),\n\t\t\thooks: hooks,\n\t\t\treliability: 'reliable',\n\t\t};\n\n\t\tthis._sessions.set(repoPath, entry);\n\t\tif (initialReliability !== 'reliable') {\n\t\t\tthis.setRootReliability(entry, initialReliability, true);\n\t\t}\n\n\t\t// Start the common watcher if this is a worktree repo and we haven't yet.\n\t\t// Standard repos don't need a separate common watcher because their root\n\t\t// watcher uses dotGitGlobCombined which already covers all patterns.\n\t\tif (!isStandard) {\n\t\t\tthis.ensureCommonWatcher();\n\t\t}\n\t}\n\n\t/** Remove a session from this group. Disposes its root watcher. */\n\tremoveSession(repoPath: string): void {\n\t\tconst entry = this._sessions.get(repoPath);\n\t\tif (entry == null) return;\n\n\t\tentry.rootWatcher.dispose();\n\t\tthis.stopPolling(entry);\n\t\tthis._sessions.delete(repoPath);\n\n\t\t// Once no linked session is left, dispose the common watcher — it delivers to linked sessions only\n\t\tif (this._commonWatcher != null && !some(this._sessions.values(), e => !e.coversCommon)) {\n\t\t\tthis.disposeCommonWatcher();\n\t\t}\n\t}\n\n\tprivate getWorktreeId(gitDirPath: string): string | undefined {\n\t\tif (!isDescendant(gitDirPath, this.commonGitDir)) return undefined;\n\n\t\tconst rel = relative(this.commonGitDir, gitDirPath);\n\t\tif (!rel.startsWith('worktrees/')) return undefined;\n\n\t\tconst id = rel.substring(10);\n\t\treturn id.length > 0 && !id.includes('/') ? id : undefined;\n\t}\n\n\tprivate ensureCommonWatcher(): void {\n\t\tif (this._commonWatcher != null || this._disposed) return;\n\n\t\t// The watcher can report its reliability before `createWatcher` returns\n\t\tlet watcher: FileWatcher | undefined;\n\t\tlet initialReliability: FileWatcherReliability = 'reliable';\n\t\tconst options = this.getWatcherOptions(state => {\n\t\t\tif (watcher == null) {\n\t\t\t\tinitialReliability = state;\n\t\t\t} else if (this._commonWatcher === watcher) {\n\t\t\t\tthis.setCommonReliability(state);\n\t\t\t}\n\t\t});\n\n\t\twatcher = this.provider.createWatcher(\n\t\t\tthis.commonGitDir,\n\t\t\tdotGitGlobCommon,\n\t\t\t(event: FileWatchEvent) => {\n\t\t\t\tif (event.reason !== 'overflow') {\n\t\t\t\t\tthis._commonTarget?.forget(event.path);\n\t\t\t\t}\n\t\t\t\tthis.onCommonEvent(event);\n\t\t\t},\n\t\t\toptions,\n\t\t);\n\t\tthis._commonWatcher = watcher;\n\t\tif (initialReliability !== 'reliable') {\n\t\t\tthis.setCommonReliability(initialReliability, true);\n\t\t}\n\t}\n\n\tprivate disposeCommonWatcher(): void {\n\t\tthis._commonWatcher?.dispose();\n\t\tthis._commonWatcher = undefined;\n\t\tthis._commonReliability = 'reliable';\n\t\tif (this._commonTarget != null) {\n\t\t\tthis.poller?.delete(this._commonTarget);\n\t\t\tthis._commonTarget = undefined;\n\t\t}\n\t}\n\n\t/** Hosts that didn't opt into polling get exactly the options they always had */\n\tprivate getWatcherOptions(onDidChangeReliability: (state: FileWatcherReliability) => void): FileWatcherOptions {\n\t\treturn this.poller != null\n\t\t\t? { excludes: gitDirExcludes, onDidChangeReliability: onDidChangeReliability }\n\t\t\t: { excludes: gitDirExcludes };\n\t}\n\n\tprivate setRootReliability(entry: SessionEntry, state: FileWatcherReliability, initial?: boolean): void {\n\t\tconst previous = entry.reliability;\n\t\tif (previous === state || this.poller == null) return;\n\n\t\tentry.reliability = state;\n\t\tif (state === 'reliable') {\n\t\t\tthis.stopPolling(entry);\n\t\t} else if (entry.target == null) {\n\t\t\tconst target = new GitDirProbeTarget(\n\t\t\t\tentry.gitDirPath,\n\t\t\t\tthis.poller,\n\t\t\t\t() => this.getRootProbes(entry),\n\t\t\t\tgitDirProbesByEmittedPath,\n\t\t\t\t(event, probe) => {\n\t\t\t\t\tif (entry.target !== target) return;\n\n\t\t\t\t\tthis.onRootEvent(event, entry);\n\t\t\t\t\tif (probe.path === 'logs/HEAD' && !entry.coversCommon) {\n\t\t\t\t\t\tthis.onLinkedHeadMoved();\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t);\n\t\t\tentry.target = target;\n\t\t\tthis.poller.add(target);\n\t\t}\n\n\t\tif ((state === 'unwatchable') === (previous === 'unwatchable')) return;\n\n\t\t// The watch went down or came back: whatever changed meanwhile went unseen\n\t\tif (!initial) {\n\t\t\tthis.deliverOverflow(entry);\n\t\t}\n\t\tentry.hooks.onUnwatchableChanged?.(entry.repoPath);\n\t}\n\n\tprivate setCommonReliability(state: FileWatcherReliability, initial?: boolean): void {\n\t\tconst previous = this._commonReliability;\n\t\tif (previous === state || this.poller == null) return;\n\n\t\tthis._commonReliability = state;\n\t\tif (state === 'reliable') {\n\t\t\tif (this._commonTarget != null) {\n\t\t\t\tthis.poller.delete(this._commonTarget);\n\t\t\t\tthis._commonTarget = undefined;\n\t\t\t}\n\t\t} else if (this._commonTarget == null) {\n\t\t\tconst target = new GitDirProbeTarget(\n\t\t\t\tthis.commonGitDir,\n\t\t\t\tthis.poller,\n\t\t\t\t() => this.getCommonProbes(),\n\t\t\t\tgitDirProbesByEmittedPath,\n\t\t\t\tevent => {\n\t\t\t\t\tif (this._commonTarget === target) {\n\t\t\t\t\t\tthis.onCommonEvent(event);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t);\n\t\t\tthis._commonTarget = target;\n\t\t\tthis.poller.add(target);\n\t\t}\n\n\t\tif ((state === 'unwatchable') === (previous === 'unwatchable')) return;\n\n\t\tfor (const entry of this._sessions.values()) {\n\t\t\tif (entry.coversCommon) continue;\n\n\t\t\tif (!initial) {\n\t\t\t\tthis.deliverOverflow(entry);\n\t\t\t}\n\t\t\tentry.hooks.onUnwatchableChanged?.(entry.repoPath);\n\t\t}\n\t}\n\n\tprivate stopPolling(entry: SessionEntry): void {\n\t\tif (entry.target == null) return;\n\n\t\tthis.poller?.delete(entry.target);\n\t\tentry.target = undefined;\n\t}\n\n\t/**\n\t * A session rooted at the common dir polls the common files too, with the refs of every session in the group: its\n\t * combined watcher would hear them all. While the common dir is polled too, the poller stats each shared file once\n\t * for both, and each keeps its own readings, so what one watcher reported is held back from its own sessions only\n\t */\n\tprivate getRootProbes(entry: SessionEntry): readonly GitDirProbe[] {\n\t\tif (!entry.coversCommon) return gitDirRootProbes;\n\n\t\tconst refs = new Set<string>();\n\t\tfor (const e of this._sessions.values()) {\n\t\t\tfor (const ref of e.hooks.getProbeRefs?.() ?? []) {\n\t\t\t\trefs.add(ref);\n\t\t\t}\n\t\t}\n\t\treturn [...gitDirRootProbes, ...gitDirCommonProbes, ...getGitDirRefProbes(refs)];\n\t}\n\n\t/**\n\t * A linked worktree's `logs/HEAD` moved — a commit or reset there, on a branch a polled session rooted at the common\n\t * dir may not probe — so each such session hears a `heads` change its combined watcher would have heard\n\t */\n\tprivate onLinkedHeadMoved(): void {\n\t\tfor (const entry of this._sessions.values()) {\n\t\t\tif (!entry.coversCommon || entry.target == null) continue;\n\n\t\t\tentry.hooks.onRepoChanged(entry.repoPath, ['heads']);\n\t\t}\n\t}\n\n\t/** The common files, with the refs of every linked session (the only ones the common watcher delivers to) */\n\tprivate getCommonProbes(): GitDirProbe[] {\n\t\tconst refs = new Set<string>();\n\t\tfor (const entry of this._sessions.values()) {\n\t\t\tif (entry.coversCommon) continue;\n\n\t\t\tfor (const ref of entry.hooks.getProbeRefs?.() ?? []) {\n\t\t\t\trefs.add(ref);\n\t\t\t}\n\t\t}\n\t\treturn [...gitDirCommonProbes, ...getGitDirRefProbes(refs)];\n\t}\n\n\tprivate onCommonEvent(event: FileWatchEvent): void {\n\t\tif (event.reason === 'overflow') {\n\t\t\tfor (const entry of this._sessions.values()) {\n\t\t\t\tif (entry.coversCommon) continue;\n\n\t\t\t\tthis.deliverOverflow(entry);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\t// Compute relative path from common git dir\n\t\tif (!isDescendant(event.path, this.commonGitDir)) return;\n\n\t\tconst relativePath = relative(this.commonGitDir, event.path);\n\t\tif (shouldIgnoreRepoPath(relativePath)) return;\n\n\t\tconst isFetchHead = relativePath === 'FETCH_HEAD';\n\t\tconst isExclude = relativePath === 'info/exclude';\n\t\tconst changes = isFetchHead ? undefined : classifyGitDirChange(relativePath);\n\t\tif (changes == null && !isFetchHead && !isExclude) return;\n\n\t\tconst ref = changes != null ? getChangedRef(relativePath) : undefined;\n\n\t\t// `worktrees/<n>/HEAD`: the owner hears its own HEAD from its root watcher. Its `locked` and `gitdir` aren't in\n\t\t// the root glob, so the owner hears those only from here\n\t\tlet ownerId: string | undefined;\n\t\tif (changes != null && relativePath.length > 10 && relativePath.startsWith('worktrees/')) {\n\t\t\tconst idEnd = relativePath.indexOf('/', 10);\n\t\t\tif (idEnd !== -1 && relativePath.substring(idEnd + 1) === 'HEAD') {\n\t\t\t\townerId = relativePath.substring(10, idEnd);\n\t\t\t}\n\t\t}\n\n\t\tfor (const entry of this._sessions.values()) {\n\t\t\t// A session rooted at the common dir sees every common file (hooks included) through its own\n\t\t\t// combined root watcher, so delivering here too would duplicate it\n\t\t\tif (entry.coversCommon) continue;\n\n\t\t\tif (isFetchHead) {\n\t\t\t\t// FETCH_HEAD lives in the main .git dir; each worktree updates its last-fetched state\n\t\t\t\tentry.hooks.onFetchHeadChanged?.(entry.repoPath);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (changes != null && (ownerId == null || entry.worktreeId !== ownerId)) {\n\t\t\t\tentry.hooks.onRepoChanged(entry.repoPath, changes, ref);\n\t\t\t}\n\n\t\t\tif (isExclude) {\n\t\t\t\tentry.hooks.onIgnoresChanged?.(entry.repoPath);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate deliverOverflow(entry: SessionEntry): void {\n\t\t// Every kind, no ref (ref kinds stay unnamed). Never `'unknown'` — the cache would unregister the path\n\t\tentry.hooks.onRepoChanged(entry.repoPath, [...allWatchedRepositoryChanges], undefined, true);\n\t\tentry.hooks.onFetchHeadChanged?.(entry.repoPath);\n\t\tentry.hooks.onIgnoresChanged?.(entry.repoPath);\n\t}\n\n\tprivate onRootEvent(event: FileWatchEvent, entry: SessionEntry): void {\n\t\tif (event.reason === 'overflow') {\n\t\t\tthis.deliverOverflow(entry);\n\t\t\treturn;\n\t\t}\n\n\t\tif (!isDescendant(event.path, entry.gitDirPath)) return;\n\n\t\tconst relativePath = relative(entry.gitDirPath, event.path);\n\t\tif (shouldIgnoreRepoPath(relativePath)) return;\n\n\t\t// Check for FETCH_HEAD before interpretation\n\t\tif (relativePath === 'FETCH_HEAD') {\n\t\t\tentry.hooks.onFetchHeadChanged?.(entry.repoPath);\n\t\t\treturn;\n\t\t}\n\n\t\tconst changes = classifyGitDirChange(relativePath);\n\t\tif (changes != null) {\n\t\t\tentry.hooks.onRepoChanged(entry.repoPath, changes, getChangedRef(relativePath));\n\t\t}\n\n\t\tif (relativePath === 'info/exclude') {\n\t\t\tentry.hooks.onIgnoresChanged?.(entry.repoPath);\n\t\t}\n\t}\n}\n\n/**\n * The ref a `.git`-relative path names: a loose ref (or ref directory) under `refs/`, or the `HEAD`/`ORIG_HEAD`\n * pseudo-refs. Git writes a ref as `<name>.lock` and renames it over `<name>`, so the lock file names the same ref.\n */\nfunction getChangedRef(relativePath: string): string | undefined {\n\tconst ref = relativePath.endsWith('.lock') ? relativePath.substring(0, relativePath.length - 5) : relativePath;\n\treturn ref.startsWith('refs/') || ref === 'HEAD' || ref === 'ORIG_HEAD' ? ref : undefined;\n}\n"],"mappings":"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"}
|
|
@@ -5,8 +5,45 @@ import { WatcherRepoChangeEvent } from "./changeEvent.js";
|
|
|
5
5
|
import { GitIgnoreFilter } from "./gitIgnoreFilter.js";
|
|
6
6
|
import { FileWatchingProvider } from "./provider.js";
|
|
7
7
|
import { RepositoryWatchSession } from "./watchSession.js";
|
|
8
|
-
import { GitDir } from "../models/repository.js";
|
|
8
|
+
import { GitDir, RepositoryChange } from "../models/repository.js";
|
|
9
9
|
//#region ../git/src/watching/watchService.d.ts
|
|
10
|
+
/**
|
|
11
|
+
* A host whose watchers can report they can't be trusted (see `FileWatcherOptions.onDidChangeReliability`) passes
|
|
12
|
+
* these to have core poll in their place. A `.git` directory is polled by statting the files its watchers read and
|
|
13
|
+
* feeding each one that moved through the same interpretation as a watcher event; a working tree is never walked.
|
|
14
|
+
*/
|
|
15
|
+
export interface WatchPollingOptions {
|
|
16
|
+
/**
|
|
17
|
+
* Reads a path's modification time (ms) and size; `undefined` when it doesn't exist. A rejection means unknown:
|
|
18
|
+
* the last reading stands, so it never counts as a change.
|
|
19
|
+
*/
|
|
20
|
+
readonly stat: (path: string) => Promise<{
|
|
21
|
+
readonly mtime: number;
|
|
22
|
+
readonly size: number;
|
|
23
|
+
} | undefined>;
|
|
24
|
+
/** Whether a poll is worth running now. Default: the service isn't suspended (`suspendAll`/`resumeAll`) */
|
|
25
|
+
readonly isActive?: () => boolean;
|
|
26
|
+
/** How often each untrusted watcher is polled. Default: 60000ms */
|
|
27
|
+
readonly intervalMs?: number;
|
|
28
|
+
/**
|
|
29
|
+
* How often `isActive` is looked at, which bounds how late the first poll after it turns true can be. Only with
|
|
30
|
+
* `isActive`: the default gate wakes only for a poll. Default: 5000ms
|
|
31
|
+
*/
|
|
32
|
+
readonly activeCheckMs?: number;
|
|
33
|
+
/**
|
|
34
|
+
* Refs to poll for a repository beyond its subscribers' `relevantRefs`: one with none (a host listing every branch)
|
|
35
|
+
* still needs its checked-out branch's upstream, which a push moves without writing `FETCH_HEAD`
|
|
36
|
+
*/
|
|
37
|
+
readonly getRefs?: (repoPath: string) => Iterable<string>;
|
|
38
|
+
/**
|
|
39
|
+
* Something that changes whenever the repository's working tree does (e.g. a digest of its status and the
|
|
40
|
+
* changed files' stats), compared at each poll while working-tree subscribers are watching a working tree whose
|
|
41
|
+
* watcher can't be trusted; a change is reported with no paths. `undefined` or a rejection skips that poll. With or
|
|
42
|
+
* without it, a working tree whose watcher is `unwatchable` is reported changed (no paths) whenever its index or
|
|
43
|
+
* `HEAD` changes.
|
|
44
|
+
*/
|
|
45
|
+
readonly getWorkingTreeFingerprint?: (repoPath: string) => Promise<string | undefined>;
|
|
46
|
+
}
|
|
10
47
|
export interface WatchServiceOptions {
|
|
11
48
|
readonly watchingProvider: FileWatchingProvider;
|
|
12
49
|
/** Default debounce for repo changes when subscriber doesn't specify. Default: 250ms */
|
|
@@ -24,6 +61,8 @@ export interface WatchServiceOptions {
|
|
|
24
61
|
* `excludes` natively (`supportsExcludes`). Never called for one that doesn't. A rejection keeps the static set.
|
|
25
62
|
*/
|
|
26
63
|
readonly getIgnoredDirectories?: (repoPath: string) => Promise<readonly string[]>;
|
|
64
|
+
/** Opts into polling the watchers that report they can't be trusted. When absent, no watcher is asked. */
|
|
65
|
+
readonly polling?: WatchPollingOptions;
|
|
27
66
|
}
|
|
28
67
|
export interface WatchHooks {
|
|
29
68
|
/** Called when FETCH_HEAD changed (for last-fetched tracking) */
|
|
@@ -63,6 +102,16 @@ export declare class RepositoryWatchService implements UnifiedDisposable {
|
|
|
63
102
|
* last-fetched time — the standalone service bridges it to the cache's `lastFetched`.
|
|
64
103
|
*/
|
|
65
104
|
get onDidChangeFetchHead(): Event<string>;
|
|
105
|
+
private readonly _onDidChangeWatchMode;
|
|
106
|
+
/**
|
|
107
|
+
* Fires as a watched repository starts being polled in place of a watcher (see {@link getWatchMode}), and with
|
|
108
|
+
* `'events'` as it is watched natively again. Never fires as the repository stops being watched; one watched again
|
|
109
|
+
* later starts from `'events'`.
|
|
110
|
+
*/
|
|
111
|
+
get onDidChangeWatchMode(): Event<{
|
|
112
|
+
repoPath: string;
|
|
113
|
+
mode: 'events' | 'poll';
|
|
114
|
+
}>;
|
|
66
115
|
private readonly defaultRepoDelayMs?;
|
|
67
116
|
private readonly defaultWorkingTreeDelayMs?;
|
|
68
117
|
private readonly defaultRepoMaxWaitMs?;
|
|
@@ -70,6 +119,7 @@ export declare class RepositoryWatchService implements UnifiedDisposable {
|
|
|
70
119
|
private readonly getIgnoreFilter?;
|
|
71
120
|
private readonly getIgnoredDirectories?;
|
|
72
121
|
private readonly provider;
|
|
122
|
+
private readonly poller;
|
|
73
123
|
private readonly sessionMap;
|
|
74
124
|
private readonly watchGroups;
|
|
75
125
|
private readonly wtWatchers;
|
|
@@ -92,8 +142,20 @@ export declare class RepositoryWatchService implements UnifiedDisposable {
|
|
|
92
142
|
* a session and advance the same status clock.
|
|
93
143
|
*/
|
|
94
144
|
watch(repoPath: string, gitDir: GitDir, watchHooks?: WatchHooks): WatchHandle | undefined;
|
|
145
|
+
/**
|
|
146
|
+
* Core announced one of its own writes for `repoPath`; the session watching it fires the announcement in place of
|
|
147
|
+
* the write's own `.git` events (see {@link RepositoryWatchSession.noteSelfWrite}). A path no session watches is
|
|
148
|
+
* ignored.
|
|
149
|
+
*/
|
|
150
|
+
noteSelfWrite(repoPath: string, changes: readonly RepositoryChange[]): void;
|
|
95
151
|
/** Get an existing session by repo path */
|
|
96
152
|
getSession(repoPath: string): RepositoryWatchSession | undefined;
|
|
153
|
+
/**
|
|
154
|
+
* `'poll'` while a watcher the repository is watched through — its `.git` watchers or its working tree's — can't
|
|
155
|
+
* watch, and is polled instead; `'events'` while it is watched natively (polled beside an `unreliable` watcher
|
|
156
|
+
* included); `undefined` when nothing watches it, which {@link onDidChangeWatchMode} doesn't announce.
|
|
157
|
+
*/
|
|
158
|
+
getWatchMode(repoPath: string): 'events' | 'poll' | undefined;
|
|
97
159
|
/** Resource usage retained by repository watchers. */
|
|
98
160
|
getResourceUsage(): ResourceUsage;
|
|
99
161
|
/** Suspend all sessions and mark the service suspended so late-joining sessions start suspended */
|
|
@@ -104,8 +166,15 @@ export declare class RepositoryWatchService implements UnifiedDisposable {
|
|
|
104
166
|
private maybeDisposeWatchGroup;
|
|
105
167
|
private connectToWatchGroup;
|
|
106
168
|
private disconnectFromWatchGroup;
|
|
169
|
+
private computeWatchMode;
|
|
170
|
+
private updateWatchMode;
|
|
107
171
|
private startWorkingTreeWatcher;
|
|
108
172
|
private stopWorkingTreeWatcher;
|
|
173
|
+
/** Creates a working-tree watcher, with the reliability it reported before returning */
|
|
174
|
+
private createWorkingTreeWatcher;
|
|
175
|
+
private setWorkingTreeReliability;
|
|
176
|
+
/** Polls the host's working-tree fingerprint, when it gives one, while the watcher isn't `reliable` */
|
|
177
|
+
private updateWorkingTreePollTarget;
|
|
109
178
|
private canPruneIgnoredDirectories;
|
|
110
179
|
/**
|
|
111
180
|
* An event the gitignore post-filter dropped. When its directory is itself ignored, the watcher is watching an
|