@gitkraken/core-gitlens 0.16.5 → 0.17.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 +32 -1
- package/dist/git/cache.d.ts +8 -2
- package/dist/git/cache.js +15 -6
- package/dist/git/cache.js.map +1 -1
- package/dist/git/models/repository.d.ts +4 -2
- package/dist/git/models/repository.js +1 -0
- package/dist/git/models/repository.js.map +1 -1
- package/dist/git/models/worktree.d.ts +5 -1
- package/dist/git/models/worktree.js +5 -3
- package/dist/git/models/worktree.js.map +1 -1
- package/dist/git/providers/provider.d.ts +17 -0
- package/dist/git/providers/worktrees.d.ts +17 -2
- package/dist/git/run.types.d.ts +7 -0
- package/dist/git/service.d.ts +5 -2
- package/dist/git/service.js +11 -5
- package/dist/git/service.js.map +1 -1
- package/dist/git/watching/classifyChange.d.ts +4 -3
- package/dist/git/watching/classifyChange.js +7 -4
- package/dist/git/watching/classifyChange.js.map +1 -1
- package/dist/git/watching/provider.d.ts +6 -0
- package/dist/git/watching/watchGroup.d.ts +142 -3
- package/dist/git/watching/watchGroup.js +424 -19
- package/dist/git/watching/watchGroup.js.map +1 -1
- package/dist/git/watching/watchService.d.ts +25 -0
- package/dist/git/watching/watchService.js +27 -7
- package/dist/git/watching/watchService.js.map +1 -1
- package/dist/git/watching/watchSession.d.ts +13 -8
- package/dist/git/watching/watchSession.js +50 -10
- package/dist/git/watching/watchSession.js.map +1 -1
- package/dist/git/watching/watcherPatterns.d.ts +14 -0
- package/dist/git/watching/watcherPatterns.js +38 -1
- package/dist/git/watching/watcherPatterns.js.map +1 -1
- package/dist/git/watching/worktreeSummary.d.ts +64 -2
- package/dist/git/watching/worktreeSummary.js +326 -13
- package/dist/git/watching/worktreeSummary.js.map +1 -1
- package/dist/git-cli/cliGitProvider.d.ts +26 -0
- package/dist/git-cli/cliGitProvider.js +66 -5
- package/dist/git-cli/cliGitProvider.js.map +1 -1
- package/dist/git-cli/exec/commandClassifier.d.ts +9 -3
- package/dist/git-cli/exec/commandClassifier.js +98 -17
- package/dist/git-cli/exec/commandClassifier.js.map +1 -1
- package/dist/git-cli/exec/git.d.ts +20 -0
- package/dist/git-cli/exec/git.js +69 -7
- package/dist/git-cli/exec/git.js.map +1 -1
- package/dist/git-cli/exec/headState.d.ts +5 -0
- package/dist/git-cli/exec/headState.js +14 -1
- package/dist/git-cli/exec/headState.js.map +1 -1
- package/dist/git-cli/parsers/worktreeParser.js +2 -2
- package/dist/git-cli/parsers/worktreeParser.js.map +1 -1
- package/dist/git-cli/providers/branches.js +8 -4
- package/dist/git-cli/providers/branches.js.map +1 -1
- package/dist/git-cli/providers/graph.js +1 -1
- package/dist/git-cli/providers/graph.js.map +1 -1
- package/dist/git-cli/providers/operations.d.ts +3 -0
- package/dist/git-cli/providers/operations.js +14 -5
- package/dist/git-cli/providers/operations.js.map +1 -1
- package/dist/git-cli/providers/worktrees.d.ts +25 -3
- package/dist/git-cli/providers/worktrees.js +125 -32
- package/dist/git-cli/providers/worktrees.js.map +1 -1
- package/dist/git-cli/service.d.ts +2 -0
- package/dist/git-cli/service.js +4 -2
- package/dist/git-cli/service.js.map +1 -1
- package/package.json +1 -1
|
@@ -3,7 +3,9 @@ import { CancellationError, isCancellationError, raceWithSignal } from "../../ut
|
|
|
3
3
|
import { Logger as _logger } from "../../utils/logger.js";
|
|
4
4
|
import { Emitter } from "../../utils/event.js";
|
|
5
5
|
import { getRepositoryKey } from "../../utils/uri.js";
|
|
6
|
+
import { joinPaths } from "../../utils/path.js";
|
|
6
7
|
import { getRefChangeKind } from "./changeEvent.js";
|
|
8
|
+
import { LruMap } from "../../utils/lruMap.js";
|
|
7
9
|
//#region ../git/src/watching/worktreeSummary.ts
|
|
8
10
|
/** Read groups, as bit flags so a pending set is a number rather than an allocation per event */
|
|
9
11
|
const dirtyField = 1;
|
|
@@ -22,12 +24,32 @@ const baseMoveField = 32;
|
|
|
22
24
|
const allFields = 7;
|
|
23
25
|
const maxConcurrentReadsPerRepository = 4;
|
|
24
26
|
const maxConcurrentReads = 8;
|
|
27
|
+
const maxUnwatchedReads = 256;
|
|
25
28
|
const emptyRefs = Object.freeze([]);
|
|
26
29
|
const noBaseKeys = [
|
|
27
30
|
void 0,
|
|
28
31
|
void 0,
|
|
29
32
|
void 0
|
|
30
33
|
];
|
|
34
|
+
/** A read of a worktree without a subscription, which concurrent callers share */
|
|
35
|
+
var UnwatchedFlight = class {
|
|
36
|
+
key;
|
|
37
|
+
probeDirty;
|
|
38
|
+
disposed;
|
|
39
|
+
abort = new AbortController();
|
|
40
|
+
/** Callers waiting for it; the read is aborted once none is */
|
|
41
|
+
callers = 0;
|
|
42
|
+
/** Read groups made due while it ran, carried into what it stores */
|
|
43
|
+
due = 0;
|
|
44
|
+
promise;
|
|
45
|
+
constructor(key, probeDirty, disposed, read) {
|
|
46
|
+
this.key = key;
|
|
47
|
+
this.probeDirty = probeDirty;
|
|
48
|
+
this.disposed = disposed;
|
|
49
|
+
this.promise = Promise.resolve().then(() => read(this));
|
|
50
|
+
this.promise.catch(noop);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
31
53
|
var SummarySubscription = class {
|
|
32
54
|
entry;
|
|
33
55
|
options;
|
|
@@ -107,6 +129,18 @@ var SummarySubscription = class {
|
|
|
107
129
|
* fire only when some field's value differs from the previous summary — a recompute that reads the same answer is
|
|
108
130
|
* silent, so a consumer can re-render on every event without diffing. A failed recompute keeps the last values and
|
|
109
131
|
* sets `stale` (firing once); the next successful one clears it.
|
|
132
|
+
*
|
|
133
|
+
* **Worktrees without a subscription.** {@link getSummary} and {@link getSummarySnapshot} answer one from its last read
|
|
134
|
+
* (kept for the {@link maxUnwatchedReads} most recently asked), re-reading only the groups whose inputs moved since:
|
|
135
|
+
* HEAD's branch or commit reads all of them; the upstream's tip, or its name, reads `branch`; a ref the base depends on
|
|
136
|
+
* (where a remote's `HEAD` symref points, for one) reads `base`, re-measured from the previous one where that can
|
|
137
|
+
* answer; the config inputs its base read consulted read `branch` and the base when they changed; and the worktree's
|
|
138
|
+
* index file reads `dirty`. The inputs come from core's caches, so nothing moved means no git at all; only a `.git`
|
|
139
|
+
* watch of the worktree (or core's own writes) keeps them current, so a host must watch the `.git` of a worktree it
|
|
140
|
+
* asks about this way for others' changes to show. Its working tree is unwatched, so an edit that leaves the index
|
|
141
|
+
* alone goes unseen until `dirty` is read again: the snapshot says when that was, and `probeDirty` reads it anyway.
|
|
142
|
+
* Concurrent reads of a worktree share one, and take no read slot: each is a caller's foreground read, which never
|
|
143
|
+
* waits behind subscribed worktrees' runs.
|
|
110
144
|
*/
|
|
111
145
|
var WorktreeSummaryService = class {
|
|
112
146
|
_onDidChangeSummary = new Emitter();
|
|
@@ -120,9 +154,14 @@ var WorktreeSummaryService = class {
|
|
|
120
154
|
activeReads = 0;
|
|
121
155
|
/** Entries waiting for a read slot, oldest first */
|
|
122
156
|
readWaiters = /* @__PURE__ */ new Map();
|
|
157
|
+
/** The last read of each worktree without a subscription, by repository key */
|
|
158
|
+
unwatchedReads = new LruMap(maxUnwatchedReads);
|
|
159
|
+
/** The read in flight for each worktree without a subscription, by repository key */
|
|
160
|
+
unwatchedFlights = /* @__PURE__ */ new Map();
|
|
123
161
|
watchService;
|
|
124
162
|
getRepository;
|
|
125
163
|
getCommonPath;
|
|
164
|
+
stat;
|
|
126
165
|
verifyBaseRemeasure;
|
|
127
166
|
mergeTargetsListener;
|
|
128
167
|
_disposed = false;
|
|
@@ -130,6 +169,7 @@ var WorktreeSummaryService = class {
|
|
|
130
169
|
this.watchService = options.watchService;
|
|
131
170
|
this.getRepository = options.getRepository;
|
|
132
171
|
this.getCommonPath = options.getCommonPath;
|
|
172
|
+
this.stat = options.stat;
|
|
133
173
|
this.verifyBaseRemeasure = options.verifyBaseRemeasure;
|
|
134
174
|
this.mergeTargetsListener = options.onDidChangeMergeTargets?.((e) => this.onMergeTargetsChanged(e));
|
|
135
175
|
}
|
|
@@ -141,6 +181,12 @@ var WorktreeSummaryService = class {
|
|
|
141
181
|
this._disposed = true;
|
|
142
182
|
for (const resolve of this.readWaiters.values()) resolve(false);
|
|
143
183
|
this.readWaiters.clear();
|
|
184
|
+
for (const flight of this.unwatchedFlights.values()) {
|
|
185
|
+
flight.disposed = true;
|
|
186
|
+
flight.abort.abort();
|
|
187
|
+
}
|
|
188
|
+
this.unwatchedFlights.clear();
|
|
189
|
+
this.unwatchedReads.clear();
|
|
144
190
|
this.mergeTargetsListener?.dispose();
|
|
145
191
|
for (const entry of [...this.entries.values()]) for (const subscription of [...entry.subscribers]) subscription.dispose();
|
|
146
192
|
this.entries.clear();
|
|
@@ -161,6 +207,8 @@ var WorktreeSummaryService = class {
|
|
|
161
207
|
target.subscribers.add(subscription);
|
|
162
208
|
if (isNew) {
|
|
163
209
|
if (!target.disposed) {
|
|
210
|
+
this.unwatchedReads.delete(key);
|
|
211
|
+
this.unwatchedFlights.delete(key);
|
|
164
212
|
target.pending = 23;
|
|
165
213
|
target.running = this.start(target);
|
|
166
214
|
}
|
|
@@ -173,21 +221,183 @@ var WorktreeSummaryService = class {
|
|
|
173
221
|
}
|
|
174
222
|
/**
|
|
175
223
|
* Returns a worktree's summary. For a subscribed worktree, the cached value — after waiting for any run in flight
|
|
176
|
-
* or pending; it rejects with the read's error when there is still no value. Otherwise
|
|
177
|
-
*
|
|
224
|
+
* or pending; it rejects with the read's error when there is still no value. Otherwise the last read of it,
|
|
225
|
+
* re-reading what moved since (see {@link getSummarySnapshot}).
|
|
178
226
|
*/
|
|
179
227
|
async getSummary(repoPath, cancellation) {
|
|
180
228
|
const key = getRepositoryKey(repoPath);
|
|
181
229
|
const entry = this.entries.get(key);
|
|
182
|
-
if (entry == null)
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
230
|
+
if (entry == null) return (await this.getUnwatchedSnapshot(key, false, cancellation)).summary;
|
|
231
|
+
return this.getSubscribedSummary(entry, cancellation);
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Returns a worktree's summary, with when its dirty flag was read. A subscribed worktree answers as
|
|
235
|
+
* {@link getSummary} does, with `dirtyReadAt` `undefined`, since its summary follows its working tree. Any other
|
|
236
|
+
* answers from its last read, re-reading only the groups whose inputs moved since (no git when none did), and in
|
|
237
|
+
* full the first time, or after a failed or stale read. Its working tree is unwatched, so the dirty flag is re-read
|
|
238
|
+
* only when the index file moved, or with `probeDirty`; a rejection drops the last read. Its inputs are core's
|
|
239
|
+
* cached answers, which only a `.git` watch of the worktree (or core's own writes) keeps current.
|
|
240
|
+
*/
|
|
241
|
+
async getSummarySnapshot(repoPath, options) {
|
|
242
|
+
const key = getRepositoryKey(repoPath);
|
|
243
|
+
const entry = this.entries.get(key);
|
|
244
|
+
if (entry == null) return this.getUnwatchedSnapshot(key, options?.probeDirty ?? false, options?.cancellation);
|
|
245
|
+
return {
|
|
246
|
+
summary: await this.getSubscribedSummary(entry, options?.cancellation),
|
|
247
|
+
dirtyReadAt: void 0
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
async getSubscribedSummary(entry, cancellation) {
|
|
187
251
|
if (entry.running != null) await (cancellation != null ? raceWithSignal(entry.running, cancellation) : entry.running);
|
|
188
252
|
if (entry.disposed) throw new CancellationError();
|
|
189
253
|
if (entry.value != null) return entry.value;
|
|
190
|
-
throw entry.error ?? /* @__PURE__ */ new Error(`No summary available for '${key}'`);
|
|
254
|
+
throw entry.error ?? /* @__PURE__ */ new Error(`No summary available for '${entry.key}'`);
|
|
255
|
+
}
|
|
256
|
+
/** Joins the read in flight for `key`, or starts one; the read is aborted only once no caller waits for it */
|
|
257
|
+
async getUnwatchedSnapshot(key, probeDirty, cancellation) {
|
|
258
|
+
if (cancellation?.aborted) throw new CancellationError();
|
|
259
|
+
let flight = this.getJoinableFlight(key);
|
|
260
|
+
if (probeDirty) {
|
|
261
|
+
while (flight != null && !flight.probeDirty) {
|
|
262
|
+
const waited = flight;
|
|
263
|
+
try {
|
|
264
|
+
await (cancellation != null ? raceWithSignal(waited.promise, cancellation) : waited.promise);
|
|
265
|
+
} catch (ex) {
|
|
266
|
+
if (cancellation?.aborted) throw ex;
|
|
267
|
+
}
|
|
268
|
+
flight = this.getJoinableFlight(key);
|
|
269
|
+
if (flight === waited) flight = void 0;
|
|
270
|
+
}
|
|
271
|
+
const entry = this.entries.get(key);
|
|
272
|
+
if (entry != null) return {
|
|
273
|
+
summary: await this.getSubscribedSummary(entry, cancellation),
|
|
274
|
+
dirtyReadAt: void 0
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
if (flight == null) {
|
|
278
|
+
flight = new UnwatchedFlight(key, probeDirty, this._disposed, (f) => this.readUnwatched(f));
|
|
279
|
+
if (!this._disposed) this.unwatchedFlights.set(key, flight);
|
|
280
|
+
}
|
|
281
|
+
const current = flight;
|
|
282
|
+
current.callers++;
|
|
283
|
+
try {
|
|
284
|
+
const read = await (cancellation != null ? raceWithSignal(current.promise, cancellation) : current.promise);
|
|
285
|
+
return {
|
|
286
|
+
summary: read.result.summary,
|
|
287
|
+
dirtyReadAt: read.dirtyReadAt
|
|
288
|
+
};
|
|
289
|
+
} finally {
|
|
290
|
+
current.callers--;
|
|
291
|
+
if (current.callers === 0) {
|
|
292
|
+
if (this.unwatchedFlights.get(key) === current) this.unwatchedFlights.delete(key);
|
|
293
|
+
current.disposed = true;
|
|
294
|
+
current.abort.abort();
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* The read in flight for `key`, unless core wrote a merge target or base while it ran: it may have read the stored
|
|
300
|
+
* values before the write, and a caller after an awaited write sees them, as one of a subscribed worktree does
|
|
301
|
+
*/
|
|
302
|
+
getJoinableFlight(key) {
|
|
303
|
+
const flight = this.unwatchedFlights.get(key);
|
|
304
|
+
return flight?.due === 0 ? flight : void 0;
|
|
305
|
+
}
|
|
306
|
+
async readUnwatched(flight) {
|
|
307
|
+
const { key } = flight;
|
|
308
|
+
const signal = flight.abort.signal;
|
|
309
|
+
if (flight.disposed) throw new CancellationError();
|
|
310
|
+
let last = this.unwatchedReads.get(key);
|
|
311
|
+
if (last?.result.summary.stale) last = void 0;
|
|
312
|
+
try {
|
|
313
|
+
const repo = this.getRepository(key);
|
|
314
|
+
if (repo == null) throw new Error(`No repository found for '${key}'`);
|
|
315
|
+
const headRemotes = last != null ? getHeadRemotes(last.result.summary.baseDependsOn) : void 0;
|
|
316
|
+
const probe = await this.probeUnwatched(repo, headRemotes, signal);
|
|
317
|
+
if (flight.disposed) throw new CancellationError();
|
|
318
|
+
let fields;
|
|
319
|
+
let baseMoves;
|
|
320
|
+
if (last == null) fields = 23;
|
|
321
|
+
else {
|
|
322
|
+
fields = last.due;
|
|
323
|
+
const { inputs } = last;
|
|
324
|
+
const previous = last.result.summary;
|
|
325
|
+
if (probe.branch !== inputs.branch || probe.detached !== inputs.detached || probe.sha !== inputs.sha) fields |= allFields;
|
|
326
|
+
else {
|
|
327
|
+
if (probe.upstream !== inputs.upstream) fields |= 18;
|
|
328
|
+
else if (getUpstreamTips(probe.tips, probe.upstream) !== inputs.upstreamTips) fields |= branchField;
|
|
329
|
+
for (const ref of previous.baseDependsOn) {
|
|
330
|
+
const value = getBaseInput(probe, ref);
|
|
331
|
+
if (value !== null && value === inputs.baseTips.get(ref)) continue;
|
|
332
|
+
baseMoves ??= /* @__PURE__ */ new Set();
|
|
333
|
+
baseMoves.add(ref);
|
|
334
|
+
}
|
|
335
|
+
if (baseMoves != null) fields |= baseMoveField;
|
|
336
|
+
if ((fields & baseConfigField) === 0 && last.result.baseConfigKey != null && repo.branches.getAheadOfBase != null && (probe.detached || probe.branch != null)) {
|
|
337
|
+
const configKey = getBaseConfigKey(await readBaseConfig(repo, signal), previous);
|
|
338
|
+
if (flight.disposed) throw new CancellationError();
|
|
339
|
+
if (configKey != null && configKey !== last.result.baseConfigKey) fields |= 18;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
if (flight.probeDirty || probe.index == null || probe.index !== inputs.index) fields |= dirtyField;
|
|
343
|
+
if (fields === 0) {
|
|
344
|
+
this.unwatchedReads.touch(key);
|
|
345
|
+
return last;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
if (headRemotes != null && (fields & 60) !== 0) {
|
|
349
|
+
await readRemoteHeads(repo, probe.remoteHeads, void 0, signal);
|
|
350
|
+
if (flight.disposed) throw new CancellationError();
|
|
351
|
+
}
|
|
352
|
+
let result = await readSummary(repo, key, fields, last?.result.summary, last?.result.baseKeys, last?.result.baseConfigKey, last?.result.baseDefaultBranch, baseMoves, signal, this.verifyBaseRemeasure);
|
|
353
|
+
const kept = last?.result.summary;
|
|
354
|
+
if (kept != null && summariesEqual(kept, result.summary)) result = {
|
|
355
|
+
...result,
|
|
356
|
+
summary: kept
|
|
357
|
+
};
|
|
358
|
+
const read = {
|
|
359
|
+
result,
|
|
360
|
+
dirtyReadAt: result.dirtyReadAt ?? last?.dirtyReadAt ?? Date.now(),
|
|
361
|
+
inputs: toUnwatchedInputs(probe, result.summary),
|
|
362
|
+
due: flight.due
|
|
363
|
+
};
|
|
364
|
+
if (this.unwatchedFlights.get(key) === flight && !this.entries.has(key)) this.unwatchedReads.set(key, read);
|
|
365
|
+
return read;
|
|
366
|
+
} catch (ex) {
|
|
367
|
+
if (!isCancellationError(ex) && this.unwatchedFlights.get(key) === flight) this.unwatchedReads.delete(key);
|
|
368
|
+
throw ex;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Reads an unwatched read's inputs: cached answers but for the index file's stat (see {@link UnwatchedInputs}), with
|
|
373
|
+
* the `HEAD` symrefs of `remotes` (every remote's when `undefined`)
|
|
374
|
+
*/
|
|
375
|
+
async probeUnwatched(repo, remotes, signal) {
|
|
376
|
+
const remoteHeads = /* @__PURE__ */ new Map();
|
|
377
|
+
const [current, tips, gitDir] = await Promise.all([
|
|
378
|
+
repo.branches.getBranch(void 0, void 0, signal),
|
|
379
|
+
repo.refs.getRefTips(void 0, signal),
|
|
380
|
+
getGitDir(repo),
|
|
381
|
+
readRemoteHeads(repo, remoteHeads, remotes, signal)
|
|
382
|
+
]);
|
|
383
|
+
let index;
|
|
384
|
+
const stat = this.stat;
|
|
385
|
+
if (stat != null && gitDir != null) try {
|
|
386
|
+
const stats = await stat(joinPaths(gitDir.uri.fsPath, "index"));
|
|
387
|
+
index = stats != null ? `${stats.size}:${stats.mtime}` : "";
|
|
388
|
+
} catch {
|
|
389
|
+
index = void 0;
|
|
390
|
+
}
|
|
391
|
+
const detached = current?.detached ?? false;
|
|
392
|
+
return {
|
|
393
|
+
branch: detached ? void 0 : current?.name || void 0,
|
|
394
|
+
detached,
|
|
395
|
+
sha: current?.sha,
|
|
396
|
+
upstream: current?.upstream?.name,
|
|
397
|
+
tips: getTipsByName(tips),
|
|
398
|
+
remoteHeads,
|
|
399
|
+
index
|
|
400
|
+
};
|
|
191
401
|
}
|
|
192
402
|
getResourceUsage() {
|
|
193
403
|
return { "worktreeSummary.entries.count": this.entries.size };
|
|
@@ -267,10 +477,23 @@ var WorktreeSummaryService = class {
|
|
|
267
477
|
if (entry.handle == null) continue;
|
|
268
478
|
const value = entry.value;
|
|
269
479
|
if (value != null && (value.branch == null || e.refs != null && !e.refs.has(value.branch))) continue;
|
|
270
|
-
|
|
271
|
-
if (commonPath != null && commonPath !== e.repoPath) continue;
|
|
480
|
+
if (!this.isInRepository(entry.key, e.repoPath)) continue;
|
|
272
481
|
this.queue(entry, baseKeysField);
|
|
273
482
|
}
|
|
483
|
+
for (const read of this.unwatchedReads.values()) {
|
|
484
|
+
const value = read.result.summary;
|
|
485
|
+
if (value.branch == null || e.refs != null && !e.refs.has(value.branch)) continue;
|
|
486
|
+
if (!this.isInRepository(value.repoPath, e.repoPath)) continue;
|
|
487
|
+
read.due |= baseKeysField;
|
|
488
|
+
}
|
|
489
|
+
for (const flight of this.unwatchedFlights.values()) {
|
|
490
|
+
if (!this.isInRepository(flight.key, e.repoPath)) continue;
|
|
491
|
+
flight.due |= baseKeysField;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
isInRepository(key, commonPath) {
|
|
495
|
+
const path = this.getCommonPath?.(key);
|
|
496
|
+
return path == null || path === commonPath;
|
|
274
497
|
}
|
|
275
498
|
queue(entry, fields, baseMoves) {
|
|
276
499
|
if (entry.disposed || fields === 0) return;
|
|
@@ -443,7 +666,12 @@ function createEntry(key) {
|
|
|
443
666
|
*/
|
|
444
667
|
async function readSummary(repo, key, fields, previous, previousBaseKeys, previousBaseConfigKey, previousBaseDefaultBranch, baseMoves, signal, verifyBaseRemeasure) {
|
|
445
668
|
if (previous == null) fields |= allFields;
|
|
446
|
-
let
|
|
669
|
+
let dirtyReadAt;
|
|
670
|
+
const startDirtyRead = () => {
|
|
671
|
+
dirtyReadAt = Date.now();
|
|
672
|
+
return readDirty(repo, signal);
|
|
673
|
+
};
|
|
674
|
+
let dirtyRead = fields & dirtyField ? startDirtyRead() : void 0;
|
|
447
675
|
let branch = previous?.branch;
|
|
448
676
|
let detached = previous?.detached ?? false;
|
|
449
677
|
let sha = previous?.sha;
|
|
@@ -460,7 +688,7 @@ async function readSummary(repo, key, fields, previous, previousBaseKeys, previo
|
|
|
460
688
|
behind = current?.upstream?.state.behind ?? 0;
|
|
461
689
|
if (previous != null && (branch !== previous.branch || detached !== previous.detached)) {
|
|
462
690
|
fields |= baseField;
|
|
463
|
-
dirtyRead ??=
|
|
691
|
+
dirtyRead ??= startDirtyRead();
|
|
464
692
|
}
|
|
465
693
|
}
|
|
466
694
|
let base = previous?.base;
|
|
@@ -555,7 +783,92 @@ async function readSummary(repo, key, fields, previous, previousBaseKeys, previo
|
|
|
555
783
|
baseKeys,
|
|
556
784
|
baseConfigKey,
|
|
557
785
|
baseDefaultBranch,
|
|
558
|
-
baseError
|
|
786
|
+
baseError,
|
|
787
|
+
dirtyReadAt
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
async function getGitDir(repo) {
|
|
791
|
+
try {
|
|
792
|
+
return await repo.config.getGitDir?.();
|
|
793
|
+
} catch (ex) {
|
|
794
|
+
if (isCancellationError(ex)) throw ex;
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
/** Each ref list's tips by full name, built once per list, which core's cache shares across a repository's worktrees */
|
|
799
|
+
const tipsByNameCache = /* @__PURE__ */ new WeakMap();
|
|
800
|
+
function getTipsByName(tips) {
|
|
801
|
+
let byName = tipsByNameCache.get(tips);
|
|
802
|
+
if (byName == null) {
|
|
803
|
+
const map = /* @__PURE__ */ new Map();
|
|
804
|
+
for (const tip of tips) map.set(tip.fullName, tip.sha);
|
|
805
|
+
byName = map;
|
|
806
|
+
tipsByNameCache.set(tips, byName);
|
|
807
|
+
}
|
|
808
|
+
return byName;
|
|
809
|
+
}
|
|
810
|
+
/** The upstream's tips as a remote-tracking and as a local branch, since its short name doesn't say which */
|
|
811
|
+
function getUpstreamTips(tips, upstream) {
|
|
812
|
+
if (upstream == null) return void 0;
|
|
813
|
+
return `${tips.get(`refs/remotes/${upstream}`) ?? ""}\0${tips.get(`refs/heads/${upstream}`) ?? ""}`;
|
|
814
|
+
}
|
|
815
|
+
/**
|
|
816
|
+
* Reads where the `HEAD` symref of each of `remotes` (every remote's when `undefined`) points (`origin/main`) into
|
|
817
|
+
* `heads`, as core's cached local reads answer it, skipping those already there: `undefined` when it has none (a
|
|
818
|
+
* remote since removed included), `null` when the read failed
|
|
819
|
+
*/
|
|
820
|
+
async function readRemoteHeads(repo, heads, remotes, signal) {
|
|
821
|
+
if (remotes?.size === 0) return;
|
|
822
|
+
const names = remotes ?? (await repo.remotes.getRemotes(void 0, signal)).map((r) => r.name);
|
|
823
|
+
await Promise.all(Array.from(names, async (name) => {
|
|
824
|
+
if (heads.has(name)) return;
|
|
825
|
+
let target;
|
|
826
|
+
try {
|
|
827
|
+
target = await repo.branches.getDefaultBranchName(name, { local: true }, signal);
|
|
828
|
+
} catch (ex) {
|
|
829
|
+
if (isCancellationError(ex)) throw ex;
|
|
830
|
+
target = null;
|
|
831
|
+
}
|
|
832
|
+
heads.set(name, target);
|
|
833
|
+
}));
|
|
834
|
+
}
|
|
835
|
+
/** The remote whose `HEAD` symref `ref` is; `undefined` for any other ref */
|
|
836
|
+
function getHeadRemote(ref) {
|
|
837
|
+
if (!ref.startsWith("refs/remotes/") || !ref.endsWith("/HEAD")) return void 0;
|
|
838
|
+
return ref.slice(13, -5);
|
|
839
|
+
}
|
|
840
|
+
/** The remotes whose `HEAD` symref is among `refs` */
|
|
841
|
+
function getHeadRemotes(refs) {
|
|
842
|
+
const remotes = /* @__PURE__ */ new Set();
|
|
843
|
+
for (const ref of refs) {
|
|
844
|
+
const remote = getHeadRemote(ref);
|
|
845
|
+
if (remote != null) remotes.add(remote);
|
|
846
|
+
}
|
|
847
|
+
return remotes;
|
|
848
|
+
}
|
|
849
|
+
/**
|
|
850
|
+
* What `probe` found of `ref`, a ref the base depends on: for a remote's `HEAD` symref, which the ref tips leave out,
|
|
851
|
+
* the ref it points at, since its tip follows that ref's, itself a dependency; else its tip. `null` for a symref of a
|
|
852
|
+
* remote it didn't read: only when the read found HEAD elsewhere than the probe did and read the base for that, which
|
|
853
|
+
* the next probe sees as HEAD moving anyway
|
|
854
|
+
*/
|
|
855
|
+
function getBaseInput(probe, ref) {
|
|
856
|
+
const remote = getHeadRemote(ref);
|
|
857
|
+
if (remote == null) return probe.tips.get(ref);
|
|
858
|
+
return probe.remoteHeads.has(remote) ? probe.remoteHeads.get(remote) : null;
|
|
859
|
+
}
|
|
860
|
+
/** The inputs `summary` was read from, as the probe before its read found them */
|
|
861
|
+
function toUnwatchedInputs(probe, summary) {
|
|
862
|
+
const baseTips = /* @__PURE__ */ new Map();
|
|
863
|
+
for (const ref of summary.baseDependsOn) baseTips.set(ref, getBaseInput(probe, ref));
|
|
864
|
+
return {
|
|
865
|
+
branch: probe.branch,
|
|
866
|
+
detached: probe.detached,
|
|
867
|
+
sha: probe.sha,
|
|
868
|
+
upstream: probe.upstream,
|
|
869
|
+
upstreamTips: getUpstreamTips(probe.tips, probe.upstream),
|
|
870
|
+
baseTips,
|
|
871
|
+
index: probe.index
|
|
559
872
|
};
|
|
560
873
|
}
|
|
561
874
|
async function readBaseConfig(repo, signal) {
|