@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
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { isDescendant, joinPaths, relative } from "../../utils/path.js";
|
|
2
|
+
//#region ../git/src/watching/gitDirPoller.ts
|
|
3
|
+
const defaultPollIntervalMs = 6e4;
|
|
4
|
+
const defaultPollActiveCheckMs = 5e3;
|
|
5
|
+
const defaultPollConcurrency = 4;
|
|
6
|
+
/**
|
|
7
|
+
* Runs passes over the poll targets of every watcher that can't be trusted, reading at most 4 targets at once across
|
|
8
|
+
* passes and first readings. A target is read once soon after it is added, so its baseline doesn't wait for the next
|
|
9
|
+
* pass; targets added together are read together.
|
|
10
|
+
*
|
|
11
|
+
* With the default gate (no `isActive`), one timer runs a pass every `intervalMs`, stopped by {@link suspend} and
|
|
12
|
+
* restarted by {@link resume}, which runs a pass at once. With a host's `isActive`, whose changes core can't see, one
|
|
13
|
+
* timer looks at it every `activeCheckMs`: a pass is due once `intervalMs` has gone by since the last one, or at the
|
|
14
|
+
* first look after the gate opens (or after {@link nudge}), so a window that comes back between passes doesn't wait out
|
|
15
|
+
* the rest of one.
|
|
16
|
+
*/
|
|
17
|
+
var GitDirPoller = class {
|
|
18
|
+
options;
|
|
19
|
+
targets = /* @__PURE__ */ new Set();
|
|
20
|
+
/** Targets added since the last pass and not yet read */
|
|
21
|
+
unread = /* @__PURE__ */ new Set();
|
|
22
|
+
/** Each target's last read asked for; a target is never read twice at once, so the next waits for it */
|
|
23
|
+
reading = /* @__PURE__ */ new Map();
|
|
24
|
+
/** Reads waiting for one of the {@link defaultPollConcurrency} slots, which a finished read hands over */
|
|
25
|
+
waiting = [];
|
|
26
|
+
/**
|
|
27
|
+
* The stats of the batches (a pass, or first readings) in flight, shared by every target that probes the same path,
|
|
28
|
+
* as the main checkout's root and its common dir do
|
|
29
|
+
*/
|
|
30
|
+
stats = /* @__PURE__ */ new Map();
|
|
31
|
+
intervalMs;
|
|
32
|
+
activeCheckMs;
|
|
33
|
+
timer;
|
|
34
|
+
running;
|
|
35
|
+
batches = 0;
|
|
36
|
+
slotsInUse = 0;
|
|
37
|
+
/** The next look runs a pass whatever the interval says */
|
|
38
|
+
due = true;
|
|
39
|
+
lastPassAt = 0;
|
|
40
|
+
suspended = false;
|
|
41
|
+
disposed = false;
|
|
42
|
+
constructor(options) {
|
|
43
|
+
this.options = options;
|
|
44
|
+
this.intervalMs = options.intervalMs ?? defaultPollIntervalMs;
|
|
45
|
+
this.activeCheckMs = options.activeCheckMs ?? defaultPollActiveCheckMs;
|
|
46
|
+
}
|
|
47
|
+
[Symbol.dispose]() {
|
|
48
|
+
this.dispose();
|
|
49
|
+
}
|
|
50
|
+
dispose() {
|
|
51
|
+
this.disposed = true;
|
|
52
|
+
this.targets.clear();
|
|
53
|
+
this.unread.clear();
|
|
54
|
+
this.stats.clear();
|
|
55
|
+
this.stopTimer();
|
|
56
|
+
}
|
|
57
|
+
get size() {
|
|
58
|
+
return this.targets.size;
|
|
59
|
+
}
|
|
60
|
+
add(target) {
|
|
61
|
+
if (this.disposed || this.targets.has(target)) return;
|
|
62
|
+
this.targets.add(target);
|
|
63
|
+
this.unread.add(target);
|
|
64
|
+
if (this.unread.size === 1) queueMicrotask(() => this.readUnread());
|
|
65
|
+
this.startTimer();
|
|
66
|
+
}
|
|
67
|
+
delete(target) {
|
|
68
|
+
this.unread.delete(target);
|
|
69
|
+
if (!this.targets.delete(target) || this.targets.size !== 0) return;
|
|
70
|
+
this.stopTimer();
|
|
71
|
+
}
|
|
72
|
+
/** Makes the next look a pass, and looks now */
|
|
73
|
+
nudge() {
|
|
74
|
+
this.due = true;
|
|
75
|
+
this.tick();
|
|
76
|
+
}
|
|
77
|
+
/** Under the default gate, stops passes (and the timer) until {@link resume}; a host's `isActive` ignores it */
|
|
78
|
+
suspend() {
|
|
79
|
+
this.suspended = true;
|
|
80
|
+
if (this.options.isActive == null) this.stopTimer();
|
|
81
|
+
}
|
|
82
|
+
/** Lifts {@link suspend}, and runs a pass now, so what moved meanwhile is polled rather than at the next look */
|
|
83
|
+
resume() {
|
|
84
|
+
this.suspended = false;
|
|
85
|
+
this.nudge();
|
|
86
|
+
}
|
|
87
|
+
/** One look at the gate: runs a pass when one is due, the gate is open and none is in flight */
|
|
88
|
+
tick() {
|
|
89
|
+
if (this.disposed || this.targets.size === 0) return Promise.resolve();
|
|
90
|
+
if (!(this.options.isActive?.() ?? !this.suspended)) {
|
|
91
|
+
this.due = true;
|
|
92
|
+
return Promise.resolve();
|
|
93
|
+
}
|
|
94
|
+
if (this.running != null) return this.running;
|
|
95
|
+
const now = performance.now();
|
|
96
|
+
if (!this.due && now - this.lastPassAt < this.intervalMs) return Promise.resolve();
|
|
97
|
+
if (this.options.isActive == null) {
|
|
98
|
+
this.stopTimer();
|
|
99
|
+
this.startTimer();
|
|
100
|
+
}
|
|
101
|
+
this.due = false;
|
|
102
|
+
this.lastPassAt = now;
|
|
103
|
+
this.running = this.pass().finally(() => {
|
|
104
|
+
this.running = void 0;
|
|
105
|
+
if (this.due) this.tick();
|
|
106
|
+
});
|
|
107
|
+
return this.running;
|
|
108
|
+
}
|
|
109
|
+
/** Stats `path` once for every target that probes it in the batches in flight */
|
|
110
|
+
stat(path) {
|
|
111
|
+
if (this.batches === 0) return this.options.stat(path);
|
|
112
|
+
let stat = this.stats.get(path);
|
|
113
|
+
if (stat == null) {
|
|
114
|
+
stat = this.options.stat(path);
|
|
115
|
+
this.stats.set(path, stat);
|
|
116
|
+
}
|
|
117
|
+
return stat;
|
|
118
|
+
}
|
|
119
|
+
/** `path` changed since it was statted, so the next target to probe it in a batch in flight stats it again */
|
|
120
|
+
invalidate(path) {
|
|
121
|
+
this.stats.delete(path);
|
|
122
|
+
}
|
|
123
|
+
pass() {
|
|
124
|
+
this.stats.clear();
|
|
125
|
+
this.unread.clear();
|
|
126
|
+
return this.readAll([...this.targets]);
|
|
127
|
+
}
|
|
128
|
+
readUnread() {
|
|
129
|
+
if (this.unread.size === 0) return;
|
|
130
|
+
const targets = [...this.unread];
|
|
131
|
+
this.unread.clear();
|
|
132
|
+
this.readAll(targets);
|
|
133
|
+
}
|
|
134
|
+
async readAll(targets) {
|
|
135
|
+
this.batches++;
|
|
136
|
+
try {
|
|
137
|
+
await Promise.allSettled(targets.map((target) => this.read(target)));
|
|
138
|
+
} finally {
|
|
139
|
+
this.batches--;
|
|
140
|
+
if (this.batches === 0) this.stats.clear();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/** Reads `target` once any read of it in flight ends, so the reading is fresh and its probes current */
|
|
144
|
+
read(target) {
|
|
145
|
+
const poll = async () => {
|
|
146
|
+
if (!this.targets.has(target)) return;
|
|
147
|
+
await this.acquireSlot();
|
|
148
|
+
try {
|
|
149
|
+
if (this.targets.has(target)) await target.poll();
|
|
150
|
+
} finally {
|
|
151
|
+
this.releaseSlot();
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
const previous = this.reading.get(target);
|
|
155
|
+
const reading = (previous != null ? previous.then(poll, poll) : poll()).finally(() => {
|
|
156
|
+
if (this.reading.get(target) === reading) this.reading.delete(target);
|
|
157
|
+
});
|
|
158
|
+
this.reading.set(target, reading);
|
|
159
|
+
return reading;
|
|
160
|
+
}
|
|
161
|
+
acquireSlot() {
|
|
162
|
+
if (this.slotsInUse < defaultPollConcurrency) {
|
|
163
|
+
this.slotsInUse++;
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
return new Promise((resolve) => this.waiting.push(resolve));
|
|
167
|
+
}
|
|
168
|
+
releaseSlot() {
|
|
169
|
+
const next = this.waiting.shift();
|
|
170
|
+
if (next != null) next();
|
|
171
|
+
else this.slotsInUse--;
|
|
172
|
+
}
|
|
173
|
+
startTimer() {
|
|
174
|
+
if (this.timer != null || this.disposed || this.targets.size === 0) return;
|
|
175
|
+
if (this.options.isActive != null) this.timer = setInterval(() => void this.tick(), this.activeCheckMs);
|
|
176
|
+
else if (!this.suspended) this.timer = setTimeout(() => {
|
|
177
|
+
this.timer = void 0;
|
|
178
|
+
this.due = true;
|
|
179
|
+
this.tick();
|
|
180
|
+
}, this.intervalMs);
|
|
181
|
+
else return;
|
|
182
|
+
if (typeof this.timer !== "number") this.timer.unref();
|
|
183
|
+
}
|
|
184
|
+
stopTimer() {
|
|
185
|
+
if (this.timer == null) return;
|
|
186
|
+
if (this.options.isActive != null) clearInterval(this.timer);
|
|
187
|
+
else clearTimeout(this.timer);
|
|
188
|
+
this.timer = void 0;
|
|
189
|
+
this.due = true;
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
/**
|
|
193
|
+
* Stands in for a watcher on `basePath`: stats each probe and reports one whose `mtime:size` moved as a `change` of
|
|
194
|
+
* the paths it is emitted as, through the same handler the watcher's own events take. A probe's first read (and a
|
|
195
|
+
* probe new to the set, as after a branch switch) only records; one that was missing and is now present moved. A stat
|
|
196
|
+
* that throws keeps the last reading, so it never counts as a move.
|
|
197
|
+
*/
|
|
198
|
+
var GitDirProbeTarget = class {
|
|
199
|
+
basePath;
|
|
200
|
+
stats;
|
|
201
|
+
getProbes;
|
|
202
|
+
probesByEmittedPath;
|
|
203
|
+
onMoved;
|
|
204
|
+
signatures = /* @__PURE__ */ new Map();
|
|
205
|
+
/** For each poll whose stats are in flight, the paths {@link forget} cleared meanwhile, which it must not record */
|
|
206
|
+
forgottenDuringPolls = /* @__PURE__ */ new Set();
|
|
207
|
+
constructor(basePath, stats, getProbes, probesByEmittedPath, onMoved) {
|
|
208
|
+
this.basePath = basePath;
|
|
209
|
+
this.stats = stats;
|
|
210
|
+
this.getProbes = getProbes;
|
|
211
|
+
this.probesByEmittedPath = probesByEmittedPath;
|
|
212
|
+
this.onMoved = onMoved;
|
|
213
|
+
}
|
|
214
|
+
async poll() {
|
|
215
|
+
const probes = [...this.getProbes()];
|
|
216
|
+
const forgotten = /* @__PURE__ */ new Set();
|
|
217
|
+
this.forgottenDuringPolls.add(forgotten);
|
|
218
|
+
const readings = await Promise.all(probes.map((p) => this.read(p.path)));
|
|
219
|
+
this.forgottenDuringPolls.delete(forgotten);
|
|
220
|
+
const seen = /* @__PURE__ */ new Set();
|
|
221
|
+
const moved = [];
|
|
222
|
+
for (let i = 0; i < probes.length; i++) {
|
|
223
|
+
const probe = probes[i];
|
|
224
|
+
seen.add(probe.path);
|
|
225
|
+
const reading = readings[i];
|
|
226
|
+
if (reading == null || forgotten.has(probe.path)) continue;
|
|
227
|
+
const previous = this.signatures.get(probe.path);
|
|
228
|
+
this.signatures.set(probe.path, reading);
|
|
229
|
+
if (previous != null && previous !== reading) moved.push(probe);
|
|
230
|
+
}
|
|
231
|
+
for (const path of this.signatures.keys()) if (!seen.has(path)) this.signatures.delete(path);
|
|
232
|
+
for (const probe of moved) for (const path of probe.emitAs ?? [probe.path]) this.onMoved({
|
|
233
|
+
path: joinPaths(this.basePath, path),
|
|
234
|
+
reason: "change"
|
|
235
|
+
}, probe);
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* The watcher reported `path` itself, so the next poll records the probes it touched (the path, a ref's lock file
|
|
239
|
+
* standing for the ref, the directories above it, and the probes emitted as it, like `logs/HEAD` as `HEAD`, which
|
|
240
|
+
* the watcher never sees) without reporting them again.
|
|
241
|
+
*/
|
|
242
|
+
forget(path) {
|
|
243
|
+
if (!isDescendant(path, this.basePath)) return;
|
|
244
|
+
let relativePath = relative(this.basePath, path);
|
|
245
|
+
if (relativePath.endsWith(".lock")) relativePath = relativePath.substring(0, relativePath.length - 5);
|
|
246
|
+
for (let p = relativePath; p.length > 0; p = p.substring(0, Math.max(0, p.lastIndexOf("/")))) this.clearSignature(p);
|
|
247
|
+
for (const probePath of this.probesByEmittedPath.get(relativePath) ?? []) this.clearSignature(probePath);
|
|
248
|
+
}
|
|
249
|
+
clearSignature(path) {
|
|
250
|
+
this.signatures.delete(path);
|
|
251
|
+
this.stats.invalidate(joinPaths(this.basePath, path));
|
|
252
|
+
for (const forgotten of this.forgottenDuringPolls) forgotten.add(path);
|
|
253
|
+
}
|
|
254
|
+
async read(path) {
|
|
255
|
+
try {
|
|
256
|
+
const stat = await this.stats.stat(joinPaths(this.basePath, path));
|
|
257
|
+
return stat == null ? "missing" : `${stat.mtime}:${stat.size}`;
|
|
258
|
+
} catch {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
//#endregion
|
|
264
|
+
export { GitDirPoller, GitDirProbeTarget };
|
|
265
|
+
|
|
266
|
+
//# sourceMappingURL=gitDirPoller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gitDirPoller.js","names":[],"sources":["../../../../git/src/watching/gitDirPoller.ts"],"sourcesContent":["import type { UnifiedDisposable } from '@gitlens/utils/disposable.js';\nimport { isDescendant, joinPaths, relative } from '@gitlens/utils/path.js';\nimport type { FileWatchEvent } from './provider.js';\nimport type { WatchPollingOptions } from './watchService.js';\n\nconst defaultPollIntervalMs = 60_000;\nconst defaultPollActiveCheckMs = 5_000;\nconst defaultPollConcurrency = 4;\n\ntype GitDirStat = Awaited<ReturnType<WatchPollingOptions['stat']>>;\n\n/** Something the {@link GitDirPoller} reads once per pass */\nexport interface PollTarget {\n\tpoll(): Promise<void>;\n}\n\n/** A file or directory a poll stats, relative to the directory it watches in place of a watcher */\nexport interface GitDirProbe {\n\t/** `/`-separated, relative to the target's base path */\n\treadonly path: string;\n\t/** The relative paths a move is reported as, in place of `path` */\n\treadonly emitAs?: readonly string[];\n}\n\n/**\n * Runs passes over the poll targets of every watcher that can't be trusted, reading at most 4 targets at once across\n * passes and first readings. A target is read once soon after it is added, so its baseline doesn't wait for the next\n * pass; targets added together are read together.\n *\n * With the default gate (no `isActive`), one timer runs a pass every `intervalMs`, stopped by {@link suspend} and\n * restarted by {@link resume}, which runs a pass at once. With a host's `isActive`, whose changes core can't see, one\n * timer looks at it every `activeCheckMs`: a pass is due once `intervalMs` has gone by since the last one, or at the\n * first look after the gate opens (or after {@link nudge}), so a window that comes back between passes doesn't wait out\n * the rest of one.\n */\nexport class GitDirPoller implements UnifiedDisposable {\n\tprivate readonly targets = new Set<PollTarget>();\n\t/** Targets added since the last pass and not yet read */\n\tprivate readonly unread = new Set<PollTarget>();\n\t/** Each target's last read asked for; a target is never read twice at once, so the next waits for it */\n\tprivate readonly reading = new Map<PollTarget, Promise<void>>();\n\t/** Reads waiting for one of the {@link defaultPollConcurrency} slots, which a finished read hands over */\n\tprivate readonly waiting: (() => void)[] = [];\n\t/**\n\t * The stats of the batches (a pass, or first readings) in flight, shared by every target that probes the same path,\n\t * as the main checkout's root and its common dir do\n\t */\n\tprivate readonly stats = new Map<string, Promise<GitDirStat>>();\n\tprivate readonly intervalMs: number;\n\tprivate readonly activeCheckMs: number;\n\tprivate timer: ReturnType<typeof setTimeout> | undefined;\n\tprivate running: Promise<void> | undefined;\n\tprivate batches = 0;\n\tprivate slotsInUse = 0;\n\t/** The next look runs a pass whatever the interval says */\n\tprivate due = true;\n\tprivate lastPassAt = 0;\n\tprivate suspended = false;\n\tprivate disposed = false;\n\n\tconstructor(readonly options: WatchPollingOptions) {\n\t\tthis.intervalMs = options.intervalMs ?? defaultPollIntervalMs;\n\t\tthis.activeCheckMs = options.activeCheckMs ?? defaultPollActiveCheckMs;\n\t}\n\n\t[Symbol.dispose](): void {\n\t\tthis.dispose();\n\t}\n\n\tdispose(): void {\n\t\tthis.disposed = true;\n\t\tthis.targets.clear();\n\t\tthis.unread.clear();\n\t\tthis.stats.clear();\n\t\tthis.stopTimer();\n\t}\n\n\tget size(): number {\n\t\treturn this.targets.size;\n\t}\n\n\tadd(target: PollTarget): void {\n\t\tif (this.disposed || this.targets.has(target)) return;\n\n\t\tthis.targets.add(target);\n\t\tthis.unread.add(target);\n\t\tif (this.unread.size === 1) {\n\t\t\tqueueMicrotask(() => this.readUnread());\n\t\t}\n\t\tthis.startTimer();\n\t}\n\n\tdelete(target: PollTarget): void {\n\t\tthis.unread.delete(target);\n\t\tif (!this.targets.delete(target) || this.targets.size !== 0) return;\n\n\t\tthis.stopTimer();\n\t}\n\n\t/** Makes the next look a pass, and looks now */\n\tnudge(): void {\n\t\tthis.due = true;\n\t\tvoid this.tick();\n\t}\n\n\t/** Under the default gate, stops passes (and the timer) until {@link resume}; a host's `isActive` ignores it */\n\tsuspend(): void {\n\t\tthis.suspended = true;\n\t\tif (this.options.isActive == null) {\n\t\t\tthis.stopTimer();\n\t\t}\n\t}\n\n\t/** Lifts {@link suspend}, and runs a pass now, so what moved meanwhile is polled rather than at the next look */\n\tresume(): void {\n\t\tthis.suspended = false;\n\t\tthis.nudge();\n\t}\n\n\t/** One look at the gate: runs a pass when one is due, the gate is open and none is in flight */\n\ttick(): Promise<void> {\n\t\tif (this.disposed || this.targets.size === 0) return Promise.resolve();\n\n\t\tif (!(this.options.isActive?.() ?? !this.suspended)) {\n\t\t\tthis.due = true;\n\t\t\treturn Promise.resolve();\n\t\t}\n\n\t\t// A pass asked for while one is in flight runs once it ends; an elapsed interval just waits for the next look\n\t\tif (this.running != null) return this.running;\n\n\t\tconst now = performance.now();\n\t\tif (!this.due && now - this.lastPassAt < this.intervalMs) return Promise.resolve();\n\n\t\t// Under the default gate the timer counts the interval from this pass, whatever started it\n\t\tif (this.options.isActive == null) {\n\t\t\tthis.stopTimer();\n\t\t\tthis.startTimer();\n\t\t}\n\t\tthis.due = false;\n\t\tthis.lastPassAt = now;\n\t\tthis.running = this.pass().finally(() => {\n\t\t\tthis.running = undefined;\n\t\t\tif (this.due) {\n\t\t\t\tvoid this.tick();\n\t\t\t}\n\t\t});\n\t\treturn this.running;\n\t}\n\n\t/** Stats `path` once for every target that probes it in the batches in flight */\n\tstat(path: string): Promise<GitDirStat> {\n\t\tif (this.batches === 0) return this.options.stat(path);\n\n\t\tlet stat = this.stats.get(path);\n\t\tif (stat == null) {\n\t\t\tstat = this.options.stat(path);\n\t\t\tthis.stats.set(path, stat);\n\t\t}\n\t\treturn stat;\n\t}\n\n\t/** `path` changed since it was statted, so the next target to probe it in a batch in flight stats it again */\n\tinvalidate(path: string): void {\n\t\tthis.stats.delete(path);\n\t}\n\n\tprivate pass(): Promise<void> {\n\t\t// A pass reads its own stats, never ones from before it began\n\t\tthis.stats.clear();\n\t\t// The pass baselines any target not read yet\n\t\tthis.unread.clear();\n\t\treturn this.readAll([...this.targets]);\n\t}\n\n\tprivate readUnread(): void {\n\t\tif (this.unread.size === 0) return;\n\n\t\tconst targets = [...this.unread];\n\t\tthis.unread.clear();\n\t\tvoid this.readAll(targets);\n\t}\n\n\tprivate async readAll(targets: readonly PollTarget[]): Promise<void> {\n\t\tthis.batches++;\n\t\ttry {\n\t\t\t// A target that fails is read again next pass; nothing it saw this time is reported\n\t\t\tawait Promise.allSettled(targets.map(target => this.read(target)));\n\t\t} finally {\n\t\t\tthis.batches--;\n\t\t\tif (this.batches === 0) {\n\t\t\t\tthis.stats.clear();\n\t\t\t}\n\t\t}\n\t}\n\n\t/** Reads `target` once any read of it in flight ends, so the reading is fresh and its probes current */\n\tprivate read(target: PollTarget): Promise<void> {\n\t\tconst poll = async (): Promise<void> => {\n\t\t\tif (!this.targets.has(target)) return;\n\n\t\t\tawait this.acquireSlot();\n\t\t\ttry {\n\t\t\t\tif (this.targets.has(target)) {\n\t\t\t\t\tawait target.poll();\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tthis.releaseSlot();\n\t\t\t}\n\t\t};\n\t\tconst previous = this.reading.get(target);\n\t\tconst reading: Promise<void> = (previous != null ? previous.then(poll, poll) : poll()).finally(() => {\n\t\t\tif (this.reading.get(target) === reading) {\n\t\t\t\tthis.reading.delete(target);\n\t\t\t}\n\t\t});\n\t\tthis.reading.set(target, reading);\n\t\treturn reading;\n\t}\n\n\tprivate acquireSlot(): Promise<void> | undefined {\n\t\tif (this.slotsInUse < defaultPollConcurrency) {\n\t\t\tthis.slotsInUse++;\n\t\t\treturn undefined;\n\t\t}\n\n\t\treturn new Promise(resolve => this.waiting.push(resolve));\n\t}\n\n\tprivate releaseSlot(): void {\n\t\tconst next = this.waiting.shift();\n\t\tif (next != null) {\n\t\t\tnext();\n\t\t} else {\n\t\t\tthis.slotsInUse--;\n\t\t}\n\t}\n\n\tprivate startTimer(): void {\n\t\tif (this.timer != null || this.disposed || this.targets.size === 0) return;\n\n\t\tif (this.options.isActive != null) {\n\t\t\tthis.timer = setInterval(() => void this.tick(), this.activeCheckMs);\n\t\t} else if (!this.suspended) {\n\t\t\tthis.timer = setTimeout(() => {\n\t\t\t\tthis.timer = undefined;\n\t\t\t\tthis.due = true;\n\t\t\t\tvoid this.tick();\n\t\t\t}, this.intervalMs);\n\t\t} else {\n\t\t\treturn;\n\t\t}\n\n\t\t// A poll alone must not keep a (Node) process alive; in the browser timers are numbers\n\t\tif (typeof this.timer !== 'number') {\n\t\t\tthis.timer.unref();\n\t\t}\n\t}\n\n\tprivate stopTimer(): void {\n\t\tif (this.timer == null) return;\n\n\t\tif (this.options.isActive != null) {\n\t\t\tclearInterval(this.timer);\n\t\t} else {\n\t\t\tclearTimeout(this.timer);\n\t\t}\n\t\tthis.timer = undefined;\n\t\tthis.due = true;\n\t}\n}\n\n/**\n * Stands in for a watcher on `basePath`: stats each probe and reports one whose `mtime:size` moved as a `change` of\n * the paths it is emitted as, through the same handler the watcher's own events take. A probe's first read (and a\n * probe new to the set, as after a branch switch) only records; one that was missing and is now present moved. A stat\n * that throws keeps the last reading, so it never counts as a move.\n */\nexport class GitDirProbeTarget implements PollTarget {\n\tprivate readonly signatures = new Map<string, string>();\n\t/** For each poll whose stats are in flight, the paths {@link forget} cleared meanwhile, which it must not record */\n\tprivate readonly forgottenDuringPolls = new Set<Set<string>>();\n\n\tconstructor(\n\t\tprivate readonly basePath: string,\n\t\tprivate readonly stats: Pick<GitDirPoller, 'stat' | 'invalidate'>,\n\t\tprivate readonly getProbes: () => Iterable<GitDirProbe>,\n\t\t/** For each path a probe may be emitted as, the paths of the probes emitted as it */\n\t\tprivate readonly probesByEmittedPath: ReadonlyMap<string, readonly string[]>,\n\t\tprivate readonly onMoved: (event: FileWatchEvent, probe: GitDirProbe) => void,\n\t) {}\n\n\tasync poll(): Promise<void> {\n\t\tconst probes = [...this.getProbes()];\n\t\tconst forgotten = new Set<string>();\n\t\tthis.forgottenDuringPolls.add(forgotten);\n\t\tconst readings = await Promise.all(probes.map(p => this.read(p.path)));\n\t\tthis.forgottenDuringPolls.delete(forgotten);\n\n\t\tconst seen = new Set<string>();\n\t\tconst moved: GitDirProbe[] = [];\n\t\tfor (let i = 0; i < probes.length; i++) {\n\t\t\tconst probe = probes[i];\n\t\t\tseen.add(probe.path);\n\n\t\t\tconst reading = readings[i];\n\t\t\tif (reading == null || forgotten.has(probe.path)) continue;\n\n\t\t\tconst previous = this.signatures.get(probe.path);\n\t\t\tthis.signatures.set(probe.path, reading);\n\t\t\tif (previous != null && previous !== reading) {\n\t\t\t\tmoved.push(probe);\n\t\t\t}\n\t\t}\n\n\t\t// A probe that left the set starts over if it comes back\n\t\tfor (const path of this.signatures.keys()) {\n\t\t\tif (!seen.has(path)) {\n\t\t\t\tthis.signatures.delete(path);\n\t\t\t}\n\t\t}\n\n\t\tfor (const probe of moved) {\n\t\t\tfor (const path of probe.emitAs ?? [probe.path]) {\n\t\t\t\tthis.onMoved({ path: joinPaths(this.basePath, path), reason: 'change' }, probe);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * The watcher reported `path` itself, so the next poll records the probes it touched (the path, a ref's lock file\n\t * standing for the ref, the directories above it, and the probes emitted as it, like `logs/HEAD` as `HEAD`, which\n\t * the watcher never sees) without reporting them again.\n\t */\n\tforget(path: string): void {\n\t\tif (!isDescendant(path, this.basePath)) return;\n\n\t\tlet relativePath = relative(this.basePath, path);\n\t\tif (relativePath.endsWith('.lock')) {\n\t\t\trelativePath = relativePath.substring(0, relativePath.length - 5);\n\t\t}\n\n\t\tfor (let p = relativePath; p.length > 0; p = p.substring(0, Math.max(0, p.lastIndexOf('/')))) {\n\t\t\tthis.clearSignature(p);\n\t\t}\n\n\t\tfor (const probePath of this.probesByEmittedPath.get(relativePath) ?? []) {\n\t\t\tthis.clearSignature(probePath);\n\t\t}\n\t}\n\n\tprivate clearSignature(path: string): void {\n\t\tthis.signatures.delete(path);\n\t\tthis.stats.invalidate(joinPaths(this.basePath, path));\n\t\tfor (const forgotten of this.forgottenDuringPolls) {\n\t\t\tforgotten.add(path);\n\t\t}\n\t}\n\n\tprivate async read(path: string): Promise<string | undefined> {\n\t\ttry {\n\t\t\tconst stat = await this.stats.stat(joinPaths(this.basePath, path));\n\t\t\treturn stat == null ? 'missing' : `${stat.mtime}:${stat.size}`;\n\t\t} catch {\n\t\t\treturn undefined;\n\t\t}\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -17,6 +17,15 @@ export interface FileWatchEvent {
|
|
|
17
17
|
}
|
|
18
18
|
/** Opaque token representing a filesystem watcher */
|
|
19
19
|
export interface FileWatcher extends UnifiedDisposable {}
|
|
20
|
+
/**
|
|
21
|
+
* How much of what changes under a watcher's `basePath` it reports:
|
|
22
|
+
* - `reliable`: everything (the state of a watcher that never reports one);
|
|
23
|
+
* - `unreliable`: what changes on this machine, but maybe not another's edits (a network volume), so core polls
|
|
24
|
+
* beside it;
|
|
25
|
+
* - `unwatchable`: nothing (a filesystem without change notifications, or a watch that went down), so core polls
|
|
26
|
+
* instead.
|
|
27
|
+
*/
|
|
28
|
+
export type FileWatcherReliability = 'reliable' | 'unreliable' | 'unwatchable';
|
|
20
29
|
export interface FileWatcherOptions {
|
|
21
30
|
/**
|
|
22
31
|
* Directories to leave unwatched, each with everything beneath it. Entries are `/`-separated and relative to
|
|
@@ -26,6 +35,13 @@ export interface FileWatcherOptions {
|
|
|
26
35
|
* (Map each entry to your backend's syntax — e.g. picomatch-based watchers need both `X` and `X/**`.)
|
|
27
36
|
*/
|
|
28
37
|
readonly excludes?: readonly string[];
|
|
38
|
+
/**
|
|
39
|
+
* Reports this watcher's {@link FileWatcherReliability} whenever it changes; passed only when the host opted into
|
|
40
|
+
* polling (`WatchServiceOptions.polling`). May be called synchronously inside `createWatcher`, and again later
|
|
41
|
+
* (a watch going down, or coming back). Each watcher reports its own state, so one created on a path that is
|
|
42
|
+
* already down reports `unwatchable` itself. Going from `unwatchable` to anything else makes core rescan.
|
|
43
|
+
*/
|
|
44
|
+
readonly onDidChangeReliability?: (state: FileWatcherReliability) => void;
|
|
29
45
|
}
|
|
30
46
|
/**
|
|
31
47
|
* Factory for creating filesystem watchers. The library calls this
|
|
@@ -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
|
}
|