@gitkraken/core-gitlens 0.15.7 → 0.16.1

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 (55) hide show
  1. package/CHANGELOG.md +25 -1
  2. package/dist/git/cache.d.ts +13 -0
  3. package/dist/git/cache.js +45 -8
  4. package/dist/git/cache.js.map +1 -1
  5. package/dist/git/features.d.ts +1 -1
  6. package/dist/git/features.js +1 -0
  7. package/dist/git/features.js.map +1 -1
  8. package/dist/git/providers/branches.d.ts +29 -0
  9. package/dist/git/run.types.d.ts +3 -0
  10. package/dist/git/service.d.ts +4 -2
  11. package/dist/git/service.js +6 -4
  12. package/dist/git/service.js.map +1 -1
  13. package/dist/git/watching/classifyChange.js +3 -0
  14. package/dist/git/watching/classifyChange.js.map +1 -1
  15. package/dist/git/watching/watchSession.d.ts +2 -0
  16. package/dist/git/watching/watchSession.js +9 -1
  17. package/dist/git/watching/watchSession.js.map +1 -1
  18. package/dist/git/watching/worktreeSummary.d.ts +19 -4
  19. package/dist/git/watching/worktreeSummary.js +121 -36
  20. package/dist/git/watching/worktreeSummary.js.map +1 -1
  21. package/dist/git-cli/cliGitProvider.d.ts +1 -1
  22. package/dist/git-cli/cliGitProvider.js +8 -5
  23. package/dist/git-cli/cliGitProvider.js.map +1 -1
  24. package/dist/git-cli/exec/commandClassifier.d.ts +18 -3
  25. package/dist/git-cli/exec/commandClassifier.js +562 -16
  26. package/dist/git-cli/exec/commandClassifier.js.map +1 -1
  27. package/dist/git-cli/exec/exec.d.ts +2 -2
  28. package/dist/git-cli/exec/git.d.ts +31 -4
  29. package/dist/git-cli/exec/git.js +105 -43
  30. package/dist/git-cli/exec/git.js.map +1 -1
  31. package/dist/git-cli/exec/headState.d.ts +32 -0
  32. package/dist/git-cli/exec/headState.js +78 -0
  33. package/dist/git-cli/exec/headState.js.map +1 -0
  34. package/dist/git-cli/providers/branches.d.ts +50 -0
  35. package/dist/git-cli/providers/branches.js +294 -25
  36. package/dist/git-cli/providers/branches.js.map +1 -1
  37. package/dist/git-cli/providers/config.js +3 -2
  38. package/dist/git-cli/providers/config.js.map +1 -1
  39. package/dist/git-cli/providers/operations.d.ts +6 -7
  40. package/dist/git-cli/providers/operations.js +46 -64
  41. package/dist/git-cli/providers/operations.js.map +1 -1
  42. package/dist/git-cli/providers/refs.d.ts +4 -1
  43. package/dist/git-cli/providers/refs.js +13 -9
  44. package/dist/git-cli/providers/refs.js.map +1 -1
  45. package/dist/git-cli/providers/revision.js +1 -1
  46. package/dist/git-cli/providers/stash.js +1 -1
  47. package/dist/git-cli/providers/worktrees.js +23 -6
  48. package/dist/git-cli/providers/worktrees.js.map +1 -1
  49. package/dist/git-cli/service.d.ts +3 -0
  50. package/dist/git-cli/service.js +1 -0
  51. package/dist/git-cli/service.js.map +1 -1
  52. package/dist/utils/env/node/exec.d.ts +38 -1
  53. package/dist/utils/env/node/exec.js +14 -6
  54. package/dist/utils/env/node/exec.js.map +1 -1
  55. package/package.json +1 -1
@@ -1,3 +1,4 @@
1
+ import { getRefChangeKind } from "../../git/watching/changeEvent.js";
1
2
  import { isAbsolute, join } from "node:path";
2
3
  //#region ../git-cli/src/exec/commandClassifier.ts
3
4
  /**
@@ -86,7 +87,11 @@ const readOnlySubcommands = {
86
87
  "show",
87
88
  "get-ref"
88
89
  ]),
89
- stash: /* @__PURE__ */ new Set(["list", "show"]),
90
+ stash: /* @__PURE__ */ new Set([
91
+ "list",
92
+ "show",
93
+ "create"
94
+ ]),
90
95
  tag: /* @__PURE__ */ new Set([
91
96
  "",
92
97
  "-l",
@@ -187,6 +192,7 @@ const globalOptionsWithValue = /* @__PURE__ */ new Set([
187
192
  */
188
193
  function leadingCommand(args) {
189
194
  let pathOverride;
195
+ let configs;
190
196
  let i = 0;
191
197
  while (i < args.length) {
192
198
  const arg = args[i];
@@ -197,6 +203,7 @@ function leadingCommand(args) {
197
203
  continue;
198
204
  }
199
205
  if (globalOptionsWithValue.has(arg)) {
206
+ if (arg === "-c" && args[i + 1] != null) (configs ??= []).push(args[i + 1]);
200
207
  i += 2;
201
208
  continue;
202
209
  }
@@ -213,7 +220,8 @@ function leadingCommand(args) {
213
220
  readOnly: !hasWriteOption(verb, rest) && (readOnlyVerbs.has(verb) || (readOnlySubcommands[verb]?.has(rest[0] ?? "") ?? false) || verb === "config" && isConfigRead(rest) || verb === "reflog" && !reflogWriteSubcommands.has(rest[0] ?? "") || verb === "symbolic-ref" && !rest.includes("-d") && !rest.includes("--delete") && rest.filter((a) => !a.startsWith("-")).length < 2),
214
221
  subcommand: rest[0],
215
222
  rest,
216
- pathOverride
223
+ pathOverride,
224
+ configs
217
225
  };
218
226
  }
219
227
  /**
@@ -250,6 +258,9 @@ const objectOnlyVerbs = /* @__PURE__ */ new Set([
250
258
  * table and {@link objectOnlyVerbs} announces `[]` — the conservative default for an unclassified
251
259
  * mutation: the caller still learns something changed, just not what. An entry covers at least what the
252
260
  * verb's typed mutator announces; `update-ref` and `symbolic-ref` name every kind their target ref could be.
261
+ * {@link getNamedChanges} narrows the forms whose argv proves what moved, which can announce less: the typed
262
+ * checkout announces `heads` for any target, but a `--detach` or full-SHA checkout moves only this tree's HEAD,
263
+ * no shared ref.
253
264
  * The kinds decide which caches the announcement resets, so a verb that can start or end a paused operation
254
265
  * names `pausedOp`: `commit` concludes a merge, cherry-pick or revert, `reset` aborts one, and `am` runs in
255
266
  * `rebase-apply`.
@@ -339,11 +350,7 @@ const changesByVerb = {
339
350
  "tags",
340
351
  "stash"
341
352
  ],
342
- branch: [
343
- "heads",
344
- "remotes",
345
- "tags"
346
- ],
353
+ branch: ["heads", "remotes"],
347
354
  "symbolic-ref": [
348
355
  "head",
349
356
  "heads",
@@ -396,8 +403,7 @@ const branchConfigOptions = [
396
403
  const branchConfigChanges = [
397
404
  "config",
398
405
  "heads",
399
- "remotes",
400
- "tags"
406
+ "remotes"
401
407
  ];
402
408
  /**
403
409
  * Whether creating a branch from `startPoint` may write its tracking to `.git/config`, as git's default
@@ -412,20 +418,560 @@ function mayWriteTracking(startPoint, noTracking) {
412
418
  }
413
419
  const worktreeChanges = ["worktrees", "heads"];
414
420
  const emptyChanges = [];
421
+ const configHeadsChanges = ["config", "heads"];
422
+ const headsChanges = ["heads"];
423
+ const remotesChanges = ["remotes"];
424
+ const tagsChanges = ["tags"];
425
+ const stashChanges = ["stash"];
426
+ const stashIndexChanges = ["stash", "index"];
427
+ const stashBranchChanges = [
428
+ "head",
429
+ "heads",
430
+ "index",
431
+ "stash"
432
+ ];
433
+ const indexChanges = ["index"];
434
+ const headHeadsChanges = ["head", "heads"];
435
+ const headIndexChanges = ["head", "index"];
436
+ const checkoutChanges = [
437
+ "head",
438
+ "heads",
439
+ "index"
440
+ ];
441
+ const checkoutTrackingChanges = [
442
+ "config",
443
+ "head",
444
+ "heads",
445
+ "index"
446
+ ];
447
+ const worktreeOnlyChanges = ["worktrees"];
448
+ const worktreeTrackingChanges = [
449
+ "config",
450
+ "worktrees",
451
+ "heads"
452
+ ];
453
+ const stashRefs = ["refs/stash"];
454
+ /** A full object name, which `checkout` and `worktree add` never DWIM into a new branch */
455
+ const fullShaRegex = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/i;
456
+ /**
457
+ * Whether a branch name in argv is the branch it names. Git expands `@{-<n>}` (and `@`) wherever it takes a branch
458
+ * name, `branch -d @{-1}` deleting the previous branch, and no ref name can hold `@{` or be `@`
459
+ */
460
+ function isLiteralBranchName(name) {
461
+ return name !== "@" && !name.includes("@{");
462
+ }
463
+ /**
464
+ * Splits a verb's arguments by its own option table, since one letter means different things to different verbs
465
+ * (`-m` moves, merges or sets a message). An option outside the table returns `undefined` rather than a guess at
466
+ * whether it takes a value. A valued option accepts `--opt=<v>`, `--opt <v>`, `-o <v>` and `-o<v>`.
467
+ */
468
+ function parseArgs(args, flags, valued) {
469
+ const options = [];
470
+ const positionals = [];
471
+ let dashDash;
472
+ let optionsEnded = false;
473
+ for (let i = 0; i < args.length; i++) {
474
+ const arg = args[i];
475
+ if (arg === "--" && dashDash == null) {
476
+ dashDash = positionals.length;
477
+ optionsEnded = true;
478
+ continue;
479
+ }
480
+ if (optionsEnded || arg === "-" || !arg.startsWith("-")) {
481
+ positionals.push(arg);
482
+ continue;
483
+ }
484
+ if (arg === "--end-of-options") {
485
+ optionsEnded = true;
486
+ continue;
487
+ }
488
+ if (flags.has(arg)) {
489
+ options.push([arg, void 0]);
490
+ continue;
491
+ }
492
+ if (valued.has(arg)) {
493
+ const value = args[i + 1];
494
+ if (value == null) return void 0;
495
+ options.push([arg, value]);
496
+ i++;
497
+ continue;
498
+ }
499
+ const long = arg.startsWith("--");
500
+ const name = long ? arg.split("=", 1)[0] : arg.slice(0, 2);
501
+ if (name === arg || !valued.has(name)) return void 0;
502
+ options.push([name, arg.slice(long ? name.length + 1 : name.length)]);
503
+ }
504
+ return {
505
+ options,
506
+ positionals,
507
+ dashDash
508
+ };
509
+ }
510
+ function hasParsedOption(parsed, ...names) {
511
+ return parsed.options.some(([option]) => names.includes(option));
512
+ }
513
+ const updateRefFlags = /* @__PURE__ */ new Set([
514
+ "-d",
515
+ "--delete",
516
+ "--no-deref",
517
+ "--create-reflog"
518
+ ]);
519
+ const updateRefValued = /* @__PURE__ */ new Set(["-m", "--message"]);
520
+ /**
521
+ * `update-ref` moves the one ref it names, or the ref a symbolic one points to without `--no-deref`, so `HEAD` and a
522
+ * remote's `HEAD` stay unnamed. `null` means a ref outside the namespaces core reads, which the typed
523
+ * `updateReference` doesn't announce either. `git log --all` (graph, search, contributors) does walk such a ref when
524
+ * it points at a commit, and argv can't tell that from a blob ref, so a row it adds or drops can stay stale until the
525
+ * next heads change: the trade the typed write makes not to refresh every worktree on each bookkeeping write. A packed
526
+ * ref's delete rewrites `packed-refs`, which the watcher still reports.
527
+ */
528
+ function getUpdateRefChanges(args) {
529
+ const parsed = parseArgs(args, updateRefFlags, updateRefValued);
530
+ if (parsed == null) return void 0;
531
+ const [ref] = parsed.positionals;
532
+ if (!ref) return void 0;
533
+ if (ref === "HEAD") return { changes: headHeadsChanges };
534
+ if (!ref.startsWith("refs/")) return void 0;
535
+ if (ref.endsWith("/HEAD") && !hasParsedOption(parsed, "--no-deref")) return void 0;
536
+ const kind = getRefChangeKind(ref);
537
+ if (kind != null) return {
538
+ changes: [kind],
539
+ refs: [ref]
540
+ };
541
+ if (ref.startsWith("refs/replace/") || ref.startsWith("refs/notes/")) return { changes: headsChanges };
542
+ return null;
543
+ }
544
+ const branchFlags = /* @__PURE__ */ new Set([
545
+ "-f",
546
+ "--force",
547
+ "--no-track",
548
+ "-d",
549
+ "-D",
550
+ "--delete",
551
+ "-m",
552
+ "-M",
553
+ "--move",
554
+ "-c",
555
+ "-C",
556
+ "--copy",
557
+ "-r",
558
+ "--remotes",
559
+ "-q",
560
+ "--quiet",
561
+ "--create-reflog"
562
+ ]);
563
+ const noValuedOptions = /* @__PURE__ */ new Set();
564
+ /**
565
+ * The create, delete, rename and copy forms of `branch` name every branch they write. A delete removes a local
566
+ * branch's `branch.<name>` section and a rename or copy moves it; a create writes it only for tracking.
567
+ */
568
+ function getBranchChanges(args) {
569
+ const parsed = parseArgs(args, branchFlags, noValuedOptions);
570
+ if (parsed == null) return void 0;
571
+ let mode;
572
+ let remote = false;
573
+ let noTrack = false;
574
+ for (const [option] of parsed.options) switch (option) {
575
+ case "-d":
576
+ case "-D":
577
+ case "--delete":
578
+ if (mode === "move") return void 0;
579
+ mode = "delete";
580
+ break;
581
+ case "-m":
582
+ case "-M":
583
+ case "--move":
584
+ case "-c":
585
+ case "-C":
586
+ case "--copy":
587
+ if (mode === "delete") return void 0;
588
+ mode = "move";
589
+ break;
590
+ case "-r":
591
+ case "--remotes":
592
+ remote = true;
593
+ break;
594
+ case "--no-track": noTrack = true;
595
+ }
596
+ const names = parsed.positionals;
597
+ if (mode === "delete") {
598
+ if (!names.length || !names.every(isLiteralBranchName)) return void 0;
599
+ if (remote) return {
600
+ changes: remotesChanges,
601
+ refs: names.map((n) => `refs/remotes/${n}`)
602
+ };
603
+ return {
604
+ changes: configHeadsChanges,
605
+ refs: names.map((n) => `refs/heads/${n}`),
606
+ configConfinedToRefs: true
607
+ };
608
+ }
609
+ if (remote) return void 0;
610
+ if (mode === "move") {
611
+ if (names.length !== 2 || !names.every(isLiteralBranchName)) return void 0;
612
+ return {
613
+ changes: configHeadsChanges,
614
+ refs: names.map((n) => `refs/heads/${n}`),
615
+ configConfinedToRefs: true
616
+ };
617
+ }
618
+ if (names.length !== 1 && names.length !== 2 || !isLiteralBranchName(names[0])) return void 0;
619
+ const refs = [`refs/heads/${names[0]}`];
620
+ if (mayWriteTracking(names[1], noTrack)) return {
621
+ changes: configHeadsChanges,
622
+ refs,
623
+ configConfinedToRefs: true
624
+ };
625
+ return {
626
+ changes: headsChanges,
627
+ refs
628
+ };
629
+ }
630
+ const tagFlags = /* @__PURE__ */ new Set([
631
+ "-f",
632
+ "--force",
633
+ "-d",
634
+ "--delete",
635
+ "-a",
636
+ "--annotate",
637
+ "-s",
638
+ "--sign",
639
+ "--no-sign",
640
+ "-e",
641
+ "--edit",
642
+ "--create-reflog"
643
+ ]);
644
+ const tagValued = /* @__PURE__ */ new Set([
645
+ "-m",
646
+ "--message",
647
+ "-F",
648
+ "--file",
649
+ "-u",
650
+ "--local-user",
651
+ "--cleanup"
652
+ ]);
653
+ function getTagChanges(args) {
654
+ const parsed = parseArgs(args, tagFlags, tagValued);
655
+ if (parsed == null) return void 0;
656
+ const names = parsed.positionals;
657
+ if (hasParsedOption(parsed, "-d", "--delete")) {
658
+ if (!names.length) return void 0;
659
+ return {
660
+ changes: tagsChanges,
661
+ refs: names.map((n) => `refs/tags/${n}`)
662
+ };
663
+ }
664
+ if (names.length !== 1 && names.length !== 2) return void 0;
665
+ return {
666
+ changes: tagsChanges,
667
+ refs: [`refs/tags/${names[0]}`]
668
+ };
669
+ }
670
+ /** The `worktree` subcommands other than `add` and `list`; none writes a branch */
671
+ const worktreeEditSubcommands = /* @__PURE__ */ new Set([
672
+ "remove",
673
+ "prune",
674
+ "lock",
675
+ "unlock",
676
+ "move",
677
+ "repair"
678
+ ]);
679
+ const worktreeAddFlags = /* @__PURE__ */ new Set([
680
+ "-f",
681
+ "--force",
682
+ "-d",
683
+ "--detach",
684
+ "--checkout",
685
+ "--no-checkout",
686
+ "--lock",
687
+ "-q",
688
+ "--quiet",
689
+ "--track",
690
+ "--no-track",
691
+ "--guess-remote",
692
+ "--no-guess-remote",
693
+ "--orphan"
694
+ ]);
695
+ const worktreeAddValued = /* @__PURE__ */ new Set([
696
+ "-b",
697
+ "-B",
698
+ "--reason"
699
+ ]);
700
+ /**
701
+ * `worktree add` is the only subcommand that can create a branch: the `-b`/`-B` one it names or, without either, one
702
+ * git DWIMs from a short commit-ish (or from the path, given none), which argv can't name. A detached or full-SHA add
703
+ * creates none.
704
+ */
705
+ function getWorktreeChanges(args) {
706
+ const [subcommand, ...rest] = args;
707
+ if (subcommand != null && worktreeEditSubcommands.has(subcommand)) return { changes: worktreeOnlyChanges };
708
+ if (subcommand !== "add") return void 0;
709
+ const parsed = parseArgs(rest, worktreeAddFlags, worktreeAddValued);
710
+ if (parsed == null) return void 0;
711
+ let branch;
712
+ let detach = false;
713
+ let orphan = false;
714
+ let track;
715
+ for (const [option, value] of parsed.options) switch (option) {
716
+ case "-b":
717
+ case "-B":
718
+ branch = value;
719
+ break;
720
+ case "-d":
721
+ case "--detach":
722
+ detach = true;
723
+ break;
724
+ case "--orphan":
725
+ orphan = true;
726
+ break;
727
+ case "--track":
728
+ track = true;
729
+ break;
730
+ case "--no-track": track = false;
731
+ }
732
+ const [path, commitish, ...extra] = parsed.positionals;
733
+ if (orphan || path == null || extra.length) return void 0;
734
+ if (branch) {
735
+ if (!isLiteralBranchName(branch)) return void 0;
736
+ const refs = [`refs/heads/${branch}`];
737
+ if (track === true || mayWriteTracking(commitish, track === false)) return {
738
+ changes: worktreeTrackingChanges,
739
+ refs,
740
+ configConfinedToRefs: true
741
+ };
742
+ return {
743
+ changes: worktreeChanges,
744
+ refs
745
+ };
746
+ }
747
+ if (detach || commitish != null && fullShaRegex.test(commitish)) return { changes: worktreeOnlyChanges };
748
+ }
749
+ const checkoutFlags = /* @__PURE__ */ new Set([
750
+ "-q",
751
+ "--quiet",
752
+ "-f",
753
+ "--force",
754
+ "--detach",
755
+ "--no-track",
756
+ "-m",
757
+ "--merge",
758
+ "--progress",
759
+ "--no-progress",
760
+ "--ignore-other-worktrees",
761
+ "--overwrite-ignore",
762
+ "--no-overwrite-ignore",
763
+ "--recurse-submodules",
764
+ "--no-recurse-submodules"
765
+ ]);
766
+ const checkoutValued = /* @__PURE__ */ new Set(["-b", "-B"]);
767
+ const switchFlags = /* @__PURE__ */ new Set([
768
+ ...checkoutFlags,
769
+ "-d",
770
+ "--discard-changes"
771
+ ]);
772
+ const switchValued = /* @__PURE__ */ new Set([
773
+ "-c",
774
+ "-C",
775
+ "--create",
776
+ "--force-create"
777
+ ]);
778
+ /**
779
+ * `checkout`/`switch` moves no existing branch, so the only shared ref it writes is one it creates: the `-b`/`-c` one
780
+ * it names, or the one git DWIMs from a remote branch named like its lone argument. A detached or full-SHA checkout
781
+ * creates none, and a pathspec checkout writes only the index and working tree.
782
+ */
783
+ function getCheckoutChanges(verb, args) {
784
+ const parsed = verb === "switch" ? parseArgs(args, switchFlags, switchValued) : parseArgs(args, checkoutFlags, checkoutValued);
785
+ if (parsed == null) return void 0;
786
+ let branch;
787
+ let detach = false;
788
+ let noTrack = false;
789
+ for (const [option, value] of parsed.options) if (value != null) branch = value;
790
+ else if (option === "-d" || option === "--detach") detach = true;
791
+ else if (option === "--no-track") noTrack = true;
792
+ const { positionals, dashDash } = parsed;
793
+ const revs = dashDash == null ? positionals : positionals.slice(0, dashDash);
794
+ const hasPaths = dashDash != null && dashDash < positionals.length;
795
+ if (branch != null) {
796
+ if (hasPaths || revs.length > 1 || !isLiteralBranchName(branch)) return void 0;
797
+ const refs = [`refs/heads/${branch}`];
798
+ if (mayWriteTracking(revs[0], noTrack)) return {
799
+ changes: checkoutTrackingChanges,
800
+ refs,
801
+ configConfinedToRefs: true
802
+ };
803
+ return {
804
+ changes: checkoutChanges,
805
+ refs
806
+ };
807
+ }
808
+ if (hasPaths) return verb === "checkout" ? { changes: indexChanges } : void 0;
809
+ if (revs.length > 1) return void 0;
810
+ if (detach) return { changes: headIndexChanges };
811
+ const [rev] = revs;
812
+ if (rev == null || rev === "-" || rev.startsWith("@{-")) return void 0;
813
+ if (fullShaRegex.test(rev)) return { changes: headIndexChanges };
814
+ return {
815
+ changes: checkoutTrackingChanges,
816
+ refs: [`refs/heads/${rev}`],
817
+ configConfinedToRefs: true
818
+ };
819
+ }
820
+ /**
821
+ * A stash write moves only `refs/stash` (or its reflog, the rest of the list), save `branch`, which also creates the
822
+ * branch it names from a commit and checks it out; `apply` moves no ref
823
+ */
824
+ function getStashChanges(subcommand, rest) {
825
+ switch (subcommand) {
826
+ case "branch": {
827
+ const name = rest[1];
828
+ if (!name || name.startsWith("-") || !isLiteralBranchName(name)) return void 0;
829
+ return {
830
+ changes: stashBranchChanges,
831
+ refs: [`refs/heads/${name}`, ...stashRefs]
832
+ };
833
+ }
834
+ case void 0:
835
+ case "push":
836
+ case "save":
837
+ case "pop": return {
838
+ changes: stashIndexChanges,
839
+ refs: stashRefs
840
+ };
841
+ case "drop":
842
+ case "clear":
843
+ case "store": return {
844
+ changes: stashChanges,
845
+ refs: stashRefs
846
+ };
847
+ case "apply": return { changes: indexChanges };
848
+ default: return subcommand.startsWith("-") ? {
849
+ changes: stashIndexChanges,
850
+ refs: stashRefs
851
+ } : void 0;
852
+ }
853
+ }
854
+ /** A `reset` given paths, or `-p`, restores index entries and moves no ref */
855
+ function isPathspecReset(args) {
856
+ const dashDash = args.indexOf("--");
857
+ if (dashDash >= 0 && dashDash < args.length - 1) return true;
858
+ return args.some((arg) => arg === "-p" || arg === "--patch" || arg === "--pathspec-from-file" || arg.startsWith("--pathspec-from-file="));
859
+ }
860
+ const falseConfigValues = /* @__PURE__ */ new Set([
861
+ "false",
862
+ "no",
863
+ "off",
864
+ "0",
865
+ ""
866
+ ]);
867
+ /**
868
+ * The `rebase` options that take the refs it writes out of argv's hands: the update-refs list was fixed when the
869
+ * rebase started, whatever this argv says, an interactive or `--exec` todo list can write any ref, and `--update-refs`
870
+ * spelled any other way than in full turns update-refs on
871
+ */
872
+ const unprovableRebaseOptions = [
873
+ "--continue",
874
+ "--skip",
875
+ "--edit-todo",
876
+ "--interactive",
877
+ "--exec",
878
+ "--update-refs"
879
+ ];
880
+ /**
881
+ * Whether `arg` is one of {@link unprovableRebaseOptions}, `-i` or `-x`, however git lets it be written: any unique
882
+ * prefix of a long option, before any `=<value>` (`--cont`, `--update-ref`, `--exe=make`), and short ones bundled
883
+ * (`-ri`, `-xmake`)
884
+ */
885
+ function isUnprovableRebaseOption(arg) {
886
+ if (arg.startsWith("--")) {
887
+ const name = arg.split("=", 1)[0];
888
+ return name.length > 2 && unprovableRebaseOptions.some((o) => o.startsWith(name));
889
+ }
890
+ return arg.length > 1 && arg.startsWith("-") && /[ix]/.test(arg.slice(1));
891
+ }
892
+ /**
893
+ * Whether a `rebase` moves only the branch being rebased. Update-refs would rewrite every branch in the rebased range,
894
+ * so it must be provably off: the last of `--update-refs`/`--no-update-refs`, else the last `-c rebase.updateRefs`
895
+ * before the verb. An interactive or `--exec` rebase can write any ref from its todo list. Git writes the listed refs
896
+ * only when the rebase finishes, so an abort or quit moves none.
897
+ */
898
+ function isHeadBranchRebase(command) {
899
+ let off;
900
+ for (const arg of command.rest) {
901
+ if (arg === "--" || arg === "--end-of-options") break;
902
+ if (arg === "--abort" || arg === "--quit") return true;
903
+ if (arg === "--no-update-refs") off = true;
904
+ else if (arg === "--update-refs") off = false;
905
+ else if (isUnprovableRebaseOption(arg)) return false;
906
+ }
907
+ if (off != null) return off;
908
+ for (const config of command.configs ?? []) {
909
+ const separator = config.indexOf("=");
910
+ if ((separator < 0 ? config : config.slice(0, separator)).toLowerCase() !== "rebase.updaterefs") continue;
911
+ off = separator >= 0 && falseConfigValues.has(config.slice(separator + 1).toLowerCase());
912
+ }
913
+ return off ?? false;
914
+ }
915
+ /** Verbs that move only the branch HEAD is on, or none from a detached HEAD */
916
+ const headBranchVerbs = /* @__PURE__ */ new Set([
917
+ "commit",
918
+ "merge",
919
+ "cherry-pick",
920
+ "revert",
921
+ "am"
922
+ ]);
923
+ /**
924
+ * What argv proves more precisely than {@link changesByVerb}: the refs a write moved, or fewer kinds. `undefined`
925
+ * keeps the verb's table entry; `null` means the write moved nothing cached.
926
+ */
927
+ function getNamedChanges(command) {
928
+ switch (command.verb) {
929
+ case "update-ref": return getUpdateRefChanges(command.rest);
930
+ case "branch": return getBranchChanges(command.rest);
931
+ case "tag": return getTagChanges(command.rest);
932
+ case "worktree": return getWorktreeChanges(command.rest);
933
+ case "checkout":
934
+ case "switch": return getCheckoutChanges(command.verb, command.rest);
935
+ case "stash": return getStashChanges(command.subcommand, command.rest);
936
+ case "reset":
937
+ if (isPathspecReset(command.rest)) return { changes: indexChanges };
938
+ return {
939
+ changes: changesByVerb.reset,
940
+ movesHeadBranch: true
941
+ };
942
+ case "rebase":
943
+ if (!isHeadBranchRebase(command)) return void 0;
944
+ return {
945
+ changes: changesByVerb.rebase,
946
+ movesHeadBranch: true
947
+ };
948
+ default:
949
+ if (!headBranchVerbs.has(command.verb)) return void 0;
950
+ return {
951
+ changes: changesByVerb[command.verb],
952
+ movesHeadBranch: true
953
+ };
954
+ }
955
+ }
415
956
  /**
416
957
  * Classifies a raw git argv for the notification `git.run`'s `notify: 'infer'` applies after the run
417
- * settles. Returns `undefined` for a read-only or object-only command — nothing to announce — otherwise
418
- * the {@link RepositoryChange} kinds the verb announces, the `-C` re-scope target (if any), and which of
419
- * those kinds every worktree sees.
958
+ * settles. Returns `undefined` for a command with nothing to announce — read-only, object-only, or a write to a
959
+ * ref nothing cached reads — otherwise the {@link RepositoryChange} kinds it announces, the refs it names when argv
960
+ * proves them, the `-C` re-scope target (if any), and which of those kinds every worktree sees.
420
961
  */
421
962
  function getChangesForCommand(args) {
422
963
  const command = leadingCommand(args);
423
964
  if (command.readOnly || objectOnlyVerbs.has(command.verb)) return void 0;
424
- let changes;
425
- if (command.verb === "worktree") changes = worktreeChanges;
426
- else if (command.verb === "branch" && hasOption(command.rest, branchConfigOptions)) changes = branchConfigChanges;
427
- else changes = changesByVerb[command.verb] ?? emptyChanges;
965
+ const named = getNamedChanges(command);
966
+ if (named === null) return void 0;
967
+ let changes = named?.changes;
968
+ if (changes == null) {
969
+ if (command.verb === "worktree") changes = worktreeChanges;
970
+ else if (command.verb === "branch" && hasOption(command.rest, branchConfigOptions)) changes = branchConfigChanges;
971
+ else changes = changesByVerb[command.verb] ?? emptyChanges;
972
+ }
428
973
  return {
974
+ ...named,
429
975
  changes,
430
976
  pathOverride: command.pathOverride,
431
977
  sharedChanges: changes.filter((c) => worktreeSharedChanges.has(c))