@gitkraken/core-gitlens 0.10.7 → 0.11.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 +26 -1
- package/dist/git/cache.d.ts +34 -2
- package/dist/git/cache.js +53 -7
- package/dist/git/cache.js.map +1 -1
- package/dist/git/context.d.ts +1 -1
- package/dist/git/models/repository.d.ts +3 -2
- package/dist/git/models/repository.js +5 -4
- package/dist/git/models/repository.js.map +1 -1
- package/dist/git/providers/branches.d.ts +22 -0
- package/dist/git/providers/config.d.ts +3 -1
- package/dist/git/providers/provider.d.ts +13 -1
- package/dist/git/providers/staging.d.ts +1 -1
- package/dist/git/service.d.ts +17 -1
- package/dist/git/service.js +59 -8
- package/dist/git/service.js.map +1 -1
- package/dist/git/watching/changeEvent.d.ts +48 -5
- package/dist/git/watching/changeEvent.js +82 -7
- package/dist/git/watching/changeEvent.js.map +1 -1
- package/dist/git/watching/classifyChange.d.ts +13 -2
- package/dist/git/watching/classifyChange.js +40 -5
- package/dist/git/watching/classifyChange.js.map +1 -1
- package/dist/git/watching/gitIgnoreFilter.d.ts +57 -10
- package/dist/git/watching/gitIgnoreFilter.js +186 -29
- package/dist/git/watching/gitIgnoreFilter.js.map +1 -1
- package/dist/git/watching/initWatcher.d.ts +5 -1
- package/dist/git/watching/initWatcher.js +1 -1
- package/dist/git/watching/initWatcher.js.map +1 -1
- package/dist/git/watching/provider.d.ts +33 -8
- package/dist/git/watching/watchGroup.d.ts +13 -2
- package/dist/git/watching/watchGroup.js +60 -14
- package/dist/git/watching/watchGroup.js.map +1 -1
- package/dist/git/watching/watchService.d.ts +42 -1
- package/dist/git/watching/watchService.js +172 -20
- package/dist/git/watching/watchService.js.map +1 -1
- package/dist/git/watching/watchSession.d.ts +50 -17
- package/dist/git/watching/watchSession.js +274 -83
- package/dist/git/watching/watchSession.js.map +1 -1
- package/dist/git/watching/watcherPatterns.d.ts +13 -2
- package/dist/git/watching/watcherPatterns.js +22 -2
- package/dist/git/watching/watcherPatterns.js.map +1 -1
- package/dist/git/watching/worktreeSummary.d.ts +121 -0
- package/dist/git/watching/worktreeSummary.js +427 -0
- package/dist/git/watching/worktreeSummary.js.map +1 -0
- package/dist/git-cli/cliGitProvider.d.ts +6 -1
- package/dist/git-cli/cliGitProvider.js +87 -2
- package/dist/git-cli/cliGitProvider.js.map +1 -1
- package/dist/git-cli/providers/blame.js +1 -1
- package/dist/git-cli/providers/branches.d.ts +32 -1
- package/dist/git-cli/providers/branches.js +195 -0
- package/dist/git-cli/providers/branches.js.map +1 -1
- package/dist/git-cli/providers/commits.js +1 -1
- package/dist/git-cli/providers/config.d.ts +3 -0
- package/dist/git-cli/providers/config.js +12 -6
- 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 +1 -1
- package/dist/git-cli/providers/maintenance.js +1 -1
- package/dist/git-cli/providers/refs.js +1 -1
- package/dist/git-cli/providers/staging.js +1 -1
- package/dist/git-cli/providers/status.js +1 -1
- package/dist/git-cli/service.js +1 -0
- package/dist/git-cli/service.js.map +1 -1
- package/dist/ipc/ipcServer.js +1 -1
- package/dist/plus/agents/providers/gkAgentProvider.js +2 -2
- package/dist/plus/git-github/api/github.d.ts +1 -1
- package/dist/plus/git-github/api/github.js +1 -1
- package/dist/plus/git-github/providers/github/blame.js +1 -1
- package/dist/plus/integrations/authentication/integrationAuthenticationProvider.js +1 -1
- package/dist/plus/integrations/context.d.ts +1 -1
- package/dist/plus/integrations/integrationService.js +1 -1
- package/dist/plus/integrations/models/integration.js +1 -1
- package/dist/plus/integrations/providers/bitbucket-server.d.ts +1 -1
- package/dist/utils/event.d.ts +1 -1
- package/package.json +1 -1
|
@@ -30,6 +30,13 @@ export interface WatchSessionOptions {
|
|
|
30
30
|
readonly defaultRepoDelayMs?: number;
|
|
31
31
|
/** Default debounce for working tree changes. Default: 2500ms */
|
|
32
32
|
readonly defaultWorkingTreeDelayMs?: number;
|
|
33
|
+
/**
|
|
34
|
+
* Default cap on how long repo changes can be held back by continuous activity — a flush happens at most this long
|
|
35
|
+
* after the first pending change, however long the burst lasts. `Infinity` disables the cap. Default: 2000ms
|
|
36
|
+
*/
|
|
37
|
+
readonly defaultRepoMaxWaitMs?: number;
|
|
38
|
+
/** Default cap on how long working tree changes can be held back by continuous activity. `Infinity` disables the cap. Default: 10000ms */
|
|
39
|
+
readonly defaultWorkingTreeMaxWaitMs?: number;
|
|
33
40
|
readonly lifecycle?: WatchSessionLifecycle;
|
|
34
41
|
/** Initial suspended state — new sessions inherit the service-global suspend state. Default: false */
|
|
35
42
|
readonly suspended?: boolean;
|
|
@@ -51,25 +58,33 @@ export interface WatchSessionOptions {
|
|
|
51
58
|
* pipeline for both repo (.git) and working-tree changes.
|
|
52
59
|
*
|
|
53
60
|
* Multiple subscribers share the same pipeline — the shortest
|
|
54
|
-
* requested debounce delay wins.
|
|
61
|
+
* requested debounce delay wins, and so does the shortest max-wait.
|
|
55
62
|
*/
|
|
56
63
|
export declare class RepositoryWatchSession implements UnifiedDisposable {
|
|
57
64
|
readonly repoPath: string;
|
|
58
65
|
private readonly repoEmitter;
|
|
59
|
-
private readonly
|
|
66
|
+
private readonly repoSubscribers;
|
|
60
67
|
private repoNextId;
|
|
61
|
-
private
|
|
62
|
-
|
|
68
|
+
private readonly pendingRepoChanges;
|
|
69
|
+
/** Full ref names, ref directories and pseudo-refs whose files changed, flushed with {@link pendingRepoChanges} */
|
|
70
|
+
private readonly pendingRepoRefs;
|
|
71
|
+
/** Ref kinds that changed without a name, flushed with {@link pendingRepoChanges} */
|
|
72
|
+
private readonly pendingRepoUnnamedRefKinds;
|
|
73
|
+
private readonly repoClock;
|
|
63
74
|
private effectiveRepoMs;
|
|
75
|
+
private effectiveRepoMaxWaitMs;
|
|
64
76
|
private readonly defaultRepoMs;
|
|
77
|
+
private readonly defaultRepoMaxWaitMs;
|
|
65
78
|
private readonly workingTreeEmitter;
|
|
66
|
-
private readonly
|
|
79
|
+
private readonly workingTreeSubscribers;
|
|
67
80
|
private workingTreeNextId;
|
|
68
81
|
private pendingWorkingTreePaths;
|
|
69
82
|
private pendingWorkingTreeIncomplete;
|
|
70
|
-
private
|
|
83
|
+
private readonly workingTreeClock;
|
|
71
84
|
private effectiveWorkingTreeMs;
|
|
85
|
+
private effectiveWorkingTreeMaxWaitMs;
|
|
72
86
|
private readonly defaultWorkingTreeMs;
|
|
87
|
+
private readonly defaultWorkingTreeMaxWaitMs;
|
|
73
88
|
private _etagWorkingTree;
|
|
74
89
|
private _suspended;
|
|
75
90
|
private _disposed;
|
|
@@ -100,36 +115,54 @@ export declare class RepositoryWatchSession implements UnifiedDisposable {
|
|
|
100
115
|
* fires `onLastRepoSubscriber`.
|
|
101
116
|
*
|
|
102
117
|
* When multiple subscribers exist, the shortest debounce wins —
|
|
103
|
-
* all subscribers fire on the same (fastest) cadence.
|
|
118
|
+
* all subscribers fire on the same (fastest) cadence. `maxWaitMs` bounds how long continuous
|
|
119
|
+
* activity can defer a flush (the shortest wins too); `Infinity` means no cap.
|
|
120
|
+
*
|
|
121
|
+
* `relevantRefs` narrows THIS subscription's events to the refs it cares about, given as full ref names
|
|
122
|
+
* (`refs/heads/main`, `refs/remotes/origin/main`, `refs/tags/v1`, `refs/stash`). It is read at delivery, so it can
|
|
123
|
+
* return the subscriber's current list. A `heads`/`remotes`/`tags`/`stash` change is delivered only when a listed
|
|
124
|
+
* ref of that kind may have moved, when the change isn't attributable to a name (e.g. a `packed-refs` rewrite),
|
|
125
|
+
* or, for `heads`, when a pseudo-ref (`HEAD`, `ORIG_HEAD`) moved — so `stash` and `tags` changes are dropped unless
|
|
126
|
+
* a ref of that kind is listed. Other change kinds always pass, and an event left with no changes isn't delivered.
|
|
127
|
+
* The filter never affects other subscribers, the debounce, or the global `onDidFireRepoChange` forwarding.
|
|
104
128
|
*/
|
|
105
129
|
subscribe(opts?: {
|
|
106
130
|
delayMs?: number;
|
|
131
|
+
maxWaitMs?: number;
|
|
132
|
+
relevantRefs?: () => Iterable<string>;
|
|
107
133
|
}): RepositorySubscription;
|
|
134
|
+
/** Subscribe to working tree changes; `delayMs` and `maxWaitMs` behave as in {@link subscribe}. */
|
|
108
135
|
subscribeToWorkingTree(opts?: {
|
|
109
136
|
delayMs?: number;
|
|
137
|
+
maxWaitMs?: number;
|
|
110
138
|
}): WorkingTreeSubscription;
|
|
111
139
|
suspend(): void;
|
|
112
140
|
resume(delayMs?: number): void;
|
|
113
|
-
/**
|
|
141
|
+
/**
|
|
142
|
+
* Inject changes (e.g., after a git operation the caller performed). Goes through debounce + suspend. Ref kinds
|
|
143
|
+
* among them are unnamed — any ref of that kind may have moved.
|
|
144
|
+
*/
|
|
114
145
|
fireChange(...changes: RepositoryChange[]): void;
|
|
115
|
-
/** Immediate fire, bypassing debounce + suspend (for Closed/Opened). */
|
|
146
|
+
/** Immediate fire, bypassing debounce + suspend (for Closed/Opened). Ref kinds among them are unnamed. */
|
|
116
147
|
fireChangeImmediate(...changes: RepositoryChange[]): void;
|
|
117
|
-
/**
|
|
118
|
-
|
|
148
|
+
/**
|
|
149
|
+
* Push interpreted repo changes into the pipeline. `ref` is the full ref name (or ref directory, or pseudo-ref) whose
|
|
150
|
+
* file changed; a ref kind in `changes` is attributed to it when the kinds match, and is otherwise unnamed.
|
|
151
|
+
*/
|
|
152
|
+
pushRepoChanges(changes: RepositoryChange[], ref?: string): void;
|
|
119
153
|
/** Push working tree file changes into the pipeline */
|
|
120
154
|
/** `incomplete` marks the batch as partial when the caller already dropped paths of its own. */
|
|
121
155
|
pushWorkingTreeChanges(paths: Iterable<string>, incomplete?: boolean): void;
|
|
122
156
|
private static minDelay;
|
|
123
|
-
|
|
124
|
-
private
|
|
157
|
+
/** Unlike the delay, the default isn't a ceiling — a subscriber can opt out of the cap with `Infinity` */
|
|
158
|
+
private static minMaxWait;
|
|
159
|
+
private recalcRepoTiming;
|
|
160
|
+
private recalcWorkingTreeTiming;
|
|
125
161
|
private flushRepo;
|
|
126
|
-
private scheduleRepoFlush;
|
|
127
162
|
private coalesceRepo;
|
|
163
|
+
private clearPendingRepoChanges;
|
|
128
164
|
private flushWorkingTree;
|
|
129
|
-
private scheduleWorkingTreeFlush;
|
|
130
165
|
private coalesceWorkingTrees;
|
|
131
|
-
private cancelRepoTimer;
|
|
132
|
-
private cancelWorkingTreeTimer;
|
|
133
166
|
}
|
|
134
167
|
//#endregion
|
|
135
168
|
//# sourceMappingURL=watchSession.d.ts.map
|
|
@@ -1,9 +1,113 @@
|
|
|
1
1
|
import { createDisposable } from "../../utils/disposable.js";
|
|
2
2
|
import { Emitter } from "../../utils/event.js";
|
|
3
|
-
import { WatcherRepoChangeEvent } from "./changeEvent.js";
|
|
3
|
+
import { WatcherRepoChangeEvent, findCoveringRefName, getRefChangeKind, isRefChangeKind } from "./changeEvent.js";
|
|
4
4
|
//#region ../git/src/watching/watchSession.ts
|
|
5
5
|
const defaultRepoDelay = 250;
|
|
6
6
|
const defaultWorkingTreeDelay = 2500;
|
|
7
|
+
const defaultRepoMaxWait = 2e3;
|
|
8
|
+
const defaultWorkingTreeMaxWait = 1e4;
|
|
9
|
+
/** Largest delay `setTimeout` honors (a signed 32-bit int); anything longer fires immediately */
|
|
10
|
+
const maxTimerDelay = 2147483647;
|
|
11
|
+
/**
|
|
12
|
+
* Trailing-deadline debounce with a max-wait, shared by the repo and working-tree pipelines.
|
|
13
|
+
*
|
|
14
|
+
* Recording an event costs a `performance.now()` and, only when no timer is armed, one `setTimeout` — never a
|
|
15
|
+
* `clearTimeout`/`setTimeout` pair per event, which is what made a busy repo allocate (and starve) on every raw
|
|
16
|
+
* watcher event. The armed timer is a deadline, not a countdown: when it fires it recomputes the due time from the
|
|
17
|
+
* latest timestamps and either flushes or re-arms for the remainder. The due time is
|
|
18
|
+
* `min(lastEventAt + delay, firstPendingAt + maxWait)`, so continuous activity is flushed at the max-wait instead of
|
|
19
|
+
* never. Monotonic time, so a wall-clock change can't push a deadline out.
|
|
20
|
+
*/
|
|
21
|
+
var DebounceClock = class {
|
|
22
|
+
getDelayMs;
|
|
23
|
+
getMaxWaitMs;
|
|
24
|
+
onDue;
|
|
25
|
+
active = false;
|
|
26
|
+
paused = false;
|
|
27
|
+
firstAt = 0;
|
|
28
|
+
lastAt = 0;
|
|
29
|
+
timer;
|
|
30
|
+
timerDueAt = 0;
|
|
31
|
+
/** Absolute flush time set by {@link resume}; dropped by the next event, which returns to normal debounce */
|
|
32
|
+
resumeDueAt;
|
|
33
|
+
constructor(getDelayMs, getMaxWaitMs, onDue) {
|
|
34
|
+
this.getDelayMs = getDelayMs;
|
|
35
|
+
this.getMaxWaitMs = getMaxWaitMs;
|
|
36
|
+
this.onDue = onDue;
|
|
37
|
+
}
|
|
38
|
+
/** Records an event; arms the timer only if none is armed. Cheap enough to call per raw event. */
|
|
39
|
+
note() {
|
|
40
|
+
const now = performance.now();
|
|
41
|
+
if (!this.active) {
|
|
42
|
+
this.active = true;
|
|
43
|
+
this.firstAt = now;
|
|
44
|
+
}
|
|
45
|
+
this.lastAt = now;
|
|
46
|
+
if (this.paused) return;
|
|
47
|
+
if (this.resumeDueAt != null) {
|
|
48
|
+
this.resumeDueAt = void 0;
|
|
49
|
+
this.rearmIfEarlier();
|
|
50
|
+
}
|
|
51
|
+
if (this.timer == null) this.arm(this.getDueAt(), now);
|
|
52
|
+
}
|
|
53
|
+
/** Moves the armed timer earlier when the due time shrank (a subscriber joined with a shorter delay or max-wait) */
|
|
54
|
+
rearmIfEarlier() {
|
|
55
|
+
if (!this.active || this.paused || this.timer == null) return;
|
|
56
|
+
const dueAt = this.getDueAt();
|
|
57
|
+
if (dueAt >= this.timerDueAt) return;
|
|
58
|
+
clearTimeout(this.timer);
|
|
59
|
+
this.arm(dueAt, performance.now());
|
|
60
|
+
}
|
|
61
|
+
/** Cancels the timer; events keep accumulating but nothing is due until {@link resume} */
|
|
62
|
+
suspend() {
|
|
63
|
+
this.paused = true;
|
|
64
|
+
this.clearTimer();
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Restarts the max-wait window from now and schedules the flush for `delayMs` from now (the effective delay when
|
|
68
|
+
* omitted). Does nothing if no events are pending.
|
|
69
|
+
*/
|
|
70
|
+
resume(delayMs) {
|
|
71
|
+
this.paused = false;
|
|
72
|
+
if (!this.active) return;
|
|
73
|
+
const now = performance.now();
|
|
74
|
+
this.firstAt = now;
|
|
75
|
+
this.resumeDueAt = now + (delayMs ?? this.getDelayMs());
|
|
76
|
+
this.clearTimer();
|
|
77
|
+
this.arm(this.resumeDueAt, now);
|
|
78
|
+
}
|
|
79
|
+
/** Drops the pending window and the timer */
|
|
80
|
+
cancel() {
|
|
81
|
+
this.active = false;
|
|
82
|
+
this.resumeDueAt = void 0;
|
|
83
|
+
this.clearTimer();
|
|
84
|
+
}
|
|
85
|
+
getDueAt() {
|
|
86
|
+
return this.resumeDueAt ?? Math.min(this.lastAt + this.getDelayMs(), this.firstAt + this.getMaxWaitMs());
|
|
87
|
+
}
|
|
88
|
+
arm(dueAt, now) {
|
|
89
|
+
this.timerDueAt = dueAt;
|
|
90
|
+
this.timer = setTimeout(() => this.onTimer(), Math.min(Math.max(0, dueAt - now), maxTimerDelay));
|
|
91
|
+
}
|
|
92
|
+
onTimer() {
|
|
93
|
+
this.timer = void 0;
|
|
94
|
+
if (!this.active || this.paused) return;
|
|
95
|
+
const now = performance.now();
|
|
96
|
+
const dueAt = this.getDueAt();
|
|
97
|
+
if (dueAt > now) {
|
|
98
|
+
this.arm(dueAt, now);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
this.cancel();
|
|
102
|
+
this.onDue();
|
|
103
|
+
}
|
|
104
|
+
clearTimer() {
|
|
105
|
+
if (this.timer != null) {
|
|
106
|
+
clearTimeout(this.timer);
|
|
107
|
+
this.timer = void 0;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
};
|
|
7
111
|
/**
|
|
8
112
|
* Cap on retained working-tree paths per flush. The set otherwise grows without bound — while the window
|
|
9
113
|
* is unfocused the session is suspended, so a branch switch or build accumulates every touched path until
|
|
@@ -13,29 +117,42 @@ const defaultWorkingTreeDelay = 2500;
|
|
|
13
117
|
*/
|
|
14
118
|
const maxWorkingTreePaths = 1e3;
|
|
15
119
|
/**
|
|
120
|
+
* Cap on retained ref names per flush, for the same reason (a fetch of many refs while suspended). Past it, a ref kind
|
|
121
|
+
* is recorded unnamed — any ref of that kind may have moved — which only makes `mayHaveChangedRef` more conservative.
|
|
122
|
+
*/
|
|
123
|
+
const maxPendingRefNames = 1e3;
|
|
124
|
+
/**
|
|
16
125
|
* Per-repository session that owns the debounce/coalesce/suspend
|
|
17
126
|
* pipeline for both repo (.git) and working-tree changes.
|
|
18
127
|
*
|
|
19
128
|
* Multiple subscribers share the same pipeline — the shortest
|
|
20
|
-
* requested debounce delay wins.
|
|
129
|
+
* requested debounce delay wins, and so does the shortest max-wait.
|
|
21
130
|
*/
|
|
22
131
|
var RepositoryWatchSession = class RepositoryWatchSession {
|
|
23
132
|
repoPath;
|
|
24
133
|
repoEmitter = new Emitter();
|
|
25
|
-
|
|
134
|
+
repoSubscribers = /* @__PURE__ */ new Map();
|
|
26
135
|
repoNextId = 0;
|
|
27
|
-
|
|
28
|
-
|
|
136
|
+
pendingRepoChanges = /* @__PURE__ */ new Set();
|
|
137
|
+
/** Full ref names, ref directories and pseudo-refs whose files changed, flushed with {@link pendingRepoChanges} */
|
|
138
|
+
pendingRepoRefs = /* @__PURE__ */ new Set();
|
|
139
|
+
/** Ref kinds that changed without a name, flushed with {@link pendingRepoChanges} */
|
|
140
|
+
pendingRepoUnnamedRefKinds = /* @__PURE__ */ new Set();
|
|
141
|
+
repoClock;
|
|
29
142
|
effectiveRepoMs;
|
|
143
|
+
effectiveRepoMaxWaitMs;
|
|
30
144
|
defaultRepoMs;
|
|
145
|
+
defaultRepoMaxWaitMs;
|
|
31
146
|
workingTreeEmitter = new Emitter();
|
|
32
|
-
|
|
147
|
+
workingTreeSubscribers = /* @__PURE__ */ new Map();
|
|
33
148
|
workingTreeNextId = 0;
|
|
34
149
|
pendingWorkingTreePaths = /* @__PURE__ */ new Set();
|
|
35
150
|
pendingWorkingTreeIncomplete = false;
|
|
36
|
-
|
|
151
|
+
workingTreeClock;
|
|
37
152
|
effectiveWorkingTreeMs;
|
|
153
|
+
effectiveWorkingTreeMaxWaitMs;
|
|
38
154
|
defaultWorkingTreeMs;
|
|
155
|
+
defaultWorkingTreeMaxWaitMs;
|
|
39
156
|
_etagWorkingTree = 0;
|
|
40
157
|
_suspended;
|
|
41
158
|
_disposed = false;
|
|
@@ -46,12 +163,22 @@ var RepositoryWatchSession = class RepositoryWatchSession {
|
|
|
46
163
|
this.repoPath = options.repoPath;
|
|
47
164
|
this.defaultRepoMs = options.defaultRepoDelayMs ?? defaultRepoDelay;
|
|
48
165
|
this.defaultWorkingTreeMs = options.defaultWorkingTreeDelayMs ?? defaultWorkingTreeDelay;
|
|
166
|
+
this.defaultRepoMaxWaitMs = options.defaultRepoMaxWaitMs ?? defaultRepoMaxWait;
|
|
167
|
+
this.defaultWorkingTreeMaxWaitMs = options.defaultWorkingTreeMaxWaitMs ?? defaultWorkingTreeMaxWait;
|
|
49
168
|
this.effectiveRepoMs = this.defaultRepoMs;
|
|
169
|
+
this.effectiveRepoMaxWaitMs = this.defaultRepoMaxWaitMs;
|
|
50
170
|
this.effectiveWorkingTreeMs = this.defaultWorkingTreeMs;
|
|
171
|
+
this.effectiveWorkingTreeMaxWaitMs = this.defaultWorkingTreeMaxWaitMs;
|
|
172
|
+
this.repoClock = new DebounceClock(() => this.effectiveRepoMs, () => this.effectiveRepoMaxWaitMs, () => this.flushRepo());
|
|
173
|
+
this.workingTreeClock = new DebounceClock(() => this.effectiveWorkingTreeMs, () => this.effectiveWorkingTreeMaxWaitMs, () => this.flushWorkingTree());
|
|
51
174
|
this.lifecycle = options.lifecycle;
|
|
52
175
|
this.onDidFireRepoChangeCallback = options.onDidFireRepoChange;
|
|
53
176
|
this.onDidFireWorkingTreeChangeCallback = options.onDidFireWorkingTreeChange;
|
|
54
177
|
this._suspended = options.suspended ?? false;
|
|
178
|
+
if (this._suspended) {
|
|
179
|
+
this.repoClock.suspend();
|
|
180
|
+
this.workingTreeClock.suspend();
|
|
181
|
+
}
|
|
55
182
|
}
|
|
56
183
|
[Symbol.dispose]() {
|
|
57
184
|
this.dispose();
|
|
@@ -59,13 +186,13 @@ var RepositoryWatchSession = class RepositoryWatchSession {
|
|
|
59
186
|
dispose() {
|
|
60
187
|
if (this._disposed) return;
|
|
61
188
|
this._disposed = true;
|
|
62
|
-
this.
|
|
63
|
-
this.
|
|
64
|
-
this.
|
|
189
|
+
this.repoClock.cancel();
|
|
190
|
+
this.workingTreeClock.cancel();
|
|
191
|
+
this.clearPendingRepoChanges();
|
|
65
192
|
this.pendingWorkingTreePaths.clear();
|
|
66
193
|
this.pendingWorkingTreeIncomplete = false;
|
|
67
|
-
this.
|
|
68
|
-
this.
|
|
194
|
+
this.repoSubscribers.clear();
|
|
195
|
+
this.workingTreeSubscribers.clear();
|
|
69
196
|
this.repoEmitter.dispose();
|
|
70
197
|
this.workingTreeEmitter.dispose();
|
|
71
198
|
}
|
|
@@ -73,7 +200,7 @@ var RepositoryWatchSession = class RepositoryWatchSession {
|
|
|
73
200
|
return this._suspended;
|
|
74
201
|
}
|
|
75
202
|
get hasPendingChanges() {
|
|
76
|
-
return this.
|
|
203
|
+
return this.pendingRepoChanges.size > 0 || this.hasPendingWorkingTreeChanges;
|
|
77
204
|
}
|
|
78
205
|
/**
|
|
79
206
|
* An incomplete batch with no surviving paths still has to fire: the caller dropped paths we never saw, so
|
|
@@ -83,10 +210,10 @@ var RepositoryWatchSession = class RepositoryWatchSession {
|
|
|
83
210
|
return this.pendingWorkingTreePaths.size > 0 || this.pendingWorkingTreeIncomplete;
|
|
84
211
|
}
|
|
85
212
|
get repoSubscriberCount() {
|
|
86
|
-
return this.
|
|
213
|
+
return this.repoSubscribers.size;
|
|
87
214
|
}
|
|
88
215
|
get workingTreeSubscriberCount() {
|
|
89
|
-
return this.
|
|
216
|
+
return this.workingTreeSubscribers.size;
|
|
90
217
|
}
|
|
91
218
|
/**
|
|
92
219
|
* Monotonic counter incremented immediately when working tree changes
|
|
@@ -102,49 +229,61 @@ var RepositoryWatchSession = class RepositoryWatchSession {
|
|
|
102
229
|
* fires `onLastRepoSubscriber`.
|
|
103
230
|
*
|
|
104
231
|
* When multiple subscribers exist, the shortest debounce wins —
|
|
105
|
-
* all subscribers fire on the same (fastest) cadence.
|
|
232
|
+
* all subscribers fire on the same (fastest) cadence. `maxWaitMs` bounds how long continuous
|
|
233
|
+
* activity can defer a flush (the shortest wins too); `Infinity` means no cap.
|
|
234
|
+
*
|
|
235
|
+
* `relevantRefs` narrows THIS subscription's events to the refs it cares about, given as full ref names
|
|
236
|
+
* (`refs/heads/main`, `refs/remotes/origin/main`, `refs/tags/v1`, `refs/stash`). It is read at delivery, so it can
|
|
237
|
+
* return the subscriber's current list. A `heads`/`remotes`/`tags`/`stash` change is delivered only when a listed
|
|
238
|
+
* ref of that kind may have moved, when the change isn't attributable to a name (e.g. a `packed-refs` rewrite),
|
|
239
|
+
* or, for `heads`, when a pseudo-ref (`HEAD`, `ORIG_HEAD`) moved — so `stash` and `tags` changes are dropped unless
|
|
240
|
+
* a ref of that kind is listed. Other change kinds always pass, and an event left with no changes isn't delivered.
|
|
241
|
+
* The filter never affects other subscribers, the debounce, or the global `onDidFireRepoChange` forwarding.
|
|
106
242
|
*/
|
|
107
243
|
subscribe(opts) {
|
|
108
244
|
if (this._disposed) return Object.assign(createDisposable(() => {}), { onDidChange: this.repoEmitter.event });
|
|
109
245
|
const id = this.repoNextId++;
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
246
|
+
const wasEmpty = this.repoSubscribers.size === 0;
|
|
247
|
+
this.repoSubscribers.set(id, {
|
|
248
|
+
delayMs: opts?.delayMs ?? this.defaultRepoMs,
|
|
249
|
+
maxWaitMs: opts?.maxWaitMs ?? this.defaultRepoMaxWaitMs
|
|
250
|
+
});
|
|
251
|
+
this.recalcRepoTiming();
|
|
115
252
|
if (wasEmpty) this.lifecycle?.onFirstRepoSubscriber?.();
|
|
116
|
-
|
|
253
|
+
this.repoClock.rearmIfEarlier();
|
|
117
254
|
let subDisposed = false;
|
|
118
255
|
return Object.assign(createDisposable(() => {
|
|
119
256
|
if (subDisposed) return;
|
|
120
257
|
subDisposed = true;
|
|
121
|
-
this.
|
|
122
|
-
this.
|
|
123
|
-
if (this.
|
|
124
|
-
this.
|
|
125
|
-
this.
|
|
258
|
+
this.repoSubscribers.delete(id);
|
|
259
|
+
this.recalcRepoTiming();
|
|
260
|
+
if (this.repoSubscribers.size === 0) {
|
|
261
|
+
this.repoClock.cancel();
|
|
262
|
+
this.clearPendingRepoChanges();
|
|
126
263
|
this.lifecycle?.onLastRepoSubscriber?.();
|
|
127
264
|
}
|
|
128
|
-
}), { onDidChange: this.repoEmitter.event });
|
|
265
|
+
}), { onDidChange: opts?.relevantRefs != null ? createRelevantRefsEvent(this.repoEmitter.event, opts.relevantRefs) : this.repoEmitter.event });
|
|
129
266
|
}
|
|
267
|
+
/** Subscribe to working tree changes; `delayMs` and `maxWaitMs` behave as in {@link subscribe}. */
|
|
130
268
|
subscribeToWorkingTree(opts) {
|
|
131
269
|
if (this._disposed) return Object.assign(createDisposable(() => {}), { onDidChangeWorkingTree: this.workingTreeEmitter.event });
|
|
132
270
|
const id = this.workingTreeNextId++;
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
271
|
+
const wasEmpty = this.workingTreeSubscribers.size === 0;
|
|
272
|
+
this.workingTreeSubscribers.set(id, {
|
|
273
|
+
delayMs: opts?.delayMs ?? this.defaultWorkingTreeMs,
|
|
274
|
+
maxWaitMs: opts?.maxWaitMs ?? this.defaultWorkingTreeMaxWaitMs
|
|
275
|
+
});
|
|
276
|
+
this.recalcWorkingTreeTiming();
|
|
138
277
|
if (wasEmpty) this.lifecycle?.onFirstWorkingTreeSubscriber?.();
|
|
139
|
-
|
|
278
|
+
this.workingTreeClock.rearmIfEarlier();
|
|
140
279
|
let subDisposed = false;
|
|
141
280
|
return Object.assign(createDisposable(() => {
|
|
142
281
|
if (subDisposed) return;
|
|
143
282
|
subDisposed = true;
|
|
144
|
-
this.
|
|
145
|
-
this.
|
|
146
|
-
if (this.
|
|
147
|
-
this.
|
|
283
|
+
this.workingTreeSubscribers.delete(id);
|
|
284
|
+
this.recalcWorkingTreeTiming();
|
|
285
|
+
if (this.workingTreeSubscribers.size === 0) {
|
|
286
|
+
this.workingTreeClock.cancel();
|
|
148
287
|
this.pendingWorkingTreePaths.clear();
|
|
149
288
|
this.pendingWorkingTreeIncomplete = false;
|
|
150
289
|
this.lifecycle?.onLastWorkingTreeSubscriber?.();
|
|
@@ -153,31 +292,37 @@ var RepositoryWatchSession = class RepositoryWatchSession {
|
|
|
153
292
|
}
|
|
154
293
|
suspend() {
|
|
155
294
|
this._suspended = true;
|
|
156
|
-
this.
|
|
157
|
-
this.
|
|
295
|
+
this.repoClock.suspend();
|
|
296
|
+
this.workingTreeClock.suspend();
|
|
158
297
|
}
|
|
159
298
|
resume(delayMs) {
|
|
160
299
|
if (!this._suspended) return;
|
|
161
300
|
this._suspended = false;
|
|
162
|
-
|
|
163
|
-
|
|
301
|
+
this.repoClock.resume(delayMs);
|
|
302
|
+
this.workingTreeClock.resume(delayMs);
|
|
164
303
|
}
|
|
165
|
-
/**
|
|
304
|
+
/**
|
|
305
|
+
* Inject changes (e.g., after a git operation the caller performed). Goes through debounce + suspend. Ref kinds
|
|
306
|
+
* among them are unnamed — any ref of that kind may have moved.
|
|
307
|
+
*/
|
|
166
308
|
fireChange(...changes) {
|
|
167
309
|
if (this._disposed || changes.length === 0) return;
|
|
168
310
|
this.coalesceRepo(changes);
|
|
169
311
|
}
|
|
170
|
-
/** Immediate fire, bypassing debounce + suspend (for Closed/Opened). */
|
|
312
|
+
/** Immediate fire, bypassing debounce + suspend (for Closed/Opened). Ref kinds among them are unnamed. */
|
|
171
313
|
fireChangeImmediate(...changes) {
|
|
172
314
|
if (this._disposed || changes.length === 0) return;
|
|
173
315
|
const event = new WatcherRepoChangeEvent(this.repoPath, changes);
|
|
174
316
|
this.onDidFireRepoChangeCallback?.(event);
|
|
175
317
|
this.repoEmitter.fire(event);
|
|
176
318
|
}
|
|
177
|
-
/**
|
|
178
|
-
|
|
319
|
+
/**
|
|
320
|
+
* Push interpreted repo changes into the pipeline. `ref` is the full ref name (or ref directory, or pseudo-ref) whose
|
|
321
|
+
* file changed; a ref kind in `changes` is attributed to it when the kinds match, and is otherwise unnamed.
|
|
322
|
+
*/
|
|
323
|
+
pushRepoChanges(changes, ref) {
|
|
179
324
|
if (this._disposed) return;
|
|
180
|
-
this.coalesceRepo(changes);
|
|
325
|
+
this.coalesceRepo(changes, ref);
|
|
181
326
|
}
|
|
182
327
|
/** Push working tree file changes into the pipeline */
|
|
183
328
|
/** `incomplete` marks the batch as partial when the caller already dropped paths of its own. */
|
|
@@ -187,38 +332,53 @@ var RepositoryWatchSession = class RepositoryWatchSession {
|
|
|
187
332
|
if (incomplete) this.pendingWorkingTreeIncomplete = true;
|
|
188
333
|
this.coalesceWorkingTrees(paths);
|
|
189
334
|
}
|
|
190
|
-
static minDelay(
|
|
191
|
-
if (delays.size === 0) return fallback;
|
|
335
|
+
static minDelay(subscribers, fallback) {
|
|
192
336
|
let min = fallback;
|
|
193
|
-
for (const
|
|
337
|
+
for (const { delayMs } of subscribers.values()) if (delayMs < min) min = delayMs;
|
|
338
|
+
return min;
|
|
339
|
+
}
|
|
340
|
+
/** Unlike the delay, the default isn't a ceiling — a subscriber can opt out of the cap with `Infinity` */
|
|
341
|
+
static minMaxWait(subscribers, fallback) {
|
|
342
|
+
if (subscribers.size === 0) return fallback;
|
|
343
|
+
let min = Infinity;
|
|
344
|
+
for (const { maxWaitMs } of subscribers.values()) if (maxWaitMs < min) min = maxWaitMs;
|
|
194
345
|
return min;
|
|
195
346
|
}
|
|
196
|
-
|
|
197
|
-
this.effectiveRepoMs = RepositoryWatchSession.minDelay(this.
|
|
347
|
+
recalcRepoTiming() {
|
|
348
|
+
this.effectiveRepoMs = RepositoryWatchSession.minDelay(this.repoSubscribers, this.defaultRepoMs);
|
|
349
|
+
this.effectiveRepoMaxWaitMs = RepositoryWatchSession.minMaxWait(this.repoSubscribers, this.defaultRepoMaxWaitMs);
|
|
198
350
|
}
|
|
199
|
-
|
|
200
|
-
this.effectiveWorkingTreeMs = RepositoryWatchSession.minDelay(this.
|
|
351
|
+
recalcWorkingTreeTiming() {
|
|
352
|
+
this.effectiveWorkingTreeMs = RepositoryWatchSession.minDelay(this.workingTreeSubscribers, this.defaultWorkingTreeMs);
|
|
353
|
+
this.effectiveWorkingTreeMaxWaitMs = RepositoryWatchSession.minMaxWait(this.workingTreeSubscribers, this.defaultWorkingTreeMaxWaitMs);
|
|
201
354
|
}
|
|
202
355
|
flushRepo() {
|
|
203
|
-
this.
|
|
204
|
-
const event = this.
|
|
205
|
-
|
|
206
|
-
|
|
356
|
+
if (this.pendingRepoChanges.size === 0) return;
|
|
357
|
+
const event = new WatcherRepoChangeEvent(this.repoPath, [...this.pendingRepoChanges], {
|
|
358
|
+
names: this.pendingRepoRefs,
|
|
359
|
+
unnamed: this.pendingRepoUnnamedRefKinds
|
|
360
|
+
});
|
|
361
|
+
this.clearPendingRepoChanges();
|
|
207
362
|
this.onDidFireRepoChangeCallback?.(event);
|
|
208
363
|
this.repoEmitter.fire(event);
|
|
209
364
|
}
|
|
210
|
-
|
|
211
|
-
if (this._suspended || this.pendingRepoEvent == null) return;
|
|
212
|
-
if (this.repoTimer != null) clearTimeout(this.repoTimer);
|
|
213
|
-
this.repoTimer = setTimeout(() => this.flushRepo(), delayOverride ?? this.effectiveRepoMs);
|
|
214
|
-
}
|
|
215
|
-
coalesceRepo(changes) {
|
|
365
|
+
coalesceRepo(changes, ref) {
|
|
216
366
|
if (changes.length === 0) return;
|
|
217
|
-
|
|
218
|
-
|
|
367
|
+
const refKind = ref != null ? getRefChangeKind(ref) : void 0;
|
|
368
|
+
for (const change of changes) {
|
|
369
|
+
this.pendingRepoChanges.add(change);
|
|
370
|
+
if (!isRefChangeKind(change)) continue;
|
|
371
|
+
if (ref != null && refKind === change && (this.pendingRepoRefs.size < maxPendingRefNames || this.pendingRepoRefs.has(ref))) this.pendingRepoRefs.add(ref);
|
|
372
|
+
else this.pendingRepoUnnamedRefKinds.add(change);
|
|
373
|
+
}
|
|
374
|
+
this.repoClock.note();
|
|
375
|
+
}
|
|
376
|
+
clearPendingRepoChanges() {
|
|
377
|
+
this.pendingRepoChanges.clear();
|
|
378
|
+
this.pendingRepoRefs.clear();
|
|
379
|
+
this.pendingRepoUnnamedRefKinds.clear();
|
|
219
380
|
}
|
|
220
381
|
flushWorkingTree() {
|
|
221
|
-
this.workingTreeTimer = void 0;
|
|
222
382
|
if (!this.hasPendingWorkingTreeChanges) return;
|
|
223
383
|
const paths = this.pendingWorkingTreePaths;
|
|
224
384
|
const incomplete = this.pendingWorkingTreeIncomplete;
|
|
@@ -231,11 +391,6 @@ var RepositoryWatchSession = class RepositoryWatchSession {
|
|
|
231
391
|
incomplete
|
|
232
392
|
});
|
|
233
393
|
}
|
|
234
|
-
scheduleWorkingTreeFlush(delayOverride) {
|
|
235
|
-
if (this._suspended || !this.hasPendingWorkingTreeChanges) return;
|
|
236
|
-
if (this.workingTreeTimer != null) clearTimeout(this.workingTreeTimer);
|
|
237
|
-
this.workingTreeTimer = setTimeout(() => this.flushWorkingTree(), delayOverride ?? this.effectiveWorkingTreeMs);
|
|
238
|
-
}
|
|
239
394
|
coalesceWorkingTrees(paths) {
|
|
240
395
|
for (const p of paths) {
|
|
241
396
|
if (this.pendingWorkingTreePaths.size >= maxWorkingTreePaths && !this.pendingWorkingTreePaths.has(p)) {
|
|
@@ -244,21 +399,57 @@ var RepositoryWatchSession = class RepositoryWatchSession {
|
|
|
244
399
|
}
|
|
245
400
|
this.pendingWorkingTreePaths.add(p);
|
|
246
401
|
}
|
|
247
|
-
this.
|
|
402
|
+
if (this.hasPendingWorkingTreeChanges) this.workingTreeClock.note();
|
|
248
403
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
404
|
+
};
|
|
405
|
+
/** Wraps a session's repo event so each delivery is narrowed to the subscriber's relevant refs (see `subscribe`) */
|
|
406
|
+
function createRelevantRefsEvent(event, relevantRefs) {
|
|
407
|
+
return (listener, thisArgs, disposables) => event((e) => {
|
|
408
|
+
const narrowed = narrowToRelevantRefs(e, relevantRefs);
|
|
409
|
+
if (narrowed != null) listener.call(thisArgs, narrowed);
|
|
410
|
+
}, void 0, disposables);
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Narrows an event to the refs a subscriber cares about. Returns the event itself when no change kind is removed (the
|
|
414
|
+
* kept names then stay a superset, which only makes `mayHaveChangedRef` more conservative), `undefined` when every kind
|
|
415
|
+
* is removed, and otherwise a new event with the kept kinds, names and unnamed kinds.
|
|
416
|
+
*/
|
|
417
|
+
function narrowToRelevantRefs(e, relevantRefs) {
|
|
418
|
+
let unresolved;
|
|
419
|
+
for (const change of e.changes) {
|
|
420
|
+
if (!isRefChangeKind(change) || e.unnamedRefKinds.has(change)) continue;
|
|
421
|
+
(unresolved ??= /* @__PURE__ */ new Set()).add(change);
|
|
254
422
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
423
|
+
if (unresolved == null) return e;
|
|
424
|
+
const pseudoRefs = [];
|
|
425
|
+
for (const name of e.refs) if (!name.startsWith("refs/")) pseudoRefs.push(name);
|
|
426
|
+
if (pseudoRefs.length !== 0) {
|
|
427
|
+
unresolved.delete("heads");
|
|
428
|
+
if (unresolved.size === 0) return e;
|
|
429
|
+
}
|
|
430
|
+
const keptNames = new Set(pseudoRefs);
|
|
431
|
+
for (const ref of relevantRefs()) {
|
|
432
|
+
const kind = getRefChangeKind(ref);
|
|
433
|
+
if (kind == null || !e.changes.has(kind)) continue;
|
|
434
|
+
if (ref === "HEAD") {
|
|
435
|
+
for (const name of e.refs) if (getRefChangeKind(name) === "heads") keptNames.add(name);
|
|
436
|
+
unresolved.delete(kind);
|
|
437
|
+
continue;
|
|
259
438
|
}
|
|
439
|
+
const covering = findCoveringRefName(e.refs, ref);
|
|
440
|
+
if (covering == null) continue;
|
|
441
|
+
keptNames.add(covering);
|
|
442
|
+
unresolved.delete(kind);
|
|
260
443
|
}
|
|
261
|
-
|
|
444
|
+
if (unresolved.size === 0) return e;
|
|
445
|
+
const keptChanges = [];
|
|
446
|
+
for (const change of e.changes) if (!isRefChangeKind(change) || !unresolved.has(change)) keptChanges.push(change);
|
|
447
|
+
if (keptChanges.length === 0) return void 0;
|
|
448
|
+
return new WatcherRepoChangeEvent(e.repoPath, keptChanges, {
|
|
449
|
+
names: keptNames,
|
|
450
|
+
unnamed: e.unnamedRefKinds
|
|
451
|
+
});
|
|
452
|
+
}
|
|
262
453
|
//#endregion
|
|
263
454
|
export { RepositoryWatchSession };
|
|
264
455
|
|