@gitkraken/core-gitlens 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (361) hide show
  1. package/CHANGELOG.md +4 -1
  2. package/dist/git/cache.d.ts +28 -4
  3. package/dist/git/cache.d.ts.map +1 -1
  4. package/dist/git/cache.js +82 -18
  5. package/dist/git/cache.js.map +1 -1
  6. package/dist/git/models/commit.d.ts.map +1 -1
  7. package/dist/git/models/commit.js.map +1 -1
  8. package/dist/git/models/reference.d.ts +31 -0
  9. package/dist/git/models/reference.d.ts.map +1 -1
  10. package/dist/git/models/remote.d.ts.map +1 -1
  11. package/dist/git/models/remote.js.map +1 -1
  12. package/dist/git/models/repository.d.ts +9 -1
  13. package/dist/git/models/repository.d.ts.map +1 -1
  14. package/dist/git/models/repository.js +19 -3
  15. package/dist/git/models/repository.js.map +1 -1
  16. package/dist/git/models/search.d.ts +2 -2
  17. package/dist/git/models/search.d.ts.map +1 -1
  18. package/dist/git/models/worktree.d.ts +4 -1
  19. package/dist/git/models/worktree.d.ts.map +1 -1
  20. package/dist/git/models/worktree.js +2 -2
  21. package/dist/git/models/worktree.js.map +1 -1
  22. package/dist/git/parsers/diffParser.d.ts.map +1 -1
  23. package/dist/git/parsers/diffParser.js.map +1 -1
  24. package/dist/git/providers/branches.d.ts +15 -4
  25. package/dist/git/providers/branches.d.ts.map +1 -1
  26. package/dist/git/providers/config.d.ts +4 -1
  27. package/dist/git/providers/config.d.ts.map +1 -1
  28. package/dist/git/providers/operations.d.ts +33 -10
  29. package/dist/git/providers/operations.d.ts.map +1 -1
  30. package/dist/git/providers/pausedOperations.d.ts +3 -1
  31. package/dist/git/providers/pausedOperations.d.ts.map +1 -1
  32. package/dist/git/providers/refs.d.ts +23 -2
  33. package/dist/git/providers/refs.d.ts.map +1 -1
  34. package/dist/git/providers/staging.d.ts +16 -0
  35. package/dist/git/providers/staging.d.ts.map +1 -1
  36. package/dist/git/providers/status.d.ts +4 -1
  37. package/dist/git/providers/status.d.ts.map +1 -1
  38. package/dist/git/remotes/azure-devops.d.ts.map +1 -1
  39. package/dist/git/remotes/azure-devops.js.map +1 -1
  40. package/dist/git/{exec.types.d.ts → run.types.d.ts} +22 -3
  41. package/dist/git/run.types.d.ts.map +1 -0
  42. package/dist/git/run.types.js +2 -0
  43. package/dist/git/run.types.js.map +1 -0
  44. package/dist/git/service.d.ts.map +1 -1
  45. package/dist/git/service.js.map +1 -1
  46. package/dist/git/utils/autolink.utils.d.ts.map +1 -1
  47. package/dist/git/utils/autolink.utils.js.map +1 -1
  48. package/dist/git/utils/blame.utils.d.ts.map +1 -1
  49. package/dist/git/utils/blame.utils.js.map +1 -1
  50. package/dist/git/utils/conflictResolution.utils.d.ts +6 -0
  51. package/dist/git/utils/conflictResolution.utils.d.ts.map +1 -0
  52. package/dist/git/utils/conflictResolution.utils.js +25 -0
  53. package/dist/git/utils/conflictResolution.utils.js.map +1 -0
  54. package/dist/git/utils/fileStatus.utils.d.ts +2 -1
  55. package/dist/git/utils/fileStatus.utils.d.ts.map +1 -1
  56. package/dist/git/utils/fileStatus.utils.js +4 -0
  57. package/dist/git/utils/fileStatus.utils.js.map +1 -1
  58. package/dist/git/utils/search.utils.d.ts.map +1 -1
  59. package/dist/git/utils/search.utils.js +3 -0
  60. package/dist/git/utils/search.utils.js.map +1 -1
  61. package/dist/git/utils/sorting.d.ts +8 -0
  62. package/dist/git/utils/sorting.d.ts.map +1 -1
  63. package/dist/git/utils/sorting.js +15 -0
  64. package/dist/git/utils/sorting.js.map +1 -1
  65. package/dist/git/utils/status.utils.d.ts.map +1 -1
  66. package/dist/git/utils/status.utils.js.map +1 -1
  67. package/dist/git/watching/classifyChange.d.ts.map +1 -1
  68. package/dist/git/watching/classifyChange.js +8 -0
  69. package/dist/git/watching/classifyChange.js.map +1 -1
  70. package/dist/git/watching/gitIgnoreFilter.js +1 -1
  71. package/dist/git/watching/gitIgnoreFilter.js.map +1 -1
  72. package/dist/git/watching/initWatcher.d.ts.map +1 -1
  73. package/dist/git/watching/initWatcher.js.map +1 -1
  74. package/dist/git/watching/watchGroup.d.ts.map +1 -1
  75. package/dist/git/watching/watchGroup.js.map +1 -1
  76. package/dist/git/watching/watchService.d.ts.map +1 -1
  77. package/dist/git/watching/watchService.js +24 -4
  78. package/dist/git/watching/watchService.js.map +1 -1
  79. package/dist/git/watching/watchSession.d.ts.map +1 -1
  80. package/dist/git/watching/watchSession.js.map +1 -1
  81. package/dist/git-cli/exec/exec.types.d.ts +1 -1
  82. package/dist/git-cli/exec/exec.types.d.ts.map +1 -1
  83. package/dist/git-cli/exec/git.d.ts +3 -3
  84. package/dist/git-cli/exec/git.d.ts.map +1 -1
  85. package/dist/git-cli/exec/git.js.map +1 -1
  86. package/dist/git-cli/exec/gitQueue.d.ts +10 -2
  87. package/dist/git-cli/exec/gitQueue.d.ts.map +1 -1
  88. package/dist/git-cli/exec/gitQueue.js +39 -12
  89. package/dist/git-cli/exec/gitQueue.js.map +1 -1
  90. package/dist/git-cli/parsers/blameParser.d.ts.map +1 -1
  91. package/dist/git-cli/parsers/blameParser.js.map +1 -1
  92. package/dist/git-cli/parsers/logParser.js.map +1 -1
  93. package/dist/git-cli/parsers/refParser.d.ts +3 -30
  94. package/dist/git-cli/parsers/refParser.d.ts.map +1 -1
  95. package/dist/git-cli/parsers/refParser.js +30 -30
  96. package/dist/git-cli/parsers/refParser.js.map +1 -1
  97. package/dist/git-cli/providers/blame.d.ts.map +1 -1
  98. package/dist/git-cli/providers/blame.js.map +1 -1
  99. package/dist/git-cli/providers/branches.d.ts +15 -5
  100. package/dist/git-cli/providers/branches.d.ts.map +1 -1
  101. package/dist/git-cli/providers/branches.js +98 -68
  102. package/dist/git-cli/providers/branches.js.map +1 -1
  103. package/dist/git-cli/providers/commits.d.ts.map +1 -1
  104. package/dist/git-cli/providers/commits.js +74 -30
  105. package/dist/git-cli/providers/commits.js.map +1 -1
  106. package/dist/git-cli/providers/config.d.ts +11 -2
  107. package/dist/git-cli/providers/config.d.ts.map +1 -1
  108. package/dist/git-cli/providers/config.js +27 -2
  109. package/dist/git-cli/providers/config.js.map +1 -1
  110. package/dist/git-cli/providers/diff.d.ts.map +1 -1
  111. package/dist/git-cli/providers/diff.js +4 -2
  112. package/dist/git-cli/providers/diff.js.map +1 -1
  113. package/dist/git-cli/providers/graph.d.ts.map +1 -1
  114. package/dist/git-cli/providers/graph.js +7 -2
  115. package/dist/git-cli/providers/graph.js.map +1 -1
  116. package/dist/git-cli/providers/operations.d.ts +13 -11
  117. package/dist/git-cli/providers/operations.d.ts.map +1 -1
  118. package/dist/git-cli/providers/operations.js +108 -38
  119. package/dist/git-cli/providers/operations.js.map +1 -1
  120. package/dist/git-cli/providers/patch.d.ts.map +1 -1
  121. package/dist/git-cli/providers/patch.js.map +1 -1
  122. package/dist/git-cli/providers/pausedOperations.d.ts +3 -1
  123. package/dist/git-cli/providers/pausedOperations.d.ts.map +1 -1
  124. package/dist/git-cli/providers/pausedOperations.js +7 -1
  125. package/dist/git-cli/providers/pausedOperations.js.map +1 -1
  126. package/dist/git-cli/providers/refs.d.ts +19 -2
  127. package/dist/git-cli/providers/refs.d.ts.map +1 -1
  128. package/dist/git-cli/providers/refs.js +286 -2
  129. package/dist/git-cli/providers/refs.js.map +1 -1
  130. package/dist/git-cli/providers/revision.js.map +1 -1
  131. package/dist/git-cli/providers/staging.d.ts +6 -0
  132. package/dist/git-cli/providers/staging.d.ts.map +1 -1
  133. package/dist/git-cli/providers/staging.js +32 -0
  134. package/dist/git-cli/providers/staging.js.map +1 -1
  135. package/dist/git-cli/providers/status.d.ts +4 -1
  136. package/dist/git-cli/providers/status.d.ts.map +1 -1
  137. package/dist/git-cli/providers/status.js +4 -3
  138. package/dist/git-cli/providers/status.js.map +1 -1
  139. package/dist/git-cli/providers/tags.d.ts.map +1 -1
  140. package/dist/git-cli/providers/tags.js +10 -6
  141. package/dist/git-cli/providers/tags.js.map +1 -1
  142. package/dist/ipc/discovery.d.ts +27 -0
  143. package/dist/ipc/discovery.d.ts.map +1 -0
  144. package/dist/ipc/discovery.js +38 -0
  145. package/dist/ipc/discovery.js.map +1 -0
  146. package/dist/ipc/ipcServer.d.ts +20 -0
  147. package/dist/ipc/ipcServer.d.ts.map +1 -0
  148. package/dist/ipc/ipcServer.js +162 -0
  149. package/dist/ipc/ipcServer.js.map +1 -0
  150. package/dist/plus/agents/providers/claudeCodeProvider.d.ts +114 -0
  151. package/dist/plus/agents/providers/claudeCodeProvider.d.ts.map +1 -0
  152. package/dist/plus/agents/providers/claudeCodeProvider.js +1480 -0
  153. package/dist/plus/agents/providers/claudeCodeProvider.js.map +1 -0
  154. package/dist/plus/agents/providers/claudeCodeTranscript.d.ts +36 -0
  155. package/dist/plus/agents/providers/claudeCodeTranscript.d.ts.map +1 -0
  156. package/dist/plus/agents/providers/claudeCodeTranscript.js +171 -0
  157. package/dist/plus/agents/providers/claudeCodeTranscript.js.map +1 -0
  158. package/dist/plus/agents/sanitizePrompt.d.ts +15 -0
  159. package/dist/plus/agents/sanitizePrompt.d.ts.map +1 -0
  160. package/dist/plus/agents/sanitizePrompt.js +103 -0
  161. package/dist/plus/agents/sanitizePrompt.js.map +1 -0
  162. package/dist/plus/agents/stateMachine.d.ts +32 -0
  163. package/dist/plus/agents/stateMachine.d.ts.map +1 -0
  164. package/dist/plus/agents/stateMachine.js +180 -0
  165. package/dist/plus/agents/stateMachine.js.map +1 -0
  166. package/dist/plus/agents/types.d.ts +203 -0
  167. package/dist/plus/agents/types.d.ts.map +1 -0
  168. package/dist/plus/agents/types.js +49 -0
  169. package/dist/plus/agents/types.js.map +1 -0
  170. package/dist/plus/ai/errors.d.ts +4 -1
  171. package/dist/plus/ai/errors.d.ts.map +1 -1
  172. package/dist/plus/ai/errors.js +38 -0
  173. package/dist/plus/ai/errors.js.map +1 -1
  174. package/dist/plus/ai/models/promptTemplates.d.ts +11 -2
  175. package/dist/plus/ai/models/promptTemplates.d.ts.map +1 -1
  176. package/dist/plus/ai/prompts.d.ts +1 -0
  177. package/dist/plus/ai/prompts.d.ts.map +1 -1
  178. package/dist/plus/ai/prompts.js +48 -8
  179. package/dist/plus/ai/prompts.js.map +1 -1
  180. package/dist/plus/ai/utils/results.utils.d.ts.map +1 -1
  181. package/dist/plus/ai/utils/results.utils.js.map +1 -1
  182. package/dist/plus/git-github/api/github.d.ts +10 -1
  183. package/dist/plus/git-github/api/github.d.ts.map +1 -1
  184. package/dist/plus/git-github/api/github.js +156 -49
  185. package/dist/plus/git-github/api/github.js.map +1 -1
  186. package/dist/plus/git-github/api/types.d.ts +8 -0
  187. package/dist/plus/git-github/api/types.d.ts.map +1 -1
  188. package/dist/plus/git-github/models.d.ts +1 -1
  189. package/dist/plus/git-github/models.d.ts.map +1 -1
  190. package/dist/plus/git-github/models.js.map +1 -1
  191. package/dist/plus/git-github/providers/github/blame.d.ts.map +1 -1
  192. package/dist/plus/git-github/providers/github/blame.js.map +1 -1
  193. package/dist/plus/git-github/providers/github/branches.d.ts +5 -1
  194. package/dist/plus/git-github/providers/github/branches.d.ts.map +1 -1
  195. package/dist/plus/git-github/providers/github/branches.js +47 -24
  196. package/dist/plus/git-github/providers/github/branches.js.map +1 -1
  197. package/dist/plus/git-github/providers/github/commits.d.ts.map +1 -1
  198. package/dist/plus/git-github/providers/github/commits.js +144 -92
  199. package/dist/plus/git-github/providers/github/commits.js.map +1 -1
  200. package/dist/plus/git-github/providers/github/diff.d.ts +1 -1
  201. package/dist/plus/git-github/providers/github/diff.d.ts.map +1 -1
  202. package/dist/plus/git-github/providers/github/diff.js +23 -9
  203. package/dist/plus/git-github/providers/github/diff.js.map +1 -1
  204. package/dist/plus/git-github/providers/github/refs.d.ts +9 -1
  205. package/dist/plus/git-github/providers/github/refs.d.ts.map +1 -1
  206. package/dist/plus/git-github/providers/github/refs.js +38 -13
  207. package/dist/plus/git-github/providers/github/refs.js.map +1 -1
  208. package/dist/plus/git-github/providers/github/revision.d.ts.map +1 -1
  209. package/dist/plus/git-github/providers/github/revision.js +1 -2
  210. package/dist/plus/git-github/providers/github/revision.js.map +1 -1
  211. package/dist/plus/git-github/providers/github/status.d.ts +4 -1
  212. package/dist/plus/git-github/providers/github/status.d.ts.map +1 -1
  213. package/dist/plus/git-github/providers/github/status.js +1 -1
  214. package/dist/plus/git-github/providers/github/status.js.map +1 -1
  215. package/dist/plus/git-github/providers/github/tags.d.ts.map +1 -1
  216. package/dist/plus/git-github/providers/github/tags.js +35 -36
  217. package/dist/plus/git-github/providers/github/tags.js.map +1 -1
  218. package/dist/plus/git-github/providers/githubGitProvider.d.ts.map +1 -1
  219. package/dist/plus/git-github/providers/githubGitProvider.js.map +1 -1
  220. package/dist/utils/color.d.ts.map +1 -1
  221. package/dist/utils/color.js.map +1 -1
  222. package/dist/utils/decorators/serializable.d.ts.map +1 -1
  223. package/dist/utils/decorators/serializable.js.map +1 -1
  224. package/dist/utils/dedupedAsyncCache.d.ts +37 -0
  225. package/dist/utils/dedupedAsyncCache.d.ts.map +1 -0
  226. package/dist/utils/dedupedAsyncCache.js +68 -0
  227. package/dist/utils/dedupedAsyncCache.js.map +1 -0
  228. package/dist/utils/diff.d.ts +2 -0
  229. package/dist/utils/diff.d.ts.map +1 -0
  230. package/dist/utils/diff.js +53 -0
  231. package/dist/utils/diff.js.map +1 -0
  232. package/dist/utils/disposable.d.ts.map +1 -1
  233. package/dist/utils/disposable.js.map +1 -1
  234. package/dist/utils/env/node/exec.d.ts.map +1 -1
  235. package/dist/utils/env/node/exec.js.map +1 -1
  236. package/dist/utils/error.d.ts.map +1 -1
  237. package/dist/utils/error.js.map +1 -1
  238. package/dist/utils/iterable.d.ts.map +1 -1
  239. package/dist/utils/iterable.js.map +1 -1
  240. package/dist/utils/logger.d.ts.map +1 -1
  241. package/dist/utils/logger.js.map +1 -1
  242. package/dist/utils/lruMap.d.ts +9 -0
  243. package/dist/utils/lruMap.d.ts.map +1 -1
  244. package/dist/utils/lruMap.js +26 -4
  245. package/dist/utils/lruMap.js.map +1 -1
  246. package/dist/utils/markdown.d.ts.map +1 -1
  247. package/dist/utils/markdown.js.map +1 -1
  248. package/dist/utils/mru.d.ts.map +1 -1
  249. package/dist/utils/mru.js.map +1 -1
  250. package/dist/utils/path.d.ts.map +1 -1
  251. package/dist/utils/path.js.map +1 -1
  252. package/dist/utils/promise.d.ts +1 -0
  253. package/dist/utils/promise.d.ts.map +1 -1
  254. package/dist/utils/promise.js +17 -0
  255. package/dist/utils/promise.js.map +1 -1
  256. package/dist/utils/promiseCache.d.ts +10 -0
  257. package/dist/utils/promiseCache.d.ts.map +1 -1
  258. package/dist/utils/promiseCache.js +22 -0
  259. package/dist/utils/promiseCache.js.map +1 -1
  260. package/dist/utils/string.d.ts.map +1 -1
  261. package/dist/utils/string.js.map +1 -1
  262. package/dist/utils/uri.d.ts.map +1 -1
  263. package/dist/utils/uri.js.map +1 -1
  264. package/dist/utils/version.d.ts.map +1 -1
  265. package/dist/utils/version.js.map +1 -1
  266. package/package.json +13 -1
  267. package/src/git/cache.ts +142 -28
  268. package/src/git/models/commit.ts +1 -0
  269. package/src/git/models/reference.ts +33 -0
  270. package/src/git/models/remote.ts +1 -0
  271. package/src/git/models/repository.ts +21 -3
  272. package/src/git/models/search.ts +2 -2
  273. package/src/git/models/worktree.ts +10 -2
  274. package/src/git/parsers/diffParser.ts +2 -0
  275. package/src/git/providers/branches.ts +22 -4
  276. package/src/git/providers/config.ts +2 -0
  277. package/src/git/providers/operations.ts +44 -0
  278. package/src/git/providers/pausedOperations.ts +1 -0
  279. package/src/git/providers/refs.ts +32 -3
  280. package/src/git/providers/staging.ts +12 -0
  281. package/src/git/providers/status.ts +6 -1
  282. package/src/git/remotes/azure-devops.ts +2 -0
  283. package/src/git/{exec.types.ts → run.types.ts} +22 -2
  284. package/src/git/service.ts +1 -0
  285. package/src/git/utils/autolink.utils.ts +1 -0
  286. package/src/git/utils/blame.utils.ts +2 -0
  287. package/src/git/utils/conflictResolution.utils.ts +31 -0
  288. package/src/git/utils/fileStatus.utils.ts +7 -1
  289. package/src/git/utils/search.utils.ts +2 -0
  290. package/src/git/utils/sorting.ts +17 -0
  291. package/src/git/utils/status.utils.ts +1 -0
  292. package/src/git/watching/classifyChange.ts +9 -0
  293. package/src/git/watching/gitIgnoreFilter.ts +2 -2
  294. package/src/git/watching/initWatcher.ts +2 -0
  295. package/src/git/watching/watchGroup.ts +3 -0
  296. package/src/git/watching/watchService.ts +38 -5
  297. package/src/git/watching/watchSession.ts +10 -0
  298. package/src/git-cli/exec/exec.types.ts +2 -2
  299. package/src/git-cli/exec/git.ts +6 -5
  300. package/src/git-cli/exec/gitQueue.ts +41 -12
  301. package/src/git-cli/parsers/blameParser.ts +1 -0
  302. package/src/git-cli/parsers/logParser.ts +1 -0
  303. package/src/git-cli/parsers/refParser.ts +33 -34
  304. package/src/git-cli/providers/blame.ts +1 -0
  305. package/src/git-cli/providers/branches.ts +164 -93
  306. package/src/git-cli/providers/commits.ts +83 -37
  307. package/src/git-cli/providers/config.ts +41 -2
  308. package/src/git-cli/providers/diff.ts +8 -2
  309. package/src/git-cli/providers/graph.ts +11 -2
  310. package/src/git-cli/providers/operations.ts +186 -49
  311. package/src/git-cli/providers/patch.ts +1 -0
  312. package/src/git-cli/providers/pausedOperations.ts +8 -0
  313. package/src/git-cli/providers/refs.ts +247 -3
  314. package/src/git-cli/providers/revision.ts +2 -2
  315. package/src/git-cli/providers/staging.ts +33 -0
  316. package/src/git-cli/providers/status.ts +15 -4
  317. package/src/git-cli/providers/tags.ts +15 -16
  318. package/src/ipc/discovery.ts +56 -0
  319. package/src/ipc/ipcServer.ts +171 -0
  320. package/src/plus/agents/providers/claudeCodeProvider.ts +1782 -0
  321. package/src/plus/agents/providers/claudeCodeTranscript.ts +214 -0
  322. package/src/plus/agents/sanitizePrompt.ts +109 -0
  323. package/src/plus/agents/stateMachine.ts +197 -0
  324. package/src/plus/agents/types.ts +277 -0
  325. package/src/plus/ai/errors.ts +40 -0
  326. package/src/plus/ai/models/promptTemplates.ts +13 -0
  327. package/src/plus/ai/prompts.ts +49 -8
  328. package/src/plus/ai/utils/results.utils.ts +2 -0
  329. package/src/plus/git-github/api/github.ts +199 -53
  330. package/src/plus/git-github/api/types.ts +15 -0
  331. package/src/plus/git-github/models.ts +1 -1
  332. package/src/plus/git-github/providers/github/blame.ts +2 -0
  333. package/src/plus/git-github/providers/github/branches.ts +61 -31
  334. package/src/plus/git-github/providers/github/commits.ts +231 -160
  335. package/src/plus/git-github/providers/github/diff.ts +27 -19
  336. package/src/plus/git-github/providers/github/refs.ts +57 -22
  337. package/src/plus/git-github/providers/github/revision.ts +1 -2
  338. package/src/plus/git-github/providers/github/status.ts +6 -1
  339. package/src/plus/git-github/providers/github/tags.ts +61 -62
  340. package/src/plus/git-github/providers/githubGitProvider.ts +1 -0
  341. package/src/utils/color.ts +2 -0
  342. package/src/utils/decorators/serializable.ts +1 -0
  343. package/src/utils/dedupedAsyncCache.ts +74 -0
  344. package/src/utils/diff.ts +61 -0
  345. package/src/utils/disposable.ts +2 -0
  346. package/src/utils/env/node/exec.ts +2 -0
  347. package/src/utils/error.ts +1 -0
  348. package/src/utils/iterable.ts +3 -0
  349. package/src/utils/logger.ts +1 -0
  350. package/src/utils/lruMap.ts +29 -4
  351. package/src/utils/markdown.ts +1 -0
  352. package/src/utils/mru.ts +2 -0
  353. package/src/utils/path.ts +1 -0
  354. package/src/utils/promise.ts +22 -0
  355. package/src/utils/promiseCache.ts +24 -0
  356. package/src/utils/string.ts +2 -0
  357. package/src/utils/uri.ts +1 -0
  358. package/src/utils/version.ts +1 -0
  359. package/dist/git/exec.types.d.ts.map +0 -1
  360. package/dist/git/exec.types.js +0 -2
  361. package/dist/git/exec.types.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import type { Cache, ConflictDetectionCacheKey } from '../../git/cache.js';
1
+ import type { Cache, ConflictDetectionCacheKey, GkConfigInvalidationTarget } from '../../git/cache.js';
2
2
  import type { GitServiceContext } from '../../git/context.js';
3
3
  import { BranchError } from '../../git/errors.js';
4
4
  import type { BranchDisposition, BranchMetadata } from '../../git/models/branch.js';
@@ -10,6 +10,7 @@ import type {
10
10
  GitBranchesSubProvider,
11
11
  GitBranchMergedStatus,
12
12
  } from '../../git/providers/branches.js';
13
+ import type { GitCommandPriority } from '../../git/run.types.js';
13
14
  import {
14
15
  formatDetachedHeadName,
15
16
  getBranchId,
@@ -41,7 +42,6 @@ import type { GitResult } from '../exec/exec.types.js';
41
42
  import type { Git } from '../exec/git.js';
42
43
  import { getGitCommandError, gitConfigsBranch, gitConfigsLog, GitError, GitErrors, GitWarnings } from '../exec/git.js';
43
44
  import { parseMergeTreeConflict } from '../parsers/mergeTreeParser.js';
44
- import { getBranchParser } from '../parsers/refParser.js';
45
45
 
46
46
  /** Minimum time between writes to the config for last accessed/modified dates */
47
47
  const dateMetadataStaleThresholdMs = 5 * 60 * 1000; // 5 minutes
@@ -96,7 +96,7 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
96
96
  const [pausedOpStatusResult, committerDateResult, defaultWorktreePathResult, metadataMapResult] =
97
97
  await Promise.allSettled([
98
98
  isDetachedHead(ref.name)
99
- ? this.provider.pausedOps?.getPausedOperationStatus(repoPath, cancellation)
99
+ ? this.provider.pausedOps?.getPausedOperationStatus(repoPath, undefined, cancellation)
100
100
  : undefined,
101
101
  this.git
102
102
  .run(
@@ -168,27 +168,22 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
168
168
  // own cancellation when bypassing the cache (e.g. paging-cursor path).
169
169
  signal ??= cancellation;
170
170
  try {
171
- const supported = await this.git.supported('git:for-each-ref');
172
- const parser = getBranchParser(supported);
173
-
174
- const [gitResult, defaultWorktreePathResult, metadataMapResult] = await Promise.allSettled([
175
- this.git.run(
176
- { cwd: commonPath, cancellation: signal },
177
- 'for-each-ref',
178
- ...parser.arguments,
179
- 'refs/heads/',
180
- 'refs/remotes/',
181
- ),
182
- this.provider.config.getDefaultWorktreePath?.(commonPath),
183
- this.getBranchMetadataMap(commonPath),
184
- ]);
185
-
186
- const result = getSettledValue(gitResult);
171
+ const [recordsResult, supportedResult, defaultWorktreePathResult, metadataMapResult] =
172
+ await Promise.allSettled([
173
+ this.provider.refs.getRefs(commonPath, signal),
174
+ this.git.supported('git:for-each-ref'),
175
+ this.provider.config.getDefaultWorktreePath?.(commonPath),
176
+ this.getBranchMetadataMap(commonPath),
177
+ ]);
178
+
179
+ const records = getSettledValue(recordsResult) ?? [];
180
+ const supported = getSettledValue(supportedResult) ?? [];
187
181
  const metadataMap = getSettledValue(metadataMapResult) ?? new Map<string, BranchMetadata>();
188
182
  const defaultWorktreePath = getSettledValue(defaultWorktreePathResult);
183
+ const worktreePathSupported = supported.includes('git:for-each-ref:worktreePath');
189
184
 
190
185
  // If we don't get any data, assume the repo doesn't have any commits yet so check if we have a current branch
191
- if (!result?.stdout) {
186
+ if (!records.length) {
192
187
  const current = await this.getCurrentBranch(
193
188
  commonPath,
194
189
  metadataMap,
@@ -202,25 +197,28 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
202
197
 
203
198
  const branches: GitBranch[] = [];
204
199
 
205
- for (const entry of parser.parse(result.stdout)) {
200
+ for (const record of records) {
201
+ const fullName = record.name;
202
+ // Filter to branches and remote-tracking branches; skip tags from the unified records.
203
+ if (!fullName.startsWith('refs/heads/') && !fullName.startsWith('refs/remotes/')) continue;
206
204
  // Skip HEAD refs in remote branches
207
- if (isRemoteHEAD(entry.name)) continue;
205
+ if (isRemoteHEAD(fullName)) continue;
208
206
 
209
- const upstream = parseUpstream(entry.upstream, entry.upstreamTracking);
207
+ const upstream = parseUpstream(record.upstream, record.upstreamTracking);
210
208
 
211
- const metadata = metadataMap.get(parseRefName(entry.name).name);
212
- const worktreePath = entry.worktreePath ? normalizePath(entry.worktreePath) : undefined;
209
+ const metadata = metadataMap.get(parseRefName(fullName).name);
210
+ const worktreePath = record.worktreePath ? normalizePath(record.worktreePath) : undefined;
213
211
 
214
212
  branches.push(
215
213
  new GitBranch(
216
214
  commonPath,
217
- entry.name,
215
+ fullName,
218
216
  false, // Don't trust %(HEAD) for current as it's per-worktree -- we will set it later
219
- entry.date ? new Date(entry.date) : undefined,
217
+ record.committerDate ? new Date(record.committerDate) : undefined,
220
218
  metadata,
221
- entry.sha,
219
+ record.objectname,
222
220
  upstream,
223
- supported.includes('git:for-each-ref:worktreePath')
221
+ worktreePathSupported
224
222
  ? worktreePath
225
223
  ? { path: worktreePath, isDefault: worktreePath === defaultWorktreePath }
226
224
  : false
@@ -360,46 +358,23 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
360
358
  async getBranchContributionsOverview(
361
359
  repoPath: string,
362
360
  ref: string,
363
- options?: { associatedPullRequest?: Promise<{ refs?: { base?: { branch: string } } } | undefined> },
361
+ options?: {
362
+ associatedPullRequest?: Promise<{ refs?: { base?: { branch: string } } } | undefined>;
363
+ priority?: GitCommandPriority;
364
+ },
364
365
  cancellation?: AbortSignal,
365
366
  ): Promise<BranchContributionsOverview | undefined> {
366
367
  const scope = getScopedLogger();
368
+ const priority = options?.priority;
369
+ const priorityOpts = priority != null ? { priority: priority } : undefined;
367
370
 
368
- const getCore = async (
369
- _commonPath: string,
370
- _cacheable: CacheController | undefined,
371
+ // Expensive body runs only on cache miss. Closes over `mergeTarget` so the cache key
372
+ // (`${ref}|${mergeTarget}`) fully captures every input that affects the result.
373
+ const fetchBody = async (
374
+ mergeTarget: string,
371
375
  signal: AbortSignal | undefined,
372
- storedTarget: string | undefined,
373
376
  ): Promise<BranchContributionsOverview | undefined> => {
374
- let mergeTarget: string | undefined;
375
-
376
- // Tier 1: Stored merge target (user-set or previously detected from PR). Caller has
377
- // already read this once for the cache decision; reuse rather than re-fetching.
378
- if (storedTarget) {
379
- const validated = await this.provider.refs.getSymbolicReferenceName(repoPath, storedTarget);
380
- mergeTarget = validated || storedTarget;
381
- }
382
-
383
- // Tier 2: PR-based lookup. The branch object is only needed here (for `remoteName`),
384
- // so lazy-load it on the cold path instead of paying for it on every call.
385
- if (mergeTarget == null && options?.associatedPullRequest != null) {
386
- const pr = await options.associatedPullRequest;
387
- if (pr?.refs?.base != null) {
388
- const branch = await this.getBranch(repoPath, ref, signal);
389
- if (branch == null) return undefined;
390
- mergeTarget = `${branch.remoteName}/${pr.refs.base.branch}`;
391
- // Store for future reuse — turns the next call into a Tier 1 cache-eligible path.
392
- void this.storeMergeTargetBranchName(repoPath, ref, mergeTarget);
393
- }
394
- }
395
-
396
- // Tier 3: Base branch, then default branch
397
- mergeTarget ??= await this.getBaseBranchName(repoPath, ref, signal);
398
- mergeTarget ??= await this.getDefaultBranchName(repoPath, undefined, signal);
399
-
400
- if (mergeTarget == null) return undefined;
401
-
402
- const mergeBase = await this.provider.refs.getMergeBase(repoPath, ref, mergeTarget, undefined, signal);
377
+ const mergeBase = await this.provider.refs.getMergeBase(repoPath, ref, mergeTarget, priorityOpts, signal);
403
378
  if (mergeBase == null) return undefined;
404
379
 
405
380
  // Fetch the merge-base commit's dates in parallel with contributors so consumers
@@ -471,20 +446,61 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
471
446
  };
472
447
 
473
448
  try {
474
- // Pre-check Tier 1: when a stored merge target exists the result is deterministic for
475
- // `ref` (Tier 2's PR resolution is short-circuited), so it's safe to cache. Otherwise
476
- // the result depends on PR resolution which the cache key can't capture bypass.
449
+ // Resolve `mergeTarget` through Tier 1 Tier 2 Tier 3. The pieces of each tier are
450
+ // already individually cached (`getStoredMergeTargetBranchName`, `getBaseBranchName`,
451
+ // `getDefaultBranchName`), so this resolution path is cheap on warm caches. Only the
452
+ // expensive body below (`getMergeBase` + `getContributors` w/ stats + `getCommitDates`)
453
+ // is wrapped by `cache.getBranchOverview`, keyed on the resolved `mergeTarget` so any
454
+ // caller that resolves to the same target shares; differing targets get independent
455
+ // slots, which is the correctness constraint that previously forced the Tier 2/3 paths
456
+ // to bypass the cache entirely.
457
+ let mergeTarget: string | undefined;
458
+
459
+ // Tier 1: Stored merge target (user-set or previously detected from PR).
477
460
  const storedTarget = await this.getStoredMergeTargetBranchName(repoPath, ref);
478
- if (storedTarget != null) {
479
- return await this.cache.getBranchOverview(
461
+ if (storedTarget) {
462
+ const validated = await this.provider.refs.getSymbolicReferenceName(
480
463
  repoPath,
481
- ref,
482
- (commonPath, cacheable, signal) =>
483
- getCore(commonPath, cacheable, signal ?? cancellation, storedTarget),
484
- { cancellation: cancellation },
464
+ storedTarget,
465
+ priorityOpts,
466
+ cancellation,
485
467
  );
468
+ mergeTarget = validated || storedTarget;
469
+ }
470
+
471
+ // Tier 2: PR-based lookup. The branch object is only needed here (for `remoteName`),
472
+ // so lazy-load it on the cold path instead of paying for it on every call.
473
+ if (mergeTarget == null && options?.associatedPullRequest != null) {
474
+ const pr = await options.associatedPullRequest;
475
+ if (pr?.refs?.base != null) {
476
+ const branch = await this.getBranch(repoPath, ref, cancellation);
477
+ if (branch == null) return undefined;
478
+
479
+ mergeTarget = `${branch.remoteName}/${pr.refs.base.branch}`;
480
+ // Store for future reuse — turns the next call into a Tier 1 path. Skip the
481
+ // `branchOverviews` invalidation that the write would otherwise trigger: we're
482
+ // about to populate `${ref}|${mergeTarget}` with this exact target, so evicting
483
+ // it would just force the next caller to redo the body. (gk-merge-target writes
484
+ // do not affect `baseBranchName`, so no need to skip that target.)
485
+ void this.storeMergeTargetBranchName(repoPath, ref, mergeTarget, {
486
+ skipInvalidation: ['branchOverviews'],
487
+ });
488
+ }
486
489
  }
487
- return await getCore(repoPath, undefined, cancellation, undefined);
490
+
491
+ // Tier 3: Base branch, then default branch.
492
+ mergeTarget ??= await this.getBaseBranchName(repoPath, ref, priorityOpts, cancellation);
493
+ mergeTarget ??= await this.getDefaultBranchName(repoPath, undefined, priorityOpts, cancellation);
494
+
495
+ if (mergeTarget == null) return undefined;
496
+
497
+ const resolvedTarget = mergeTarget;
498
+ return await this.cache.getBranchOverview(
499
+ repoPath,
500
+ `${ref}|${resolvedTarget}`,
501
+ (_commonPath, _cacheable, signal) => fetchBody(resolvedTarget, signal ?? cancellation),
502
+ { cancellation: cancellation },
503
+ );
488
504
  } catch (ex) {
489
505
  scope?.error(ex);
490
506
  if (isCancellationError(ex)) throw ex;
@@ -624,7 +640,12 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
624
640
  }
625
641
 
626
642
  if (data == null) {
627
- const symbolicRef = await this.getDefaultBranchName(repoPath, 'origin', signal);
643
+ const symbolicRef = await this.getDefaultBranchName(
644
+ repoPath,
645
+ 'origin',
646
+ undefined,
647
+ signal,
648
+ );
628
649
  if (symbolicRef != null) {
629
650
  data = [
630
651
  symbolicRef.startsWith('origin/')
@@ -705,18 +726,21 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
705
726
  async getDefaultBranchName(
706
727
  repoPath: string | undefined,
707
728
  remote?: string,
729
+ options?: { priority?: GitCommandPriority },
708
730
  cancellation?: AbortSignal,
709
731
  ): Promise<string | undefined> {
710
732
  if (repoPath == null) return undefined;
711
733
 
712
734
  remote ??= 'origin';
735
+ const priority = options?.priority;
736
+ const priorityOpts = priority != null ? { priority: priority } : undefined;
713
737
 
714
738
  return this.cache.getDefaultBranchName(repoPath, remote, async commonPath => {
715
739
  let retried = false;
716
740
  while (true) {
717
741
  try {
718
742
  const result = await this.git.run(
719
- { cwd: commonPath, cancellation: cancellation },
743
+ { cwd: commonPath, cancellation: cancellation, ...priorityOpts },
720
744
  'symbolic-ref',
721
745
  '--short',
722
746
  `refs/remotes/${remote}/HEAD`,
@@ -728,7 +752,7 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
728
752
  if (!retried) {
729
753
  retried = true;
730
754
  await this.git.run(
731
- { cwd: commonPath, cancellation: cancellation },
755
+ { cwd: commonPath, cancellation: cancellation, ...priorityOpts },
732
756
  'remote',
733
757
  'set-head',
734
758
  '-a',
@@ -738,7 +762,7 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
738
762
  }
739
763
 
740
764
  const result = await this.git.run(
741
- { cwd: commonPath, cancellation: cancellation },
765
+ { cwd: commonPath, cancellation: cancellation, ...priorityOpts },
742
766
  'ls-remote',
743
767
  '--symref',
744
768
  remote,
@@ -1156,6 +1180,7 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
1156
1180
  currentTreeOid = treeResult.stdout.trim();
1157
1181
  } catch (ex) {
1158
1182
  if (isCancellationError(ex)) throw ex;
1183
+
1159
1184
  scope?.error(ex, `Failed to resolve target branch '${targetBranch}'`);
1160
1185
  return createConflictDetectionError('refNotFound');
1161
1186
  }
@@ -1263,7 +1288,15 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
1263
1288
  }
1264
1289
 
1265
1290
  @debug({ exit: true })
1266
- getBaseBranchName(repoPath: string, ref: string, cancellation?: AbortSignal): Promise<string | undefined> {
1291
+ getBaseBranchName(
1292
+ repoPath: string,
1293
+ ref: string,
1294
+ options?: { priority?: GitCommandPriority },
1295
+ cancellation?: AbortSignal,
1296
+ ): Promise<string | undefined> {
1297
+ const priority = options?.priority;
1298
+ const priorityOpts = priority != null ? { priority: priority } : undefined;
1299
+
1267
1300
  return this.cache.getBaseBranchName(
1268
1301
  repoPath,
1269
1302
  ref,
@@ -1280,19 +1313,36 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
1280
1313
  }
1281
1314
 
1282
1315
  if (mergeBase != null) {
1283
- const branch = await this.provider.refs.getSymbolicReferenceName(commonPath, mergeBase);
1316
+ const branch = await this.provider.refs.getSymbolicReferenceName(
1317
+ commonPath,
1318
+ mergeBase,
1319
+ priorityOpts,
1320
+ );
1284
1321
  if (branch != null) {
1285
1322
  if (update) {
1286
- void this.storeBaseBranchName(commonPath, ref, branch);
1323
+ // Self-write: the value we're storing is exactly what this factory is
1324
+ // returning, so any in-flight `branchOverviews` or `baseBranchName` entry
1325
+ // being populated against this resolution should NOT be evicted.
1326
+ void this.storeBaseBranchName(commonPath, ref, branch, {
1327
+ skipInvalidation: ['branchOverviews', 'baseBranchName'],
1328
+ });
1287
1329
  }
1288
1330
  return branch;
1289
1331
  }
1290
1332
  }
1291
1333
  } catch {}
1292
1334
 
1293
- const branch = await this.getBaseBranchFromReflog(commonPath, ref, { upstream: true }, signal);
1335
+ const branch = await this.getBaseBranchFromReflog(
1336
+ commonPath,
1337
+ ref,
1338
+ { upstream: true, priority: priority },
1339
+ signal,
1340
+ );
1294
1341
  if (branch != null) {
1295
- void this.storeBaseBranchName(commonPath, ref, branch);
1342
+ // Self-write: see comment on the migration path above.
1343
+ void this.storeBaseBranchName(commonPath, ref, branch, {
1344
+ skipInvalidation: ['branchOverviews', 'baseBranchName'],
1345
+ });
1296
1346
  return branch;
1297
1347
  }
1298
1348
 
@@ -1305,12 +1355,15 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
1305
1355
  private async getBaseBranchFromReflog(
1306
1356
  repoPath: string,
1307
1357
  ref: string,
1308
- options?: { upstream: true },
1358
+ options?: { upstream: true; priority?: GitCommandPriority },
1309
1359
  cancellation?: AbortSignal,
1310
1360
  ): Promise<string | undefined> {
1361
+ const priority = options?.priority;
1362
+ const priorityOpts = priority != null ? { priority: priority } : undefined;
1363
+
1311
1364
  try {
1312
1365
  let result = await this.git.run(
1313
- { cwd: repoPath, cancellation: cancellation },
1366
+ { cwd: repoPath, cancellation: cancellation, ...priorityOpts },
1314
1367
  'reflog',
1315
1368
  ref,
1316
1369
  '--grep-reflog=branch: Created from *.',
@@ -1325,18 +1378,22 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
1325
1378
  let name: string | undefined = match[1];
1326
1379
  if (name !== 'HEAD') {
1327
1380
  if (options?.upstream) {
1328
- const upstream = await this.provider.refs.getSymbolicReferenceName(repoPath, `${name}@{u}`);
1381
+ const upstream = await this.provider.refs.getSymbolicReferenceName(
1382
+ repoPath,
1383
+ `${name}@{u}`,
1384
+ priorityOpts,
1385
+ );
1329
1386
  if (upstream) return upstream;
1330
1387
  }
1331
1388
 
1332
- name = await this.provider.refs.getSymbolicReferenceName(repoPath, name);
1389
+ name = await this.provider.refs.getSymbolicReferenceName(repoPath, name, priorityOpts);
1333
1390
  if (name) return name;
1334
1391
  }
1335
1392
  }
1336
1393
 
1337
1394
  // Check if branch was created from HEAD
1338
1395
  result = await this.git.run(
1339
- { cwd: repoPath, cancellation: cancellation },
1396
+ { cwd: repoPath, cancellation: cancellation, ...priorityOpts },
1340
1397
  'reflog',
1341
1398
  'HEAD',
1342
1399
  `--grep-reflog=checkout: moving from .* to ${ref.replace('refs/heads/', '')}`,
@@ -1349,11 +1406,15 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
1349
1406
  if (match?.length === 2) {
1350
1407
  let name: string | undefined = match[1];
1351
1408
  if (options?.upstream) {
1352
- const upstream = await this.provider.refs.getSymbolicReferenceName(repoPath, `${name}@{u}`);
1409
+ const upstream = await this.provider.refs.getSymbolicReferenceName(
1410
+ repoPath,
1411
+ `${name}@{u}`,
1412
+ priorityOpts,
1413
+ );
1353
1414
  if (upstream) return upstream;
1354
1415
  }
1355
1416
 
1356
- name = await this.provider.refs.getSymbolicReferenceName(repoPath, name);
1417
+ name = await this.provider.refs.getSymbolicReferenceName(repoPath, name, priorityOpts);
1357
1418
  if (name) return name;
1358
1419
  }
1359
1420
  } catch (ex) {
@@ -1469,13 +1530,23 @@ export class BranchesGitSubProvider implements GitBranchesSubProvider {
1469
1530
  }
1470
1531
 
1471
1532
  @debug()
1472
- async storeBaseBranchName(repoPath: string, ref: string, base: string): Promise<void> {
1473
- await this.provider.config.setGkConfig(repoPath, `branch.${ref}.gk-merge-base`, base);
1533
+ async storeBaseBranchName(
1534
+ repoPath: string,
1535
+ ref: string,
1536
+ base: string,
1537
+ options?: { skipInvalidation?: readonly GkConfigInvalidationTarget[] },
1538
+ ): Promise<void> {
1539
+ await this.provider.config.setGkConfig(repoPath, `branch.${ref}.gk-merge-base`, base, options);
1474
1540
  }
1475
1541
 
1476
1542
  @debug()
1477
- async storeMergeTargetBranchName(repoPath: string, ref: string, target: string): Promise<void> {
1478
- await this.provider.config.setGkConfig(repoPath, `branch.${ref}.gk-merge-target`, target);
1543
+ async storeMergeTargetBranchName(
1544
+ repoPath: string,
1545
+ ref: string,
1546
+ target: string,
1547
+ options?: { skipInvalidation?: readonly GkConfigInvalidationTarget[] },
1548
+ ): Promise<void> {
1549
+ await this.provider.config.setGkConfig(repoPath, `branch.${ref}.gk-merge-target`, target, options);
1479
1550
  }
1480
1551
 
1481
1552
  @debug()
@@ -41,7 +41,7 @@ import { escapeRegex } from '../../utils/string.js';
41
41
  import type { Uri } from '../../utils/uri.js';
42
42
  import { fileUri, joinUriPath, toFsPath } from '../../utils/uri.js';
43
43
  import type { CliGitProviderInternal } from '../cliGitProvider.js';
44
- import type { GitExecOptions, GitResult } from '../exec/exec.types.js';
44
+ import type { GitResult, GitRunOptions } from '../exec/exec.types.js';
45
45
  import type { Git } from '../exec/git.js';
46
46
  import { gitConfigsLog, gitConfigsLogWithFiles, GitErrors } from '../exec/git.js';
47
47
  import type {
@@ -172,9 +172,7 @@ export class CommitsGitSubProvider implements GitCommitsSubProvider {
172
172
  async getCommitFiles(repoPath: string, rev: string, cancellation?: AbortSignal): Promise<GitFileChange[]> {
173
173
  const parser = getShaAndFilesAndStatsLogParser();
174
174
  const result = await this.git.run(
175
- // Single-commit log is cheap and almost always serves a user-initiated read; override
176
- // the inferred 'background' priority so it doesn't queue behind heavier graph/log work.
177
- { cwd: repoPath, cancellation: cancellation, configs: gitConfigsLog, priority: 'normal' },
175
+ { cwd: repoPath, cancellation: cancellation, configs: gitConfigsLog },
178
176
  'log',
179
177
  ...parser.arguments,
180
178
  '-n1',
@@ -354,7 +352,7 @@ export class CommitsGitSubProvider implements GitCommitsSubProvider {
354
352
 
355
353
  try {
356
354
  const result = await this.git.run(
357
- { cwd: repoPath, cancellation: cancellation, configs: gitConfigsLog },
355
+ { cwd: repoPath, cancellation: cancellation, configs: gitConfigsLog, priority: 'background' },
358
356
  'log',
359
357
  ...args,
360
358
  );
@@ -479,7 +477,7 @@ export class CommitsGitSubProvider implements GitCommitsSubProvider {
479
477
  // Cache key: range + excludeMerges. RepositoryChange events affecting branches/remotes
480
478
  // invalidate this cache via clearCaches('branches').
481
479
  const cacheKey = `${range}\x1f${options?.excludeMerges ? 'no-merges' : ''}`;
482
- return this.cache.getLeftRightCommitCount(repoPath, cacheKey, run);
480
+ return this.cache.leftRightCommitCount.getOrCreate(repoPath, cacheKey, run);
483
481
  }
484
482
 
485
483
  @debug()
@@ -573,24 +571,15 @@ export class CommitsGitSubProvider implements GitCommitsSubProvider {
573
571
  args.push(`-n${isSingleCommit ? 1 : limit + 1}`);
574
572
  }
575
573
 
576
- let stashes: Map<string, GitStashCommit> | undefined;
577
- let stdin: string | undefined;
578
-
579
- if (!isSingleCommit) {
580
- if (options?.stashes) {
581
- // There *HAS* to be a better way to get git log to return stashes, but this is the best we've found
582
- ({ stdin, stashes } = convertStashesToStdin(
583
- typeof options.stashes === 'boolean'
584
- ? await this.provider.stash?.getStash(repoPath, { reachableFrom: rev }, cancellation)
585
- : options.stashes,
586
- ));
587
- if (stashes.size) {
588
- rev ??= 'HEAD';
589
- }
590
- }
574
+ let stashes: ReadonlyMap<string, GitStashCommit> | undefined;
591
575
 
592
- if (stdin) {
593
- args.push('--stdin');
576
+ if (!isSingleCommit && options?.stashes) {
577
+ stashes =
578
+ typeof options.stashes === 'boolean'
579
+ ? (await this.provider.stash?.getStash(repoPath, { reachableFrom: rev }, cancellation))?.stashes
580
+ : options.stashes;
581
+ if (stashes?.size) {
582
+ rev ??= 'HEAD';
594
583
  }
595
584
  }
596
585
 
@@ -628,24 +617,30 @@ export class CommitsGitSubProvider implements GitCommitsSubProvider {
628
617
  const currentUser = await currentUserPromise.catch(() => undefined);
629
618
  if (cancellation?.aborted) throw new CancellationError();
630
619
 
631
- const cmdOpts: GitExecOptions = {
620
+ const cmdOpts: GitRunOptions = {
632
621
  cwd: repoPath,
633
622
  cancellation: cancellation,
634
623
  configs: gitConfigsLogWithFiles,
635
- stdin: stdin,
636
- // Single-commit log is cheap (typically <50ms) and almost always serves a user-
637
- // initiated read (commit details, hover, etc.). Override the inferred 'background'
638
- // priority so it doesn't queue behind heavier graph/log work without this, a click
639
- // on a commit while the graph is still loading can wait seconds for files to appear.
640
- ...(isSingleCommit ? { priority: 'normal' as const } : undefined),
624
+ // Single-commit log serves user-initiated reads (commit details, hover, etc.).
625
+ // Mirrors getCommitDates: full SHAs are immutable so a 5-min TTL is safe; non-SHA refs
626
+ // rely on gitResults being cleared on head/heads/remotes events (60s is the failsafe
627
+ // for watcher latency / web with no fs watcher).
628
+ ...(isSingleCommit && rev
629
+ ? {
630
+ caching: {
631
+ cache: this.cache.gitResults,
632
+ options: { accessTTL: isSha(rev) ? 5 * 60 * 1000 : 60 * 1000 },
633
+ },
634
+ }
635
+ : undefined),
641
636
  };
642
- let { commits, count, countStashChildCommits } = await parseCommits(
637
+ let { commits, count } = await parseCommits(
643
638
  parser,
644
639
  isSingleCommit ? this.git.run(cmdOpts, ...args) : this.git.stream(cmdOpts, ...args),
645
640
  repoPath,
646
641
  pathspec,
647
642
  limit,
648
- stashes,
643
+ undefined,
649
644
  currentUser,
650
645
  );
651
646
 
@@ -667,25 +662,67 @@ export class CommitsGitSubProvider implements GitCommitsSubProvider {
667
662
  args.splice(args.length - 1, 1, pathspec);
668
663
  }
669
664
 
670
- ({ commits, count, countStashChildCommits } = await parseCommits(
665
+ ({ commits, count } = await parseCommits(
671
666
  parser,
672
667
  isSingleCommit ? this.git.run(cmdOpts, ...args) : this.git.stream(cmdOpts, ...args),
673
668
  repoPath,
674
669
  pathspec,
675
670
  limit,
676
- stashes,
671
+ undefined,
677
672
  currentUser,
678
673
  ));
679
674
  }
680
675
  }
681
676
 
677
+ // Merge stashes in-memory rather than via `git log --stdin <stash>` — git would
678
+ // walk each stash's parent chains and pull in pre-rebase ancestors that aren't
679
+ // reachable from <rev>. Slot each stash directly above its first parent; stashes
680
+ // whose parent isn't in the result (rebased away, or below the current limit) are
681
+ // dropped — the Stashes view still surfaces them, and they reappear here if the
682
+ // parent loads via "Load more".
683
+ if (stashes?.size) {
684
+ // `stashes` arrives in `git stash list` order (newest @{0} first), so per-parent
685
+ // groups naturally land newest-first without an explicit sort
686
+ const stashesByParent = new Map<string, GitStashCommit[]>();
687
+ for (const stash of stashes.values()) {
688
+ const parentSha = stash.parents[0];
689
+ if (parentSha == null || !commits.has(parentSha)) continue;
690
+
691
+ const group = stashesByParent.get(parentSha);
692
+ if (group != null) {
693
+ group.push(stash);
694
+ } else {
695
+ stashesByParent.set(parentSha, [stash]);
696
+ }
697
+ }
698
+
699
+ if (stashesByParent.size) {
700
+ const cap = limit > 0 ? limit + 1 : Number.POSITIVE_INFINITY;
701
+ const merged = new Map<string, GitCommit>();
702
+ outer: for (const c of commits.values()) {
703
+ const group = stashesByParent.get(c.sha);
704
+ if (group != null) {
705
+ for (const s of group) {
706
+ if (merged.size >= cap) break outer;
707
+
708
+ merged.set(s.sha, s);
709
+ }
710
+ }
711
+ if (merged.size >= cap) break;
712
+
713
+ merged.set(c.sha, c);
714
+ }
715
+ commits = merged;
716
+ }
717
+ }
718
+
682
719
  const log: GitLog = {
683
720
  repoPath: repoPath,
684
721
  commits: commits,
685
722
  sha: isRevisionRange(rev) ? undefined : rev,
686
723
  count: commits.size,
687
724
  limit: limit,
688
- hasMore: overrideHasMore ?? count - countStashChildCommits > commits.size,
725
+ hasMore: overrideHasMore ?? (limit > 0 && count > limit),
689
726
  };
690
727
 
691
728
  if (!isSingleCommit) {
@@ -950,6 +987,7 @@ export class CommitsGitSubProvider implements GitCommitsSubProvider {
950
987
  filterMap<[string, GitCommit], [string, GitCommit]>(log.commits.entries(), ([sha, c]) => {
951
988
  if (skip) {
952
989
  if (sha !== rev) return undefined;
990
+
953
991
  skip = false;
954
992
  }
955
993
 
@@ -1120,6 +1158,7 @@ export class CommitsGitSubProvider implements GitCommitsSubProvider {
1120
1158
  } catch (ex) {
1121
1159
  scope?.error(ex);
1122
1160
  if (isCancellationError(ex)) throw ex;
1161
+
1123
1162
  debugger;
1124
1163
 
1125
1164
  return [];
@@ -1167,6 +1206,7 @@ export class CommitsGitSubProvider implements GitCommitsSubProvider {
1167
1206
  } catch (ex) {
1168
1207
  scope?.error(ex);
1169
1208
  if (isCancellationError(ex)) throw ex;
1209
+
1170
1210
  debugger;
1171
1211
 
1172
1212
  return undefined;
@@ -1367,7 +1407,10 @@ export class CommitsGitSubProvider implements GitCommitsSubProvider {
1367
1407
  {
1368
1408
  cwd: repoPath,
1369
1409
  errors: 'ignore',
1370
- caching: { cache: this.cache.gitResults, options: { accessTTL: 60 * 1000 } },
1410
+ caching: {
1411
+ cache: this.cache.gitResults,
1412
+ options: { accessTTL: isSha(sha) ? 5 * 60 * 1000 : 60 * 1000 },
1413
+ },
1371
1414
  configs: gitConfigsLog,
1372
1415
  },
1373
1416
  'log',
@@ -1394,7 +1437,10 @@ export class CommitsGitSubProvider implements GitCommitsSubProvider {
1394
1437
  {
1395
1438
  cwd: repoPath,
1396
1439
  errors: 'ignore',
1397
- caching: { cache: this.cache.gitResults, options: { accessTTL: 60 * 1000 } },
1440
+ caching: {
1441
+ cache: this.cache.gitResults,
1442
+ options: { accessTTL: isSha(sha) ? 5 * 60 * 1000 : 60 * 1000 },
1443
+ },
1398
1444
  },
1399
1445
  'cat-file',
1400
1446
  'commit',