@gitkraken/core-gitlens 0.15.4 → 0.15.6

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 (61) hide show
  1. package/CHANGELOG.md +25 -1
  2. package/dist/git/cache.d.ts +9 -12
  3. package/dist/git/cache.js +34 -16
  4. package/dist/git/cache.js.map +1 -1
  5. package/dist/git/context.d.ts +19 -2
  6. package/dist/git/context.js.map +1 -1
  7. package/dist/git/providers/provider.d.ts +7 -0
  8. package/dist/git/service.d.ts +5 -1
  9. package/dist/git/service.js +8 -4
  10. package/dist/git/service.js.map +1 -1
  11. package/dist/git/watching/changeEvent.d.ts +25 -1
  12. package/dist/git/watching/changeEvent.js +36 -2
  13. package/dist/git/watching/changeEvent.js.map +1 -1
  14. package/dist/git/watching/configSections.d.ts +35 -0
  15. package/dist/git/watching/configSections.js +210 -0
  16. package/dist/git/watching/configSections.js.map +1 -0
  17. package/dist/git/watching/watchGroup.d.ts +36 -3
  18. package/dist/git/watching/watchGroup.js +107 -2
  19. package/dist/git/watching/watchGroup.js.map +1 -1
  20. package/dist/git/watching/watchService.d.ts +14 -3
  21. package/dist/git/watching/watchService.js +10 -7
  22. package/dist/git/watching/watchService.js.map +1 -1
  23. package/dist/git/watching/watchSession.d.ts +52 -21
  24. package/dist/git/watching/watchSession.js +164 -42
  25. package/dist/git/watching/watchSession.js.map +1 -1
  26. package/dist/git/watching/worktreeSummary.d.ts +9 -7
  27. package/dist/git/watching/worktreeSummary.js +21 -10
  28. package/dist/git/watching/worktreeSummary.js.map +1 -1
  29. package/dist/git-cli/cliGitProvider.d.ts +6 -1
  30. package/dist/git-cli/cliGitProvider.js +45 -5
  31. package/dist/git-cli/cliGitProvider.js.map +1 -1
  32. package/dist/git-cli/providers/branches.d.ts +2 -2
  33. package/dist/git-cli/providers/branches.js +17 -15
  34. package/dist/git-cli/providers/branches.js.map +1 -1
  35. package/dist/git-cli/providers/config.d.ts +13 -0
  36. package/dist/git-cli/providers/config.js +38 -1
  37. package/dist/git-cli/providers/config.js.map +1 -1
  38. package/dist/git-cli/providers/operations.d.ts +35 -1
  39. package/dist/git-cli/providers/operations.js +175 -16
  40. package/dist/git-cli/providers/operations.js.map +1 -1
  41. package/dist/git-cli/providers/stash.d.ts +1 -0
  42. package/dist/git-cli/providers/stash.js +9 -3
  43. package/dist/git-cli/providers/stash.js.map +1 -1
  44. package/dist/git-cli/providers/worktrees.js +11 -4
  45. package/dist/git-cli/providers/worktrees.js.map +1 -1
  46. package/dist/git-cli/service.js +5 -1
  47. package/dist/git-cli/service.js.map +1 -1
  48. package/dist/plus/integrations/collectionMetadata.js +4 -2
  49. package/dist/plus/integrations/collectionMetadata.js.map +1 -1
  50. package/dist/plus/integrations/providers/issueSorts.d.ts +0 -1
  51. package/dist/plus/integrations/providers/providerErrors.d.ts +22 -0
  52. package/dist/plus/integrations/providers/providerErrors.js +43 -2
  53. package/dist/plus/integrations/providers/providerErrors.js.map +1 -1
  54. package/dist/plus/integrations/providers/providersApi.js +12 -8
  55. package/dist/plus/integrations/providers/providersApi.js.map +1 -1
  56. package/dist/plus/integrations/reads/drains.d.ts +0 -1
  57. package/dist/plus/integrations/results.d.ts +13 -3
  58. package/dist/plus/integrations/results.js +12 -2
  59. package/dist/plus/integrations/results.js.map +1 -1
  60. package/docs/integrations.md +14 -0
  61. package/package.json +1 -1
@@ -120,6 +120,7 @@ const maxWorkingTreePaths = 1e3;
120
120
  /**
121
121
  * Cap on retained ref names per flush, for the same reason (a fetch of many refs while suspended). Past it, a ref kind
122
122
  * is recorded unnamed — any ref of that kind may have moved — which only makes `mayHaveChangedRef` more conservative.
123
+ * A pending `config` confined to more branches than this is recorded unconfined, for the same reason.
123
124
  */
124
125
  const maxPendingRefNames = 1e3;
125
126
  /**
@@ -144,10 +145,15 @@ var RepositoryWatchSession = class RepositoryWatchSession {
144
145
  /** Kinds among {@link pendingRepoChanges} injected or pushed unfenced, which an announcement never replaces */
145
146
  pendingRepoUnfencedChanges = /* @__PURE__ */ new Set();
146
147
  /**
147
- * Kinds among {@link pendingRepoChanges} that only core's announced writes reported, with when each was announced
148
- * (see {@link noteSelfWrite})
148
+ * Kinds among {@link pendingRepoChanges} that only core's announced writes, and their own echoes, reported (see
149
+ * {@link noteSelfWrite})
149
150
  */
150
- pendingRepoAnnouncedAt = /* @__PURE__ */ new Map();
151
+ pendingRepoAnnounced = /* @__PURE__ */ new Map();
152
+ /**
153
+ * For a pending `config` change, the branches (full ref names) whose sections are all that every report of it
154
+ * changed; `undefined` once any report may have changed anything else
155
+ */
156
+ pendingRepoConfigBranches;
151
157
  repoClock;
152
158
  effectiveRepoMs;
153
159
  effectiveRepoMaxWaitMs;
@@ -248,7 +254,9 @@ var RepositoryWatchSession = class RepositoryWatchSession {
248
254
  * return the subscriber's current list. A `heads`/`remotes`/`tags`/`stash` change is delivered only when a listed
249
255
  * ref of that kind may have moved, when the change isn't attributable to a name (e.g. a `packed-refs` rewrite),
250
256
  * or, for `heads`, when a pseudo-ref (`HEAD`, `ORIG_HEAD`) moved — so `stash` and `tags` changes are dropped unless
251
- * a ref of that kind is listed. Other change kinds always pass, and an event left with no changes isn't delivered.
257
+ * a ref of that kind is listed. A `config` change confined to some branches' `branch.<name>` sections
258
+ * (`configBranches`) is delivered only when `HEAD` or one of those branches is listed. Other change kinds always
259
+ * pass, and an event left with no changes isn't delivered.
252
260
  * The filter never affects other subscribers, the debounce, or the global `onDidFireRepoChange` forwarding.
253
261
  */
254
262
  subscribe(opts) {
@@ -339,11 +347,13 @@ var RepositoryWatchSession = class RepositoryWatchSession {
339
347
  /**
340
348
  * Push interpreted repo changes into the pipeline. `ref` is the full ref name (or ref directory, or pseudo-ref) whose
341
349
  * file changed; a ref kind in `changes` is attributed to it when the kinds match, and is otherwise unnamed.
342
- * `false` preserves cache invalidations for a config edit without claiming any ref moved.
350
+ * `false` preserves cache invalidations for a config edit without claiming any ref moved. `configBranches`, for a
351
+ * `config` among `changes`, are the local branches (full ref names) whose `branch.<name>` sections are all that
352
+ * changed in `.git/config`; without it, the config change may be anywhere.
343
353
  */
344
- pushRepoChanges(changes, ref) {
354
+ pushRepoChanges(changes, ref, configBranches) {
345
355
  if (this._disposed) return;
346
- this.coalesceRepo(changes, ref, "raw");
356
+ this.coalesceRepo(changes, ref, "raw", configBranches);
347
357
  }
348
358
  /**
349
359
  * Push watcher-observed repo changes that no write of core's can account for — an overflow's rescan, or an ignore
@@ -358,38 +368,62 @@ var RepositoryWatchSession = class RepositoryWatchSession {
358
368
  * Core announced one of its own writes for this repository: its caches were evicted and its host notified, both
359
369
  * after the write finished. The announcement becomes this session's one event for the write:
360
370
  *
361
- * - raw changes of an announced kind still pending are replaced by it — they happened before the announcement,
362
- * which already covers them, whether they came from the write or from an outside change;
363
- * - the announced kinds are pushed through the debounce and suspend like {@link fireChange}, ref kinds unnamed,
364
- * and the event carries them in `announced`;
365
- * - raw changes of an announced kind that arrive later are never dropped. One arriving within the repo debounce
366
- * delay of the announcement, before its event fires, is taken for the write's own late echo and keeps the kind
367
- * announced; any other report of the kind before the event fires clears the mark. Once it fires, nothing is
368
- * special.
371
+ * - the announced kinds are pushed through the debounce and suspend like {@link fireChange}, and the event carries
372
+ * them in `announced`. A ref kind is unnamed unless `refs` names refs of that kind (the full ref names the write
373
+ * moved), in which case it carries those names and no other ref of the kind counts as changed by the write.
374
+ * `config` is confined to the `branch.<name>` sections of the local branches `refs` names when
375
+ * `options.configConfinedToRefs` says so, and the event carries them (`configBranches`);
376
+ * - a raw change of an announced kind is the write's own report of it only when it is provably within what the
377
+ * write announced: a ref the write named, or a config change confined to branches the write's config was confined
378
+ * to. Pending when the announcement arrives, it is replaced. Arriving later, it merges and the kind stays
379
+ * announced only within the repo debounce delay of the announcement; a window can stay pending for minutes while
380
+ * the session is suspended, and a later move of the same ref is as likely an outside one. Any other report of
381
+ * the kind is an outside change: it stays, and the kind isn't `announced`, since the host has not handled the
382
+ * outside part. A write never names `HEAD`, but a commit locks it to write its reflog, so under a write that
383
+ * announced `head`, a report of the `HEAD` file within the same delay is the write's echo, its `heads` part
384
+ * included;
385
+ * - a kind the write announced open (unnamed, or `config` unconfined) covers any change of the kind: raw changes
386
+ * of it still pending are replaced, since the host evicts everything of the kind after the write. One arriving
387
+ * within the repo debounce delay of the announcement, before its event fires, is taken for the write's own late
388
+ * echo; any later report of the kind before the event fires clears the mark.
369
389
  *
370
- * The trade-off: an echo the watcher delivers after the announced event fired causes one extra, unannounced event
371
- * (a duplicate refresh), rather than risk losing an outside change of the same kind.
390
+ * Once the event fires, nothing is special: an echo the watcher delivers after it causes one extra, unannounced
391
+ * event (a duplicate refresh, narrowed like any other), rather than risk losing an outside change of the same kind.
372
392
  *
373
393
  * Empty `changes`, or `'unknown'`, announces every kind a watcher can report (never `'unknown'` itself, which a
374
- * cache reads as a close); `'closed'` and `'opened'` are ignored. Only raw `.git` changes are replaced or kept
375
- * announced: {@link fireChange}, {@link fireChangeImmediate}, {@link pushUnfencedRepoChanges} and working-tree
376
- * changes always pass.
394
+ * cache reads as a close), open whatever `refs` says; `'closed'` and `'opened'` are ignored. Only raw `.git` changes
395
+ * are replaced or kept announced: {@link fireChange}, {@link fireChangeImmediate}, {@link pushUnfencedRepoChanges}
396
+ * and working-tree changes always pass.
377
397
  */
378
- noteSelfWrite(changes) {
398
+ noteSelfWrite(changes, refs, options) {
379
399
  if (this._disposed) return;
380
400
  const all = changes.length === 0 || changes.includes("unknown");
381
401
  const announced = all ? allWatchedRepositoryChanges : changes.filter((c) => isAnnounceable(c));
402
+ const named = all ? void 0 : groupNamedRefs(changes, refs);
403
+ const configBranches = !all && options?.configConfinedToRefs === true && changes.includes("config") ? getBranchRefs(refs) : void 0;
382
404
  for (const change of this.pendingRepoChanges) {
383
405
  if (this.pendingRepoUnfencedChanges.has(change) || !isAnnounceable(change)) continue;
384
406
  if (!all && !changes.includes(change)) continue;
407
+ if (change === "config" && configBranches != null) {
408
+ if (this.pendingRepoAnnounced.has(change)) continue;
409
+ if (!isSubsetOf(this.pendingRepoConfigBranches, configBranches)) continue;
410
+ }
411
+ const names = isRefChangeKind(change) ? named?.get(change) : void 0;
412
+ if (names != null) {
413
+ for (const name of names) this.pendingRepoRefs.delete(name);
414
+ if (this.hasPendingRefOfKind(change)) continue;
415
+ }
385
416
  this.pendingRepoChanges.delete(change);
386
- this.pendingRepoAnnouncedAt.delete(change);
417
+ this.pendingRepoAnnounced.delete(change);
418
+ if (change === "config") this.pendingRepoConfigBranches = void 0;
387
419
  if (!isRefChangeKind(change)) continue;
388
420
  this.pendingRepoUnnamedRefKinds.delete(change);
389
421
  for (const ref of this.pendingRepoRefs) if (getRefChangeKind(ref) === change) this.pendingRepoRefs.delete(ref);
390
422
  }
391
- if (announced.length !== 0) this.coalesceRepo(announced, void 0, "announced");
392
- else if (this.pendingRepoChanges.size === 0) this.repoClock.cancel();
423
+ if (announced.length !== 0) {
424
+ this.coalesceRepo(named != null ? announced.filter((c) => !isRefChangeKind(c) || !named.has(c)) : announced, void 0, "announced", configBranches);
425
+ if (named != null) for (const [kind, names] of named) for (const name of names) this.coalesceRepo([kind], name, "announced");
426
+ } else if (this.pendingRepoChanges.size === 0) this.repoClock.cancel();
393
427
  }
394
428
  /** Push working tree file changes into the pipeline */
395
429
  /** `incomplete` marks the batch as partial when the caller already dropped paths of its own. */
@@ -423,8 +457,9 @@ var RepositoryWatchSession = class RepositoryWatchSession {
423
457
  if (this.pendingRepoChanges.size === 0) return;
424
458
  const event = new WatcherRepoChangeEvent(this.repoPath, [...this.pendingRepoChanges], {
425
459
  names: this.pendingRepoRefs,
426
- unnamed: this.pendingRepoUnnamedRefKinds
427
- }, this.pendingRepoAnnouncedAt.keys());
460
+ unnamed: this.pendingRepoUnnamedRefKinds,
461
+ configBranches: this.pendingRepoConfigBranches
462
+ }, this.pendingRepoAnnounced.keys());
428
463
  this.clearPendingRepoChanges();
429
464
  this.onDidFireRepoChangeCallback?.(event);
430
465
  this.repoEmitter.fire(event);
@@ -432,20 +467,41 @@ var RepositoryWatchSession = class RepositoryWatchSession {
432
467
  /**
433
468
  * `origin` is a raw `.git` event, a push no announcement replaces ({@link fireChange},
434
469
  * {@link pushUnfencedRepoChanges}), or core's announced write. A kind stays announced only while nothing else
435
- * reports it in the pending window, save the write's own late echo (see {@link noteSelfWrite}).
470
+ * reports it in the pending window, save the write's own report of it (see {@link noteSelfWrite}).
471
+ * `configBranches` confines a `config` among `changes` to those branches' sections; without it, it may be anywhere.
436
472
  */
437
- coalesceRepo(changes, ref, origin) {
473
+ coalesceRepo(changes, ref, origin, configBranches) {
438
474
  if (changes.length === 0) return;
439
475
  const now = performance.now();
440
476
  const refKind = typeof ref === "string" ? getRefChangeKind(ref) : void 0;
441
477
  for (const change of changes) {
478
+ const pending = this.pendingRepoChanges.has(change);
479
+ let names;
480
+ if (origin !== "unfenced") {
481
+ if (change === "config") names = configBranches;
482
+ else if (typeof ref === "string" && refKind === change) names = /* @__PURE__ */ new Set([ref]);
483
+ }
484
+ const announcement = this.pendingRepoAnnounced.get(change);
442
485
  if (origin === "announced") {
443
- if (!this.pendingRepoChanges.has(change)) this.pendingRepoAnnouncedAt.set(change, now);
486
+ if (!pending) this.pendingRepoAnnounced.set(change, {
487
+ at: now,
488
+ names: names != null ? new Set(names) : void 0
489
+ });
490
+ else if (announcement != null) {
491
+ if (announcement.names != null && names != null) for (const name of names) announcement.names.add(name);
492
+ else announcement.names = void 0;
493
+ }
444
494
  } else {
445
- const announcedAt = origin === "raw" ? this.pendingRepoAnnouncedAt.get(change) : void 0;
446
- if (announcedAt == null || now - announcedAt > this.effectiveRepoMs) this.pendingRepoAnnouncedAt.delete(change);
495
+ const own = origin === "raw" && announcement != null && this.isOwnReport(change, announcement, names, now);
496
+ if (announcement != null && !own) this.pendingRepoAnnounced.delete(change);
447
497
  if (origin === "unfenced") this.pendingRepoUnfencedChanges.add(change);
448
498
  }
499
+ if (change === "config") {
500
+ let branches;
501
+ if (!pending) branches = names != null ? new Set(names) : void 0;
502
+ else if (this.pendingRepoConfigBranches != null && names != null) branches = /* @__PURE__ */ new Set([...this.pendingRepoConfigBranches, ...names]);
503
+ this.pendingRepoConfigBranches = branches != null && branches.size <= maxPendingRefNames ? branches : void 0;
504
+ }
449
505
  this.pendingRepoChanges.add(change);
450
506
  if (ref === false || !isRefChangeKind(change)) continue;
451
507
  if (ref != null && refKind === change && (this.pendingRepoRefs.size < maxPendingRefNames || this.pendingRepoRefs.has(ref))) this.pendingRepoRefs.add(ref);
@@ -453,12 +509,35 @@ var RepositoryWatchSession = class RepositoryWatchSession {
453
509
  }
454
510
  this.repoClock.note();
455
511
  }
512
+ /**
513
+ * Whether a raw report of `change`, confined to `names` (`undefined`: not confined), is the announced write's own.
514
+ * It must arrive within the repo debounce delay of the announcement, as it always had to. An open announcement
515
+ * asks nothing more; one confined to names also needs every name the report carries to be one the write announced,
516
+ * save the `heads` part of a `HEAD` report under a write that announced `head` in time, which is a commit's echo.
517
+ */
518
+ isOwnReport(change, announcement, names, now) {
519
+ if (now - announcement.at > this.effectiveRepoMs) return false;
520
+ if (announcement.names == null) return true;
521
+ if (names == null) return false;
522
+ if (isSubsetOf(names, announcement.names)) return true;
523
+ if (change !== "heads" || names.size !== 1 || !names.has("HEAD")) return false;
524
+ const head = this.pendingRepoAnnounced.get("head");
525
+ return head != null && now - head.at <= this.effectiveRepoMs;
526
+ }
527
+ /** Whether a pending report of `kind` remains, unnamed or under a name */
528
+ hasPendingRefOfKind(kind) {
529
+ if (!isRefChangeKind(kind)) return false;
530
+ if (this.pendingRepoUnnamedRefKinds.has(kind)) return true;
531
+ for (const ref of this.pendingRepoRefs) if (getRefChangeKind(ref) === kind) return true;
532
+ return false;
533
+ }
456
534
  clearPendingRepoChanges() {
457
535
  this.pendingRepoChanges.clear();
458
536
  this.pendingRepoRefs.clear();
459
537
  this.pendingRepoUnnamedRefKinds.clear();
460
538
  this.pendingRepoUnfencedChanges.clear();
461
- this.pendingRepoAnnouncedAt.clear();
539
+ this.pendingRepoAnnounced.clear();
540
+ this.pendingRepoConfigBranches = void 0;
462
541
  }
463
542
  flushWorkingTree() {
464
543
  if (!this.hasPendingWorkingTreeChanges) return;
@@ -488,6 +567,39 @@ var RepositoryWatchSession = class RepositoryWatchSession {
488
567
  function isAnnounceable(change) {
489
568
  return change !== "closed" && change !== "opened";
490
569
  }
570
+ /**
571
+ * The names in `refs` grouped by their ref kind, for the ref kinds among `changes` only; `undefined` when none is named.
572
+ * Pseudo-refs (`HEAD`) are left out: a write names the refs it moved, and those are full ref names.
573
+ */
574
+ function groupNamedRefs(changes, refs) {
575
+ if (!refs?.length) return void 0;
576
+ let named;
577
+ for (const ref of refs) {
578
+ if (!ref.startsWith("refs/")) continue;
579
+ const kind = getRefChangeKind(ref);
580
+ if (kind == null || !changes.includes(kind)) continue;
581
+ named ??= /* @__PURE__ */ new Map();
582
+ let names = named.get(kind);
583
+ if (names == null) {
584
+ names = /* @__PURE__ */ new Set();
585
+ named.set(kind, names);
586
+ }
587
+ names.add(ref);
588
+ }
589
+ return named;
590
+ }
591
+ /** The local branches among `refs` (full `refs/heads/` names), or `undefined` when there are none */
592
+ function getBranchRefs(refs) {
593
+ let branches;
594
+ for (const ref of refs ?? []) if (ref.startsWith("refs/heads/")) (branches ??= /* @__PURE__ */ new Set()).add(ref);
595
+ return branches;
596
+ }
597
+ /** Whether every name in `names` is in `of`; never for `undefined`, which stands for any name */
598
+ function isSubsetOf(names, of) {
599
+ if (names == null) return false;
600
+ for (const name of names) if (!of.has(name)) return false;
601
+ return true;
602
+ }
491
603
  /** Wraps a session's repo event so each delivery is narrowed to the subscriber's relevant refs (see `subscribe`) */
492
604
  function createRelevantRefsEvent(event, relevantRefs) {
493
605
  return (listener, thisArgs, disposables) => event((e) => {
@@ -498,7 +610,9 @@ function createRelevantRefsEvent(event, relevantRefs) {
498
610
  /**
499
611
  * Narrows an event to the refs a subscriber cares about. Returns the event itself when no change kind is removed (the
500
612
  * kept names then stay a superset, which only makes `mayHaveChangedRef` more conservative), `undefined` when every kind
501
- * is removed, and otherwise a new event with the kept kinds, names and unnamed kinds.
613
+ * is removed, and otherwise a new event with the kept kinds, names and unnamed kinds. A `config` change confined to
614
+ * some local branches' sections is removed unless a relevant ref is `HEAD` or one of those branches (see
615
+ * `mayHaveChangedConfig`).
502
616
  */
503
617
  function narrowToRelevantRefs(e, relevantRefs) {
504
618
  let unresolved;
@@ -506,34 +620,42 @@ function narrowToRelevantRefs(e, relevantRefs) {
506
620
  if (!isRefChangeKind(change) || e.unnamedRefKinds.has(change)) continue;
507
621
  (unresolved ??= /* @__PURE__ */ new Set()).add(change);
508
622
  }
509
- if (unresolved == null) return e;
623
+ const configBranches = e.configBranches;
624
+ let keepConfig = configBranches == null;
625
+ if (unresolved == null && keepConfig) return e;
626
+ unresolved ??= /* @__PURE__ */ new Set();
510
627
  const pseudoRefs = [];
511
628
  for (const name of e.refs) if (!name.startsWith("refs/")) pseudoRefs.push(name);
512
629
  if (pseudoRefs.length !== 0) {
513
630
  unresolved.delete("heads");
514
- if (unresolved.size === 0) return e;
631
+ if (unresolved.size === 0 && keepConfig) return e;
515
632
  }
516
633
  const keptNames = new Set(pseudoRefs);
517
634
  for (const ref of relevantRefs()) {
518
- const kind = getRefChangeKind(ref);
519
- if (kind == null || !e.changes.has(kind)) continue;
520
635
  if (ref === "HEAD") {
521
- for (const name of e.refs) if (getRefChangeKind(name) === "heads") keptNames.add(name);
522
- unresolved.delete(kind);
636
+ if (e.changes.has("heads")) {
637
+ for (const name of e.refs) if (getRefChangeKind(name) === "heads") keptNames.add(name);
638
+ }
639
+ keepConfig = true;
640
+ unresolved.delete("heads");
523
641
  continue;
524
642
  }
643
+ if (!keepConfig && configBranches?.has(ref)) keepConfig = true;
644
+ const kind = getRefChangeKind(ref);
645
+ if (kind == null || !e.changes.has(kind)) continue;
525
646
  const covering = findCoveringRefName(e.refs, ref);
526
647
  if (covering == null) continue;
527
648
  keptNames.add(covering);
528
649
  unresolved.delete(kind);
529
650
  }
530
- if (unresolved.size === 0) return e;
651
+ if (unresolved.size === 0 && keepConfig) return e;
531
652
  const keptChanges = [];
532
- for (const change of e.changes) if (!isRefChangeKind(change) || !unresolved.has(change)) keptChanges.push(change);
653
+ for (const change of e.changes) if (change === "config" ? keepConfig : !isRefChangeKind(change) || !unresolved.has(change)) keptChanges.push(change);
533
654
  if (keptChanges.length === 0) return void 0;
534
655
  return new WatcherRepoChangeEvent(e.repoPath, keptChanges, {
535
656
  names: keptNames,
536
- unnamed: e.unnamedRefKinds
657
+ unnamed: e.unnamedRefKinds,
658
+ configBranches
537
659
  }, e.announced);
538
660
  }
539
661
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"watchSession.js","names":[],"sources":["../../../../git/src/watching/watchSession.ts"],"sourcesContent":["import type { UnifiedDisposable } from '@gitlens/utils/disposable.js';\nimport { createDisposable } from '@gitlens/utils/disposable.js';\nimport type { Event } from '@gitlens/utils/event.js';\nimport { Emitter } from '@gitlens/utils/event.js';\nimport type { RepositoryChange } from '../models/repository.js';\nimport type { RefChangeKind, WorkingTreeChangeEvent } from './changeEvent.js';\nimport { findCoveringRefName, getRefChangeKind, isRefChangeKind, WatcherRepoChangeEvent } from './changeEvent.js';\nimport { allWatchedRepositoryChanges } from './classifyChange.js';\n\nexport interface RepositorySubscription extends UnifiedDisposable {\n\treadonly onDidChange: Event<WatcherRepoChangeEvent>;\n}\n\nexport interface WorkingTreeSubscription extends UnifiedDisposable {\n\treadonly onDidChangeWorkingTree: Event<WorkingTreeChangeEvent>;\n}\n\n/**\n * Lifecycle callbacks fired by the session when subscriber counts\n * cross the 0↔1 boundary. The host (WatchService) uses these to\n * lazily create/destroy watchers.\n */\nexport interface WatchSessionLifecycle {\n\t/** Called when the first repo-change subscriber joins */\n\tonFirstRepoSubscriber?(): void;\n\t/** Called when the last repo-change subscriber leaves */\n\tonLastRepoSubscriber?(): void;\n\t/** Called when the first working-tree subscriber joins */\n\tonFirstWorkingTreeSubscriber?(): void;\n\t/** Called when the last working-tree subscriber leaves */\n\tonLastWorkingTreeSubscriber?(): void;\n}\n\nexport interface WatchSessionOptions {\n\treadonly repoPath: string;\n\t/** Default debounce for repo changes. Default: 250ms */\n\treadonly defaultRepoDelayMs?: number;\n\t/** Default debounce for working tree changes. Default: 2500ms */\n\treadonly defaultWorkingTreeDelayMs?: number;\n\t/**\n\t * Default cap on how long repo changes can be held back by continuous activity — a flush happens at most this long\n\t * after the first pending change, however long the burst lasts. `Infinity` disables the cap. Default: 2000ms\n\t */\n\treadonly defaultRepoMaxWaitMs?: number;\n\t/** Default cap on how long working tree changes can be held back by continuous activity. `Infinity` disables the cap. Default: 10000ms */\n\treadonly defaultWorkingTreeMaxWaitMs?: number;\n\treadonly lifecycle?: WatchSessionLifecycle;\n\t/** Initial suspended state — new sessions inherit the service-global suspend state. Default: false */\n\treadonly suspended?: boolean;\n\t/**\n\t * Called whenever a debounced repo change event is dispatched to subscribers.\n\t * Does NOT count as a subscriber — used by WatchService for\n\t * global event forwarding without affecting lifecycle callbacks.\n\t */\n\treadonly onDidFireRepoChange?: (event: WatcherRepoChangeEvent) => void;\n\t/**\n\t * Called whenever a debounced working-tree change is dispatched to subscribers. Like\n\t * {@link onDidFireRepoChange}, does NOT count as a subscriber — used by WatchService for global\n\t * event forwarding (the single point that drives the cache's status clock for working-tree edits).\n\t */\n\treadonly onDidFireWorkingTreeChange?: (repoPath: string) => void;\n}\n\nconst defaultRepoDelay = 250;\nconst defaultWorkingTreeDelay = 2500;\nconst defaultRepoMaxWait = 2000;\nconst defaultWorkingTreeMaxWait = 10000;\n\n/** Largest delay `setTimeout` honors (a signed 32-bit int); anything longer fires immediately */\nconst maxTimerDelay = 0x7fffffff;\n\ninterface DebounceTiming {\n\treadonly delayMs: number;\n\treadonly maxWaitMs: number;\n}\n\n/**\n * Trailing-deadline debounce with a max-wait, shared by the repo and working-tree pipelines.\n *\n * Recording an event costs a `performance.now()` and, only when no timer is armed, one `setTimeout` — never a\n * `clearTimeout`/`setTimeout` pair per event, which is what made a busy repo allocate (and starve) on every raw\n * watcher event. The armed timer is a deadline, not a countdown: when it fires it recomputes the due time from the\n * latest timestamps and either flushes or re-arms for the remainder. The due time is\n * `min(lastEventAt + delay, firstPendingAt + maxWait)`, so continuous activity is flushed at the max-wait instead of\n * never. Monotonic time, so a wall-clock change can't push a deadline out.\n */\nclass DebounceClock {\n\tprivate active = false;\n\tprivate paused = false;\n\tprivate firstAt = 0;\n\tprivate lastAt = 0;\n\tprivate timer: ReturnType<typeof setTimeout> | undefined;\n\tprivate timerDueAt = 0;\n\t/** Absolute flush time set by {@link resume}; dropped by the next event, which returns to normal debounce */\n\tprivate resumeDueAt: number | undefined;\n\n\tconstructor(\n\t\tprivate readonly getDelayMs: () => number,\n\t\tprivate readonly getMaxWaitMs: () => number,\n\t\tprivate readonly onDue: () => void,\n\t) {}\n\n\t/** Records an event; arms the timer only if none is armed. Cheap enough to call per raw event. */\n\tnote(): void {\n\t\tconst now = performance.now();\n\t\tif (!this.active) {\n\t\t\tthis.active = true;\n\t\t\tthis.firstAt = now;\n\t\t}\n\t\tthis.lastAt = now;\n\n\t\tif (this.paused) return;\n\n\t\tif (this.resumeDueAt != null) {\n\t\t\t// The first event after a resume goes back to normal debounce — which may be due sooner than the resume deadline\n\t\t\tthis.resumeDueAt = undefined;\n\t\t\tthis.rearmIfEarlier();\n\t\t}\n\n\t\tif (this.timer == null) {\n\t\t\tthis.arm(this.getDueAt(), now);\n\t\t}\n\t}\n\n\t/** Moves the armed timer earlier when the due time shrank (a subscriber joined with a shorter delay or max-wait) */\n\trearmIfEarlier(): void {\n\t\tif (!this.active || this.paused || this.timer == null) return;\n\n\t\tconst dueAt = this.getDueAt();\n\t\tif (dueAt >= this.timerDueAt) return;\n\n\t\tclearTimeout(this.timer);\n\t\tthis.arm(dueAt, performance.now());\n\t}\n\n\t/** Cancels the timer; events keep accumulating but nothing is due until {@link resume} */\n\tsuspend(): void {\n\t\tthis.paused = true;\n\t\tthis.clearTimer();\n\t}\n\n\t/**\n\t * Restarts the max-wait window from now and schedules the flush for `delayMs` from now (the effective delay when\n\t * omitted). Does nothing if no events are pending.\n\t */\n\tresume(delayMs?: number): void {\n\t\tthis.paused = false;\n\t\tif (!this.active) return;\n\n\t\tconst now = performance.now();\n\t\tthis.firstAt = now;\n\t\tthis.resumeDueAt = now + (delayMs ?? this.getDelayMs());\n\t\tthis.clearTimer();\n\t\tthis.arm(this.resumeDueAt, now);\n\t}\n\n\t/** Drops the pending window and the timer */\n\tcancel(): void {\n\t\tthis.active = false;\n\t\tthis.resumeDueAt = undefined;\n\t\tthis.clearTimer();\n\t}\n\n\tprivate getDueAt(): number {\n\t\treturn this.resumeDueAt ?? Math.min(this.lastAt + this.getDelayMs(), this.firstAt + this.getMaxWaitMs());\n\t}\n\n\tprivate arm(dueAt: number, now: number): void {\n\t\tthis.timerDueAt = dueAt;\n\t\tthis.timer = setTimeout(() => this.onTimer(), Math.min(Math.max(0, dueAt - now), maxTimerDelay));\n\t}\n\n\tprivate onTimer(): void {\n\t\tthis.timer = undefined;\n\t\tif (!this.active || this.paused) return;\n\n\t\tconst now = performance.now();\n\t\tconst dueAt = this.getDueAt();\n\t\tif (dueAt > now) {\n\t\t\tthis.arm(dueAt, now);\n\t\t\treturn;\n\t\t}\n\n\t\t// Reset before firing so an event raised by a subscriber starts a fresh window\n\t\tthis.cancel();\n\t\tthis.onDue();\n\t}\n\n\tprivate clearTimer(): void {\n\t\tif (this.timer != null) {\n\t\t\tclearTimeout(this.timer);\n\t\t\tthis.timer = undefined;\n\t\t}\n\t}\n}\n\n/**\n * Cap on retained working-tree paths per flush. The set otherwise grows without bound — while the window\n * is unfocused the session is suspended, so a branch switch or build accumulates every touched path until\n * refocus. Set low deliberately: ordinary edits are a handful of paths, anything past this was a\n * refresh-everything event anyway, and dropping past it only costs the matching consumers a\n * conservative refresh.\n */\nconst maxWorkingTreePaths = 1000;\n\n/**\n * Cap on retained ref names per flush, for the same reason (a fetch of many refs while suspended). Past it, a ref kind\n * is recorded unnamed — any ref of that kind may have moved — which only makes `mayHaveChangedRef` more conservative.\n */\nconst maxPendingRefNames = 1000;\n\n/**\n * Per-repository session that owns the debounce/coalesce/suspend\n * pipeline for both repo (.git) and working-tree changes.\n *\n * Multiple subscribers share the same pipeline — the shortest\n * requested debounce delay wins, and so does the shortest max-wait.\n */\nexport class RepositoryWatchSession implements UnifiedDisposable {\n\treadonly repoPath: string;\n\n\tprivate readonly repoEmitter = new Emitter<WatcherRepoChangeEvent>();\n\tprivate readonly repoSubscribers = new Map<number, DebounceTiming>(); // subscriberId → timing\n\tprivate repoNextId = 0;\n\t/** Each repo subscriber's `relevantRefs`, for {@link getRelevantRefs} */\n\tprivate readonly relevantRefsSources = new Map<number, () => Iterable<string>>(); // subscriberId → relevantRefs\n\tprivate readonly pendingRepoChanges = new Set<RepositoryChange>();\n\t/** Full ref names, ref directories and pseudo-refs whose files changed, flushed with {@link pendingRepoChanges} */\n\tprivate readonly pendingRepoRefs = new Set<string>();\n\t/** Ref kinds that changed without a name, flushed with {@link pendingRepoChanges} */\n\tprivate readonly pendingRepoUnnamedRefKinds = new Set<RefChangeKind>();\n\t/** Kinds among {@link pendingRepoChanges} injected or pushed unfenced, which an announcement never replaces */\n\tprivate readonly pendingRepoUnfencedChanges = new Set<RepositoryChange>();\n\t/**\n\t * Kinds among {@link pendingRepoChanges} that only core's announced writes reported, with when each was announced\n\t * (see {@link noteSelfWrite})\n\t */\n\tprivate readonly pendingRepoAnnouncedAt = new Map<RepositoryChange, number>();\n\tprivate readonly repoClock: DebounceClock;\n\tprivate effectiveRepoMs: number;\n\tprivate effectiveRepoMaxWaitMs: number;\n\tprivate readonly defaultRepoMs: number;\n\tprivate readonly defaultRepoMaxWaitMs: number;\n\n\tprivate readonly workingTreeEmitter = new Emitter<WorkingTreeChangeEvent>();\n\tprivate readonly workingTreeSubscribers = new Map<number, DebounceTiming>();\n\tprivate workingTreeNextId = 0;\n\tprivate pendingWorkingTreePaths = new Set<string>();\n\tprivate pendingWorkingTreeIncomplete = false;\n\tprivate readonly workingTreeClock: DebounceClock;\n\tprivate effectiveWorkingTreeMs: number;\n\tprivate effectiveWorkingTreeMaxWaitMs: number;\n\tprivate readonly defaultWorkingTreeMs: number;\n\tprivate readonly defaultWorkingTreeMaxWaitMs: number;\n\n\tprivate _etagWorkingTree = 0;\n\tprivate _suspended: boolean;\n\tprivate _disposed = false;\n\n\tprivate readonly lifecycle?: WatchSessionLifecycle;\n\tprivate readonly onDidFireRepoChangeCallback?: (event: WatcherRepoChangeEvent) => void;\n\tprivate readonly onDidFireWorkingTreeChangeCallback?: (repoPath: string) => void;\n\n\tconstructor(options: WatchSessionOptions) {\n\t\tthis.repoPath = options.repoPath;\n\t\tthis.defaultRepoMs = options.defaultRepoDelayMs ?? defaultRepoDelay;\n\t\tthis.defaultWorkingTreeMs = options.defaultWorkingTreeDelayMs ?? defaultWorkingTreeDelay;\n\t\tthis.defaultRepoMaxWaitMs = options.defaultRepoMaxWaitMs ?? defaultRepoMaxWait;\n\t\tthis.defaultWorkingTreeMaxWaitMs = options.defaultWorkingTreeMaxWaitMs ?? defaultWorkingTreeMaxWait;\n\t\tthis.effectiveRepoMs = this.defaultRepoMs;\n\t\tthis.effectiveRepoMaxWaitMs = this.defaultRepoMaxWaitMs;\n\t\tthis.effectiveWorkingTreeMs = this.defaultWorkingTreeMs;\n\t\tthis.effectiveWorkingTreeMaxWaitMs = this.defaultWorkingTreeMaxWaitMs;\n\t\tthis.repoClock = new DebounceClock(\n\t\t\t() => this.effectiveRepoMs,\n\t\t\t() => this.effectiveRepoMaxWaitMs,\n\t\t\t() => this.flushRepo(),\n\t\t);\n\t\tthis.workingTreeClock = new DebounceClock(\n\t\t\t() => this.effectiveWorkingTreeMs,\n\t\t\t() => this.effectiveWorkingTreeMaxWaitMs,\n\t\t\t() => this.flushWorkingTree(),\n\t\t);\n\t\tthis.lifecycle = options.lifecycle;\n\t\tthis.onDidFireRepoChangeCallback = options.onDidFireRepoChange;\n\t\tthis.onDidFireWorkingTreeChangeCallback = options.onDidFireWorkingTreeChange;\n\t\tthis._suspended = options.suspended ?? false;\n\t\tif (this._suspended) {\n\t\t\tthis.repoClock.suspend();\n\t\t\tthis.workingTreeClock.suspend();\n\t\t}\n\t}\n\n\t[Symbol.dispose](): void {\n\t\tthis.dispose();\n\t}\n\n\tdispose(): void {\n\t\tif (this._disposed) return;\n\n\t\tthis._disposed = true;\n\n\t\tthis.repoClock.cancel();\n\t\tthis.workingTreeClock.cancel();\n\n\t\tthis.clearPendingRepoChanges();\n\t\tthis.pendingWorkingTreePaths.clear();\n\t\tthis.pendingWorkingTreeIncomplete = false;\n\t\tthis.repoSubscribers.clear();\n\t\tthis.relevantRefsSources.clear();\n\t\tthis.workingTreeSubscribers.clear();\n\n\t\tthis.repoEmitter.dispose();\n\t\tthis.workingTreeEmitter.dispose();\n\t}\n\n\tget suspended(): boolean {\n\t\treturn this._suspended;\n\t}\n\n\tget hasPendingChanges(): boolean {\n\t\treturn this.pendingRepoChanges.size > 0 || this.hasPendingWorkingTreeChanges;\n\t}\n\n\t/**\n\t * An incomplete batch with no surviving paths still has to fire: the caller dropped paths we never saw, so\n\t * gating the pipeline on the retained set alone would swallow the change entirely.\n\t */\n\tprivate get hasPendingWorkingTreeChanges(): boolean {\n\t\treturn this.pendingWorkingTreePaths.size > 0 || this.pendingWorkingTreeIncomplete;\n\t}\n\n\tget repoSubscriberCount(): number {\n\t\treturn this.repoSubscribers.size;\n\t}\n\n\tget workingTreeSubscriberCount(): number {\n\t\treturn this.workingTreeSubscribers.size;\n\t}\n\n\t/**\n\t * Monotonic counter incremented immediately when working tree changes\n\t * are pushed (before debounce). Used by `GitCommit.hasFullDetails()`\n\t * to detect staleness of uncommitted commit data.\n\t */\n\tget etagWorkingTree(): number {\n\t\treturn this._etagWorkingTree;\n\t}\n\n\t/**\n\t * Subscribe to .git directory changes with configurable debounce.\n\t * First subscribe fires `onFirstRepoSubscriber`, last dispose\n\t * fires `onLastRepoSubscriber`.\n\t *\n\t * When multiple subscribers exist, the shortest debounce wins —\n\t * all subscribers fire on the same (fastest) cadence. `maxWaitMs` bounds how long continuous\n\t * activity can defer a flush (the shortest wins too); `Infinity` means no cap.\n\t *\n\t * `relevantRefs` narrows THIS subscription's events to the refs it cares about, given as full ref names\n\t * (`refs/heads/main`, `refs/remotes/origin/main`, `refs/tags/v1`, `refs/stash`). It is read at delivery, so it can\n\t * return the subscriber's current list. A `heads`/`remotes`/`tags`/`stash` change is delivered only when a listed\n\t * ref of that kind may have moved, when the change isn't attributable to a name (e.g. a `packed-refs` rewrite),\n\t * or, for `heads`, when a pseudo-ref (`HEAD`, `ORIG_HEAD`) moved — so `stash` and `tags` changes are dropped unless\n\t * a ref of that kind is listed. Other change kinds always pass, and an event left with no changes isn't delivered.\n\t * The filter never affects other subscribers, the debounce, or the global `onDidFireRepoChange` forwarding.\n\t */\n\tsubscribe(opts?: {\n\t\tdelayMs?: number;\n\t\tmaxWaitMs?: number;\n\t\trelevantRefs?: () => Iterable<string>;\n\t}): RepositorySubscription {\n\t\tif (this._disposed) {\n\t\t\treturn Object.assign(\n\t\t\t\tcreateDisposable(() => {}),\n\t\t\t\t{\n\t\t\t\t\tonDidChange: this.repoEmitter.event,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\n\t\tconst id = this.repoNextId++;\n\t\tconst wasEmpty = this.repoSubscribers.size === 0;\n\n\t\tthis.repoSubscribers.set(id, {\n\t\t\tdelayMs: opts?.delayMs ?? this.defaultRepoMs,\n\t\t\tmaxWaitMs: opts?.maxWaitMs ?? this.defaultRepoMaxWaitMs,\n\t\t});\n\t\tthis.recalcRepoTiming();\n\n\t\tconst relevantRefs = opts?.relevantRefs;\n\t\tif (relevantRefs != null) {\n\t\t\tthis.relevantRefsSources.set(id, relevantRefs);\n\t\t}\n\n\t\tif (wasEmpty) {\n\t\t\tthis.lifecycle?.onFirstRepoSubscriber?.();\n\t\t}\n\n\t\t// If the effective delay or max-wait got shorter while a flush is pending, move it sooner\n\t\tthis.repoClock.rearmIfEarlier();\n\n\t\tlet subDisposed = false;\n\t\treturn Object.assign(\n\t\t\tcreateDisposable(() => {\n\t\t\t\tif (subDisposed) return;\n\n\t\t\t\tsubDisposed = true;\n\n\t\t\t\tthis.repoSubscribers.delete(id);\n\t\t\t\tthis.relevantRefsSources.delete(id);\n\t\t\t\tthis.recalcRepoTiming();\n\n\t\t\t\tif (this.repoSubscribers.size === 0) {\n\t\t\t\t\tthis.repoClock.cancel();\n\t\t\t\t\tthis.clearPendingRepoChanges();\n\t\t\t\t\tthis.lifecycle?.onLastRepoSubscriber?.();\n\t\t\t\t}\n\t\t\t}),\n\t\t\t{\n\t\t\t\tonDidChange:\n\t\t\t\t\trelevantRefs != null\n\t\t\t\t\t\t? createRelevantRefsEvent(this.repoEmitter.event, relevantRefs)\n\t\t\t\t\t\t: this.repoEmitter.event,\n\t\t\t},\n\t\t);\n\t}\n\n\t/** Every repo subscriber's current `relevantRefs` (see {@link subscribe}), unioned */\n\tgetRelevantRefs(): Set<string> {\n\t\tconst refs = new Set<string>();\n\t\tfor (const relevantRefs of this.relevantRefsSources.values()) {\n\t\t\tfor (const ref of relevantRefs()) {\n\t\t\t\trefs.add(ref);\n\t\t\t}\n\t\t}\n\t\treturn refs;\n\t}\n\n\t/** Subscribe to working tree changes; `delayMs` and `maxWaitMs` behave as in {@link subscribe}. */\n\tsubscribeToWorkingTree(opts?: { delayMs?: number; maxWaitMs?: number }): WorkingTreeSubscription {\n\t\tif (this._disposed) {\n\t\t\treturn Object.assign(\n\t\t\t\tcreateDisposable(() => {}),\n\t\t\t\t{\n\t\t\t\t\tonDidChangeWorkingTree: this.workingTreeEmitter.event,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\n\t\tconst id = this.workingTreeNextId++;\n\t\tconst wasEmpty = this.workingTreeSubscribers.size === 0;\n\n\t\tthis.workingTreeSubscribers.set(id, {\n\t\t\tdelayMs: opts?.delayMs ?? this.defaultWorkingTreeMs,\n\t\t\tmaxWaitMs: opts?.maxWaitMs ?? this.defaultWorkingTreeMaxWaitMs,\n\t\t});\n\t\tthis.recalcWorkingTreeTiming();\n\n\t\tif (wasEmpty) {\n\t\t\tthis.lifecycle?.onFirstWorkingTreeSubscriber?.();\n\t\t}\n\n\t\t// If the effective delay or max-wait got shorter while a flush is pending, move it sooner\n\t\tthis.workingTreeClock.rearmIfEarlier();\n\n\t\tlet subDisposed = false;\n\t\treturn Object.assign(\n\t\t\tcreateDisposable(() => {\n\t\t\t\tif (subDisposed) return;\n\n\t\t\t\tsubDisposed = true;\n\n\t\t\t\tthis.workingTreeSubscribers.delete(id);\n\t\t\t\tthis.recalcWorkingTreeTiming();\n\n\t\t\t\tif (this.workingTreeSubscribers.size === 0) {\n\t\t\t\t\tthis.workingTreeClock.cancel();\n\t\t\t\t\tthis.pendingWorkingTreePaths.clear();\n\t\t\t\t\t// Dropped with the paths it describes — a stale flag would make the next subscriber's\n\t\t\t\t\t// first ordinary flush claim a partial set and force needless full refreshes.\n\t\t\t\t\tthis.pendingWorkingTreeIncomplete = false;\n\t\t\t\t\tthis.lifecycle?.onLastWorkingTreeSubscriber?.();\n\t\t\t\t}\n\t\t\t}),\n\t\t\t{ onDidChangeWorkingTree: this.workingTreeEmitter.event },\n\t\t);\n\t}\n\n\tsuspend(): void {\n\t\tthis._suspended = true;\n\t\t// Cancel active timers — events continue to coalesce but don't fire\n\t\tthis.repoClock.suspend();\n\t\tthis.workingTreeClock.suspend();\n\t}\n\n\tresume(delayMs?: number): void {\n\t\tif (!this._suspended) return;\n\n\t\tthis._suspended = false;\n\n\t\t// Flush accumulated changes (a clock with nothing pending stays idle); the max-wait window restarts from now\n\t\tthis.repoClock.resume(delayMs);\n\t\tthis.workingTreeClock.resume(delayMs);\n\t}\n\n\t/**\n\t * Inject changes (e.g., after a git operation the caller performed). Goes through debounce + suspend. Ref kinds\n\t * among them are unnamed — any ref of that kind may have moved.\n\t */\n\tfireChange(...changes: RepositoryChange[]): void {\n\t\tif (this._disposed || changes.length === 0) return;\n\n\t\tthis.coalesceRepo(changes, undefined, 'unfenced');\n\t}\n\n\t/** Immediate fire, bypassing debounce + suspend (for Closed/Opened). Ref kinds among them are unnamed. */\n\tfireChangeImmediate(...changes: RepositoryChange[]): void {\n\t\tif (this._disposed || changes.length === 0) return;\n\n\t\tconst event = new WatcherRepoChangeEvent(this.repoPath, changes);\n\t\t// Same contract as `flushRepo`: notify the global forwarder (which advances the cache's status clock) BEFORE\n\t\t// subscribers. Skipping it here would route changes to subscribers that no one advanced the clock for —\n\t\t// subscribers see them as session-routed and (correctly) don't re-advance, so the cache would go stale.\n\t\tthis.onDidFireRepoChangeCallback?.(event);\n\t\tthis.repoEmitter.fire(event);\n\t}\n\n\t/**\n\t * Push interpreted repo changes into the pipeline. `ref` is the full ref name (or ref directory, or pseudo-ref) whose\n\t * file changed; a ref kind in `changes` is attributed to it when the kinds match, and is otherwise unnamed.\n\t * `false` preserves cache invalidations for a config edit without claiming any ref moved.\n\t */\n\tpushRepoChanges(changes: RepositoryChange[], ref?: string | false): void {\n\t\tif (this._disposed) return;\n\n\t\tthis.coalesceRepo(changes, ref, 'raw');\n\t}\n\n\t/**\n\t * Push watcher-observed repo changes that no write of core's can account for — an overflow's rescan, or an ignore\n\t * rule changed in the working tree — so {@link noteSelfWrite} never replaces them or takes them for its echo. Ref\n\t * kinds are unnamed.\n\t */\n\tpushUnfencedRepoChanges(changes: RepositoryChange[]): void {\n\t\tif (this._disposed) return;\n\n\t\tthis.coalesceRepo(changes, undefined, 'unfenced');\n\t}\n\n\t/**\n\t * Core announced one of its own writes for this repository: its caches were evicted and its host notified, both\n\t * after the write finished. The announcement becomes this session's one event for the write:\n\t *\n\t * - raw changes of an announced kind still pending are replaced by it — they happened before the announcement,\n\t * which already covers them, whether they came from the write or from an outside change;\n\t * - the announced kinds are pushed through the debounce and suspend like {@link fireChange}, ref kinds unnamed,\n\t * and the event carries them in `announced`;\n\t * - raw changes of an announced kind that arrive later are never dropped. One arriving within the repo debounce\n\t * delay of the announcement, before its event fires, is taken for the write's own late echo and keeps the kind\n\t * announced; any other report of the kind before the event fires clears the mark. Once it fires, nothing is\n\t * special.\n\t *\n\t * The trade-off: an echo the watcher delivers after the announced event fired causes one extra, unannounced event\n\t * (a duplicate refresh), rather than risk losing an outside change of the same kind.\n\t *\n\t * Empty `changes`, or `'unknown'`, announces every kind a watcher can report (never `'unknown'` itself, which a\n\t * cache reads as a close); `'closed'` and `'opened'` are ignored. Only raw `.git` changes are replaced or kept\n\t * announced: {@link fireChange}, {@link fireChangeImmediate}, {@link pushUnfencedRepoChanges} and working-tree\n\t * changes always pass.\n\t */\n\tnoteSelfWrite(changes: readonly RepositoryChange[]): void {\n\t\tif (this._disposed) return;\n\n\t\tconst all = changes.length === 0 || changes.includes('unknown');\n\t\tconst announced = all ? allWatchedRepositoryChanges : changes.filter(c => isAnnounceable(c));\n\n\t\tfor (const change of this.pendingRepoChanges) {\n\t\t\tif (this.pendingRepoUnfencedChanges.has(change) || !isAnnounceable(change)) continue;\n\t\t\tif (!all && !changes.includes(change)) continue;\n\n\t\t\tthis.pendingRepoChanges.delete(change);\n\t\t\tthis.pendingRepoAnnouncedAt.delete(change);\n\t\t\tif (!isRefChangeKind(change)) continue;\n\n\t\t\t// Announcements carry no ref names, so the replaced kind takes its named refs with it\n\t\t\tthis.pendingRepoUnnamedRefKinds.delete(change);\n\t\t\tfor (const ref of this.pendingRepoRefs) {\n\t\t\t\tif (getRefChangeKind(ref) === change) {\n\t\t\t\t\tthis.pendingRepoRefs.delete(ref);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (announced.length !== 0) {\n\t\t\tthis.coalesceRepo(announced, undefined, 'announced');\n\t\t} else if (this.pendingRepoChanges.size === 0) {\n\t\t\t// Nothing left to flush: idle the clock rather than let it fire an empty window\n\t\t\tthis.repoClock.cancel();\n\t\t}\n\t}\n\n\t/** Push working tree file changes into the pipeline */\n\t/** `incomplete` marks the batch as partial when the caller already dropped paths of its own. */\n\tpushWorkingTreeChanges(paths: Iterable<string>, incomplete?: boolean): void {\n\t\tif (this._disposed) return;\n\n\t\tthis._etagWorkingTree++;\n\t\tif (incomplete) {\n\t\t\tthis.pendingWorkingTreeIncomplete = true;\n\t\t}\n\t\tthis.coalesceWorkingTrees(paths);\n\t}\n\n\tprivate static minDelay(subscribers: Map<number, DebounceTiming>, fallback: number): number {\n\t\tlet min = fallback;\n\t\tfor (const { delayMs } of subscribers.values()) {\n\t\t\tif (delayMs < min) {\n\t\t\t\tmin = delayMs;\n\t\t\t}\n\t\t}\n\t\treturn min;\n\t}\n\n\t/** Unlike the delay, the default isn't a ceiling — a subscriber can opt out of the cap with `Infinity` */\n\tprivate static minMaxWait(subscribers: Map<number, DebounceTiming>, fallback: number): number {\n\t\tif (subscribers.size === 0) return fallback;\n\n\t\tlet min = Infinity;\n\t\tfor (const { maxWaitMs } of subscribers.values()) {\n\t\t\tif (maxWaitMs < min) {\n\t\t\t\tmin = maxWaitMs;\n\t\t\t}\n\t\t}\n\t\treturn min;\n\t}\n\n\tprivate recalcRepoTiming(): void {\n\t\tthis.effectiveRepoMs = RepositoryWatchSession.minDelay(this.repoSubscribers, this.defaultRepoMs);\n\t\tthis.effectiveRepoMaxWaitMs = RepositoryWatchSession.minMaxWait(\n\t\t\tthis.repoSubscribers,\n\t\t\tthis.defaultRepoMaxWaitMs,\n\t\t);\n\t}\n\n\tprivate recalcWorkingTreeTiming(): void {\n\t\tthis.effectiveWorkingTreeMs = RepositoryWatchSession.minDelay(\n\t\t\tthis.workingTreeSubscribers,\n\t\t\tthis.defaultWorkingTreeMs,\n\t\t);\n\t\tthis.effectiveWorkingTreeMaxWaitMs = RepositoryWatchSession.minMaxWait(\n\t\t\tthis.workingTreeSubscribers,\n\t\t\tthis.defaultWorkingTreeMaxWaitMs,\n\t\t);\n\t}\n\n\tprivate flushRepo(): void {\n\t\tif (this.pendingRepoChanges.size === 0) return;\n\n\t\t// The event is built only here — raw events just add to the pending sets (which the event copies)\n\t\tconst event = new WatcherRepoChangeEvent(\n\t\t\tthis.repoPath,\n\t\t\t[...this.pendingRepoChanges],\n\t\t\t{ names: this.pendingRepoRefs, unnamed: this.pendingRepoUnnamedRefKinds },\n\t\t\tthis.pendingRepoAnnouncedAt.keys(),\n\t\t);\n\t\tthis.clearPendingRepoChanges();\n\t\t// Advance any cache clock BEFORE notifying subscribers (same rationale as `flushWorkingTree`): a subscriber\n\t\t// that synchronously reads status must see the post-change generation, not a joinable pre-change run.\n\t\tthis.onDidFireRepoChangeCallback?.(event);\n\t\tthis.repoEmitter.fire(event);\n\t}\n\n\t/**\n\t * `origin` is a raw `.git` event, a push no announcement replaces ({@link fireChange},\n\t * {@link pushUnfencedRepoChanges}), or core's announced write. A kind stays announced only while nothing else\n\t * reports it in the pending window, save the write's own late echo (see {@link noteSelfWrite}).\n\t */\n\tprivate coalesceRepo(\n\t\tchanges: readonly RepositoryChange[],\n\t\tref: string | false | undefined,\n\t\torigin: 'raw' | 'unfenced' | 'announced',\n\t): void {\n\t\tif (changes.length === 0) return;\n\n\t\tconst now = performance.now();\n\t\tconst refKind = typeof ref === 'string' ? getRefChangeKind(ref) : undefined;\n\t\tfor (const change of changes) {\n\t\t\tif (origin === 'announced') {\n\t\t\t\tif (!this.pendingRepoChanges.has(change)) {\n\t\t\t\t\tthis.pendingRepoAnnouncedAt.set(change, now);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tconst announcedAt = origin === 'raw' ? this.pendingRepoAnnouncedAt.get(change) : undefined;\n\t\t\t\tif (announcedAt == null || now - announcedAt > this.effectiveRepoMs) {\n\t\t\t\t\tthis.pendingRepoAnnouncedAt.delete(change);\n\t\t\t\t}\n\t\t\t\tif (origin === 'unfenced') {\n\t\t\t\t\tthis.pendingRepoUnfencedChanges.add(change);\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.pendingRepoChanges.add(change);\n\t\t\tif (ref === false || !isRefChangeKind(change)) continue;\n\n\t\t\tif (\n\t\t\t\tref != null &&\n\t\t\t\trefKind === change &&\n\t\t\t\t(this.pendingRepoRefs.size < maxPendingRefNames || this.pendingRepoRefs.has(ref))\n\t\t\t) {\n\t\t\t\tthis.pendingRepoRefs.add(ref);\n\t\t\t} else {\n\t\t\t\tthis.pendingRepoUnnamedRefKinds.add(change);\n\t\t\t}\n\t\t}\n\t\tthis.repoClock.note();\n\t}\n\n\tprivate clearPendingRepoChanges(): void {\n\t\tthis.pendingRepoChanges.clear();\n\t\tthis.pendingRepoRefs.clear();\n\t\tthis.pendingRepoUnnamedRefKinds.clear();\n\t\tthis.pendingRepoUnfencedChanges.clear();\n\t\tthis.pendingRepoAnnouncedAt.clear();\n\t}\n\n\tprivate flushWorkingTree(): void {\n\t\tif (!this.hasPendingWorkingTreeChanges) return;\n\n\t\tconst paths = this.pendingWorkingTreePaths;\n\t\tconst incomplete = this.pendingWorkingTreeIncomplete;\n\t\tthis.pendingWorkingTreePaths = new Set();\n\t\tthis.pendingWorkingTreeIncomplete = false;\n\t\t// Advance the cache's status clock (via WatchService's global forwarding) BEFORE notifying subscribers,\n\t\t// so a subscriber that synchronously reads status sees the post-change generation, not a joinable pre-change run.\n\t\tthis.onDidFireWorkingTreeChangeCallback?.(this.repoPath);\n\t\tthis.workingTreeEmitter.fire({ repoPath: this.repoPath, paths: paths, incomplete: incomplete });\n\t}\n\n\tprivate coalesceWorkingTrees(paths: Iterable<string>): void {\n\t\tfor (const p of paths) {\n\t\t\t// Stop retaining paths past the cap, but keep draining the iterable — the flag is what makes the\n\t\t\t// partial set safe to consume, and dropping out early would leave later paths for the next window.\n\t\t\tif (this.pendingWorkingTreePaths.size >= maxWorkingTreePaths && !this.pendingWorkingTreePaths.has(p)) {\n\t\t\t\tthis.pendingWorkingTreeIncomplete = true;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tthis.pendingWorkingTreePaths.add(p);\n\t\t}\n\n\t\tif (this.hasPendingWorkingTreeChanges) {\n\t\t\tthis.workingTreeClock.note();\n\t\t}\n\t}\n}\n\n/** `'closed'` and `'opened'` are lifecycle, not a write's effect, so an announcement never replaces or names them */\nfunction isAnnounceable(change: RepositoryChange): boolean {\n\treturn change !== 'closed' && change !== 'opened';\n}\n\n/** Wraps a session's repo event so each delivery is narrowed to the subscriber's relevant refs (see `subscribe`) */\nfunction createRelevantRefsEvent(\n\tevent: Event<WatcherRepoChangeEvent>,\n\trelevantRefs: () => Iterable<string>,\n): Event<WatcherRepoChangeEvent> {\n\treturn (listener, thisArgs, disposables) =>\n\t\tevent(\n\t\t\te => {\n\t\t\t\tconst narrowed = narrowToRelevantRefs(e, relevantRefs);\n\t\t\t\tif (narrowed != null) {\n\t\t\t\t\tlistener.call(thisArgs, narrowed);\n\t\t\t\t}\n\t\t\t},\n\t\t\tundefined,\n\t\t\tdisposables,\n\t\t);\n}\n\n/**\n * Narrows an event to the refs a subscriber cares about. Returns the event itself when no change kind is removed (the\n * kept names then stay a superset, which only makes `mayHaveChangedRef` more conservative), `undefined` when every kind\n * is removed, and otherwise a new event with the kept kinds, names and unnamed kinds.\n */\nfunction narrowToRelevantRefs(\n\te: WatcherRepoChangeEvent,\n\trelevantRefs: () => Iterable<string>,\n): WatcherRepoChangeEvent | undefined {\n\t// Ref kinds that pass only if a relevant ref of that kind may have moved\n\tlet unresolved: Set<RefChangeKind> | undefined;\n\tfor (const change of e.changes) {\n\t\tif (!isRefChangeKind(change) || e.unnamedRefKinds.has(change)) continue;\n\n\t\t(unresolved ??= new Set()).add(change);\n\t}\n\tif (unresolved == null) return e;\n\n\tconst pseudoRefs: string[] = [];\n\tfor (const name of e.refs) {\n\t\tif (!name.startsWith('refs/')) {\n\t\t\tpseudoRefs.push(name);\n\t\t}\n\t}\n\tif (pseudoRefs.length !== 0) {\n\t\t// A pseudo-ref (`HEAD`, `ORIG_HEAD`) moving is a `heads` change for every subscriber\n\t\tunresolved.delete('heads');\n\t\tif (unresolved.size === 0) return e;\n\t}\n\n\t// Names that equal or cover a relevant ref — collected as we go, since `relevantRefs` may be a one-shot iterable\n\tconst keptNames = new Set<string>(pseudoRefs);\n\tfor (const ref of relevantRefs()) {\n\t\tconst kind = getRefChangeKind(ref);\n\t\tif (kind == null || !e.changes.has(kind)) continue;\n\n\t\t// `HEAD` is usually a symref to the checked-out branch, so any `heads` change may have moved it\n\t\tif (ref === 'HEAD') {\n\t\t\tfor (const name of e.refs) {\n\t\t\t\tif (getRefChangeKind(name) === 'heads') {\n\t\t\t\t\tkeptNames.add(name);\n\t\t\t\t}\n\t\t\t}\n\t\t\tunresolved.delete(kind);\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst covering = findCoveringRefName(e.refs, ref);\n\t\tif (covering == null) continue;\n\n\t\tkeptNames.add(covering);\n\t\tunresolved.delete(kind);\n\t}\n\tif (unresolved.size === 0) return e;\n\n\tconst keptChanges: RepositoryChange[] = [];\n\tfor (const change of e.changes) {\n\t\tif (!isRefChangeKind(change) || !unresolved.has(change)) {\n\t\t\tkeptChanges.push(change);\n\t\t}\n\t}\n\tif (keptChanges.length === 0) return undefined;\n\n\treturn new WatcherRepoChangeEvent(\n\t\te.repoPath,\n\t\tkeptChanges,\n\t\t{ names: keptNames, unnamed: e.unnamedRefKinds },\n\t\te.announced,\n\t);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"watchSession.js","names":[],"sources":["../../../../git/src/watching/watchSession.ts"],"sourcesContent":["import type { UnifiedDisposable } from '@gitlens/utils/disposable.js';\nimport { createDisposable } from '@gitlens/utils/disposable.js';\nimport type { Event } from '@gitlens/utils/event.js';\nimport { Emitter } from '@gitlens/utils/event.js';\nimport type { RepositoryChange } from '../models/repository.js';\nimport type { RefChangeKind, WorkingTreeChangeEvent } from './changeEvent.js';\nimport { findCoveringRefName, getRefChangeKind, isRefChangeKind, WatcherRepoChangeEvent } from './changeEvent.js';\nimport { allWatchedRepositoryChanges } from './classifyChange.js';\n\nexport interface RepositorySubscription extends UnifiedDisposable {\n\treadonly onDidChange: Event<WatcherRepoChangeEvent>;\n}\n\nexport interface WorkingTreeSubscription extends UnifiedDisposable {\n\treadonly onDidChangeWorkingTree: Event<WorkingTreeChangeEvent>;\n}\n\n/**\n * Lifecycle callbacks fired by the session when subscriber counts\n * cross the 0↔1 boundary. The host (WatchService) uses these to\n * lazily create/destroy watchers.\n */\nexport interface WatchSessionLifecycle {\n\t/** Called when the first repo-change subscriber joins */\n\tonFirstRepoSubscriber?(): void;\n\t/** Called when the last repo-change subscriber leaves */\n\tonLastRepoSubscriber?(): void;\n\t/** Called when the first working-tree subscriber joins */\n\tonFirstWorkingTreeSubscriber?(): void;\n\t/** Called when the last working-tree subscriber leaves */\n\tonLastWorkingTreeSubscriber?(): void;\n}\n\nexport interface WatchSessionOptions {\n\treadonly repoPath: string;\n\t/** Default debounce for repo changes. Default: 250ms */\n\treadonly defaultRepoDelayMs?: number;\n\t/** Default debounce for working tree changes. Default: 2500ms */\n\treadonly defaultWorkingTreeDelayMs?: number;\n\t/**\n\t * Default cap on how long repo changes can be held back by continuous activity — a flush happens at most this long\n\t * after the first pending change, however long the burst lasts. `Infinity` disables the cap. Default: 2000ms\n\t */\n\treadonly defaultRepoMaxWaitMs?: number;\n\t/** Default cap on how long working tree changes can be held back by continuous activity. `Infinity` disables the cap. Default: 10000ms */\n\treadonly defaultWorkingTreeMaxWaitMs?: number;\n\treadonly lifecycle?: WatchSessionLifecycle;\n\t/** Initial suspended state — new sessions inherit the service-global suspend state. Default: false */\n\treadonly suspended?: boolean;\n\t/**\n\t * Called whenever a debounced repo change event is dispatched to subscribers.\n\t * Does NOT count as a subscriber — used by WatchService for\n\t * global event forwarding without affecting lifecycle callbacks.\n\t */\n\treadonly onDidFireRepoChange?: (event: WatcherRepoChangeEvent) => void;\n\t/**\n\t * Called whenever a debounced working-tree change is dispatched to subscribers. Like\n\t * {@link onDidFireRepoChange}, does NOT count as a subscriber — used by WatchService for global\n\t * event forwarding (the single point that drives the cache's status clock for working-tree edits).\n\t */\n\treadonly onDidFireWorkingTreeChange?: (repoPath: string) => void;\n}\n\nconst defaultRepoDelay = 250;\nconst defaultWorkingTreeDelay = 2500;\nconst defaultRepoMaxWait = 2000;\nconst defaultWorkingTreeMaxWait = 10000;\n\n/** Largest delay `setTimeout` honors (a signed 32-bit int); anything longer fires immediately */\nconst maxTimerDelay = 0x7fffffff;\n\ninterface DebounceTiming {\n\treadonly delayMs: number;\n\treadonly maxWaitMs: number;\n}\n\n/**\n * Trailing-deadline debounce with a max-wait, shared by the repo and working-tree pipelines.\n *\n * Recording an event costs a `performance.now()` and, only when no timer is armed, one `setTimeout` — never a\n * `clearTimeout`/`setTimeout` pair per event, which is what made a busy repo allocate (and starve) on every raw\n * watcher event. The armed timer is a deadline, not a countdown: when it fires it recomputes the due time from the\n * latest timestamps and either flushes or re-arms for the remainder. The due time is\n * `min(lastEventAt + delay, firstPendingAt + maxWait)`, so continuous activity is flushed at the max-wait instead of\n * never. Monotonic time, so a wall-clock change can't push a deadline out.\n */\nclass DebounceClock {\n\tprivate active = false;\n\tprivate paused = false;\n\tprivate firstAt = 0;\n\tprivate lastAt = 0;\n\tprivate timer: ReturnType<typeof setTimeout> | undefined;\n\tprivate timerDueAt = 0;\n\t/** Absolute flush time set by {@link resume}; dropped by the next event, which returns to normal debounce */\n\tprivate resumeDueAt: number | undefined;\n\n\tconstructor(\n\t\tprivate readonly getDelayMs: () => number,\n\t\tprivate readonly getMaxWaitMs: () => number,\n\t\tprivate readonly onDue: () => void,\n\t) {}\n\n\t/** Records an event; arms the timer only if none is armed. Cheap enough to call per raw event. */\n\tnote(): void {\n\t\tconst now = performance.now();\n\t\tif (!this.active) {\n\t\t\tthis.active = true;\n\t\t\tthis.firstAt = now;\n\t\t}\n\t\tthis.lastAt = now;\n\n\t\tif (this.paused) return;\n\n\t\tif (this.resumeDueAt != null) {\n\t\t\t// The first event after a resume goes back to normal debounce — which may be due sooner than the resume deadline\n\t\t\tthis.resumeDueAt = undefined;\n\t\t\tthis.rearmIfEarlier();\n\t\t}\n\n\t\tif (this.timer == null) {\n\t\t\tthis.arm(this.getDueAt(), now);\n\t\t}\n\t}\n\n\t/** Moves the armed timer earlier when the due time shrank (a subscriber joined with a shorter delay or max-wait) */\n\trearmIfEarlier(): void {\n\t\tif (!this.active || this.paused || this.timer == null) return;\n\n\t\tconst dueAt = this.getDueAt();\n\t\tif (dueAt >= this.timerDueAt) return;\n\n\t\tclearTimeout(this.timer);\n\t\tthis.arm(dueAt, performance.now());\n\t}\n\n\t/** Cancels the timer; events keep accumulating but nothing is due until {@link resume} */\n\tsuspend(): void {\n\t\tthis.paused = true;\n\t\tthis.clearTimer();\n\t}\n\n\t/**\n\t * Restarts the max-wait window from now and schedules the flush for `delayMs` from now (the effective delay when\n\t * omitted). Does nothing if no events are pending.\n\t */\n\tresume(delayMs?: number): void {\n\t\tthis.paused = false;\n\t\tif (!this.active) return;\n\n\t\tconst now = performance.now();\n\t\tthis.firstAt = now;\n\t\tthis.resumeDueAt = now + (delayMs ?? this.getDelayMs());\n\t\tthis.clearTimer();\n\t\tthis.arm(this.resumeDueAt, now);\n\t}\n\n\t/** Drops the pending window and the timer */\n\tcancel(): void {\n\t\tthis.active = false;\n\t\tthis.resumeDueAt = undefined;\n\t\tthis.clearTimer();\n\t}\n\n\tprivate getDueAt(): number {\n\t\treturn this.resumeDueAt ?? Math.min(this.lastAt + this.getDelayMs(), this.firstAt + this.getMaxWaitMs());\n\t}\n\n\tprivate arm(dueAt: number, now: number): void {\n\t\tthis.timerDueAt = dueAt;\n\t\tthis.timer = setTimeout(() => this.onTimer(), Math.min(Math.max(0, dueAt - now), maxTimerDelay));\n\t}\n\n\tprivate onTimer(): void {\n\t\tthis.timer = undefined;\n\t\tif (!this.active || this.paused) return;\n\n\t\tconst now = performance.now();\n\t\tconst dueAt = this.getDueAt();\n\t\tif (dueAt > now) {\n\t\t\tthis.arm(dueAt, now);\n\t\t\treturn;\n\t\t}\n\n\t\t// Reset before firing so an event raised by a subscriber starts a fresh window\n\t\tthis.cancel();\n\t\tthis.onDue();\n\t}\n\n\tprivate clearTimer(): void {\n\t\tif (this.timer != null) {\n\t\t\tclearTimeout(this.timer);\n\t\t\tthis.timer = undefined;\n\t\t}\n\t}\n}\n\n/**\n * Cap on retained working-tree paths per flush. The set otherwise grows without bound — while the window\n * is unfocused the session is suspended, so a branch switch or build accumulates every touched path until\n * refocus. Set low deliberately: ordinary edits are a handful of paths, anything past this was a\n * refresh-everything event anyway, and dropping past it only costs the matching consumers a\n * conservative refresh.\n */\nconst maxWorkingTreePaths = 1000;\n\n/**\n * Cap on retained ref names per flush, for the same reason (a fetch of many refs while suspended). Past it, a ref kind\n * is recorded unnamed — any ref of that kind may have moved — which only makes `mayHaveChangedRef` more conservative.\n * A pending `config` confined to more branches than this is recorded unconfined, for the same reason.\n */\nconst maxPendingRefNames = 1000;\n\n/**\n * What core's announced writes in the pending window said about one kind: when it was first announced, and the full\n * ref names it was confined to (for `config`, the branches whose sections it changed); `names` is absent when an\n * announcement left the kind open — any ref of it, or any part of the config, may have changed.\n */\ninterface PendingAnnouncement {\n\treadonly at: number;\n\tnames: Set<string> | undefined;\n}\n\n/**\n * Per-repository session that owns the debounce/coalesce/suspend\n * pipeline for both repo (.git) and working-tree changes.\n *\n * Multiple subscribers share the same pipeline — the shortest\n * requested debounce delay wins, and so does the shortest max-wait.\n */\nexport class RepositoryWatchSession implements UnifiedDisposable {\n\treadonly repoPath: string;\n\n\tprivate readonly repoEmitter = new Emitter<WatcherRepoChangeEvent>();\n\tprivate readonly repoSubscribers = new Map<number, DebounceTiming>(); // subscriberId → timing\n\tprivate repoNextId = 0;\n\t/** Each repo subscriber's `relevantRefs`, for {@link getRelevantRefs} */\n\tprivate readonly relevantRefsSources = new Map<number, () => Iterable<string>>(); // subscriberId → relevantRefs\n\tprivate readonly pendingRepoChanges = new Set<RepositoryChange>();\n\t/** Full ref names, ref directories and pseudo-refs whose files changed, flushed with {@link pendingRepoChanges} */\n\tprivate readonly pendingRepoRefs = new Set<string>();\n\t/** Ref kinds that changed without a name, flushed with {@link pendingRepoChanges} */\n\tprivate readonly pendingRepoUnnamedRefKinds = new Set<RefChangeKind>();\n\t/** Kinds among {@link pendingRepoChanges} injected or pushed unfenced, which an announcement never replaces */\n\tprivate readonly pendingRepoUnfencedChanges = new Set<RepositoryChange>();\n\t/**\n\t * Kinds among {@link pendingRepoChanges} that only core's announced writes, and their own echoes, reported (see\n\t * {@link noteSelfWrite})\n\t */\n\tprivate readonly pendingRepoAnnounced = new Map<RepositoryChange, PendingAnnouncement>();\n\t/**\n\t * For a pending `config` change, the branches (full ref names) whose sections are all that every report of it\n\t * changed; `undefined` once any report may have changed anything else\n\t */\n\tprivate pendingRepoConfigBranches: Set<string> | undefined;\n\tprivate readonly repoClock: DebounceClock;\n\tprivate effectiveRepoMs: number;\n\tprivate effectiveRepoMaxWaitMs: number;\n\tprivate readonly defaultRepoMs: number;\n\tprivate readonly defaultRepoMaxWaitMs: number;\n\n\tprivate readonly workingTreeEmitter = new Emitter<WorkingTreeChangeEvent>();\n\tprivate readonly workingTreeSubscribers = new Map<number, DebounceTiming>();\n\tprivate workingTreeNextId = 0;\n\tprivate pendingWorkingTreePaths = new Set<string>();\n\tprivate pendingWorkingTreeIncomplete = false;\n\tprivate readonly workingTreeClock: DebounceClock;\n\tprivate effectiveWorkingTreeMs: number;\n\tprivate effectiveWorkingTreeMaxWaitMs: number;\n\tprivate readonly defaultWorkingTreeMs: number;\n\tprivate readonly defaultWorkingTreeMaxWaitMs: number;\n\n\tprivate _etagWorkingTree = 0;\n\tprivate _suspended: boolean;\n\tprivate _disposed = false;\n\n\tprivate readonly lifecycle?: WatchSessionLifecycle;\n\tprivate readonly onDidFireRepoChangeCallback?: (event: WatcherRepoChangeEvent) => void;\n\tprivate readonly onDidFireWorkingTreeChangeCallback?: (repoPath: string) => void;\n\n\tconstructor(options: WatchSessionOptions) {\n\t\tthis.repoPath = options.repoPath;\n\t\tthis.defaultRepoMs = options.defaultRepoDelayMs ?? defaultRepoDelay;\n\t\tthis.defaultWorkingTreeMs = options.defaultWorkingTreeDelayMs ?? defaultWorkingTreeDelay;\n\t\tthis.defaultRepoMaxWaitMs = options.defaultRepoMaxWaitMs ?? defaultRepoMaxWait;\n\t\tthis.defaultWorkingTreeMaxWaitMs = options.defaultWorkingTreeMaxWaitMs ?? defaultWorkingTreeMaxWait;\n\t\tthis.effectiveRepoMs = this.defaultRepoMs;\n\t\tthis.effectiveRepoMaxWaitMs = this.defaultRepoMaxWaitMs;\n\t\tthis.effectiveWorkingTreeMs = this.defaultWorkingTreeMs;\n\t\tthis.effectiveWorkingTreeMaxWaitMs = this.defaultWorkingTreeMaxWaitMs;\n\t\tthis.repoClock = new DebounceClock(\n\t\t\t() => this.effectiveRepoMs,\n\t\t\t() => this.effectiveRepoMaxWaitMs,\n\t\t\t() => this.flushRepo(),\n\t\t);\n\t\tthis.workingTreeClock = new DebounceClock(\n\t\t\t() => this.effectiveWorkingTreeMs,\n\t\t\t() => this.effectiveWorkingTreeMaxWaitMs,\n\t\t\t() => this.flushWorkingTree(),\n\t\t);\n\t\tthis.lifecycle = options.lifecycle;\n\t\tthis.onDidFireRepoChangeCallback = options.onDidFireRepoChange;\n\t\tthis.onDidFireWorkingTreeChangeCallback = options.onDidFireWorkingTreeChange;\n\t\tthis._suspended = options.suspended ?? false;\n\t\tif (this._suspended) {\n\t\t\tthis.repoClock.suspend();\n\t\t\tthis.workingTreeClock.suspend();\n\t\t}\n\t}\n\n\t[Symbol.dispose](): void {\n\t\tthis.dispose();\n\t}\n\n\tdispose(): void {\n\t\tif (this._disposed) return;\n\n\t\tthis._disposed = true;\n\n\t\tthis.repoClock.cancel();\n\t\tthis.workingTreeClock.cancel();\n\n\t\tthis.clearPendingRepoChanges();\n\t\tthis.pendingWorkingTreePaths.clear();\n\t\tthis.pendingWorkingTreeIncomplete = false;\n\t\tthis.repoSubscribers.clear();\n\t\tthis.relevantRefsSources.clear();\n\t\tthis.workingTreeSubscribers.clear();\n\n\t\tthis.repoEmitter.dispose();\n\t\tthis.workingTreeEmitter.dispose();\n\t}\n\n\tget suspended(): boolean {\n\t\treturn this._suspended;\n\t}\n\n\tget hasPendingChanges(): boolean {\n\t\treturn this.pendingRepoChanges.size > 0 || this.hasPendingWorkingTreeChanges;\n\t}\n\n\t/**\n\t * An incomplete batch with no surviving paths still has to fire: the caller dropped paths we never saw, so\n\t * gating the pipeline on the retained set alone would swallow the change entirely.\n\t */\n\tprivate get hasPendingWorkingTreeChanges(): boolean {\n\t\treturn this.pendingWorkingTreePaths.size > 0 || this.pendingWorkingTreeIncomplete;\n\t}\n\n\tget repoSubscriberCount(): number {\n\t\treturn this.repoSubscribers.size;\n\t}\n\n\tget workingTreeSubscriberCount(): number {\n\t\treturn this.workingTreeSubscribers.size;\n\t}\n\n\t/**\n\t * Monotonic counter incremented immediately when working tree changes\n\t * are pushed (before debounce). Used by `GitCommit.hasFullDetails()`\n\t * to detect staleness of uncommitted commit data.\n\t */\n\tget etagWorkingTree(): number {\n\t\treturn this._etagWorkingTree;\n\t}\n\n\t/**\n\t * Subscribe to .git directory changes with configurable debounce.\n\t * First subscribe fires `onFirstRepoSubscriber`, last dispose\n\t * fires `onLastRepoSubscriber`.\n\t *\n\t * When multiple subscribers exist, the shortest debounce wins —\n\t * all subscribers fire on the same (fastest) cadence. `maxWaitMs` bounds how long continuous\n\t * activity can defer a flush (the shortest wins too); `Infinity` means no cap.\n\t *\n\t * `relevantRefs` narrows THIS subscription's events to the refs it cares about, given as full ref names\n\t * (`refs/heads/main`, `refs/remotes/origin/main`, `refs/tags/v1`, `refs/stash`). It is read at delivery, so it can\n\t * return the subscriber's current list. A `heads`/`remotes`/`tags`/`stash` change is delivered only when a listed\n\t * ref of that kind may have moved, when the change isn't attributable to a name (e.g. a `packed-refs` rewrite),\n\t * or, for `heads`, when a pseudo-ref (`HEAD`, `ORIG_HEAD`) moved — so `stash` and `tags` changes are dropped unless\n\t * a ref of that kind is listed. A `config` change confined to some branches' `branch.<name>` sections\n\t * (`configBranches`) is delivered only when `HEAD` or one of those branches is listed. Other change kinds always\n\t * pass, and an event left with no changes isn't delivered.\n\t * The filter never affects other subscribers, the debounce, or the global `onDidFireRepoChange` forwarding.\n\t */\n\tsubscribe(opts?: {\n\t\tdelayMs?: number;\n\t\tmaxWaitMs?: number;\n\t\trelevantRefs?: () => Iterable<string>;\n\t}): RepositorySubscription {\n\t\tif (this._disposed) {\n\t\t\treturn Object.assign(\n\t\t\t\tcreateDisposable(() => {}),\n\t\t\t\t{\n\t\t\t\t\tonDidChange: this.repoEmitter.event,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\n\t\tconst id = this.repoNextId++;\n\t\tconst wasEmpty = this.repoSubscribers.size === 0;\n\n\t\tthis.repoSubscribers.set(id, {\n\t\t\tdelayMs: opts?.delayMs ?? this.defaultRepoMs,\n\t\t\tmaxWaitMs: opts?.maxWaitMs ?? this.defaultRepoMaxWaitMs,\n\t\t});\n\t\tthis.recalcRepoTiming();\n\n\t\tconst relevantRefs = opts?.relevantRefs;\n\t\tif (relevantRefs != null) {\n\t\t\tthis.relevantRefsSources.set(id, relevantRefs);\n\t\t}\n\n\t\tif (wasEmpty) {\n\t\t\tthis.lifecycle?.onFirstRepoSubscriber?.();\n\t\t}\n\n\t\t// If the effective delay or max-wait got shorter while a flush is pending, move it sooner\n\t\tthis.repoClock.rearmIfEarlier();\n\n\t\tlet subDisposed = false;\n\t\treturn Object.assign(\n\t\t\tcreateDisposable(() => {\n\t\t\t\tif (subDisposed) return;\n\n\t\t\t\tsubDisposed = true;\n\n\t\t\t\tthis.repoSubscribers.delete(id);\n\t\t\t\tthis.relevantRefsSources.delete(id);\n\t\t\t\tthis.recalcRepoTiming();\n\n\t\t\t\tif (this.repoSubscribers.size === 0) {\n\t\t\t\t\tthis.repoClock.cancel();\n\t\t\t\t\tthis.clearPendingRepoChanges();\n\t\t\t\t\tthis.lifecycle?.onLastRepoSubscriber?.();\n\t\t\t\t}\n\t\t\t}),\n\t\t\t{\n\t\t\t\tonDidChange:\n\t\t\t\t\trelevantRefs != null\n\t\t\t\t\t\t? createRelevantRefsEvent(this.repoEmitter.event, relevantRefs)\n\t\t\t\t\t\t: this.repoEmitter.event,\n\t\t\t},\n\t\t);\n\t}\n\n\t/** Every repo subscriber's current `relevantRefs` (see {@link subscribe}), unioned */\n\tgetRelevantRefs(): Set<string> {\n\t\tconst refs = new Set<string>();\n\t\tfor (const relevantRefs of this.relevantRefsSources.values()) {\n\t\t\tfor (const ref of relevantRefs()) {\n\t\t\t\trefs.add(ref);\n\t\t\t}\n\t\t}\n\t\treturn refs;\n\t}\n\n\t/** Subscribe to working tree changes; `delayMs` and `maxWaitMs` behave as in {@link subscribe}. */\n\tsubscribeToWorkingTree(opts?: { delayMs?: number; maxWaitMs?: number }): WorkingTreeSubscription {\n\t\tif (this._disposed) {\n\t\t\treturn Object.assign(\n\t\t\t\tcreateDisposable(() => {}),\n\t\t\t\t{\n\t\t\t\t\tonDidChangeWorkingTree: this.workingTreeEmitter.event,\n\t\t\t\t},\n\t\t\t);\n\t\t}\n\n\t\tconst id = this.workingTreeNextId++;\n\t\tconst wasEmpty = this.workingTreeSubscribers.size === 0;\n\n\t\tthis.workingTreeSubscribers.set(id, {\n\t\t\tdelayMs: opts?.delayMs ?? this.defaultWorkingTreeMs,\n\t\t\tmaxWaitMs: opts?.maxWaitMs ?? this.defaultWorkingTreeMaxWaitMs,\n\t\t});\n\t\tthis.recalcWorkingTreeTiming();\n\n\t\tif (wasEmpty) {\n\t\t\tthis.lifecycle?.onFirstWorkingTreeSubscriber?.();\n\t\t}\n\n\t\t// If the effective delay or max-wait got shorter while a flush is pending, move it sooner\n\t\tthis.workingTreeClock.rearmIfEarlier();\n\n\t\tlet subDisposed = false;\n\t\treturn Object.assign(\n\t\t\tcreateDisposable(() => {\n\t\t\t\tif (subDisposed) return;\n\n\t\t\t\tsubDisposed = true;\n\n\t\t\t\tthis.workingTreeSubscribers.delete(id);\n\t\t\t\tthis.recalcWorkingTreeTiming();\n\n\t\t\t\tif (this.workingTreeSubscribers.size === 0) {\n\t\t\t\t\tthis.workingTreeClock.cancel();\n\t\t\t\t\tthis.pendingWorkingTreePaths.clear();\n\t\t\t\t\t// Dropped with the paths it describes — a stale flag would make the next subscriber's\n\t\t\t\t\t// first ordinary flush claim a partial set and force needless full refreshes.\n\t\t\t\t\tthis.pendingWorkingTreeIncomplete = false;\n\t\t\t\t\tthis.lifecycle?.onLastWorkingTreeSubscriber?.();\n\t\t\t\t}\n\t\t\t}),\n\t\t\t{ onDidChangeWorkingTree: this.workingTreeEmitter.event },\n\t\t);\n\t}\n\n\tsuspend(): void {\n\t\tthis._suspended = true;\n\t\t// Cancel active timers — events continue to coalesce but don't fire\n\t\tthis.repoClock.suspend();\n\t\tthis.workingTreeClock.suspend();\n\t}\n\n\tresume(delayMs?: number): void {\n\t\tif (!this._suspended) return;\n\n\t\tthis._suspended = false;\n\n\t\t// Flush accumulated changes (a clock with nothing pending stays idle); the max-wait window restarts from now\n\t\tthis.repoClock.resume(delayMs);\n\t\tthis.workingTreeClock.resume(delayMs);\n\t}\n\n\t/**\n\t * Inject changes (e.g., after a git operation the caller performed). Goes through debounce + suspend. Ref kinds\n\t * among them are unnamed — any ref of that kind may have moved.\n\t */\n\tfireChange(...changes: RepositoryChange[]): void {\n\t\tif (this._disposed || changes.length === 0) return;\n\n\t\tthis.coalesceRepo(changes, undefined, 'unfenced');\n\t}\n\n\t/** Immediate fire, bypassing debounce + suspend (for Closed/Opened). Ref kinds among them are unnamed. */\n\tfireChangeImmediate(...changes: RepositoryChange[]): void {\n\t\tif (this._disposed || changes.length === 0) return;\n\n\t\tconst event = new WatcherRepoChangeEvent(this.repoPath, changes);\n\t\t// Same contract as `flushRepo`: notify the global forwarder (which advances the cache's status clock) BEFORE\n\t\t// subscribers. Skipping it here would route changes to subscribers that no one advanced the clock for —\n\t\t// subscribers see them as session-routed and (correctly) don't re-advance, so the cache would go stale.\n\t\tthis.onDidFireRepoChangeCallback?.(event);\n\t\tthis.repoEmitter.fire(event);\n\t}\n\n\t/**\n\t * Push interpreted repo changes into the pipeline. `ref` is the full ref name (or ref directory, or pseudo-ref) whose\n\t * file changed; a ref kind in `changes` is attributed to it when the kinds match, and is otherwise unnamed.\n\t * `false` preserves cache invalidations for a config edit without claiming any ref moved. `configBranches`, for a\n\t * `config` among `changes`, are the local branches (full ref names) whose `branch.<name>` sections are all that\n\t * changed in `.git/config`; without it, the config change may be anywhere.\n\t */\n\tpushRepoChanges(changes: RepositoryChange[], ref?: string | false, configBranches?: ReadonlySet<string>): void {\n\t\tif (this._disposed) return;\n\n\t\tthis.coalesceRepo(changes, ref, 'raw', configBranches);\n\t}\n\n\t/**\n\t * Push watcher-observed repo changes that no write of core's can account for — an overflow's rescan, or an ignore\n\t * rule changed in the working tree — so {@link noteSelfWrite} never replaces them or takes them for its echo. Ref\n\t * kinds are unnamed.\n\t */\n\tpushUnfencedRepoChanges(changes: RepositoryChange[]): void {\n\t\tif (this._disposed) return;\n\n\t\tthis.coalesceRepo(changes, undefined, 'unfenced');\n\t}\n\n\t/**\n\t * Core announced one of its own writes for this repository: its caches were evicted and its host notified, both\n\t * after the write finished. The announcement becomes this session's one event for the write:\n\t *\n\t * - the announced kinds are pushed through the debounce and suspend like {@link fireChange}, and the event carries\n\t * them in `announced`. A ref kind is unnamed unless `refs` names refs of that kind (the full ref names the write\n\t * moved), in which case it carries those names and no other ref of the kind counts as changed by the write.\n\t * `config` is confined to the `branch.<name>` sections of the local branches `refs` names when\n\t * `options.configConfinedToRefs` says so, and the event carries them (`configBranches`);\n\t * - a raw change of an announced kind is the write's own report of it only when it is provably within what the\n\t * write announced: a ref the write named, or a config change confined to branches the write's config was confined\n\t * to. Pending when the announcement arrives, it is replaced. Arriving later, it merges and the kind stays\n\t * announced only within the repo debounce delay of the announcement; a window can stay pending for minutes while\n\t * the session is suspended, and a later move of the same ref is as likely an outside one. Any other report of\n\t * the kind is an outside change: it stays, and the kind isn't `announced`, since the host has not handled the\n\t * outside part. A write never names `HEAD`, but a commit locks it to write its reflog, so under a write that\n\t * announced `head`, a report of the `HEAD` file within the same delay is the write's echo, its `heads` part\n\t * included;\n\t * - a kind the write announced open (unnamed, or `config` unconfined) covers any change of the kind: raw changes\n\t * of it still pending are replaced, since the host evicts everything of the kind after the write. One arriving\n\t * within the repo debounce delay of the announcement, before its event fires, is taken for the write's own late\n\t * echo; any later report of the kind before the event fires clears the mark.\n\t *\n\t * Once the event fires, nothing is special: an echo the watcher delivers after it causes one extra, unannounced\n\t * event (a duplicate refresh, narrowed like any other), rather than risk losing an outside change of the same kind.\n\t *\n\t * Empty `changes`, or `'unknown'`, announces every kind a watcher can report (never `'unknown'` itself, which a\n\t * cache reads as a close), open whatever `refs` says; `'closed'` and `'opened'` are ignored. Only raw `.git` changes\n\t * are replaced or kept announced: {@link fireChange}, {@link fireChangeImmediate}, {@link pushUnfencedRepoChanges}\n\t * and working-tree changes always pass.\n\t */\n\tnoteSelfWrite(\n\t\tchanges: readonly RepositoryChange[],\n\t\trefs?: readonly string[],\n\t\toptions?: { readonly configConfinedToRefs?: boolean },\n\t): void {\n\t\tif (this._disposed) return;\n\n\t\tconst all = changes.length === 0 || changes.includes('unknown');\n\t\tconst announced = all ? allWatchedRepositoryChanges : changes.filter(c => isAnnounceable(c));\n\t\tconst named = all ? undefined : groupNamedRefs(changes, refs);\n\t\tconst configBranches =\n\t\t\t!all && options?.configConfinedToRefs === true && changes.includes('config')\n\t\t\t\t? getBranchRefs(refs)\n\t\t\t\t: undefined;\n\n\t\tfor (const change of this.pendingRepoChanges) {\n\t\t\tif (this.pendingRepoUnfencedChanges.has(change) || !isAnnounceable(change)) continue;\n\t\t\tif (!all && !changes.includes(change)) continue;\n\n\t\t\tif (change === 'config' && configBranches != null) {\n\t\t\t\t// An earlier announcement's config stays and merges with this one; a raw one is replaced only when it\n\t\t\t\t// is provably this write's own report\n\t\t\t\tif (this.pendingRepoAnnounced.has(change)) continue;\n\t\t\t\tif (!isSubsetOf(this.pendingRepoConfigBranches, configBranches)) continue;\n\t\t\t}\n\n\t\t\tconst names = isRefChangeKind(change) ? named?.get(change) : undefined;\n\t\t\tif (names != null) {\n\t\t\t\tfor (const name of names) {\n\t\t\t\t\tthis.pendingRepoRefs.delete(name);\n\t\t\t\t}\n\t\t\t\t// What's left of the kind came from outside the write, so it stays, unannounced\n\t\t\t\tif (this.hasPendingRefOfKind(change)) continue;\n\t\t\t}\n\n\t\t\tthis.pendingRepoChanges.delete(change);\n\t\t\tthis.pendingRepoAnnounced.delete(change);\n\t\t\tif (change === 'config') {\n\t\t\t\tthis.pendingRepoConfigBranches = undefined;\n\t\t\t}\n\t\t\tif (!isRefChangeKind(change)) continue;\n\n\t\t\t// An unnamed announcement covers any ref of the kind, so the replaced kind takes its named refs with it\n\t\t\tthis.pendingRepoUnnamedRefKinds.delete(change);\n\t\t\tfor (const ref of this.pendingRepoRefs) {\n\t\t\t\tif (getRefChangeKind(ref) === change) {\n\t\t\t\t\tthis.pendingRepoRefs.delete(ref);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (announced.length !== 0) {\n\t\t\tthis.coalesceRepo(\n\t\t\t\tnamed != null ? announced.filter(c => !isRefChangeKind(c) || !named.has(c)) : announced,\n\t\t\t\tundefined,\n\t\t\t\t'announced',\n\t\t\t\tconfigBranches,\n\t\t\t);\n\t\t\tif (named != null) {\n\t\t\t\tfor (const [kind, names] of named) {\n\t\t\t\t\tfor (const name of names) {\n\t\t\t\t\t\tthis.coalesceRepo([kind], name, 'announced');\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (this.pendingRepoChanges.size === 0) {\n\t\t\t// Nothing left to flush: idle the clock rather than let it fire an empty window\n\t\t\tthis.repoClock.cancel();\n\t\t}\n\t}\n\n\t/** Push working tree file changes into the pipeline */\n\t/** `incomplete` marks the batch as partial when the caller already dropped paths of its own. */\n\tpushWorkingTreeChanges(paths: Iterable<string>, incomplete?: boolean): void {\n\t\tif (this._disposed) return;\n\n\t\tthis._etagWorkingTree++;\n\t\tif (incomplete) {\n\t\t\tthis.pendingWorkingTreeIncomplete = true;\n\t\t}\n\t\tthis.coalesceWorkingTrees(paths);\n\t}\n\n\tprivate static minDelay(subscribers: Map<number, DebounceTiming>, fallback: number): number {\n\t\tlet min = fallback;\n\t\tfor (const { delayMs } of subscribers.values()) {\n\t\t\tif (delayMs < min) {\n\t\t\t\tmin = delayMs;\n\t\t\t}\n\t\t}\n\t\treturn min;\n\t}\n\n\t/** Unlike the delay, the default isn't a ceiling — a subscriber can opt out of the cap with `Infinity` */\n\tprivate static minMaxWait(subscribers: Map<number, DebounceTiming>, fallback: number): number {\n\t\tif (subscribers.size === 0) return fallback;\n\n\t\tlet min = Infinity;\n\t\tfor (const { maxWaitMs } of subscribers.values()) {\n\t\t\tif (maxWaitMs < min) {\n\t\t\t\tmin = maxWaitMs;\n\t\t\t}\n\t\t}\n\t\treturn min;\n\t}\n\n\tprivate recalcRepoTiming(): void {\n\t\tthis.effectiveRepoMs = RepositoryWatchSession.minDelay(this.repoSubscribers, this.defaultRepoMs);\n\t\tthis.effectiveRepoMaxWaitMs = RepositoryWatchSession.minMaxWait(\n\t\t\tthis.repoSubscribers,\n\t\t\tthis.defaultRepoMaxWaitMs,\n\t\t);\n\t}\n\n\tprivate recalcWorkingTreeTiming(): void {\n\t\tthis.effectiveWorkingTreeMs = RepositoryWatchSession.minDelay(\n\t\t\tthis.workingTreeSubscribers,\n\t\t\tthis.defaultWorkingTreeMs,\n\t\t);\n\t\tthis.effectiveWorkingTreeMaxWaitMs = RepositoryWatchSession.minMaxWait(\n\t\t\tthis.workingTreeSubscribers,\n\t\t\tthis.defaultWorkingTreeMaxWaitMs,\n\t\t);\n\t}\n\n\tprivate flushRepo(): void {\n\t\tif (this.pendingRepoChanges.size === 0) return;\n\n\t\t// The event is built only here — raw events just add to the pending sets (which the event copies)\n\t\tconst event = new WatcherRepoChangeEvent(\n\t\t\tthis.repoPath,\n\t\t\t[...this.pendingRepoChanges],\n\t\t\t{\n\t\t\t\tnames: this.pendingRepoRefs,\n\t\t\t\tunnamed: this.pendingRepoUnnamedRefKinds,\n\t\t\t\tconfigBranches: this.pendingRepoConfigBranches,\n\t\t\t},\n\t\t\tthis.pendingRepoAnnounced.keys(),\n\t\t);\n\t\tthis.clearPendingRepoChanges();\n\t\t// Advance any cache clock BEFORE notifying subscribers (same rationale as `flushWorkingTree`): a subscriber\n\t\t// that synchronously reads status must see the post-change generation, not a joinable pre-change run.\n\t\tthis.onDidFireRepoChangeCallback?.(event);\n\t\tthis.repoEmitter.fire(event);\n\t}\n\n\t/**\n\t * `origin` is a raw `.git` event, a push no announcement replaces ({@link fireChange},\n\t * {@link pushUnfencedRepoChanges}), or core's announced write. A kind stays announced only while nothing else\n\t * reports it in the pending window, save the write's own report of it (see {@link noteSelfWrite}).\n\t * `configBranches` confines a `config` among `changes` to those branches' sections; without it, it may be anywhere.\n\t */\n\tprivate coalesceRepo(\n\t\tchanges: readonly RepositoryChange[],\n\t\tref: string | false | undefined,\n\t\torigin: 'raw' | 'unfenced' | 'announced',\n\t\tconfigBranches?: ReadonlySet<string>,\n\t): void {\n\t\tif (changes.length === 0) return;\n\n\t\tconst now = performance.now();\n\t\tconst refKind = typeof ref === 'string' ? getRefChangeKind(ref) : undefined;\n\t\tfor (const change of changes) {\n\t\t\tconst pending = this.pendingRepoChanges.has(change);\n\t\t\t// What this report is confined to: the ref it names, or the branches whose config sections it changed\n\t\t\tlet names: ReadonlySet<string> | undefined;\n\t\t\tif (origin !== 'unfenced') {\n\t\t\t\tif (change === 'config') {\n\t\t\t\t\tnames = configBranches;\n\t\t\t\t} else if (typeof ref === 'string' && refKind === change) {\n\t\t\t\t\tnames = new Set([ref]);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst announcement = this.pendingRepoAnnounced.get(change);\n\t\t\tif (origin === 'announced') {\n\t\t\t\tif (!pending) {\n\t\t\t\t\tthis.pendingRepoAnnounced.set(change, {\n\t\t\t\t\t\tat: now,\n\t\t\t\t\t\tnames: names != null ? new Set(names) : undefined,\n\t\t\t\t\t});\n\t\t\t\t} else if (announcement != null) {\n\t\t\t\t\t// Added in place: a write naming many refs arrives here once per name, and copying the set each\n\t\t\t\t\t// time would cost quadratic time in the number of refs it moved\n\t\t\t\t\tif (announcement.names != null && names != null) {\n\t\t\t\t\t\tfor (const name of names) {\n\t\t\t\t\t\t\tannouncement.names.add(name);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tannouncement.names = undefined;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tconst own =\n\t\t\t\t\torigin === 'raw' && announcement != null && this.isOwnReport(change, announcement, names, now);\n\t\t\t\tif (announcement != null && !own) {\n\t\t\t\t\tthis.pendingRepoAnnounced.delete(change);\n\t\t\t\t}\n\t\t\t\tif (origin === 'unfenced') {\n\t\t\t\t\tthis.pendingRepoUnfencedChanges.add(change);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (change === 'config') {\n\t\t\t\tlet branches: Set<string> | undefined;\n\t\t\t\tif (!pending) {\n\t\t\t\t\tbranches = names != null ? new Set(names) : undefined;\n\t\t\t\t} else if (this.pendingRepoConfigBranches != null && names != null) {\n\t\t\t\t\tbranches = new Set([...this.pendingRepoConfigBranches, ...names]);\n\t\t\t\t}\n\t\t\t\tthis.pendingRepoConfigBranches =\n\t\t\t\t\tbranches != null && branches.size <= maxPendingRefNames ? branches : undefined;\n\t\t\t}\n\n\t\t\tthis.pendingRepoChanges.add(change);\n\t\t\tif (ref === false || !isRefChangeKind(change)) continue;\n\n\t\t\tif (\n\t\t\t\tref != null &&\n\t\t\t\trefKind === change &&\n\t\t\t\t(this.pendingRepoRefs.size < maxPendingRefNames || this.pendingRepoRefs.has(ref))\n\t\t\t) {\n\t\t\t\tthis.pendingRepoRefs.add(ref);\n\t\t\t} else {\n\t\t\t\tthis.pendingRepoUnnamedRefKinds.add(change);\n\t\t\t}\n\t\t}\n\t\tthis.repoClock.note();\n\t}\n\n\t/**\n\t * Whether a raw report of `change`, confined to `names` (`undefined`: not confined), is the announced write's own.\n\t * It must arrive within the repo debounce delay of the announcement, as it always had to. An open announcement\n\t * asks nothing more; one confined to names also needs every name the report carries to be one the write announced,\n\t * save the `heads` part of a `HEAD` report under a write that announced `head` in time, which is a commit's echo.\n\t */\n\tprivate isOwnReport(\n\t\tchange: RepositoryChange,\n\t\tannouncement: PendingAnnouncement,\n\t\tnames: ReadonlySet<string> | undefined,\n\t\tnow: number,\n\t): boolean {\n\t\tif (now - announcement.at > this.effectiveRepoMs) return false;\n\t\tif (announcement.names == null) return true;\n\t\tif (names == null) return false;\n\t\tif (isSubsetOf(names, announcement.names)) return true;\n\n\t\tif (change !== 'heads' || names.size !== 1 || !names.has('HEAD')) return false;\n\n\t\tconst head = this.pendingRepoAnnounced.get('head');\n\t\treturn head != null && now - head.at <= this.effectiveRepoMs;\n\t}\n\n\t/** Whether a pending report of `kind` remains, unnamed or under a name */\n\tprivate hasPendingRefOfKind(kind: RepositoryChange): boolean {\n\t\tif (!isRefChangeKind(kind)) return false;\n\t\tif (this.pendingRepoUnnamedRefKinds.has(kind)) return true;\n\n\t\tfor (const ref of this.pendingRepoRefs) {\n\t\t\tif (getRefChangeKind(ref) === kind) return true;\n\t\t}\n\t\treturn false;\n\t}\n\n\tprivate clearPendingRepoChanges(): void {\n\t\tthis.pendingRepoChanges.clear();\n\t\tthis.pendingRepoRefs.clear();\n\t\tthis.pendingRepoUnnamedRefKinds.clear();\n\t\tthis.pendingRepoUnfencedChanges.clear();\n\t\tthis.pendingRepoAnnounced.clear();\n\t\tthis.pendingRepoConfigBranches = undefined;\n\t}\n\n\tprivate flushWorkingTree(): void {\n\t\tif (!this.hasPendingWorkingTreeChanges) return;\n\n\t\tconst paths = this.pendingWorkingTreePaths;\n\t\tconst incomplete = this.pendingWorkingTreeIncomplete;\n\t\tthis.pendingWorkingTreePaths = new Set();\n\t\tthis.pendingWorkingTreeIncomplete = false;\n\t\t// Advance the cache's status clock (via WatchService's global forwarding) BEFORE notifying subscribers,\n\t\t// so a subscriber that synchronously reads status sees the post-change generation, not a joinable pre-change run.\n\t\tthis.onDidFireWorkingTreeChangeCallback?.(this.repoPath);\n\t\tthis.workingTreeEmitter.fire({ repoPath: this.repoPath, paths: paths, incomplete: incomplete });\n\t}\n\n\tprivate coalesceWorkingTrees(paths: Iterable<string>): void {\n\t\tfor (const p of paths) {\n\t\t\t// Stop retaining paths past the cap, but keep draining the iterable — the flag is what makes the\n\t\t\t// partial set safe to consume, and dropping out early would leave later paths for the next window.\n\t\t\tif (this.pendingWorkingTreePaths.size >= maxWorkingTreePaths && !this.pendingWorkingTreePaths.has(p)) {\n\t\t\t\tthis.pendingWorkingTreeIncomplete = true;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tthis.pendingWorkingTreePaths.add(p);\n\t\t}\n\n\t\tif (this.hasPendingWorkingTreeChanges) {\n\t\t\tthis.workingTreeClock.note();\n\t\t}\n\t}\n}\n\n/** `'closed'` and `'opened'` are lifecycle, not a write's effect, so an announcement never replaces or names them */\nfunction isAnnounceable(change: RepositoryChange): boolean {\n\treturn change !== 'closed' && change !== 'opened';\n}\n\n/**\n * The names in `refs` grouped by their ref kind, for the ref kinds among `changes` only; `undefined` when none is named.\n * Pseudo-refs (`HEAD`) are left out: a write names the refs it moved, and those are full ref names.\n */\nfunction groupNamedRefs(\n\tchanges: readonly RepositoryChange[],\n\trefs: readonly string[] | undefined,\n): Map<RefChangeKind, Set<string>> | undefined {\n\tif (!refs?.length) return undefined;\n\n\tlet named: Map<RefChangeKind, Set<string>> | undefined;\n\tfor (const ref of refs) {\n\t\tif (!ref.startsWith('refs/')) continue;\n\n\t\tconst kind = getRefChangeKind(ref);\n\t\tif (kind == null || !changes.includes(kind)) continue;\n\n\t\tnamed ??= new Map();\n\t\tlet names = named.get(kind);\n\t\tif (names == null) {\n\t\t\tnames = new Set();\n\t\t\tnamed.set(kind, names);\n\t\t}\n\t\tnames.add(ref);\n\t}\n\treturn named;\n}\n\n/** The local branches among `refs` (full `refs/heads/` names), or `undefined` when there are none */\nfunction getBranchRefs(refs: readonly string[] | undefined): Set<string> | undefined {\n\tlet branches: Set<string> | undefined;\n\tfor (const ref of refs ?? []) {\n\t\tif (ref.startsWith('refs/heads/')) {\n\t\t\t(branches ??= new Set()).add(ref);\n\t\t}\n\t}\n\treturn branches;\n}\n\n/** Whether every name in `names` is in `of`; never for `undefined`, which stands for any name */\nfunction isSubsetOf(names: ReadonlySet<string> | undefined, of: ReadonlySet<string>): boolean {\n\tif (names == null) return false;\n\n\tfor (const name of names) {\n\t\tif (!of.has(name)) return false;\n\t}\n\treturn true;\n}\n\n/** Wraps a session's repo event so each delivery is narrowed to the subscriber's relevant refs (see `subscribe`) */\nfunction createRelevantRefsEvent(\n\tevent: Event<WatcherRepoChangeEvent>,\n\trelevantRefs: () => Iterable<string>,\n): Event<WatcherRepoChangeEvent> {\n\treturn (listener, thisArgs, disposables) =>\n\t\tevent(\n\t\t\te => {\n\t\t\t\tconst narrowed = narrowToRelevantRefs(e, relevantRefs);\n\t\t\t\tif (narrowed != null) {\n\t\t\t\t\tlistener.call(thisArgs, narrowed);\n\t\t\t\t}\n\t\t\t},\n\t\t\tundefined,\n\t\t\tdisposables,\n\t\t);\n}\n\n/**\n * Narrows an event to the refs a subscriber cares about. Returns the event itself when no change kind is removed (the\n * kept names then stay a superset, which only makes `mayHaveChangedRef` more conservative), `undefined` when every kind\n * is removed, and otherwise a new event with the kept kinds, names and unnamed kinds. A `config` change confined to\n * some local branches' sections is removed unless a relevant ref is `HEAD` or one of those branches (see\n * `mayHaveChangedConfig`).\n */\nfunction narrowToRelevantRefs(\n\te: WatcherRepoChangeEvent,\n\trelevantRefs: () => Iterable<string>,\n): WatcherRepoChangeEvent | undefined {\n\t// Ref kinds that pass only if a relevant ref of that kind may have moved\n\tlet unresolved: Set<RefChangeKind> | undefined;\n\tfor (const change of e.changes) {\n\t\tif (!isRefChangeKind(change) || e.unnamedRefKinds.has(change)) continue;\n\n\t\t(unresolved ??= new Set()).add(change);\n\t}\n\t// A confined `config` passes only if a relevant ref is one of the branches it is confined to\n\tconst configBranches = e.configBranches;\n\tlet keepConfig = configBranches == null;\n\tif (unresolved == null && keepConfig) return e;\n\n\tunresolved ??= new Set();\n\n\tconst pseudoRefs: string[] = [];\n\tfor (const name of e.refs) {\n\t\tif (!name.startsWith('refs/')) {\n\t\t\tpseudoRefs.push(name);\n\t\t}\n\t}\n\tif (pseudoRefs.length !== 0) {\n\t\t// A pseudo-ref (`HEAD`, `ORIG_HEAD`) moving is a `heads` change for every subscriber\n\t\tunresolved.delete('heads');\n\t\tif (unresolved.size === 0 && keepConfig) return e;\n\t}\n\n\t// Names that equal or cover a relevant ref — collected as we go, since `relevantRefs` may be a one-shot iterable\n\tconst keptNames = new Set<string>(pseudoRefs);\n\tfor (const ref of relevantRefs()) {\n\t\t// `HEAD` is usually a symref to the checked-out branch, so any `heads` change may have moved it, and any branch's\n\t\t// config section may be its branch's\n\t\tif (ref === 'HEAD') {\n\t\t\tif (e.changes.has('heads')) {\n\t\t\t\tfor (const name of e.refs) {\n\t\t\t\t\tif (getRefChangeKind(name) === 'heads') {\n\t\t\t\t\t\tkeptNames.add(name);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tkeepConfig = true;\n\t\t\tunresolved.delete('heads');\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (!keepConfig && configBranches?.has(ref)) {\n\t\t\tkeepConfig = true;\n\t\t}\n\n\t\tconst kind = getRefChangeKind(ref);\n\t\tif (kind == null || !e.changes.has(kind)) continue;\n\n\t\tconst covering = findCoveringRefName(e.refs, ref);\n\t\tif (covering == null) continue;\n\n\t\tkeptNames.add(covering);\n\t\tunresolved.delete(kind);\n\t}\n\tif (unresolved.size === 0 && keepConfig) return e;\n\n\tconst keptChanges: RepositoryChange[] = [];\n\tfor (const change of e.changes) {\n\t\tif (change === 'config' ? keepConfig : !isRefChangeKind(change) || !unresolved.has(change)) {\n\t\t\tkeptChanges.push(change);\n\t\t}\n\t}\n\tif (keptChanges.length === 0) return undefined;\n\n\treturn new WatcherRepoChangeEvent(\n\t\te.repoPath,\n\t\tkeptChanges,\n\t\t{ names: keptNames, unnamed: e.unnamedRefKinds, configBranches: configBranches },\n\t\te.announced,\n\t);\n}\n"],"mappings":"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"}
@@ -112,13 +112,15 @@ export interface WorktreeSummaryServiceOptions {
112
112
  * `dirty`; the upstream moving reads only `branch`; a ref the base depends on reads only `base`; a `gk` config change
113
113
  * (mostly bookkeeping writes to the shared `.git/gk/config`), or core's own write of the branch's merge target or
114
114
  * base, re-reads the branch's stored merge targets and recorded base, and reads `base` only when one of them changed;
115
- * a `.git/config` change reads `branch` and the provider's config inputs to the base (one read per repository), and
116
- * reads `base` only when they, the stored values, HEAD's commit or the upstream changed; the checked-out branch
117
- * moving, or a HEAD change, reads all of them. Stash, unrelated tags and branches, and the like read nothing. One run
118
- * is in flight per worktree at a time, at most four per repository (the worktrees sharing a common git directory), and
119
- * at most eight across the service. Runs take turns first come, first served, except that one whose repository is at
120
- * its cap lets later runs of other repositories go first, so a burst in one repository never delays another's reads:
121
- * events while a run waits for its turn join it, and events during a run are merged into a single follow-up run.
115
+ * a `.git/config` change reads `branch` and the provider's config inputs to the base (one cached read per checkout), and
116
+ * reads `base` only when they, the stored values, HEAD's commit or the upstream changed, unless it was confined to the
117
+ * `branch.<name>` sections of branches the summary neither is on, tracks nor bases on (its `configBranches`), which
118
+ * reads nothing; the checked-out branch moving, or a HEAD change, reads all of them. Stash, unrelated tags and
119
+ * branches, and the like read nothing. One run is in flight per worktree at a time, at most four per repository (the
120
+ * worktrees sharing a common git directory), and at most eight across the service. Runs take turns first come, first
121
+ * served, except that one whose repository is at its cap lets later runs of other repositories go first, so a burst in
122
+ * one repository never delays another's reads: events while a run waits for its turn join it, and events during a run
123
+ * are merged into a single follow-up run.
122
124
  *
123
125
  * **Value-change-only notification.** {@link WorktreeSummarySubscription.onDidChange} and {@link onDidChangeSummary}
124
126
  * fire only when some field's value differs from the previous summary — a recompute that reads the same answer is