@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,5 +1,5 @@
1
1
  //#region ../git/src/features.d.ts
2
- export type GitFeatures = 'git:checkout:pathspec-from-file' | 'git:commit-graph' | 'git:commit-graph:changed-paths' | 'git:fetch:all-config' | 'git:fetch:follow-remote-head' | 'git:fetch:no-write-fetch-head' | 'git:for-each-ref:ahead-behind' | 'git:for-each-ref:worktreePath' | 'git:fsmonitor' | 'git:fsmonitor:linux' | 'git:ignoreRevsFile' | 'git:index:skipHash' | 'git:maintenance' | 'git:maintenance:pack-refs' | 'git:maintenance:start' | 'git:maintenance:start:systemd' | 'git:manyFiles' | 'git:merge-tree:merge-base' | 'git:merge-tree:write-tree' | 'git:patch-id:binary-oids' | 'git:patch-id:verbatim' | 'git:push:force-if-includes' | 'git:rebase:autosquash' | 'git:rev-parse:end-of-options' | 'git:signing:ssh' | 'git:signing:x509' | 'git:sparse-index' | 'git:stash:push:pathspecs' | 'git:stash:push:staged' | 'git:stash:push:stdin' | 'git:status:find-renames' | 'git:status:ignored-matching' | 'git:status:porcelain-v2' | 'git:untrackedCache' | 'git:worktree:list:lock-reason' | 'git:worktrees';
2
+ export type GitFeatures = 'git:checkout:pathspec-from-file' | 'git:commit-graph' | 'git:commit-graph:changed-paths' | 'git:fetch:all-config' | 'git:fetch:follow-remote-head' | 'git:fetch:no-write-fetch-head' | 'git:for-each-ref:ahead-behind' | 'git:for-each-ref:worktreePath' | 'git:fsmonitor' | 'git:fsmonitor:linux' | 'git:ignoreRevsFile' | 'git:index:skipHash' | 'git:maintenance' | 'git:maintenance:pack-refs' | 'git:maintenance:start' | 'git:maintenance:start:systemd' | 'git:manyFiles' | 'git:merge-tree:merge-base' | 'git:merge-tree:write-tree' | 'git:patch-id:binary-oids' | 'git:patch-id:verbatim' | 'git:push:force-if-includes' | 'git:rebase:autosquash' | 'git:rev-parse:end-of-options' | 'git:rev-parse:reflog-oldest-old-value' | 'git:signing:ssh' | 'git:signing:x509' | 'git:sparse-index' | 'git:stash:push:pathspecs' | 'git:stash:push:staged' | 'git:stash:push:stdin' | 'git:status:find-renames' | 'git:status:ignored-matching' | 'git:status:porcelain-v2' | 'git:untrackedCache' | 'git:worktree:list:lock-reason' | 'git:worktrees';
3
3
  type ExtractPrefix<T> = T extends `${infer Prefix}:${infer Rest}` ? Rest extends `${infer SubPrefix}:${string}` ? T | `${Prefix}:${SubPrefix}` | Prefix : T | Prefix : never;
4
4
  export type GitFeatureOrPrefix = ExtractPrefix<GitFeatures>;
5
5
  export type FilteredGitFeatures<T extends GitFeatureOrPrefix> = T extends GitFeatures ? T : Extract<GitFeatures, T | `${T}:${string}`>;
@@ -25,6 +25,7 @@ const gitFeaturesByVersion = /* @__PURE__ */ new Map([
25
25
  ["git:push:force-if-includes", "2.30.0"],
26
26
  ["git:rebase:autosquash", "2.44"],
27
27
  ["git:rev-parse:end-of-options", "2.30"],
28
+ ["git:rev-parse:reflog-oldest-old-value", "2.31"],
28
29
  ["git:signing:ssh", "2.34.0"],
29
30
  ["git:signing:x509", "2.19.0"],
30
31
  ["git:sparse-index", "2.34"],
@@ -1 +1 @@
1
- {"version":3,"file":"features.js","names":[],"sources":["../../../git/src/features.ts"],"sourcesContent":["// GitFeature's must start with `git:` to be recognized in all usages\nexport type GitFeatures =\n\t| 'git:checkout:pathspec-from-file'\n\t| 'git:commit-graph'\n\t| 'git:commit-graph:changed-paths'\n\t| 'git:fetch:all-config'\n\t| 'git:fetch:follow-remote-head'\n\t| 'git:fetch:no-write-fetch-head'\n\t| 'git:for-each-ref:ahead-behind'\n\t| 'git:for-each-ref:worktreePath'\n\t| 'git:fsmonitor'\n\t| 'git:fsmonitor:linux'\n\t| 'git:ignoreRevsFile'\n\t| 'git:index:skipHash'\n\t| 'git:maintenance'\n\t| 'git:maintenance:pack-refs'\n\t| 'git:maintenance:start'\n\t| 'git:maintenance:start:systemd'\n\t| 'git:manyFiles'\n\t| 'git:merge-tree:merge-base'\n\t| 'git:merge-tree:write-tree'\n\t| 'git:patch-id:binary-oids'\n\t| 'git:patch-id:verbatim'\n\t| 'git:push:force-if-includes'\n\t| 'git:rebase:autosquash'\n\t| 'git:rev-parse:end-of-options'\n\t| 'git:signing:ssh'\n\t| 'git:signing:x509'\n\t| 'git:sparse-index'\n\t| 'git:stash:push:pathspecs'\n\t| 'git:stash:push:staged'\n\t| 'git:stash:push:stdin'\n\t| 'git:status:find-renames'\n\t| 'git:status:ignored-matching'\n\t| 'git:status:porcelain-v2'\n\t| 'git:untrackedCache'\n\t| 'git:worktree:list:lock-reason'\n\t| 'git:worktrees';\n\ntype ExtractPrefix<T> = T extends `${infer Prefix}:${infer Rest}`\n\t? Rest extends `${infer SubPrefix}:${string}`\n\t\t? T | `${Prefix}:${SubPrefix}` | Prefix\n\t\t: T | Prefix\n\t: never;\n\nexport type GitFeatureOrPrefix = ExtractPrefix<GitFeatures>;\nexport type FilteredGitFeatures<T extends GitFeatureOrPrefix> = T extends GitFeatures\n\t? T\n\t: Extract<GitFeatures, T | `${T}:${string}`>;\n\nexport const gitMinimumVersion = '2.7.2';\nexport const gitFeaturesByVersion = new Map<GitFeatures, string>([\n\t['git:checkout:pathspec-from-file', '2.26'],\n\t// 2.24 is when git READS the commit-graph file by default (core.commitGraph on); writing it for\n\t// older gits would be dead weight.\n\t['git:commit-graph', '2.24'],\n\t// Changed-path Bloom filters (`--changed-paths`), the v2 filter format — pre-2.31 shipped Bloom filters\n\t// with real correctness bugs (bad results for merge commits), so 2.31 is the safe floor to write them.\n\t['git:commit-graph:changed-paths', '2.31'],\n\t// A bare `fetch` reads every remote when `fetch.all` is set\n\t['git:fetch:all-config', '2.44'],\n\t// A fetch using a remote's configured refspecs creates its missing `refs/remotes/<remote>/HEAD`\n\t// (`remote.<name>.followRemoteHEAD`, default `create`)\n\t['git:fetch:follow-remote-head', '2.48'],\n\t['git:fetch:no-write-fetch-head', '2.29'],\n\t// `%(ahead-behind:<commit>)` in for-each-ref formats\n\t['git:for-each-ref:ahead-behind', '2.41'],\n\t['git:for-each-ref:worktreePath', '2.23'],\n\t// `core.fsmonitor=true` selects the built-in FSMonitor daemon (accepting a bool rather than only a\n\t// hook path). This 2.37 floor is the Windows + macOS one.\n\t['git:fsmonitor', '2.37'],\n\t// Linux got its own FSMonitor backend (inotify) only in 2.55, hence a separate floor. Note the backend\n\t// places a watch on EVERY directory, so a large repo can exhaust `fs.inotify.max_user_watches` and the\n\t// daemon fails at runtime even on a new enough git — the apply path must still probe, not just gate.\n\t['git:fsmonitor:linux', '2.55'],\n\t['git:ignoreRevsFile', '2.23'],\n\t// `index.skipHash` (skip the trailing index checksum for faster writes); part of `feature.manyFiles`.\n\t['git:index:skipHash', '2.40'],\n\t// `git maintenance run --task=…`. The `maintenance` builtin + `commit-graph`/`gc` tasks are 2.29, but\n\t// the loose-objects + incremental-repack tasks the auto tier uses landed in 2.30 (and `core.multiPackIndex`\n\t// is default-on from 2.30, so no separate midx config write is needed) — 2.30 is the safe floor.\n\t['git:maintenance', '2.30'],\n\t// The pack-refs maintenance task landed one release after the maintenance builtin.\n\t['git:maintenance:pack-refs', '2.31'],\n\t// `git maintenance start` — the subcommands exist from 2.30 (cron only); launchctl (macOS) + schtasks\n\t// (Windows) scheduling arrived in 2.31, so 2.31 is the cross-platform-safe floor.\n\t['git:maintenance:start', '2.31'],\n\t// `git maintenance start` systemd-timer scheduling. On Linux, 2.31–2.33 is cron-only and modern distros\n\t// often ship without cron (scheduling then silently fails); the systemd fallback arrived in 2.34.\n\t['git:maintenance:start:systemd', '2.34'],\n\t// `feature.manyFiles` umbrella (index v4 + untracked cache + skipHash where available).\n\t['git:manyFiles', '2.24'],\n\t['git:merge-tree:merge-base', '2.40'],\n\t// `merge-tree --write-tree` mode with `-z`/`--name-only`/`--no-messages`; the older trivial mode can't check conflicts\n\t['git:merge-tree:write-tree', '2.38'],\n\t// `patch-id` hashes a binary change through its blob ids; older ones hash the diff's text, so need `--binary` data\n\t['git:patch-id:binary-oids', '2.39'],\n\t// `patch-id --verbatim`: hashes whitespace and file-mode header lines instead of normalizing them away\n\t['git:patch-id:verbatim', '2.39'],\n\t['git:push:force-if-includes', '2.30.0'],\n\t// `--autosquash` WITHOUT `-i` (folding `fixup!`/`squash!` commits into a plain/automatic rebase).\n\t// Interactive rebases support autosquash on every git version GitLens supports, so this floor only\n\t// gates the non-interactive path.\n\t['git:rebase:autosquash', '2.44'],\n\t['git:rev-parse:end-of-options', '2.30'],\n\t['git:signing:ssh', '2.34.0'],\n\t['git:signing:x509', '2.19.0'],\n\t// Sparse-index support debuted earlier, but 2.33 shipped a corruption fix and 2.34 broadened command\n\t// support. Use 2.34 as the conservative floor before offering a repository mutation.\n\t['git:sparse-index', '2.34'],\n\t['git:stash:push:pathspecs', '2.13.2'],\n\t['git:stash:push:staged', '2.35.0'],\n\t['git:stash:push:stdin', '2.30.0'],\n\t['git:status:find-renames', '2.18'],\n\t// `status --ignored=matching`: an ignored folder is one entry, without a walk of what is inside it\n\t['git:status:ignored-matching', '2.16'],\n\t['git:status:porcelain-v2', '2.11'],\n\t// `core.untrackedCache=true` (repo-local cache of untracked-file scans — the auto-tier config lever).\n\t['git:untrackedCache', '2.8'],\n\t// `worktree list --porcelain` prints a lock's reason (`locked <reason>`); older ones print a bare `locked`\n\t['git:worktree:list:lock-reason', '2.31'],\n\t['git:worktrees', '2.17.0'],\n]);\n"],"mappings":";AAkDA,MAAa,oBAAoB;AACjC,MAAa,uCAAuB,IAAI,IAAyB;CAChE,CAAC,mCAAmC,MAAM;CAG1C,CAAC,oBAAoB,MAAM;CAG3B,CAAC,kCAAkC,MAAM;CAEzC,CAAC,wBAAwB,MAAM;CAG/B,CAAC,gCAAgC,MAAM;CACvC,CAAC,iCAAiC,MAAM;CAExC,CAAC,iCAAiC,MAAM;CACxC,CAAC,iCAAiC,MAAM;CAGxC,CAAC,iBAAiB,MAAM;CAIxB,CAAC,uBAAuB,MAAM;CAC9B,CAAC,sBAAsB,MAAM;CAE7B,CAAC,sBAAsB,MAAM;CAI7B,CAAC,mBAAmB,MAAM;CAE1B,CAAC,6BAA6B,MAAM;CAGpC,CAAC,yBAAyB,MAAM;CAGhC,CAAC,iCAAiC,MAAM;CAExC,CAAC,iBAAiB,MAAM;CACxB,CAAC,6BAA6B,MAAM;CAEpC,CAAC,6BAA6B,MAAM;CAEpC,CAAC,4BAA4B,MAAM;CAEnC,CAAC,yBAAyB,MAAM;CAChC,CAAC,8BAA8B,QAAQ;CAIvC,CAAC,yBAAyB,MAAM;CAChC,CAAC,gCAAgC,MAAM;CACvC,CAAC,mBAAmB,QAAQ;CAC5B,CAAC,oBAAoB,QAAQ;CAG7B,CAAC,oBAAoB,MAAM;CAC3B,CAAC,4BAA4B,QAAQ;CACrC,CAAC,yBAAyB,QAAQ;CAClC,CAAC,wBAAwB,QAAQ;CACjC,CAAC,2BAA2B,MAAM;CAElC,CAAC,+BAA+B,MAAM;CACtC,CAAC,2BAA2B,MAAM;CAElC,CAAC,sBAAsB,KAAK;CAE5B,CAAC,iCAAiC,MAAM;CACxC,CAAC,iBAAiB,QAAQ;AAC3B,CAAC"}
1
+ {"version":3,"file":"features.js","names":[],"sources":["../../../git/src/features.ts"],"sourcesContent":["// GitFeature's must start with `git:` to be recognized in all usages\nexport type GitFeatures =\n\t| 'git:checkout:pathspec-from-file'\n\t| 'git:commit-graph'\n\t| 'git:commit-graph:changed-paths'\n\t| 'git:fetch:all-config'\n\t| 'git:fetch:follow-remote-head'\n\t| 'git:fetch:no-write-fetch-head'\n\t| 'git:for-each-ref:ahead-behind'\n\t| 'git:for-each-ref:worktreePath'\n\t| 'git:fsmonitor'\n\t| 'git:fsmonitor:linux'\n\t| 'git:ignoreRevsFile'\n\t| 'git:index:skipHash'\n\t| 'git:maintenance'\n\t| 'git:maintenance:pack-refs'\n\t| 'git:maintenance:start'\n\t| 'git:maintenance:start:systemd'\n\t| 'git:manyFiles'\n\t| 'git:merge-tree:merge-base'\n\t| 'git:merge-tree:write-tree'\n\t| 'git:patch-id:binary-oids'\n\t| 'git:patch-id:verbatim'\n\t| 'git:push:force-if-includes'\n\t| 'git:rebase:autosquash'\n\t| 'git:rev-parse:end-of-options'\n\t| 'git:rev-parse:reflog-oldest-old-value'\n\t| 'git:signing:ssh'\n\t| 'git:signing:x509'\n\t| 'git:sparse-index'\n\t| 'git:stash:push:pathspecs'\n\t| 'git:stash:push:staged'\n\t| 'git:stash:push:stdin'\n\t| 'git:status:find-renames'\n\t| 'git:status:ignored-matching'\n\t| 'git:status:porcelain-v2'\n\t| 'git:untrackedCache'\n\t| 'git:worktree:list:lock-reason'\n\t| 'git:worktrees';\n\ntype ExtractPrefix<T> = T extends `${infer Prefix}:${infer Rest}`\n\t? Rest extends `${infer SubPrefix}:${string}`\n\t\t? T | `${Prefix}:${SubPrefix}` | Prefix\n\t\t: T | Prefix\n\t: never;\n\nexport type GitFeatureOrPrefix = ExtractPrefix<GitFeatures>;\nexport type FilteredGitFeatures<T extends GitFeatureOrPrefix> = T extends GitFeatures\n\t? T\n\t: Extract<GitFeatures, T | `${T}:${string}`>;\n\nexport const gitMinimumVersion = '2.7.2';\nexport const gitFeaturesByVersion = new Map<GitFeatures, string>([\n\t['git:checkout:pathspec-from-file', '2.26'],\n\t// 2.24 is when git READS the commit-graph file by default (core.commitGraph on); writing it for\n\t// older gits would be dead weight.\n\t['git:commit-graph', '2.24'],\n\t// Changed-path Bloom filters (`--changed-paths`), the v2 filter format — pre-2.31 shipped Bloom filters\n\t// with real correctness bugs (bad results for merge commits), so 2.31 is the safe floor to write them.\n\t['git:commit-graph:changed-paths', '2.31'],\n\t// A bare `fetch` reads every remote when `fetch.all` is set\n\t['git:fetch:all-config', '2.44'],\n\t// A fetch using a remote's configured refspecs creates its missing `refs/remotes/<remote>/HEAD`\n\t// (`remote.<name>.followRemoteHEAD`, default `create`)\n\t['git:fetch:follow-remote-head', '2.48'],\n\t['git:fetch:no-write-fetch-head', '2.29'],\n\t// `%(ahead-behind:<commit>)` in for-each-ref formats\n\t['git:for-each-ref:ahead-behind', '2.41'],\n\t['git:for-each-ref:worktreePath', '2.23'],\n\t// `core.fsmonitor=true` selects the built-in FSMonitor daemon (accepting a bool rather than only a\n\t// hook path). This 2.37 floor is the Windows + macOS one.\n\t['git:fsmonitor', '2.37'],\n\t// Linux got its own FSMonitor backend (inotify) only in 2.55, hence a separate floor. Note the backend\n\t// places a watch on EVERY directory, so a large repo can exhaust `fs.inotify.max_user_watches` and the\n\t// daemon fails at runtime even on a new enough git — the apply path must still probe, not just gate.\n\t['git:fsmonitor:linux', '2.55'],\n\t['git:ignoreRevsFile', '2.23'],\n\t// `index.skipHash` (skip the trailing index checksum for faster writes); part of `feature.manyFiles`.\n\t['git:index:skipHash', '2.40'],\n\t// `git maintenance run --task=…`. The `maintenance` builtin + `commit-graph`/`gc` tasks are 2.29, but\n\t// the loose-objects + incremental-repack tasks the auto tier uses landed in 2.30 (and `core.multiPackIndex`\n\t// is default-on from 2.30, so no separate midx config write is needed) — 2.30 is the safe floor.\n\t['git:maintenance', '2.30'],\n\t// The pack-refs maintenance task landed one release after the maintenance builtin.\n\t['git:maintenance:pack-refs', '2.31'],\n\t// `git maintenance start` — the subcommands exist from 2.30 (cron only); launchctl (macOS) + schtasks\n\t// (Windows) scheduling arrived in 2.31, so 2.31 is the cross-platform-safe floor.\n\t['git:maintenance:start', '2.31'],\n\t// `git maintenance start` systemd-timer scheduling. On Linux, 2.31–2.33 is cron-only and modern distros\n\t// often ship without cron (scheduling then silently fails); the systemd fallback arrived in 2.34.\n\t['git:maintenance:start:systemd', '2.34'],\n\t// `feature.manyFiles` umbrella (index v4 + untracked cache + skipHash where available).\n\t['git:manyFiles', '2.24'],\n\t['git:merge-tree:merge-base', '2.40'],\n\t// `merge-tree --write-tree` mode with `-z`/`--name-only`/`--no-messages`; the older trivial mode can't check conflicts\n\t['git:merge-tree:write-tree', '2.38'],\n\t// `patch-id` hashes a binary change through its blob ids; older ones hash the diff's text, so need `--binary` data\n\t['git:patch-id:binary-oids', '2.39'],\n\t// `patch-id --verbatim`: hashes whitespace and file-mode header lines instead of normalizing them away\n\t['git:patch-id:verbatim', '2.39'],\n\t['git:push:force-if-includes', '2.30.0'],\n\t// `--autosquash` WITHOUT `-i` (folding `fixup!`/`squash!` commits into a plain/automatic rebase).\n\t// Interactive rebases support autosquash on every git version GitLens supports, so this floor only\n\t// gates the non-interactive path.\n\t['git:rebase:autosquash', '2.44'],\n\t['git:rev-parse:end-of-options', '2.30'],\n\t// `<ref>@{<n>}` for a reflog of exactly n entries names the value before the oldest one, refusing only a null one\n\t// (the entry that created the ref); older ones refuse it either way\n\t['git:rev-parse:reflog-oldest-old-value', '2.31'],\n\t['git:signing:ssh', '2.34.0'],\n\t['git:signing:x509', '2.19.0'],\n\t// Sparse-index support debuted earlier, but 2.33 shipped a corruption fix and 2.34 broadened command\n\t// support. Use 2.34 as the conservative floor before offering a repository mutation.\n\t['git:sparse-index', '2.34'],\n\t['git:stash:push:pathspecs', '2.13.2'],\n\t['git:stash:push:staged', '2.35.0'],\n\t['git:stash:push:stdin', '2.30.0'],\n\t['git:status:find-renames', '2.18'],\n\t// `status --ignored=matching`: an ignored folder is one entry, without a walk of what is inside it\n\t['git:status:ignored-matching', '2.16'],\n\t['git:status:porcelain-v2', '2.11'],\n\t// `core.untrackedCache=true` (repo-local cache of untracked-file scans — the auto-tier config lever).\n\t['git:untrackedCache', '2.8'],\n\t// `worktree list --porcelain` prints a lock's reason (`locked <reason>`); older ones print a bare `locked`\n\t['git:worktree:list:lock-reason', '2.31'],\n\t['git:worktrees', '2.17.0'],\n]);\n"],"mappings":";AAmDA,MAAa,oBAAoB;AACjC,MAAa,uCAAuB,IAAI,IAAyB;CAChE,CAAC,mCAAmC,MAAM;CAG1C,CAAC,oBAAoB,MAAM;CAG3B,CAAC,kCAAkC,MAAM;CAEzC,CAAC,wBAAwB,MAAM;CAG/B,CAAC,gCAAgC,MAAM;CACvC,CAAC,iCAAiC,MAAM;CAExC,CAAC,iCAAiC,MAAM;CACxC,CAAC,iCAAiC,MAAM;CAGxC,CAAC,iBAAiB,MAAM;CAIxB,CAAC,uBAAuB,MAAM;CAC9B,CAAC,sBAAsB,MAAM;CAE7B,CAAC,sBAAsB,MAAM;CAI7B,CAAC,mBAAmB,MAAM;CAE1B,CAAC,6BAA6B,MAAM;CAGpC,CAAC,yBAAyB,MAAM;CAGhC,CAAC,iCAAiC,MAAM;CAExC,CAAC,iBAAiB,MAAM;CACxB,CAAC,6BAA6B,MAAM;CAEpC,CAAC,6BAA6B,MAAM;CAEpC,CAAC,4BAA4B,MAAM;CAEnC,CAAC,yBAAyB,MAAM;CAChC,CAAC,8BAA8B,QAAQ;CAIvC,CAAC,yBAAyB,MAAM;CAChC,CAAC,gCAAgC,MAAM;CAGvC,CAAC,yCAAyC,MAAM;CAChD,CAAC,mBAAmB,QAAQ;CAC5B,CAAC,oBAAoB,QAAQ;CAG7B,CAAC,oBAAoB,MAAM;CAC3B,CAAC,4BAA4B,QAAQ;CACrC,CAAC,yBAAyB,QAAQ;CAClC,CAAC,wBAAwB,QAAQ;CACjC,CAAC,2BAA2B,MAAM;CAElC,CAAC,+BAA+B,MAAM;CACtC,CAAC,2BAA2B,MAAM;CAElC,CAAC,sBAAsB,KAAK;CAE5B,CAAC,iCAAiC,MAAM;CACxC,CAAC,iBAAiB,QAAQ;AAC3B,CAAC"}
@@ -41,6 +41,8 @@ export interface AheadOfBase {
41
41
  readonly reason?: 'none' | 'self' | 'unborn';
42
42
  /** Full ref names (`refs/heads/main`, `refs/remotes/origin/main`, `refs/remotes/origin/HEAD`) whose movement can change this answer, given with or without a base so a watcher also learns when one appears: the branch, every candidate compared up to the chosen one, their tracking refs, the default-branch symrefs and the `<remote>/main`/`<remote>/master` refs when the default was consulted, the `upstream` remote's copy of the created-from or default branch when it was compared, and the branch's upstream when it is the default branch. Lets a watcher recompute only when one of them moves. */
43
43
  readonly dependsOn: readonly string[];
44
+ /** The default branch the read resolved from local refs and the remotes' `HEAD` symrefs, `null` when none did; absent when it didn't consult the default branch (a recorded candidate decided the base first) */
45
+ readonly defaultBranch?: string | null;
44
46
  }
45
47
  /** A ref {@link GitPushTarget} names, with its commit from the cached ref list */
46
48
  export interface GitPushTargetRef {
@@ -179,6 +181,33 @@ export interface GitBranchesSubProvider {
179
181
  * `undefined` means the inputs could not be read; providers without this operation require a full base read.
180
182
  */
181
183
  getAheadOfBaseConfigKey?(repoPath: string, cancellation?: AbortSignal): Promise<string | undefined>;
184
+ /**
185
+ * The Git config entries {@link getAheadOfBaseConfigKey} is taken from, in Git's order with repeated keys kept: each
186
+ * key as Git lists it (section and name lowercased, the subsection as written) with its value, `undefined` for a key
187
+ * set without one. A worktree summary keys its base on every entry except the `branch.<name>` ones of branches its
188
+ * base read didn't depend on, so another branch's tracking changing leaves its base alone. `undefined` means the
189
+ * inputs could not be read.
190
+ */
191
+ getAheadOfBaseConfigEntries?(repoPath: string, cancellation?: AbortSignal): Promise<readonly (readonly [key: string, value: string | undefined])[] | undefined>;
192
+ /**
193
+ * Re-measures `previous`, a `getAheadOfBase` answer for `branch` read with HEAD at `previous.head`, after a write
194
+ * moved `moved` (full ref names from `previous.dependsOn`), without resolving the base again. It answers only when
195
+ * that provably equals a fresh `getAheadOfBase`: HEAD is still at `previous.head`, the refs that moved are the base's
196
+ * own ref (the only listed ref its name answers to) and any `<remote>/HEAD` symref still pointing at it, with the
197
+ * default branch resolving as `previous.defaultBranch` when one did (absent, it can't be shown), the repository
198
+ * isn't shallow, the base moved only forward (its old tip and every value its reflog
199
+ * took since are in its new tip's history), and HEAD's count past its new tip, `previous.base.forkPoint` and every
200
+ * commit the base's reflog reaches off its new tip's history is unchanged. Equal counts mean none of HEAD's commits
201
+ * reached the base, the fork point is in its history, and the reflog shares no more of HEAD's history than the tip
202
+ * does, so HEAD's common history with the base, and with that the fork point and the choice of base, stay as they
203
+ * were; only `base.sha` moves, and `defaultBranch` is kept. `undefined` when any of that can't be shown, and a full `getAheadOfBase` is due.
204
+ */
205
+ getAheadOfBaseAfterBaseMove?(repoPath: string, branch: string | undefined, previous: {
206
+ readonly head: string;
207
+ readonly base: NonNullable<AheadOfBase['base']>;
208
+ readonly dependsOn: readonly string[];
209
+ readonly defaultBranch?: string | null;
210
+ }, moved: readonly string[], cancellation?: AbortSignal): Promise<AheadOfBase | undefined>;
182
211
  /**
183
212
  * How far each branch has diverged from `base`, keyed by full ref name (`refs/heads/x`, plus `refs/remotes/<remote>/x`
184
213
  * when `options.remotes` is set) so a local and a remote name can't collide. Symbolic refs such as
@@ -154,6 +154,9 @@ export interface GitRunOptions {
154
154
  * for every existing caller. The change is announced for `cwd`, or the `-C` target, as given: run from the
155
155
  * repository root, since that's the path core's caches are keyed by. A `-C`-scoped verb whose effect every
156
156
  * worktree shares (`branch`, `fetch`, …) is announced for the caller's `cwd` too, resetting once per repository.
157
+ * `'infer'` names the refs the argv proves it moved, and for a verb that moves only HEAD's branch (a commit, a
158
+ * reset) reads HEAD before and after the run: on one branch both times it names that branch, detached both times
159
+ * it announces no branch move.
157
160
  *
158
161
  * The announcement resets only the caches the announced kinds can have made stale, the same ones a file
159
162
  * watcher's change of those kinds clears, and drops the repository's pending git runs. A write `'infer'`
@@ -6,7 +6,7 @@ import { RepositoryWatchService, WatchPollingOptions } from "./watching/watchSer
6
6
  import { MergeTargetsChangeEvent } from "./cache.js";
7
7
  import { GitProvider } from "./providers/provider.js";
8
8
  import { RepositoryService } from "./repositoryService.js";
9
- import { WorktreeSummaryService } from "./watching/worktreeSummary.js";
9
+ import { WorktreeSummaryService, WorktreeSummaryServiceOptions } from "./watching/worktreeSummary.js";
10
10
  //#region ../git/src/service.d.ts
11
11
  export interface ProvidersChangeEvent {
12
12
  readonly added: readonly GitProvider[];
@@ -62,11 +62,13 @@ export declare class GitService implements UnifiedDisposable {
62
62
  private static _instance;
63
63
  /**
64
64
  * `options.watchPolling` opts the watch service into polling what `watchingProvider` reports it can't watch, and
65
- * `options.readFile` lets it tell what a reported `.git/config` change changed (see `WatchServiceOptions.readFile`)
65
+ * `options.readFile` lets it tell what a reported `.git/config` change changed (see `WatchServiceOptions.readFile`).
66
+ * `options.verifyBaseRemeasure` is for tests only (see `WorktreeSummaryServiceOptions.verifyBaseRemeasure`)
66
67
  */
67
68
  static createSingleton(watchingProvider?: FileWatchingProvider, options?: {
68
69
  watchPolling?: WatchPollingOptions;
69
70
  readFile?: (path: string) => Promise<Uint8Array>;
71
+ verifyBaseRemeasure?: WorktreeSummaryServiceOptions['verifyBaseRemeasure'];
70
72
  }): GitService;
71
73
  private readonly _onDidChangeProviders;
72
74
  get onDidChangeProviders(): Event<ProvidersChangeEvent>;
@@ -35,11 +35,12 @@ var GitService = class GitService {
35
35
  static _instance;
36
36
  /**
37
37
  * `options.watchPolling` opts the watch service into polling what `watchingProvider` reports it can't watch, and
38
- * `options.readFile` lets it tell what a reported `.git/config` change changed (see `WatchServiceOptions.readFile`)
38
+ * `options.readFile` lets it tell what a reported `.git/config` change changed (see `WatchServiceOptions.readFile`).
39
+ * `options.verifyBaseRemeasure` is for tests only (see `WorktreeSummaryServiceOptions.verifyBaseRemeasure`)
39
40
  */
40
41
  static createSingleton(watchingProvider, options) {
41
42
  if (GitService._instance != null) throw new Error("GitService already exists — only one instance is allowed");
42
- const instance = new GitService(watchingProvider, options?.watchPolling, options?.readFile);
43
+ const instance = new GitService(watchingProvider, options?.watchPolling, options?.readFile, options?.verifyBaseRemeasure);
43
44
  GitService._instance = instance;
44
45
  return instance;
45
46
  }
@@ -63,7 +64,7 @@ var GitService = class GitService {
63
64
  _serviceCache = /* @__PURE__ */ new Map();
64
65
  _watchService;
65
66
  _summaries;
66
- constructor(watchingProvider, watchPolling, readFile) {
67
+ constructor(watchingProvider, watchPolling, readFile, verifyBaseRemeasure) {
67
68
  if (watchingProvider != null) {
68
69
  this._watchService = new RepositoryWatchService({
69
70
  watchingProvider,
@@ -78,7 +79,8 @@ var GitService = class GitService {
78
79
  watchService: this._watchService,
79
80
  getRepository: (repoPath) => this.forRepo(repoPath),
80
81
  onDidChangeMergeTargets: this.onDidChangeMergeTargets,
81
- getCommonPath: (repoPath) => this.getProvider(repoPath)?.provider.getCommonPath?.(repoPath)
82
+ getCommonPath: (repoPath) => this.getProvider(repoPath)?.provider.getCommonPath?.(repoPath),
83
+ verifyBaseRemeasure
82
84
  });
83
85
  }
84
86
  setGlobalRepositoryServiceResolver((repoPath) => this.forRepo(repoPath));
@@ -1 +1 @@
1
- {"version":3,"file":"service.js","names":[],"sources":["../../../git/src/service.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 { normalizePath } from '@gitlens/utils/path.js';\nimport type { Uri } from '@gitlens/utils/uri.js';\nimport { getRepositoryKey } from '@gitlens/utils/uri.js';\nimport type { MergeTargetsChangeEvent } from './cache.js';\nimport type { GitProvider } from './providers/provider.js';\nimport type { RepositoryService, SubProviderForRepo } from './repositoryService.js';\nimport {\n\tclearGlobalRepositoryServiceResolver,\n\tcreateSubProviderProxyForRepo,\n\tsetGlobalRepositoryServiceResolver,\n} from './repositoryService.js';\nimport type { FileWatchingProvider } from './watching/provider.js';\nimport type { WatchPollingOptions } from './watching/watchService.js';\nimport { RepositoryWatchService } from './watching/watchService.js';\nimport { WorktreeSummaryService } from './watching/worktreeSummary.js';\n\nexport interface ProvidersChangeEvent {\n\treadonly added: readonly GitProvider[];\n\treadonly removed: readonly GitProvider[];\n\treadonly etag: number;\n}\n\n/**\n * Result of {@link GitService.validateRepo}.\n *\n * - `valid: true, safe: true` — a usable git repository at `repoPath`; `gitDir` / `commonGitDir` / `superprojectPath` are populated when available\n * - `valid: true, safe: false` — git detected unsafe (dubious) ownership at the given path; no `repoPath` is surfaced (the raw call doesn't carry one back)\n * - `valid: false` — not a git repository, no registered provider can handle the path, or the routed provider doesn't implement `getRepositoryInfo`\n */\nexport type ValidateRepoResult =\n\t| {\n\t\t\tvalid: true;\n\t\t\tsafe: true;\n\t\t\trepoPath: string;\n\t\t\tgitDir?: string;\n\t\t\tcommonGitDir?: string;\n\t\t\tsuperprojectPath?: string;\n\t }\n\t| { valid: true; safe: false }\n\t| { valid: false };\n\ninterface RegisteredProvider {\n\tprovider: GitProvider;\n\tcanHandle: (repoPath: string) => boolean;\n\t/** Forwards the provider's write announcements to {@link GitService.watchService}, when there is one */\n\tannouncements?: UnifiedDisposable;\n}\n\ninterface MergeTargetsSource {\n\treadonly subscription: UnifiedDisposable;\n\trefCount: number;\n}\n\n/**\n * Entry point — provider router, repo-scoped proxy cache, and global configuration.\n *\n * Creating an instance wires up the module-level repository\n * resolver. Calling {@link dispose} tears it down.\n *\n * Consumers register providers with routing predicates, then call\n * {@link forRepo} to get a {@link RepositoryService} with sub-provider\n * proxies that auto-inject the repository path.\n *\n * The service has no discovery or repository tracking state — it trusts\n * the caller to pass valid repository paths. Discovery and lifecycle\n * (open/close, file watching, etc.) are application-level concerns.\n *\n * @example\n * ```typescript\n * const service = GitService.createSingleton();\n * service.register(cliProvider, (path) => true);\n *\n * const repo = service.forRepo('/home/user/repo');\n * const branches = await repo.branches.getBranches();\n *\n * service.dispose(); // cleans up module-level hooks\n * ```\n */\nexport class GitService implements UnifiedDisposable {\n\tprivate static _instance: GitService | undefined;\n\n\t/**\n\t * `options.watchPolling` opts the watch service into polling what `watchingProvider` reports it can't watch, and\n\t * `options.readFile` lets it tell what a reported `.git/config` change changed (see `WatchServiceOptions.readFile`)\n\t */\n\tstatic createSingleton(\n\t\twatchingProvider?: FileWatchingProvider,\n\t\toptions?: { watchPolling?: WatchPollingOptions; readFile?: (path: string) => Promise<Uint8Array> },\n\t): GitService {\n\t\tif (GitService._instance != null) {\n\t\t\tthrow new Error('GitService already exists — only one instance is allowed');\n\t\t}\n\n\t\tconst instance = new GitService(watchingProvider, options?.watchPolling, options?.readFile);\n\t\tGitService._instance = instance;\n\t\treturn instance;\n\t}\n\n\tprivate readonly _onDidChangeProviders = new Emitter<ProvidersChangeEvent>();\n\tget onDidChangeProviders(): Event<ProvidersChangeEvent> {\n\t\treturn this._onDidChangeProviders.event;\n\t}\n\n\tprivate readonly _onDidChangeMergeTargets = new Emitter<MergeTargetsChangeEvent>();\n\t/**\n\t * Fires when a write changed a branch's merge-relevant `.git/gk/config` keys, for any registered provider that\n\t * reports it — never for activity timestamps. Core's own key write fires it once, as the write completes, naming\n\t * the branch. Once per change per repository (`repoPath` is its common path); see {@link MergeTargetsChangeEvent}.\n\t */\n\tget onDidChangeMergeTargets(): Event<MergeTargetsChangeEvent> {\n\t\treturn this._onDidChangeMergeTargets.event;\n\t}\n\n\t/** Keyed by the provider's event, so providers sharing one cache are forwarded once */\n\tprivate readonly _mergeTargetsSources = new Map<Event<MergeTargetsChangeEvent>, MergeTargetsSource>();\n\n\tprivate _etag = 0;\n\tprivate readonly _providers: RegisteredProvider[] = [];\n\tprivate readonly _serviceCache = new Map<string, RepositoryService>();\n\tprivate readonly _watchService: RepositoryWatchService | undefined;\n\tprivate readonly _summaries: WorktreeSummaryService | undefined;\n\n\tprivate constructor(\n\t\twatchingProvider?: FileWatchingProvider,\n\t\twatchPolling?: WatchPollingOptions,\n\t\treadFile?: (path: string) => Promise<Uint8Array>,\n\t) {\n\t\tif (watchingProvider != null) {\n\t\t\tthis._watchService = new RepositoryWatchService({\n\t\t\t\twatchingProvider: watchingProvider,\n\t\t\t\tpolling: watchPolling,\n\t\t\t\treadFile: readFile,\n\t\t\t\tgetIgnoreFilter: (repoPath, gitDirPath) => {\n\t\t\t\t\treturn this.getProvider(repoPath)?.provider.getIgnoreFilter?.(repoPath, gitDirPath);\n\t\t\t\t},\n\t\t\t\tgetIgnoredDirectories: repoPath =>\n\t\t\t\t\tthis.getProvider(repoPath)?.provider.getIgnoredDirectories?.(repoPath) ?? Promise.resolve([]),\n\t\t\t});\n\t\t\tthis._summaries = new WorktreeSummaryService({\n\t\t\t\twatchService: this._watchService,\n\t\t\t\tgetRepository: repoPath => this.forRepo(repoPath),\n\t\t\t\tonDidChangeMergeTargets: this.onDidChangeMergeTargets,\n\t\t\t\tgetCommonPath: repoPath => this.getProvider(repoPath)?.provider.getCommonPath?.(repoPath),\n\t\t\t});\n\t\t}\n\n\t\tsetGlobalRepositoryServiceResolver(repoPath => this.forRepo(repoPath));\n\t}\n\n\t[Symbol.dispose](): void {\n\t\tthis.dispose();\n\t}\n\n\tdispose(): void {\n\t\tclearGlobalRepositoryServiceResolver();\n\t\tGitService._instance = undefined;\n\t\tthis._serviceCache.clear();\n\t\tthis._summaries?.dispose();\n\t\tthis._watchService?.dispose();\n\n\t\t// Don't dispose providers since they're owned by the caller, but do clear the array to release references\n\t\tfor (const entry of this._providers) {\n\t\t\tentry.announcements?.dispose();\n\t\t}\n\t\tthis._providers.length = 0;\n\n\t\tfor (const source of this._mergeTargetsSources.values()) {\n\t\t\tsource.subscription.dispose();\n\t\t}\n\t\tthis._mergeTargetsSources.clear();\n\n\t\tthis._onDidChangeProviders.dispose();\n\t\tthis._onDidChangeMergeTargets.dispose();\n\t}\n\n\t/** Monotonically increasing counter incremented on structural changes. */\n\tget etag(): number {\n\t\treturn this._etag;\n\t}\n\n\t/** Whether any providers are registered. */\n\tget hasProviders(): boolean {\n\t\treturn this._providers.length > 0;\n\t}\n\n\tget watchService(): RepositoryWatchService | undefined {\n\t\treturn this._watchService;\n\t}\n\n\t/** Cached, change-notifying per-worktree summaries, kept current by {@link watchService}; present when it is */\n\tget summaries(): WorktreeSummaryService | undefined {\n\t\treturn this._summaries;\n\t}\n\n\t/**\n\t * Registers a provider with a routing predicate.\n\t *\n\t * @returns A disposable that unregisters the provider.\n\t */\n\tregister(provider: GitProvider, canHandle: (repoPath: string) => boolean): UnifiedDisposable {\n\t\tconst watchService = this._watchService;\n\t\tconst entry: RegisteredProvider = {\n\t\t\tprovider: provider,\n\t\t\tcanHandle: canHandle,\n\t\t\tannouncements:\n\t\t\t\twatchService != null\n\t\t\t\t\t? provider.onDidAnnounceChanges?.(e =>\n\t\t\t\t\t\t\twatchService.noteSelfWrite(e.repoPath, e.changes, e.refs, {\n\t\t\t\t\t\t\t\tconfigConfinedToRefs: e.configConfinedToRefs,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t)\n\t\t\t\t\t: undefined,\n\t\t};\n\t\tthis._providers.push(entry);\n\t\tthis._etag++;\n\t\tthis.addMergeTargetsSource(provider);\n\n\t\tthis._onDidChangeProviders.fire({\n\t\t\tadded: [provider],\n\t\t\tremoved: [],\n\t\t\tetag: this._etag,\n\t\t});\n\n\t\t// Invalidate cached proxies since provider routing may have changed\n\t\tthis._serviceCache.clear();\n\n\t\treturn createDisposable(() => {\n\t\t\tconst idx = this._providers.indexOf(entry);\n\t\t\tif (idx !== -1) {\n\t\t\t\tthis._providers.splice(idx, 1);\n\t\t\t\tentry.announcements?.dispose();\n\t\t\t\tthis._etag++;\n\t\t\t\tthis.removeMergeTargetsSource(provider);\n\t\t\t\tthis._serviceCache.clear();\n\n\t\t\t\tthis._onDidChangeProviders.fire({\n\t\t\t\t\tadded: [],\n\t\t\t\t\tremoved: [provider],\n\t\t\t\t\tetag: this._etag,\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\t}\n\n\tprivate addMergeTargetsSource(provider: GitProvider): void {\n\t\tconst event = provider.onDidChangeMergeTargets;\n\t\tif (event == null) return;\n\n\t\tconst source = this._mergeTargetsSources.get(event);\n\t\tif (source != null) {\n\t\t\tsource.refCount++;\n\t\t\treturn;\n\t\t}\n\n\t\tthis._mergeTargetsSources.set(event, {\n\t\t\tsubscription: event(e => this._onDidChangeMergeTargets.fire(e)),\n\t\t\trefCount: 1,\n\t\t});\n\t}\n\n\tprivate removeMergeTargetsSource(provider: GitProvider): void {\n\t\tconst event = provider.onDidChangeMergeTargets;\n\t\tif (event == null) return;\n\n\t\tconst source = this._mergeTargetsSources.get(event);\n\t\tif (source == null || --source.refCount > 0) return;\n\n\t\tsource.subscription.dispose();\n\t\tthis._mergeTargetsSources.delete(event);\n\t}\n\n\t/** Returns the provider that can handle the given path, or undefined if none matches. */\n\tgetProvider(repoPath: string): { provider: GitProvider; path: string } | undefined {\n\t\trepoPath = normalizePath(repoPath);\n\t\tfor (const entry of this._providers) {\n\t\t\tif (entry.canHandle(repoPath)) {\n\t\t\t\treturn { provider: entry.provider, path: repoPath };\n\t\t\t}\n\t\t}\n\t\treturn undefined;\n\t}\n\n\t/** Returns all registered providers. */\n\t*getProviders(): Iterable<GitProvider> {\n\t\tfor (const entry of this._providers) {\n\t\t\tyield entry.provider;\n\t\t}\n\t}\n\n\t/**\n\t * Routes a repo URI/path to a provider and returns a cached\n\t * {@link RepositoryService} with repo-scoped sub-provider proxies.\n\t * Returns undefined if no provider can handle the path.\n\t */\n\tforRepo(repoUri: Uri | string): RepositoryService | undefined {\n\t\tconst repoPath = getRepositoryKey(repoUri);\n\n\t\tlet repo = this._serviceCache.get(repoPath);\n\t\tif (repo != null) return repo;\n\n\t\tconst result = this.getProvider(repoPath);\n\t\tif (result == null) return undefined;\n\n\t\trepo = createRepositoryService(result.provider, repoPath, this._watchService);\n\t\tthis._serviceCache.set(repoPath, repo);\n\t\treturn repo;\n\t}\n\n\t/**\n\t * Evicts the cached {@link RepositoryService} for a closed or removed repository.\n\t * No-op if the path isn't cached.\n\t */\n\tcloseRepo(repoUri: Uri | string): void {\n\t\tconst repoPath = getRepositoryKey(repoUri);\n\t\tthis._serviceCache.delete(repoPath);\n\t}\n\n\t/**\n\t * Validates whether `pathOrUri` points to (or resides inside) a git repository,\n\t * and returns information about it when it does.\n\t *\n\t * Routes the path to a registered provider and delegates to its config sub-provider's\n\t * `getRepositoryInfo` implementation, then normalizes the raw result into a clean\n\t * discriminated union. For a simple yes/no check, use `(await validateRepo(path)).valid`.\n\t *\n\t * Returns `{ valid: false }` if no provider can handle the path or if the routed\n\t * provider does not implement `getRepositoryInfo`.\n\t */\n\tasync validateRepo(pathOrUri: Uri | string): Promise<ValidateRepoResult> {\n\t\tconst key = getRepositoryKey(pathOrUri);\n\t\tconst routed = this.getProvider(key);\n\t\tif (routed == null) return { valid: false };\n\n\t\tconst info = await routed.provider.config.getRepositoryInfo?.(routed.path);\n\t\tif (info == null) return { valid: false };\n\n\t\tif (Array.isArray(info)) {\n\t\t\tif (info.length === 0) return { valid: false };\n\t\t\tif (info[0] === false) return { valid: true, safe: false };\n\t\t\treturn { valid: true, safe: true, repoPath: info[1] };\n\t\t}\n\n\t\treturn {\n\t\t\tvalid: true,\n\t\t\tsafe: true,\n\t\t\trepoPath: info.repoPath,\n\t\t\tgitDir: info.gitDir,\n\t\t\tcommonGitDir: info.commonGitDir,\n\t\t\tsuperprojectPath: info.superprojectPath,\n\t\t};\n\t}\n}\n\nfunction createRepositoryService(\n\tprovider: GitProvider,\n\trepoPath: string,\n\twatchService?: RepositoryWatchService,\n): RepositoryService {\n\tconst proxies = new Map<string, SubProviderForRepo<unknown>>();\n\n\tfunction getProxy<T extends object>(prop: string, subProvider: T | undefined): SubProviderForRepo<T> | undefined {\n\t\tif (subProvider == null) return undefined;\n\n\t\tlet proxy = proxies.get(prop);\n\t\tif (proxy == null) {\n\t\t\tproxy = createSubProviderProxyForRepo(subProvider, repoPath);\n\t\t\tproxies.set(prop, proxy);\n\t\t}\n\t\treturn proxy as SubProviderForRepo<T>;\n\t}\n\n\treturn {\n\t\tpath: repoPath,\n\t\tprovider: provider.descriptor,\n\n\t\tget etagWorkingTree(): number | undefined {\n\t\t\treturn watchService?.getSession(repoPath)?.etagWorkingTree;\n\t\t},\n\n\t\tgetAbsoluteUri: (relativePath: string): Uri => {\n\t\t\treturn provider.getAbsoluteUri(relativePath, repoPath);\n\t\t},\n\n\t\t// Required sub-providers\n\t\tget branches() {\n\t\t\tconst proxy = getProxy('branches', provider.branches);\n\t\t\tif (proxy == null) throw new Error('GitService: branches sub-provider is required but not registered');\n\t\t\treturn proxy;\n\t\t},\n\t\tget commits() {\n\t\t\tconst proxy = getProxy('commits', provider.commits);\n\t\t\tif (proxy == null) throw new Error('GitService: commits sub-provider is required but not registered');\n\t\t\treturn proxy;\n\t\t},\n\t\tget config() {\n\t\t\tconst proxy = getProxy('config', provider.config);\n\t\t\tif (proxy == null) throw new Error('GitService: config sub-provider is required but not registered');\n\t\t\treturn proxy;\n\t\t},\n\t\tget contributors() {\n\t\t\tconst proxy = getProxy('contributors', provider.contributors);\n\t\t\tif (proxy == null) throw new Error('GitService: contributors sub-provider is required but not registered');\n\t\t\treturn proxy;\n\t\t},\n\t\tget diff() {\n\t\t\tconst proxy = getProxy('diff', provider.diff);\n\t\t\tif (proxy == null) throw new Error('GitService: diff sub-provider is required but not registered');\n\t\t\treturn proxy;\n\t\t},\n\t\tget graph() {\n\t\t\tconst proxy = getProxy('graph', provider.graph);\n\t\t\tif (proxy == null) throw new Error('GitService: graph sub-provider is required but not registered');\n\t\t\treturn proxy;\n\t\t},\n\t\tget refs() {\n\t\t\tconst proxy = getProxy('refs', provider.refs);\n\t\t\tif (proxy == null) throw new Error('GitService: refs sub-provider is required but not registered');\n\t\t\treturn proxy;\n\t\t},\n\t\tget remotes() {\n\t\t\tconst proxy = getProxy('remotes', provider.remotes);\n\t\t\tif (proxy == null) throw new Error('GitService: remotes sub-provider is required but not registered');\n\t\t\treturn proxy;\n\t\t},\n\t\tget revision() {\n\t\t\tconst proxy = getProxy('revision', provider.revision);\n\t\t\tif (proxy == null) throw new Error('GitService: revision sub-provider is required but not registered');\n\t\t\treturn proxy;\n\t\t},\n\t\tget status() {\n\t\t\tconst proxy = getProxy('status', provider.status);\n\t\t\tif (proxy == null) throw new Error('GitService: status sub-provider is required but not registered');\n\t\t\treturn proxy;\n\t\t},\n\t\tget tags() {\n\t\t\tconst proxy = getProxy('tags', provider.tags);\n\t\t\tif (proxy == null) throw new Error('GitService: tags sub-provider is required but not registered');\n\t\t\treturn proxy;\n\t\t},\n\n\t\t// Optional sub-providers\n\t\tget blame() {\n\t\t\treturn getProxy('blame', provider.blame);\n\t\t},\n\t\tget maintenance() {\n\t\t\treturn getProxy('maintenance', provider.maintenance);\n\t\t},\n\t\tget ops() {\n\t\t\treturn getProxy('ops', provider.ops);\n\t\t},\n\t\tget patch() {\n\t\t\treturn getProxy('patch', provider.patch);\n\t\t},\n\t\tget pausedOps() {\n\t\t\treturn getProxy('pausedOps', provider.pausedOps);\n\t\t},\n\t\tget staging() {\n\t\t\treturn getProxy('staging', provider.staging);\n\t\t},\n\t\tget stash() {\n\t\t\treturn getProxy('stash', provider.stash);\n\t\t},\n\t\tget worktrees() {\n\t\t\treturn getProxy('worktrees', provider.worktrees);\n\t\t},\n\t};\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"service.js","names":[],"sources":["../../../git/src/service.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 { normalizePath } from '@gitlens/utils/path.js';\nimport type { Uri } from '@gitlens/utils/uri.js';\nimport { getRepositoryKey } from '@gitlens/utils/uri.js';\nimport type { MergeTargetsChangeEvent } from './cache.js';\nimport type { GitProvider } from './providers/provider.js';\nimport type { RepositoryService, SubProviderForRepo } from './repositoryService.js';\nimport {\n\tclearGlobalRepositoryServiceResolver,\n\tcreateSubProviderProxyForRepo,\n\tsetGlobalRepositoryServiceResolver,\n} from './repositoryService.js';\nimport type { FileWatchingProvider } from './watching/provider.js';\nimport type { WatchPollingOptions } from './watching/watchService.js';\nimport { RepositoryWatchService } from './watching/watchService.js';\nimport type { WorktreeSummaryServiceOptions } from './watching/worktreeSummary.js';\nimport { WorktreeSummaryService } from './watching/worktreeSummary.js';\n\nexport interface ProvidersChangeEvent {\n\treadonly added: readonly GitProvider[];\n\treadonly removed: readonly GitProvider[];\n\treadonly etag: number;\n}\n\n/**\n * Result of {@link GitService.validateRepo}.\n *\n * - `valid: true, safe: true` — a usable git repository at `repoPath`; `gitDir` / `commonGitDir` / `superprojectPath` are populated when available\n * - `valid: true, safe: false` — git detected unsafe (dubious) ownership at the given path; no `repoPath` is surfaced (the raw call doesn't carry one back)\n * - `valid: false` — not a git repository, no registered provider can handle the path, or the routed provider doesn't implement `getRepositoryInfo`\n */\nexport type ValidateRepoResult =\n\t| {\n\t\t\tvalid: true;\n\t\t\tsafe: true;\n\t\t\trepoPath: string;\n\t\t\tgitDir?: string;\n\t\t\tcommonGitDir?: string;\n\t\t\tsuperprojectPath?: string;\n\t }\n\t| { valid: true; safe: false }\n\t| { valid: false };\n\ninterface RegisteredProvider {\n\tprovider: GitProvider;\n\tcanHandle: (repoPath: string) => boolean;\n\t/** Forwards the provider's write announcements to {@link GitService.watchService}, when there is one */\n\tannouncements?: UnifiedDisposable;\n}\n\ninterface MergeTargetsSource {\n\treadonly subscription: UnifiedDisposable;\n\trefCount: number;\n}\n\n/**\n * Entry point — provider router, repo-scoped proxy cache, and global configuration.\n *\n * Creating an instance wires up the module-level repository\n * resolver. Calling {@link dispose} tears it down.\n *\n * Consumers register providers with routing predicates, then call\n * {@link forRepo} to get a {@link RepositoryService} with sub-provider\n * proxies that auto-inject the repository path.\n *\n * The service has no discovery or repository tracking state — it trusts\n * the caller to pass valid repository paths. Discovery and lifecycle\n * (open/close, file watching, etc.) are application-level concerns.\n *\n * @example\n * ```typescript\n * const service = GitService.createSingleton();\n * service.register(cliProvider, (path) => true);\n *\n * const repo = service.forRepo('/home/user/repo');\n * const branches = await repo.branches.getBranches();\n *\n * service.dispose(); // cleans up module-level hooks\n * ```\n */\nexport class GitService implements UnifiedDisposable {\n\tprivate static _instance: GitService | undefined;\n\n\t/**\n\t * `options.watchPolling` opts the watch service into polling what `watchingProvider` reports it can't watch, and\n\t * `options.readFile` lets it tell what a reported `.git/config` change changed (see `WatchServiceOptions.readFile`).\n\t * `options.verifyBaseRemeasure` is for tests only (see `WorktreeSummaryServiceOptions.verifyBaseRemeasure`)\n\t */\n\tstatic createSingleton(\n\t\twatchingProvider?: FileWatchingProvider,\n\t\toptions?: {\n\t\t\twatchPolling?: WatchPollingOptions;\n\t\t\treadFile?: (path: string) => Promise<Uint8Array>;\n\t\t\tverifyBaseRemeasure?: WorktreeSummaryServiceOptions['verifyBaseRemeasure'];\n\t\t},\n\t): GitService {\n\t\tif (GitService._instance != null) {\n\t\t\tthrow new Error('GitService already exists — only one instance is allowed');\n\t\t}\n\n\t\tconst instance = new GitService(\n\t\t\twatchingProvider,\n\t\t\toptions?.watchPolling,\n\t\t\toptions?.readFile,\n\t\t\toptions?.verifyBaseRemeasure,\n\t\t);\n\t\tGitService._instance = instance;\n\t\treturn instance;\n\t}\n\n\tprivate readonly _onDidChangeProviders = new Emitter<ProvidersChangeEvent>();\n\tget onDidChangeProviders(): Event<ProvidersChangeEvent> {\n\t\treturn this._onDidChangeProviders.event;\n\t}\n\n\tprivate readonly _onDidChangeMergeTargets = new Emitter<MergeTargetsChangeEvent>();\n\t/**\n\t * Fires when a write changed a branch's merge-relevant `.git/gk/config` keys, for any registered provider that\n\t * reports it — never for activity timestamps. Core's own key write fires it once, as the write completes, naming\n\t * the branch. Once per change per repository (`repoPath` is its common path); see {@link MergeTargetsChangeEvent}.\n\t */\n\tget onDidChangeMergeTargets(): Event<MergeTargetsChangeEvent> {\n\t\treturn this._onDidChangeMergeTargets.event;\n\t}\n\n\t/** Keyed by the provider's event, so providers sharing one cache are forwarded once */\n\tprivate readonly _mergeTargetsSources = new Map<Event<MergeTargetsChangeEvent>, MergeTargetsSource>();\n\n\tprivate _etag = 0;\n\tprivate readonly _providers: RegisteredProvider[] = [];\n\tprivate readonly _serviceCache = new Map<string, RepositoryService>();\n\tprivate readonly _watchService: RepositoryWatchService | undefined;\n\tprivate readonly _summaries: WorktreeSummaryService | undefined;\n\n\tprivate constructor(\n\t\twatchingProvider?: FileWatchingProvider,\n\t\twatchPolling?: WatchPollingOptions,\n\t\treadFile?: (path: string) => Promise<Uint8Array>,\n\t\tverifyBaseRemeasure?: WorktreeSummaryServiceOptions['verifyBaseRemeasure'],\n\t) {\n\t\tif (watchingProvider != null) {\n\t\t\tthis._watchService = new RepositoryWatchService({\n\t\t\t\twatchingProvider: watchingProvider,\n\t\t\t\tpolling: watchPolling,\n\t\t\t\treadFile: readFile,\n\t\t\t\tgetIgnoreFilter: (repoPath, gitDirPath) => {\n\t\t\t\t\treturn this.getProvider(repoPath)?.provider.getIgnoreFilter?.(repoPath, gitDirPath);\n\t\t\t\t},\n\t\t\t\tgetIgnoredDirectories: repoPath =>\n\t\t\t\t\tthis.getProvider(repoPath)?.provider.getIgnoredDirectories?.(repoPath) ?? Promise.resolve([]),\n\t\t\t});\n\t\t\tthis._summaries = new WorktreeSummaryService({\n\t\t\t\twatchService: this._watchService,\n\t\t\t\tgetRepository: repoPath => this.forRepo(repoPath),\n\t\t\t\tonDidChangeMergeTargets: this.onDidChangeMergeTargets,\n\t\t\t\tgetCommonPath: repoPath => this.getProvider(repoPath)?.provider.getCommonPath?.(repoPath),\n\t\t\t\tverifyBaseRemeasure: verifyBaseRemeasure,\n\t\t\t});\n\t\t}\n\n\t\tsetGlobalRepositoryServiceResolver(repoPath => this.forRepo(repoPath));\n\t}\n\n\t[Symbol.dispose](): void {\n\t\tthis.dispose();\n\t}\n\n\tdispose(): void {\n\t\tclearGlobalRepositoryServiceResolver();\n\t\tGitService._instance = undefined;\n\t\tthis._serviceCache.clear();\n\t\tthis._summaries?.dispose();\n\t\tthis._watchService?.dispose();\n\n\t\t// Don't dispose providers since they're owned by the caller, but do clear the array to release references\n\t\tfor (const entry of this._providers) {\n\t\t\tentry.announcements?.dispose();\n\t\t}\n\t\tthis._providers.length = 0;\n\n\t\tfor (const source of this._mergeTargetsSources.values()) {\n\t\t\tsource.subscription.dispose();\n\t\t}\n\t\tthis._mergeTargetsSources.clear();\n\n\t\tthis._onDidChangeProviders.dispose();\n\t\tthis._onDidChangeMergeTargets.dispose();\n\t}\n\n\t/** Monotonically increasing counter incremented on structural changes. */\n\tget etag(): number {\n\t\treturn this._etag;\n\t}\n\n\t/** Whether any providers are registered. */\n\tget hasProviders(): boolean {\n\t\treturn this._providers.length > 0;\n\t}\n\n\tget watchService(): RepositoryWatchService | undefined {\n\t\treturn this._watchService;\n\t}\n\n\t/** Cached, change-notifying per-worktree summaries, kept current by {@link watchService}; present when it is */\n\tget summaries(): WorktreeSummaryService | undefined {\n\t\treturn this._summaries;\n\t}\n\n\t/**\n\t * Registers a provider with a routing predicate.\n\t *\n\t * @returns A disposable that unregisters the provider.\n\t */\n\tregister(provider: GitProvider, canHandle: (repoPath: string) => boolean): UnifiedDisposable {\n\t\tconst watchService = this._watchService;\n\t\tconst entry: RegisteredProvider = {\n\t\t\tprovider: provider,\n\t\t\tcanHandle: canHandle,\n\t\t\tannouncements:\n\t\t\t\twatchService != null\n\t\t\t\t\t? provider.onDidAnnounceChanges?.(e =>\n\t\t\t\t\t\t\twatchService.noteSelfWrite(e.repoPath, e.changes, e.refs, {\n\t\t\t\t\t\t\t\tconfigConfinedToRefs: e.configConfinedToRefs,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t)\n\t\t\t\t\t: undefined,\n\t\t};\n\t\tthis._providers.push(entry);\n\t\tthis._etag++;\n\t\tthis.addMergeTargetsSource(provider);\n\n\t\tthis._onDidChangeProviders.fire({\n\t\t\tadded: [provider],\n\t\t\tremoved: [],\n\t\t\tetag: this._etag,\n\t\t});\n\n\t\t// Invalidate cached proxies since provider routing may have changed\n\t\tthis._serviceCache.clear();\n\n\t\treturn createDisposable(() => {\n\t\t\tconst idx = this._providers.indexOf(entry);\n\t\t\tif (idx !== -1) {\n\t\t\t\tthis._providers.splice(idx, 1);\n\t\t\t\tentry.announcements?.dispose();\n\t\t\t\tthis._etag++;\n\t\t\t\tthis.removeMergeTargetsSource(provider);\n\t\t\t\tthis._serviceCache.clear();\n\n\t\t\t\tthis._onDidChangeProviders.fire({\n\t\t\t\t\tadded: [],\n\t\t\t\t\tremoved: [provider],\n\t\t\t\t\tetag: this._etag,\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\t}\n\n\tprivate addMergeTargetsSource(provider: GitProvider): void {\n\t\tconst event = provider.onDidChangeMergeTargets;\n\t\tif (event == null) return;\n\n\t\tconst source = this._mergeTargetsSources.get(event);\n\t\tif (source != null) {\n\t\t\tsource.refCount++;\n\t\t\treturn;\n\t\t}\n\n\t\tthis._mergeTargetsSources.set(event, {\n\t\t\tsubscription: event(e => this._onDidChangeMergeTargets.fire(e)),\n\t\t\trefCount: 1,\n\t\t});\n\t}\n\n\tprivate removeMergeTargetsSource(provider: GitProvider): void {\n\t\tconst event = provider.onDidChangeMergeTargets;\n\t\tif (event == null) return;\n\n\t\tconst source = this._mergeTargetsSources.get(event);\n\t\tif (source == null || --source.refCount > 0) return;\n\n\t\tsource.subscription.dispose();\n\t\tthis._mergeTargetsSources.delete(event);\n\t}\n\n\t/** Returns the provider that can handle the given path, or undefined if none matches. */\n\tgetProvider(repoPath: string): { provider: GitProvider; path: string } | undefined {\n\t\trepoPath = normalizePath(repoPath);\n\t\tfor (const entry of this._providers) {\n\t\t\tif (entry.canHandle(repoPath)) {\n\t\t\t\treturn { provider: entry.provider, path: repoPath };\n\t\t\t}\n\t\t}\n\t\treturn undefined;\n\t}\n\n\t/** Returns all registered providers. */\n\t*getProviders(): Iterable<GitProvider> {\n\t\tfor (const entry of this._providers) {\n\t\t\tyield entry.provider;\n\t\t}\n\t}\n\n\t/**\n\t * Routes a repo URI/path to a provider and returns a cached\n\t * {@link RepositoryService} with repo-scoped sub-provider proxies.\n\t * Returns undefined if no provider can handle the path.\n\t */\n\tforRepo(repoUri: Uri | string): RepositoryService | undefined {\n\t\tconst repoPath = getRepositoryKey(repoUri);\n\n\t\tlet repo = this._serviceCache.get(repoPath);\n\t\tif (repo != null) return repo;\n\n\t\tconst result = this.getProvider(repoPath);\n\t\tif (result == null) return undefined;\n\n\t\trepo = createRepositoryService(result.provider, repoPath, this._watchService);\n\t\tthis._serviceCache.set(repoPath, repo);\n\t\treturn repo;\n\t}\n\n\t/**\n\t * Evicts the cached {@link RepositoryService} for a closed or removed repository.\n\t * No-op if the path isn't cached.\n\t */\n\tcloseRepo(repoUri: Uri | string): void {\n\t\tconst repoPath = getRepositoryKey(repoUri);\n\t\tthis._serviceCache.delete(repoPath);\n\t}\n\n\t/**\n\t * Validates whether `pathOrUri` points to (or resides inside) a git repository,\n\t * and returns information about it when it does.\n\t *\n\t * Routes the path to a registered provider and delegates to its config sub-provider's\n\t * `getRepositoryInfo` implementation, then normalizes the raw result into a clean\n\t * discriminated union. For a simple yes/no check, use `(await validateRepo(path)).valid`.\n\t *\n\t * Returns `{ valid: false }` if no provider can handle the path or if the routed\n\t * provider does not implement `getRepositoryInfo`.\n\t */\n\tasync validateRepo(pathOrUri: Uri | string): Promise<ValidateRepoResult> {\n\t\tconst key = getRepositoryKey(pathOrUri);\n\t\tconst routed = this.getProvider(key);\n\t\tif (routed == null) return { valid: false };\n\n\t\tconst info = await routed.provider.config.getRepositoryInfo?.(routed.path);\n\t\tif (info == null) return { valid: false };\n\n\t\tif (Array.isArray(info)) {\n\t\t\tif (info.length === 0) return { valid: false };\n\t\t\tif (info[0] === false) return { valid: true, safe: false };\n\t\t\treturn { valid: true, safe: true, repoPath: info[1] };\n\t\t}\n\n\t\treturn {\n\t\t\tvalid: true,\n\t\t\tsafe: true,\n\t\t\trepoPath: info.repoPath,\n\t\t\tgitDir: info.gitDir,\n\t\t\tcommonGitDir: info.commonGitDir,\n\t\t\tsuperprojectPath: info.superprojectPath,\n\t\t};\n\t}\n}\n\nfunction createRepositoryService(\n\tprovider: GitProvider,\n\trepoPath: string,\n\twatchService?: RepositoryWatchService,\n): RepositoryService {\n\tconst proxies = new Map<string, SubProviderForRepo<unknown>>();\n\n\tfunction getProxy<T extends object>(prop: string, subProvider: T | undefined): SubProviderForRepo<T> | undefined {\n\t\tif (subProvider == null) return undefined;\n\n\t\tlet proxy = proxies.get(prop);\n\t\tif (proxy == null) {\n\t\t\tproxy = createSubProviderProxyForRepo(subProvider, repoPath);\n\t\t\tproxies.set(prop, proxy);\n\t\t}\n\t\treturn proxy as SubProviderForRepo<T>;\n\t}\n\n\treturn {\n\t\tpath: repoPath,\n\t\tprovider: provider.descriptor,\n\n\t\tget etagWorkingTree(): number | undefined {\n\t\t\treturn watchService?.getSession(repoPath)?.etagWorkingTree;\n\t\t},\n\n\t\tgetAbsoluteUri: (relativePath: string): Uri => {\n\t\t\treturn provider.getAbsoluteUri(relativePath, repoPath);\n\t\t},\n\n\t\t// Required sub-providers\n\t\tget branches() {\n\t\t\tconst proxy = getProxy('branches', provider.branches);\n\t\t\tif (proxy == null) throw new Error('GitService: branches sub-provider is required but not registered');\n\t\t\treturn proxy;\n\t\t},\n\t\tget commits() {\n\t\t\tconst proxy = getProxy('commits', provider.commits);\n\t\t\tif (proxy == null) throw new Error('GitService: commits sub-provider is required but not registered');\n\t\t\treturn proxy;\n\t\t},\n\t\tget config() {\n\t\t\tconst proxy = getProxy('config', provider.config);\n\t\t\tif (proxy == null) throw new Error('GitService: config sub-provider is required but not registered');\n\t\t\treturn proxy;\n\t\t},\n\t\tget contributors() {\n\t\t\tconst proxy = getProxy('contributors', provider.contributors);\n\t\t\tif (proxy == null) throw new Error('GitService: contributors sub-provider is required but not registered');\n\t\t\treturn proxy;\n\t\t},\n\t\tget diff() {\n\t\t\tconst proxy = getProxy('diff', provider.diff);\n\t\t\tif (proxy == null) throw new Error('GitService: diff sub-provider is required but not registered');\n\t\t\treturn proxy;\n\t\t},\n\t\tget graph() {\n\t\t\tconst proxy = getProxy('graph', provider.graph);\n\t\t\tif (proxy == null) throw new Error('GitService: graph sub-provider is required but not registered');\n\t\t\treturn proxy;\n\t\t},\n\t\tget refs() {\n\t\t\tconst proxy = getProxy('refs', provider.refs);\n\t\t\tif (proxy == null) throw new Error('GitService: refs sub-provider is required but not registered');\n\t\t\treturn proxy;\n\t\t},\n\t\tget remotes() {\n\t\t\tconst proxy = getProxy('remotes', provider.remotes);\n\t\t\tif (proxy == null) throw new Error('GitService: remotes sub-provider is required but not registered');\n\t\t\treturn proxy;\n\t\t},\n\t\tget revision() {\n\t\t\tconst proxy = getProxy('revision', provider.revision);\n\t\t\tif (proxy == null) throw new Error('GitService: revision sub-provider is required but not registered');\n\t\t\treturn proxy;\n\t\t},\n\t\tget status() {\n\t\t\tconst proxy = getProxy('status', provider.status);\n\t\t\tif (proxy == null) throw new Error('GitService: status sub-provider is required but not registered');\n\t\t\treturn proxy;\n\t\t},\n\t\tget tags() {\n\t\t\tconst proxy = getProxy('tags', provider.tags);\n\t\t\tif (proxy == null) throw new Error('GitService: tags sub-provider is required but not registered');\n\t\t\treturn proxy;\n\t\t},\n\n\t\t// Optional sub-providers\n\t\tget blame() {\n\t\t\treturn getProxy('blame', provider.blame);\n\t\t},\n\t\tget maintenance() {\n\t\t\treturn getProxy('maintenance', provider.maintenance);\n\t\t},\n\t\tget ops() {\n\t\t\treturn getProxy('ops', provider.ops);\n\t\t},\n\t\tget patch() {\n\t\t\treturn getProxy('patch', provider.patch);\n\t\t},\n\t\tget pausedOps() {\n\t\t\treturn getProxy('pausedOps', provider.pausedOps);\n\t\t},\n\t\tget staging() {\n\t\t\treturn getProxy('staging', provider.staging);\n\t\t},\n\t\tget stash() {\n\t\t\treturn getProxy('stash', provider.stash);\n\t\t},\n\t\tget worktrees() {\n\t\t\treturn getProxy('worktrees', provider.worktrees);\n\t\t},\n\t};\n}\n"],"mappings":"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"}
@@ -22,6 +22,8 @@ const allWatchedRepositoryChanges = [
22
22
  "gkConfig"
23
23
  ];
24
24
  const classifyRegex = /(index|HEAD|FETCH_HEAD|ORIG_HEAD|CHERRY_PICK_HEAD|MERGE_HEAD|REBASE_HEAD|rebase-merge|rebase-apply|sequencer|REVERT_HEAD|config|gk\/config|info\/exclude|refs\/(?:heads|remotes|stash|tags|replace)|packed-refs|shallow)/;
25
+ /** A ref, ref directory or ref lock in a namespace {@link classifyRegex} maps (`refs/stash.lock` included) */
26
+ const watchedRefRegex = /^refs\/(?:heads|remotes|stash|tags|replace)(?:\/|\.lock$|$)/;
25
27
  /**
26
28
  * Maps a path relative to a `.git` directory to the corresponding
27
29
  * {@link RepositoryChange} types. This is pure Git knowledge — the
@@ -51,6 +53,7 @@ function classifyGitDirChange(relativePath) {
51
53
  return file === "HEAD" || file === "locked" || file === "gitdir" ? ["worktrees"] : void 0;
52
54
  }
53
55
  }
56
+ if (relativePath.startsWith("refs/") && !watchedRefRegex.test(relativePath)) return void 0;
54
57
  const match = classifyRegex.exec(relativePath);
55
58
  if (match == null) return void 0;
56
59
  switch (match[1]) {
@@ -1 +1 @@
1
- {"version":3,"file":"classifyChange.js","names":[],"sources":["../../../../git/src/watching/classifyChange.ts"],"sourcesContent":["import type { RepositoryChange } from '../models/repository.js';\n\n/**\n * Everything a `.git` watcher can report. Used when events were lost (`overflow`), so every cache type those\n * kinds clear is refreshed. Deliberately not `'unknown'`: `Cache.onRepositoryChanged` treats that as a close and\n * unregisters the path, after which shared evictions stop reaching it.\n */\nexport const allWatchedRepositoryChanges: readonly RepositoryChange[] = [\n\t'index',\n\t'head',\n\t'heads',\n\t'tags',\n\t'stash',\n\t'remotes',\n\t'worktrees',\n\t'config',\n\t'cherryPick',\n\t'merge',\n\t'rebase',\n\t'revert',\n\t'pausedOp',\n\t'ignores',\n\t'gkConfig',\n];\n\nconst classifyRegex =\n\t/(index|HEAD|FETCH_HEAD|ORIG_HEAD|CHERRY_PICK_HEAD|MERGE_HEAD|REBASE_HEAD|rebase-merge|rebase-apply|sequencer|REVERT_HEAD|config|gk\\/config|info\\/exclude|refs\\/(?:heads|remotes|stash|tags|replace)|packed-refs|shallow)/;\n\n/**\n * Maps a path relative to a `.git` directory to the corresponding\n * {@link RepositoryChange} types. This is pure Git knowledge — the\n * regex and switch table encode which files in `.git/` correspond\n * to which logical repository changes.\n *\n * `worktrees` means the worktree list, or what `git worktree list` reports per worktree, changed:\n * `worktrees`, `worktrees/<n>` (add/remove/prune) and `worktrees/<n>/{HEAD,locked,gitdir}`. Any other\n * path under `worktrees/<n>/` (index, `*_HEAD`, `MERGE_*`, rebase state, logs, …) is that worktree's\n * own state, which its own gitdir watcher classifies, so it yields `undefined` here.\n *\n * @param relativePath - Path relative to the common `.git` directory or a worktree's gitdir\n * (e.g., `refs/heads/main`, `HEAD`, `config`, `worktrees/foo/HEAD`)\n * @returns An array of change types, or `undefined` if the path\n * is noise or unrecognized. `undefined` for `FETCH_HEAD` which\n * is intentionally not mapped to a change type (the extension\n * handles it as a last-fetched timestamp).\n */\nexport function classifyGitDirChange(relativePath: string): RepositoryChange[] | undefined {\n\t// User-edited todo file (and its backup) — handled by VS Code's TextDocument\n\t// events in the rebase webview (see `rebaseWebviewProvider.ts`). Suppress here\n\t// to avoid thrashing the repo update path on every save during an interactive\n\t// rebase; rebase lifecycle (start/stop/step) still fires from sibling files\n\t// in `rebase-merge/` and the directory itself.\n\tif (relativePath.endsWith('/git-rebase-todo') || relativePath.endsWith('/git-rebase-todo.backup')) {\n\t\treturn undefined;\n\t}\n\n\tif (relativePath.startsWith('worktrees')) {\n\t\tif (relativePath.length === 9) return ['worktrees'];\n\n\t\tif (relativePath.charCodeAt(9) === 47 /* / */) {\n\t\t\tconst idEnd = relativePath.indexOf('/', 10);\n\t\t\tif (idEnd === -1) return relativePath.length > 10 ? ['worktrees'] : undefined;\n\n\t\t\tconst file = relativePath.substring(idEnd + 1);\n\t\t\treturn file === 'HEAD' || file === 'locked' || file === 'gitdir' ? ['worktrees'] : undefined;\n\t\t}\n\t}\n\n\tconst match = classifyRegex.exec(relativePath);\n\tif (match == null) return undefined;\n\n\tswitch (match[1]) {\n\t\tcase 'config':\n\t\t\treturn ['config', 'remotes'];\n\n\t\tcase 'gk/config':\n\t\t\treturn ['gkConfig'];\n\n\t\tcase 'info/exclude':\n\t\t\treturn ['ignores'];\n\n\t\tcase 'index':\n\t\t\treturn ['index'];\n\n\t\tcase 'FETCH_HEAD':\n\t\t\t// No RepositoryChange — extension handles this for last-fetched timestamps\n\t\t\treturn undefined;\n\n\t\tcase 'HEAD':\n\t\t\treturn ['head', 'heads'];\n\n\t\tcase 'ORIG_HEAD':\n\t\t\treturn ['heads'];\n\n\t\tcase 'shallow':\n\t\tcase 'refs/replace':\n\t\t\t// Ancestry changes affect graph, blame/history, health, and ahead/behind consumers even\n\t\t\t// when tips stay put. Reuse their shared history invalidation rather than only refreshing Graph.\n\t\t\treturn ['heads'];\n\n\t\tcase 'CHERRY_PICK_HEAD':\n\t\t\treturn ['cherryPick', 'pausedOp'];\n\n\t\tcase 'MERGE_HEAD':\n\t\t\treturn ['merge', 'pausedOp'];\n\n\t\tcase 'REBASE_HEAD':\n\t\tcase 'rebase-merge':\n\t\tcase 'rebase-apply':\n\t\t\treturn ['rebase', 'pausedOp'];\n\n\t\tcase 'REVERT_HEAD':\n\t\t\treturn ['revert', 'pausedOp'];\n\n\t\tcase 'sequencer':\n\t\t\treturn ['pausedOp'];\n\n\t\t// `'heads'` is deliberately the ONLY thing a `refs/heads/*` event yields, including a create. It is\n\t\t// tempting to read a create as \"a new branch exists under this name\" and use it to drop anything\n\t\t// derived from the old branch's identity (its base, its merge target). That signal does not mean\n\t\t// what it looks like: git rewrites an EXISTING loose ref by writing `<name>.lock` and renaming it\n\t\t// over the file, which watchers report as a create of the destination — so every ordinary commit\n\t\t// would look like a new branch and discard a live branch's metadata. Nothing here can distinguish\n\t\t// the two; identity changes are handled where GitLens performs the operation itself.\n\t\tcase 'refs/heads':\n\t\t\treturn ['heads'];\n\n\t\tcase 'refs/remotes':\n\t\t\treturn ['remotes'];\n\n\t\tcase 'refs/stash':\n\t\t\treturn ['stash'];\n\n\t\tcase 'refs/tags':\n\t\t\treturn ['tags'];\n\n\t\t// A `packed-refs` rewrite (after `git pack-refs` or `git gc`) changes ref state\n\t\t// WITHOUT any `refs/**` file event — deleting a packed branch, for example, only\n\t\t// rewrites this file. The file content is not inspected here, so classification\n\t\t// is deliberately broad: it can add, remove, or move refs of any of these types.\n\t\tcase 'packed-refs':\n\t\t\treturn ['heads', 'remotes', 'tags'];\n\n\t\tdefault:\n\t\t\treturn undefined;\n\t}\n}\n"],"mappings":";;;;;;AAOA,MAAa,8BAA2D;CACvE;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACD;AAEA,MAAM,gBACL;;;;;;;;;;;;;;;;;;;AAoBD,SAAgB,qBAAqB,cAAsD;CAM1F,IAAI,aAAa,SAAS,kBAAkB,KAAK,aAAa,SAAS,yBAAyB,GAC/F;CAGD,IAAI,aAAa,WAAW,WAAW,GAAG;EACzC,IAAI,aAAa,WAAW,GAAG,OAAO,CAAC,WAAW;EAElD,IAAI,aAAa,WAAW,CAAC,MAAM,IAAY;GAC9C,MAAM,QAAQ,aAAa,QAAQ,KAAK,EAAE;GAC1C,IAAI,UAAU,IAAI,OAAO,aAAa,SAAS,KAAK,CAAC,WAAW,IAAI,KAAA;GAEpE,MAAM,OAAO,aAAa,UAAU,QAAQ,CAAC;GAC7C,OAAO,SAAS,UAAU,SAAS,YAAY,SAAS,WAAW,CAAC,WAAW,IAAI,KAAA;EACpF;CACD;CAEA,MAAM,QAAQ,cAAc,KAAK,YAAY;CAC7C,IAAI,SAAS,MAAM,OAAO,KAAA;CAE1B,QAAQ,MAAM,IAAd;EACC,KAAK,UACJ,OAAO,CAAC,UAAU,SAAS;EAE5B,KAAK,aACJ,OAAO,CAAC,UAAU;EAEnB,KAAK,gBACJ,OAAO,CAAC,SAAS;EAElB,KAAK,SACJ,OAAO,CAAC,OAAO;EAEhB,KAAK,cAEJ;EAED,KAAK,QACJ,OAAO,CAAC,QAAQ,OAAO;EAExB,KAAK,aACJ,OAAO,CAAC,OAAO;EAEhB,KAAK;EACL,KAAK,gBAGJ,OAAO,CAAC,OAAO;EAEhB,KAAK,oBACJ,OAAO,CAAC,cAAc,UAAU;EAEjC,KAAK,cACJ,OAAO,CAAC,SAAS,UAAU;EAE5B,KAAK;EACL,KAAK;EACL,KAAK,gBACJ,OAAO,CAAC,UAAU,UAAU;EAE7B,KAAK,eACJ,OAAO,CAAC,UAAU,UAAU;EAE7B,KAAK,aACJ,OAAO,CAAC,UAAU;EASnB,KAAK,cACJ,OAAO,CAAC,OAAO;EAEhB,KAAK,gBACJ,OAAO,CAAC,SAAS;EAElB,KAAK,cACJ,OAAO,CAAC,OAAO;EAEhB,KAAK,aACJ,OAAO,CAAC,MAAM;EAMf,KAAK,eACJ,OAAO;GAAC;GAAS;GAAW;EAAM;EAEnC,SACC;CACF;AACD"}
1
+ {"version":3,"file":"classifyChange.js","names":[],"sources":["../../../../git/src/watching/classifyChange.ts"],"sourcesContent":["import type { RepositoryChange } from '../models/repository.js';\n\n/**\n * Everything a `.git` watcher can report. Used when events were lost (`overflow`), so every cache type those\n * kinds clear is refreshed. Deliberately not `'unknown'`: `Cache.onRepositoryChanged` treats that as a close and\n * unregisters the path, after which shared evictions stop reaching it.\n */\nexport const allWatchedRepositoryChanges: readonly RepositoryChange[] = [\n\t'index',\n\t'head',\n\t'heads',\n\t'tags',\n\t'stash',\n\t'remotes',\n\t'worktrees',\n\t'config',\n\t'cherryPick',\n\t'merge',\n\t'rebase',\n\t'revert',\n\t'pausedOp',\n\t'ignores',\n\t'gkConfig',\n];\n\nconst classifyRegex =\n\t/(index|HEAD|FETCH_HEAD|ORIG_HEAD|CHERRY_PICK_HEAD|MERGE_HEAD|REBASE_HEAD|rebase-merge|rebase-apply|sequencer|REVERT_HEAD|config|gk\\/config|info\\/exclude|refs\\/(?:heads|remotes|stash|tags|replace)|packed-refs|shallow)/;\n/** A ref, ref directory or ref lock in a namespace {@link classifyRegex} maps (`refs/stash.lock` included) */\nconst watchedRefRegex = /^refs\\/(?:heads|remotes|stash|tags|replace)(?:\\/|\\.lock$|$)/;\n\n/**\n * Maps a path relative to a `.git` directory to the corresponding\n * {@link RepositoryChange} types. This is pure Git knowledge — the\n * regex and switch table encode which files in `.git/` correspond\n * to which logical repository changes.\n *\n * `worktrees` means the worktree list, or what `git worktree list` reports per worktree, changed:\n * `worktrees`, `worktrees/<n>` (add/remove/prune) and `worktrees/<n>/{HEAD,locked,gitdir}`. Any other\n * path under `worktrees/<n>/` (index, `*_HEAD`, `MERGE_*`, rebase state, logs, …) is that worktree's\n * own state, which its own gitdir watcher classifies, so it yields `undefined` here.\n *\n * @param relativePath - Path relative to the common `.git` directory or a worktree's gitdir\n * (e.g., `refs/heads/main`, `HEAD`, `config`, `worktrees/foo/HEAD`)\n * @returns An array of change types, or `undefined` if the path\n * is noise or unrecognized. `undefined` for `FETCH_HEAD` which\n * is intentionally not mapped to a change type (the extension\n * handles it as a last-fetched timestamp).\n */\nexport function classifyGitDirChange(relativePath: string): RepositoryChange[] | undefined {\n\t// User-edited todo file (and its backup) — handled by VS Code's TextDocument\n\t// events in the rebase webview (see `rebaseWebviewProvider.ts`). Suppress here\n\t// to avoid thrashing the repo update path on every save during an interactive\n\t// rebase; rebase lifecycle (start/stop/step) still fires from sibling files\n\t// in `rebase-merge/` and the directory itself.\n\tif (relativePath.endsWith('/git-rebase-todo') || relativePath.endsWith('/git-rebase-todo.backup')) {\n\t\treturn undefined;\n\t}\n\n\tif (relativePath.startsWith('worktrees')) {\n\t\tif (relativePath.length === 9) return ['worktrees'];\n\n\t\tif (relativePath.charCodeAt(9) === 47 /* / */) {\n\t\t\tconst idEnd = relativePath.indexOf('/', 10);\n\t\t\tif (idEnd === -1) return relativePath.length > 10 ? ['worktrees'] : undefined;\n\n\t\t\tconst file = relativePath.substring(idEnd + 1);\n\t\t\treturn file === 'HEAD' || file === 'locked' || file === 'gitdir' ? ['worktrees'] : undefined;\n\t\t}\n\t}\n\n\t// A ref outside the namespaces core reads (a tool's own, such as `refs/undo/*`) is no change, whatever words its\n\t// name holds: `refs/kepler/x/fix-index` must not read as the index\n\tif (relativePath.startsWith('refs/') && !watchedRefRegex.test(relativePath)) return undefined;\n\n\tconst match = classifyRegex.exec(relativePath);\n\tif (match == null) return undefined;\n\n\tswitch (match[1]) {\n\t\tcase 'config':\n\t\t\treturn ['config', 'remotes'];\n\n\t\tcase 'gk/config':\n\t\t\treturn ['gkConfig'];\n\n\t\tcase 'info/exclude':\n\t\t\treturn ['ignores'];\n\n\t\tcase 'index':\n\t\t\treturn ['index'];\n\n\t\tcase 'FETCH_HEAD':\n\t\t\t// No RepositoryChange — extension handles this for last-fetched timestamps\n\t\t\treturn undefined;\n\n\t\tcase 'HEAD':\n\t\t\treturn ['head', 'heads'];\n\n\t\tcase 'ORIG_HEAD':\n\t\t\treturn ['heads'];\n\n\t\tcase 'shallow':\n\t\tcase 'refs/replace':\n\t\t\t// Ancestry changes affect graph, blame/history, health, and ahead/behind consumers even\n\t\t\t// when tips stay put. Reuse their shared history invalidation rather than only refreshing Graph.\n\t\t\treturn ['heads'];\n\n\t\tcase 'CHERRY_PICK_HEAD':\n\t\t\treturn ['cherryPick', 'pausedOp'];\n\n\t\tcase 'MERGE_HEAD':\n\t\t\treturn ['merge', 'pausedOp'];\n\n\t\tcase 'REBASE_HEAD':\n\t\tcase 'rebase-merge':\n\t\tcase 'rebase-apply':\n\t\t\treturn ['rebase', 'pausedOp'];\n\n\t\tcase 'REVERT_HEAD':\n\t\t\treturn ['revert', 'pausedOp'];\n\n\t\tcase 'sequencer':\n\t\t\treturn ['pausedOp'];\n\n\t\t// `'heads'` is deliberately the ONLY thing a `refs/heads/*` event yields, including a create. It is\n\t\t// tempting to read a create as \"a new branch exists under this name\" and use it to drop anything\n\t\t// derived from the old branch's identity (its base, its merge target). That signal does not mean\n\t\t// what it looks like: git rewrites an EXISTING loose ref by writing `<name>.lock` and renaming it\n\t\t// over the file, which watchers report as a create of the destination — so every ordinary commit\n\t\t// would look like a new branch and discard a live branch's metadata. Nothing here can distinguish\n\t\t// the two; identity changes are handled where GitLens performs the operation itself.\n\t\tcase 'refs/heads':\n\t\t\treturn ['heads'];\n\n\t\tcase 'refs/remotes':\n\t\t\treturn ['remotes'];\n\n\t\tcase 'refs/stash':\n\t\t\treturn ['stash'];\n\n\t\tcase 'refs/tags':\n\t\t\treturn ['tags'];\n\n\t\t// A `packed-refs` rewrite (after `git pack-refs` or `git gc`) changes ref state\n\t\t// WITHOUT any `refs/**` file event — deleting a packed branch, for example, only\n\t\t// rewrites this file. The file content is not inspected here, so classification\n\t\t// is deliberately broad: it can add, remove, or move refs of any of these types.\n\t\tcase 'packed-refs':\n\t\t\treturn ['heads', 'remotes', 'tags'];\n\n\t\tdefault:\n\t\t\treturn undefined;\n\t}\n}\n"],"mappings":";;;;;;AAOA,MAAa,8BAA2D;CACvE;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACD;AAEA,MAAM,gBACL;;AAED,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;AAoBxB,SAAgB,qBAAqB,cAAsD;CAM1F,IAAI,aAAa,SAAS,kBAAkB,KAAK,aAAa,SAAS,yBAAyB,GAC/F;CAGD,IAAI,aAAa,WAAW,WAAW,GAAG;EACzC,IAAI,aAAa,WAAW,GAAG,OAAO,CAAC,WAAW;EAElD,IAAI,aAAa,WAAW,CAAC,MAAM,IAAY;GAC9C,MAAM,QAAQ,aAAa,QAAQ,KAAK,EAAE;GAC1C,IAAI,UAAU,IAAI,OAAO,aAAa,SAAS,KAAK,CAAC,WAAW,IAAI,KAAA;GAEpE,MAAM,OAAO,aAAa,UAAU,QAAQ,CAAC;GAC7C,OAAO,SAAS,UAAU,SAAS,YAAY,SAAS,WAAW,CAAC,WAAW,IAAI,KAAA;EACpF;CACD;CAIA,IAAI,aAAa,WAAW,OAAO,KAAK,CAAC,gBAAgB,KAAK,YAAY,GAAG,OAAO,KAAA;CAEpF,MAAM,QAAQ,cAAc,KAAK,YAAY;CAC7C,IAAI,SAAS,MAAM,OAAO,KAAA;CAE1B,QAAQ,MAAM,IAAd;EACC,KAAK,UACJ,OAAO,CAAC,UAAU,SAAS;EAE5B,KAAK,aACJ,OAAO,CAAC,UAAU;EAEnB,KAAK,gBACJ,OAAO,CAAC,SAAS;EAElB,KAAK,SACJ,OAAO,CAAC,OAAO;EAEhB,KAAK,cAEJ;EAED,KAAK,QACJ,OAAO,CAAC,QAAQ,OAAO;EAExB,KAAK,aACJ,OAAO,CAAC,OAAO;EAEhB,KAAK;EACL,KAAK,gBAGJ,OAAO,CAAC,OAAO;EAEhB,KAAK,oBACJ,OAAO,CAAC,cAAc,UAAU;EAEjC,KAAK,cACJ,OAAO,CAAC,SAAS,UAAU;EAE5B,KAAK;EACL,KAAK;EACL,KAAK,gBACJ,OAAO,CAAC,UAAU,UAAU;EAE7B,KAAK,eACJ,OAAO,CAAC,UAAU,UAAU;EAE7B,KAAK,aACJ,OAAO,CAAC,UAAU;EASnB,KAAK,cACJ,OAAO,CAAC,OAAO;EAEhB,KAAK,gBACJ,OAAO,CAAC,SAAS;EAElB,KAAK,cACJ,OAAO,CAAC,OAAO;EAEhB,KAAK,aACJ,OAAO,CAAC,MAAM;EAMf,KAAK,eACJ,OAAO;GAAC;GAAS;GAAW;EAAM;EAEnC,SACC;CACF;AACD"}
@@ -234,6 +234,8 @@ export declare class RepositoryWatchSession implements UnifiedDisposable {
234
234
  * save the `heads` part of a `HEAD` report under a write that announced `head` in time, which is a commit's echo.
235
235
  */
236
236
  private isOwnReport;
237
+ /** Whether a raw `heads` report confined to `HEAD` is the echo of a write that announced `head` in time */
238
+ private isHeadEcho;
237
239
  /** Whether a pending report of `kind` remains, unnamed or under a name */
238
240
  private hasPendingRefOfKind;
239
241
  private clearPendingRepoChanges;
@@ -494,6 +494,10 @@ var RepositoryWatchSession = class RepositoryWatchSession {
494
494
  } else {
495
495
  const own = origin === "raw" && announcement != null && this.isOwnReport(change, announcement, names, now);
496
496
  if (announcement != null && !own) this.pendingRepoAnnounced.delete(change);
497
+ else if (!pending && origin === "raw" && this.isHeadEcho(change, names, now)) this.pendingRepoAnnounced.set(change, {
498
+ at: now,
499
+ names: new Set(names)
500
+ });
497
501
  if (origin === "unfenced") this.pendingRepoUnfencedChanges.add(change);
498
502
  }
499
503
  if (change === "config") {
@@ -520,7 +524,11 @@ var RepositoryWatchSession = class RepositoryWatchSession {
520
524
  if (announcement.names == null) return true;
521
525
  if (names == null) return false;
522
526
  if (isSubsetOf(names, announcement.names)) return true;
523
- if (change !== "heads" || names.size !== 1 || !names.has("HEAD")) return false;
527
+ return this.isHeadEcho(change, names, now);
528
+ }
529
+ /** Whether a raw `heads` report confined to `HEAD` is the echo of a write that announced `head` in time */
530
+ isHeadEcho(change, names, now) {
531
+ if (change !== "heads" || names?.size !== 1 || !names.has("HEAD")) return false;
524
532
  const head = this.pendingRepoAnnounced.get("head");
525
533
  return head != null && now - head.at <= this.effectiveRepoMs;
526
534
  }