@gitkraken/core-gitlens 0.16.4 → 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.
Files changed (90) hide show
  1. package/CHANGELOG.md +39 -1
  2. package/dist/git/cache.d.ts +8 -2
  3. package/dist/git/cache.js +15 -6
  4. package/dist/git/cache.js.map +1 -1
  5. package/dist/git/models/issue.d.ts +20 -1
  6. package/dist/git/models/issue.js +3 -1
  7. package/dist/git/models/issue.js.map +1 -1
  8. package/dist/git/models/repository.d.ts +4 -2
  9. package/dist/git/models/repository.js +1 -0
  10. package/dist/git/models/repository.js.map +1 -1
  11. package/dist/git/models/worktree.d.ts +5 -1
  12. package/dist/git/models/worktree.js +5 -3
  13. package/dist/git/models/worktree.js.map +1 -1
  14. package/dist/git/providers/provider.d.ts +17 -0
  15. package/dist/git/providers/worktrees.d.ts +17 -2
  16. package/dist/git/run.types.d.ts +7 -0
  17. package/dist/git/service.d.ts +5 -2
  18. package/dist/git/service.js +11 -5
  19. package/dist/git/service.js.map +1 -1
  20. package/dist/git/utils/issue.utils.js +5 -1
  21. package/dist/git/utils/issue.utils.js.map +1 -1
  22. package/dist/git/watching/classifyChange.d.ts +4 -3
  23. package/dist/git/watching/classifyChange.js +7 -4
  24. package/dist/git/watching/classifyChange.js.map +1 -1
  25. package/dist/git/watching/provider.d.ts +6 -0
  26. package/dist/git/watching/watchGroup.d.ts +142 -3
  27. package/dist/git/watching/watchGroup.js +424 -19
  28. package/dist/git/watching/watchGroup.js.map +1 -1
  29. package/dist/git/watching/watchService.d.ts +25 -0
  30. package/dist/git/watching/watchService.js +27 -7
  31. package/dist/git/watching/watchService.js.map +1 -1
  32. package/dist/git/watching/watchSession.d.ts +13 -8
  33. package/dist/git/watching/watchSession.js +50 -10
  34. package/dist/git/watching/watchSession.js.map +1 -1
  35. package/dist/git/watching/watcherPatterns.d.ts +14 -0
  36. package/dist/git/watching/watcherPatterns.js +38 -1
  37. package/dist/git/watching/watcherPatterns.js.map +1 -1
  38. package/dist/git/watching/worktreeSummary.d.ts +64 -2
  39. package/dist/git/watching/worktreeSummary.js +326 -13
  40. package/dist/git/watching/worktreeSummary.js.map +1 -1
  41. package/dist/git-cli/cliGitProvider.d.ts +26 -0
  42. package/dist/git-cli/cliGitProvider.js +66 -5
  43. package/dist/git-cli/cliGitProvider.js.map +1 -1
  44. package/dist/git-cli/exec/commandClassifier.d.ts +9 -3
  45. package/dist/git-cli/exec/commandClassifier.js +98 -17
  46. package/dist/git-cli/exec/commandClassifier.js.map +1 -1
  47. package/dist/git-cli/exec/git.d.ts +20 -0
  48. package/dist/git-cli/exec/git.js +69 -7
  49. package/dist/git-cli/exec/git.js.map +1 -1
  50. package/dist/git-cli/exec/headState.d.ts +5 -0
  51. package/dist/git-cli/exec/headState.js +14 -1
  52. package/dist/git-cli/exec/headState.js.map +1 -1
  53. package/dist/git-cli/parsers/worktreeParser.js +2 -2
  54. package/dist/git-cli/parsers/worktreeParser.js.map +1 -1
  55. package/dist/git-cli/providers/branches.js +8 -4
  56. package/dist/git-cli/providers/branches.js.map +1 -1
  57. package/dist/git-cli/providers/graph.js +1 -1
  58. package/dist/git-cli/providers/graph.js.map +1 -1
  59. package/dist/git-cli/providers/operations.d.ts +3 -0
  60. package/dist/git-cli/providers/operations.js +14 -5
  61. package/dist/git-cli/providers/operations.js.map +1 -1
  62. package/dist/git-cli/providers/worktrees.d.ts +25 -3
  63. package/dist/git-cli/providers/worktrees.js +125 -32
  64. package/dist/git-cli/providers/worktrees.js.map +1 -1
  65. package/dist/git-cli/service.d.ts +2 -0
  66. package/dist/git-cli/service.js +4 -2
  67. package/dist/git-cli/service.js.map +1 -1
  68. package/dist/plus/integrations/models/gitHostIntegration.d.ts +13 -0
  69. package/dist/plus/integrations/models/gitHostIntegration.js +20 -3
  70. package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
  71. package/dist/plus/integrations/models/integration.d.ts +12 -0
  72. package/dist/plus/integrations/models/integration.js +7 -1
  73. package/dist/plus/integrations/models/integration.js.map +1 -1
  74. package/dist/plus/integrations/providers/azure/azure.d.ts +13 -0
  75. package/dist/plus/integrations/providers/azure/azure.js +11 -3
  76. package/dist/plus/integrations/providers/azure/azure.js.map +1 -1
  77. package/dist/plus/integrations/providers/azure/models.d.ts +3 -1
  78. package/dist/plus/integrations/providers/azure/models.js +11 -5
  79. package/dist/plus/integrations/providers/azure/models.js.map +1 -1
  80. package/dist/plus/integrations/providers/azureDevOps.d.ts +33 -5
  81. package/dist/plus/integrations/providers/azureDevOps.js +75 -11
  82. package/dist/plus/integrations/providers/azureDevOps.js.map +1 -1
  83. package/dist/plus/integrations/providers/azureDevOpsServer.js +1 -0
  84. package/dist/plus/integrations/providers/azureDevOpsServer.js.map +1 -1
  85. package/dist/plus/integrations/providers/models.d.ts +19 -0
  86. package/dist/plus/integrations/providers/models.js +12 -3
  87. package/dist/plus/integrations/providers/models.js.map +1 -1
  88. package/dist/plus/integrations/reads/etag.js +6 -1
  89. package/dist/plus/integrations/reads/etag.js.map +1 -1
  90. 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 a one-off full read that
177
- * is neither cached nor watched.
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
- const repo = this.getRepository(key);
184
- if (repo == null) throw new Error(`No repository found for '${key}'`);
185
- return (await readSummary(repo, key, allFields, void 0, void 0, void 0, void 0, void 0, cancellation, this.verifyBaseRemeasure)).summary;
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
- const commonPath = this.getCommonPath?.(entry.key);
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 dirtyRead = fields & dirtyField ? readDirty(repo, signal) : void 0;
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 ??= readDirty(repo, signal);
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) {