@gitkraken/core-gitlens 0.5.110 → 0.5.111

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 (362) hide show
  1. package/CHANGELOG.md +16 -1
  2. package/dist/git/cache.d.ts +111 -6
  3. package/dist/git/cache.d.ts.map +1 -1
  4. package/dist/git/cache.js +312 -43
  5. package/dist/git/cache.js.map +1 -1
  6. package/dist/git/context.d.ts +11 -0
  7. package/dist/git/context.d.ts.map +1 -1
  8. package/dist/git/context.js.map +1 -1
  9. package/dist/git/models/fileStatus.d.ts +1 -0
  10. package/dist/git/models/fileStatus.d.ts.map +1 -1
  11. package/dist/git/models/fileStatus.js +1 -0
  12. package/dist/git/models/fileStatus.js.map +1 -1
  13. package/dist/git/models/issue.d.ts +4 -0
  14. package/dist/git/models/issue.d.ts.map +1 -1
  15. package/dist/git/models/mergeConflicts.d.ts +2 -0
  16. package/dist/git/models/mergeConflicts.d.ts.map +1 -1
  17. package/dist/git/models/pullRequest.d.ts +38 -2
  18. package/dist/git/models/pullRequest.d.ts.map +1 -1
  19. package/dist/git/models/pullRequest.js +12 -1
  20. package/dist/git/models/pullRequest.js.map +1 -1
  21. package/dist/git/models/repository.d.ts +32 -5
  22. package/dist/git/models/repository.d.ts.map +1 -1
  23. package/dist/git/models/repository.js +58 -12
  24. package/dist/git/models/repository.js.map +1 -1
  25. package/dist/git/models/statusFile.d.ts.map +1 -1
  26. package/dist/git/models/statusFile.js +3 -0
  27. package/dist/git/models/statusFile.js.map +1 -1
  28. package/dist/git/providers/branches.d.ts +2 -0
  29. package/dist/git/providers/branches.d.ts.map +1 -1
  30. package/dist/git/providers/commits.d.ts +8 -0
  31. package/dist/git/providers/commits.d.ts.map +1 -1
  32. package/dist/git/providers/config.d.ts +19 -1
  33. package/dist/git/providers/config.d.ts.map +1 -1
  34. package/dist/git/providers/operations.d.ts +1 -0
  35. package/dist/git/providers/operations.d.ts.map +1 -1
  36. package/dist/git/providers/pausedOperations.d.ts +2 -0
  37. package/dist/git/providers/pausedOperations.d.ts.map +1 -1
  38. package/dist/git/providers/status.d.ts +1 -0
  39. package/dist/git/providers/status.d.ts.map +1 -1
  40. package/dist/git/utils/config.utils.d.ts +3 -0
  41. package/dist/git/utils/config.utils.d.ts.map +1 -0
  42. package/dist/git/utils/config.utils.js +8 -0
  43. package/dist/git/utils/config.utils.js.map +1 -0
  44. package/dist/git/utils/issue.utils.d.ts.map +1 -1
  45. package/dist/git/utils/issue.utils.js +2 -0
  46. package/dist/git/utils/issue.utils.js.map +1 -1
  47. package/dist/git/utils/pausedOperationStatus.utils.d.ts +14 -0
  48. package/dist/git/utils/pausedOperationStatus.utils.d.ts.map +1 -1
  49. package/dist/git/utils/pausedOperationStatus.utils.js +23 -0
  50. package/dist/git/utils/pausedOperationStatus.utils.js.map +1 -1
  51. package/dist/git/utils/pullRequest.utils.d.ts +14 -1
  52. package/dist/git/utils/pullRequest.utils.d.ts.map +1 -1
  53. package/dist/git/utils/pullRequest.utils.js +31 -0
  54. package/dist/git/utils/pullRequest.utils.js.map +1 -1
  55. package/dist/git/watching/changeEvent.d.ts +6 -1
  56. package/dist/git/watching/changeEvent.d.ts.map +1 -1
  57. package/dist/git/watching/classifyChange.d.ts.map +1 -1
  58. package/dist/git/watching/classifyChange.js +14 -1
  59. package/dist/git/watching/classifyChange.js.map +1 -1
  60. package/dist/git/watching/watchService.d.ts.map +1 -1
  61. package/dist/git/watching/watchService.js +18 -3
  62. package/dist/git/watching/watchService.js.map +1 -1
  63. package/dist/git/watching/watchSession.d.ts +8 -1
  64. package/dist/git/watching/watchSession.d.ts.map +1 -1
  65. package/dist/git/watching/watchSession.js +38 -6
  66. package/dist/git/watching/watchSession.js.map +1 -1
  67. package/dist/git/watching/watcherPatterns.d.ts +2 -2
  68. package/dist/git/watching/watcherPatterns.d.ts.map +1 -1
  69. package/dist/git/watching/watcherPatterns.js +1 -1
  70. package/dist/git/watching/watcherPatterns.js.map +1 -1
  71. package/dist/git-cli/exec/git.d.ts +1 -1
  72. package/dist/git-cli/exec/git.d.ts.map +1 -1
  73. package/dist/git-cli/exec/git.js +13 -3
  74. package/dist/git-cli/exec/git.js.map +1 -1
  75. package/dist/git-cli/providers/branches.d.ts +19 -0
  76. package/dist/git-cli/providers/branches.d.ts.map +1 -1
  77. package/dist/git-cli/providers/branches.js +282 -78
  78. package/dist/git-cli/providers/branches.js.map +1 -1
  79. package/dist/git-cli/providers/commits.d.ts +4 -0
  80. package/dist/git-cli/providers/commits.d.ts.map +1 -1
  81. package/dist/git-cli/providers/commits.js +84 -11
  82. package/dist/git-cli/providers/commits.js.map +1 -1
  83. package/dist/git-cli/providers/config.d.ts +42 -0
  84. package/dist/git-cli/providers/config.d.ts.map +1 -1
  85. package/dist/git-cli/providers/config.js +186 -5
  86. package/dist/git-cli/providers/config.js.map +1 -1
  87. package/dist/git-cli/providers/contributors.d.ts.map +1 -1
  88. package/dist/git-cli/providers/contributors.js +1 -1
  89. package/dist/git-cli/providers/contributors.js.map +1 -1
  90. package/dist/git-cli/providers/graph.d.ts +2 -2
  91. package/dist/git-cli/providers/graph.d.ts.map +1 -1
  92. package/dist/git-cli/providers/graph.js +27 -9
  93. package/dist/git-cli/providers/graph.js.map +1 -1
  94. package/dist/git-cli/providers/operations.d.ts.map +1 -1
  95. package/dist/git-cli/providers/operations.js +40 -12
  96. package/dist/git-cli/providers/operations.js.map +1 -1
  97. package/dist/git-cli/providers/pausedOperations.d.ts +5 -0
  98. package/dist/git-cli/providers/pausedOperations.d.ts.map +1 -1
  99. package/dist/git-cli/providers/pausedOperations.js +87 -16
  100. package/dist/git-cli/providers/pausedOperations.js.map +1 -1
  101. package/dist/git-cli/providers/refs.d.ts +5 -3
  102. package/dist/git-cli/providers/refs.d.ts.map +1 -1
  103. package/dist/git-cli/providers/refs.js +33 -4
  104. package/dist/git-cli/providers/refs.js.map +1 -1
  105. package/dist/git-cli/providers/status.d.ts +1 -0
  106. package/dist/git-cli/providers/status.d.ts.map +1 -1
  107. package/dist/git-cli/providers/status.js +18 -3
  108. package/dist/git-cli/providers/status.js.map +1 -1
  109. package/dist/git-cli/providers/worktrees.d.ts.map +1 -1
  110. package/dist/git-cli/providers/worktrees.js +4 -0
  111. package/dist/git-cli/providers/worktrees.js.map +1 -1
  112. package/dist/git-cli/service.d.ts.map +1 -1
  113. package/dist/git-cli/service.js.map +1 -1
  114. package/dist/plus/agents/providers/claudeCodeProvider.d.ts +39 -1
  115. package/dist/plus/agents/providers/claudeCodeProvider.d.ts.map +1 -1
  116. package/dist/plus/agents/providers/claudeCodeProvider.js +978 -71
  117. package/dist/plus/agents/providers/claudeCodeProvider.js.map +1 -1
  118. package/dist/plus/agents/providers/claudeCodeTranscript.d.ts +24 -2
  119. package/dist/plus/agents/providers/claudeCodeTranscript.d.ts.map +1 -1
  120. package/dist/plus/agents/providers/claudeCodeTranscript.js +84 -9
  121. package/dist/plus/agents/providers/claudeCodeTranscript.js.map +1 -1
  122. package/dist/plus/agents/types.d.ts +37 -2
  123. package/dist/plus/agents/types.d.ts.map +1 -1
  124. package/dist/plus/agents/types.js +2 -0
  125. package/dist/plus/agents/types.js.map +1 -1
  126. package/dist/plus/ai/errors.d.ts +10 -0
  127. package/dist/plus/ai/errors.d.ts.map +1 -1
  128. package/dist/plus/ai/errors.js +29 -0
  129. package/dist/plus/ai/errors.js.map +1 -1
  130. package/dist/plus/ai/models/model.d.ts +4 -0
  131. package/dist/plus/ai/models/model.d.ts.map +1 -1
  132. package/dist/plus/ai/models/model.js +51 -6
  133. package/dist/plus/ai/models/model.js.map +1 -1
  134. package/dist/plus/ai/models/promptTemplates.d.ts +1 -1
  135. package/dist/plus/ai/models/promptTemplates.d.ts.map +1 -1
  136. package/dist/plus/ai/models/provider.d.ts +60 -2
  137. package/dist/plus/ai/models/provider.d.ts.map +1 -1
  138. package/dist/plus/ai/prompts.d.ts +12 -0
  139. package/dist/plus/ai/prompts.d.ts.map +1 -1
  140. package/dist/plus/ai/prompts.js +177 -61
  141. package/dist/plus/ai/prompts.js.map +1 -1
  142. package/dist/plus/ai/providers/anthropicProvider.d.ts +10 -2
  143. package/dist/plus/ai/providers/anthropicProvider.d.ts.map +1 -1
  144. package/dist/plus/ai/providers/anthropicProvider.js +139 -46
  145. package/dist/plus/ai/providers/anthropicProvider.js.map +1 -1
  146. package/dist/plus/ai/providers/deepSeekProvider.d.ts +2 -0
  147. package/dist/plus/ai/providers/deepSeekProvider.d.ts.map +1 -1
  148. package/dist/plus/ai/providers/deepSeekProvider.js +10 -21
  149. package/dist/plus/ai/providers/deepSeekProvider.js.map +1 -1
  150. package/dist/plus/ai/providers/geminiProvider.d.ts +1 -0
  151. package/dist/plus/ai/providers/geminiProvider.d.ts.map +1 -1
  152. package/dist/plus/ai/providers/geminiProvider.js +19 -0
  153. package/dist/plus/ai/providers/geminiProvider.js.map +1 -1
  154. package/dist/plus/ai/providers/gitkrakenProvider.d.ts +5 -1
  155. package/dist/plus/ai/providers/gitkrakenProvider.d.ts.map +1 -1
  156. package/dist/plus/ai/providers/gitkrakenProvider.js +118 -3
  157. package/dist/plus/ai/providers/gitkrakenProvider.js.map +1 -1
  158. package/dist/plus/ai/providers/huggingFaceProvider.d.ts +1 -0
  159. package/dist/plus/ai/providers/huggingFaceProvider.d.ts.map +1 -1
  160. package/dist/plus/ai/providers/huggingFaceProvider.js +5 -0
  161. package/dist/plus/ai/providers/huggingFaceProvider.js.map +1 -1
  162. package/dist/plus/ai/providers/mistralProvider.d.ts +3 -1
  163. package/dist/plus/ai/providers/mistralProvider.d.ts.map +1 -1
  164. package/dist/plus/ai/providers/mistralProvider.js +99 -19
  165. package/dist/plus/ai/providers/mistralProvider.js.map +1 -1
  166. package/dist/plus/ai/providers/ollamaProvider.d.ts +4 -3
  167. package/dist/plus/ai/providers/ollamaProvider.d.ts.map +1 -1
  168. package/dist/plus/ai/providers/ollamaProvider.js +38 -3
  169. package/dist/plus/ai/providers/ollamaProvider.js.map +1 -1
  170. package/dist/plus/ai/providers/openAICompatibleProvider.d.ts +1 -0
  171. package/dist/plus/ai/providers/openAICompatibleProvider.d.ts.map +1 -1
  172. package/dist/plus/ai/providers/openAICompatibleProvider.js +4 -0
  173. package/dist/plus/ai/providers/openAICompatibleProvider.js.map +1 -1
  174. package/dist/plus/ai/providers/openAICompatibleProviderBase.d.ts +154 -5
  175. package/dist/plus/ai/providers/openAICompatibleProviderBase.d.ts.map +1 -1
  176. package/dist/plus/ai/providers/openAICompatibleProviderBase.js +259 -10
  177. package/dist/plus/ai/providers/openAICompatibleProviderBase.js.map +1 -1
  178. package/dist/plus/ai/providers/openRouterProvider.d.ts +4 -0
  179. package/dist/plus/ai/providers/openRouterProvider.d.ts.map +1 -1
  180. package/dist/plus/ai/providers/openRouterProvider.js +9 -0
  181. package/dist/plus/ai/providers/openRouterProvider.js.map +1 -1
  182. package/dist/plus/ai/providers/openaiProvider.d.ts +1 -0
  183. package/dist/plus/ai/providers/openaiProvider.d.ts.map +1 -1
  184. package/dist/plus/ai/providers/openaiProvider.js +1 -0
  185. package/dist/plus/ai/providers/openaiProvider.js.map +1 -1
  186. package/dist/plus/ai/providers/responseFormatCache.d.ts +8 -0
  187. package/dist/plus/ai/providers/responseFormatCache.d.ts.map +1 -0
  188. package/dist/plus/ai/providers/responseFormatCache.js +25 -0
  189. package/dist/plus/ai/providers/responseFormatCache.js.map +1 -0
  190. package/dist/plus/ai/providers/xaiProvider.d.ts +1 -0
  191. package/dist/plus/ai/providers/xaiProvider.d.ts.map +1 -1
  192. package/dist/plus/ai/providers/xaiProvider.js +10 -1
  193. package/dist/plus/ai/providers/xaiProvider.js.map +1 -1
  194. package/dist/plus/ai/utils/ai.utils.d.ts +3 -0
  195. package/dist/plus/ai/utils/ai.utils.d.ts.map +1 -1
  196. package/dist/plus/ai/utils/ai.utils.js +13 -0
  197. package/dist/plus/ai/utils/ai.utils.js.map +1 -1
  198. package/dist/plus/ai/utils/results.utils.d.ts +16 -2
  199. package/dist/plus/ai/utils/results.utils.d.ts.map +1 -1
  200. package/dist/plus/ai/utils/results.utils.js +204 -28
  201. package/dist/plus/ai/utils/results.utils.js.map +1 -1
  202. package/dist/plus/git-github/api/github.d.ts +55 -0
  203. package/dist/plus/git-github/api/github.d.ts.map +1 -1
  204. package/dist/plus/git-github/api/github.js +177 -4
  205. package/dist/plus/git-github/api/github.js.map +1 -1
  206. package/dist/plus/git-github/models.d.ts +16 -0
  207. package/dist/plus/git-github/models.d.ts.map +1 -1
  208. package/dist/plus/git-github/models.js +29 -3
  209. package/dist/plus/git-github/models.js.map +1 -1
  210. package/dist/plus/git-github/providers/github/status.d.ts +1 -0
  211. package/dist/plus/git-github/providers/github/status.d.ts.map +1 -1
  212. package/dist/plus/git-github/providers/github/status.js.map +1 -1
  213. package/dist/plus/integrations/context.d.ts +4 -0
  214. package/dist/plus/integrations/context.d.ts.map +1 -1
  215. package/dist/plus/integrations/index.d.ts.map +1 -1
  216. package/dist/plus/integrations/index.js +2 -0
  217. package/dist/plus/integrations/index.js.map +1 -1
  218. package/dist/plus/integrations/models/gitHostIntegration.d.ts +6 -3
  219. package/dist/plus/integrations/models/gitHostIntegration.d.ts.map +1 -1
  220. package/dist/plus/integrations/models/gitHostIntegration.js +12 -2
  221. package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -1
  222. package/dist/plus/integrations/models/integration.d.ts +3 -1
  223. package/dist/plus/integrations/models/integration.d.ts.map +1 -1
  224. package/dist/plus/integrations/models/integration.js +2 -2
  225. package/dist/plus/integrations/models/integration.js.map +1 -1
  226. package/dist/plus/integrations/providers/accounts.d.ts.map +1 -1
  227. package/dist/plus/integrations/providers/accounts.js +4 -0
  228. package/dist/plus/integrations/providers/accounts.js.map +1 -1
  229. package/dist/plus/integrations/providers/azure/models.d.ts.map +1 -1
  230. package/dist/plus/integrations/providers/azure/models.js +3 -0
  231. package/dist/plus/integrations/providers/azure/models.js.map +1 -1
  232. package/dist/plus/integrations/providers/bitbucket/models.d.ts.map +1 -1
  233. package/dist/plus/integrations/providers/bitbucket/models.js +1 -0
  234. package/dist/plus/integrations/providers/bitbucket/models.js.map +1 -1
  235. package/dist/plus/integrations/providers/github.d.ts +14 -2
  236. package/dist/plus/integrations/providers/github.d.ts.map +1 -1
  237. package/dist/plus/integrations/providers/github.js +55 -3
  238. package/dist/plus/integrations/providers/github.js.map +1 -1
  239. package/dist/plus/integrations/providers/gitlab.d.ts.map +1 -1
  240. package/dist/plus/integrations/providers/gitlab.js.map +1 -1
  241. package/dist/plus/integrations/providers/models.d.ts.map +1 -1
  242. package/dist/plus/integrations/providers/models.js +30 -4
  243. package/dist/plus/integrations/providers/models.js.map +1 -1
  244. package/dist/plus/integrations/reads/broaden.d.ts.map +1 -1
  245. package/dist/plus/integrations/reads/broaden.js +6 -1
  246. package/dist/plus/integrations/reads/broaden.js.map +1 -1
  247. package/dist/plus/integrations/reads/drains.d.ts.map +1 -1
  248. package/dist/plus/integrations/reads/drains.js +7 -4
  249. package/dist/plus/integrations/reads/drains.js.map +1 -1
  250. package/dist/plus/integrations/reads/hierarchy.d.ts.map +1 -1
  251. package/dist/plus/integrations/reads/hierarchy.js +3 -1
  252. package/dist/plus/integrations/reads/hierarchy.js.map +1 -1
  253. package/dist/plus/integrations/reads/issueTracker.d.ts.map +1 -1
  254. package/dist/plus/integrations/reads/issueTracker.js +8 -3
  255. package/dist/plus/integrations/reads/issueTracker.js.map +1 -1
  256. package/dist/plus/integrations/reads/issues.d.ts.map +1 -1
  257. package/dist/plus/integrations/reads/issues.js +9 -4
  258. package/dist/plus/integrations/reads/issues.js.map +1 -1
  259. package/dist/plus/integrations/reads/pullRequests.d.ts.map +1 -1
  260. package/dist/plus/integrations/reads/pullRequests.js +9 -4
  261. package/dist/plus/integrations/reads/pullRequests.js.map +1 -1
  262. package/dist/utils/env/browser/base64.d.ts +1 -1
  263. package/dist/utils/env/browser/base64.d.ts.map +1 -1
  264. package/dist/utils/env/node/base64.d.ts +1 -1
  265. package/dist/utils/env/node/base64.d.ts.map +1 -1
  266. package/dist/utils/env/node/base64.js +2 -0
  267. package/dist/utils/env/node/base64.js.map +1 -1
  268. package/dist/utils/keys/chord.d.ts +52 -0
  269. package/dist/utils/keys/chord.d.ts.map +1 -0
  270. package/dist/utils/keys/chord.js +135 -0
  271. package/dist/utils/keys/chord.js.map +1 -0
  272. package/dist/utils/keys/keybinding.d.ts +53 -0
  273. package/dist/utils/keys/keybinding.d.ts.map +1 -0
  274. package/dist/utils/keys/keybinding.js +58 -0
  275. package/dist/utils/keys/keybinding.js.map +1 -0
  276. package/dist/utils/path.d.ts +11 -0
  277. package/dist/utils/path.d.ts.map +1 -1
  278. package/dist/utils/path.js +13 -0
  279. package/dist/utils/path.js.map +1 -1
  280. package/package.json +13 -5
  281. package/src/git/cache.ts +383 -47
  282. package/src/git/context.ts +11 -0
  283. package/src/git/models/fileStatus.ts +1 -0
  284. package/src/git/models/issue.ts +4 -0
  285. package/src/git/models/mergeConflicts.ts +3 -2
  286. package/src/git/models/pullRequest.ts +33 -1
  287. package/src/git/models/repository.ts +58 -17
  288. package/src/git/models/statusFile.ts +3 -0
  289. package/src/git/providers/branches.ts +6 -0
  290. package/src/git/providers/commits.ts +11 -0
  291. package/src/git/providers/config.ts +18 -0
  292. package/src/git/providers/operations.ts +1 -0
  293. package/src/git/providers/pausedOperations.ts +2 -0
  294. package/src/git/providers/status.ts +1 -0
  295. package/src/git/utils/config.utils.ts +8 -0
  296. package/src/git/utils/issue.utils.ts +2 -0
  297. package/src/git/utils/pausedOperationStatus.utils.ts +28 -0
  298. package/src/git/utils/pullRequest.utils.ts +36 -0
  299. package/src/git/watching/changeEvent.ts +6 -1
  300. package/src/git/watching/classifyChange.ts +15 -1
  301. package/src/git/watching/watchService.ts +20 -3
  302. package/src/git/watching/watchSession.ts +41 -6
  303. package/src/git/watching/watcherPatterns.ts +1 -1
  304. package/src/git-cli/exec/git.ts +13 -3
  305. package/src/git-cli/providers/branches.ts +384 -132
  306. package/src/git-cli/providers/commits.ts +106 -15
  307. package/src/git-cli/providers/config.ts +229 -8
  308. package/src/git-cli/providers/contributors.ts +5 -1
  309. package/src/git-cli/providers/graph.ts +26 -13
  310. package/src/git-cli/providers/operations.ts +45 -20
  311. package/src/git-cli/providers/pausedOperations.ts +109 -17
  312. package/src/git-cli/providers/refs.ts +33 -3
  313. package/src/git-cli/providers/status.ts +25 -4
  314. package/src/git-cli/providers/worktrees.ts +5 -0
  315. package/src/git-cli/service.ts +0 -1
  316. package/src/plus/agents/providers/claudeCodeProvider.ts +1130 -87
  317. package/src/plus/agents/providers/claudeCodeTranscript.ts +95 -9
  318. package/src/plus/agents/types.ts +46 -2
  319. package/src/plus/ai/errors.ts +30 -0
  320. package/src/plus/ai/models/model.ts +56 -6
  321. package/src/plus/ai/models/promptTemplates.ts +1 -1
  322. package/src/plus/ai/models/provider.ts +69 -2
  323. package/src/plus/ai/prompts.ts +186 -61
  324. package/src/plus/ai/providers/anthropicProvider.ts +155 -47
  325. package/src/plus/ai/providers/deepSeekProvider.ts +11 -21
  326. package/src/plus/ai/providers/geminiProvider.ts +19 -0
  327. package/src/plus/ai/providers/gitkrakenProvider.ts +126 -3
  328. package/src/plus/ai/providers/huggingFaceProvider.ts +6 -0
  329. package/src/plus/ai/providers/mistralProvider.ts +102 -19
  330. package/src/plus/ai/providers/ollamaProvider.ts +55 -4
  331. package/src/plus/ai/providers/openAICompatibleProvider.ts +5 -0
  332. package/src/plus/ai/providers/openAICompatibleProviderBase.ts +360 -17
  333. package/src/plus/ai/providers/openRouterProvider.ts +17 -0
  334. package/src/plus/ai/providers/openaiProvider.ts +1 -0
  335. package/src/plus/ai/providers/responseFormatCache.ts +39 -0
  336. package/src/plus/ai/providers/xaiProvider.ts +10 -1
  337. package/src/plus/ai/utils/ai.utils.ts +14 -0
  338. package/src/plus/ai/utils/results.utils.ts +255 -31
  339. package/src/plus/git-github/api/github.ts +291 -7
  340. package/src/plus/git-github/models.ts +48 -2
  341. package/src/plus/git-github/providers/github/status.ts +1 -0
  342. package/src/plus/integrations/context.ts +4 -0
  343. package/src/plus/integrations/index.ts +2 -0
  344. package/src/plus/integrations/models/gitHostIntegration.ts +25 -2
  345. package/src/plus/integrations/models/integration.ts +23 -13
  346. package/src/plus/integrations/providers/accounts.ts +4 -0
  347. package/src/plus/integrations/providers/azure/models.ts +3 -0
  348. package/src/plus/integrations/providers/bitbucket/models.ts +1 -0
  349. package/src/plus/integrations/providers/github.ts +85 -1
  350. package/src/plus/integrations/providers/gitlab.ts +2 -10
  351. package/src/plus/integrations/providers/models.ts +36 -4
  352. package/src/plus/integrations/reads/broaden.ts +6 -1
  353. package/src/plus/integrations/reads/drains.ts +7 -4
  354. package/src/plus/integrations/reads/hierarchy.ts +3 -1
  355. package/src/plus/integrations/reads/issueTracker.ts +9 -3
  356. package/src/plus/integrations/reads/issues.ts +9 -4
  357. package/src/plus/integrations/reads/pullRequests.ts +9 -4
  358. package/src/utils/env/browser/base64.ts +1 -1
  359. package/src/utils/env/node/base64.ts +3 -1
  360. package/src/utils/keys/chord.ts +184 -0
  361. package/src/utils/keys/keybinding.ts +116 -0
  362. package/src/utils/path.ts +14 -0
@@ -159,6 +159,13 @@ export class BranchesGitSubProvider {
159
159
  const worktreePathSupported = supported.includes('git:for-each-ref:worktreePath');
160
160
  // If we don't get any data, assume the repo doesn't have any commits yet so check if we have a current branch
161
161
  if (!records.length) {
162
+ // Never CACHE that conclusion. `getRefs` reports a failed enumeration the same way as a
163
+ // genuinely ref-less repo — an empty array (it invalidates its own entry but doesn't
164
+ // throw) — so a transient `for-each-ref` failure would otherwise pin this degraded
165
+ // current-branch-only list in a map with no TTL, and every branch consumer would see a
166
+ // one-branch repo for the session. A truly empty repo re-reads cheaply, so refusing the
167
+ // entry costs nothing in the case this is actually meant to serve.
168
+ cacheable?.invalidate();
162
169
  const current = await this.getCurrentBranch(commonPath, metadataMap, options?.ordering ?? this.context.config?.commits.ordering ?? undefined, signal);
163
170
  return current != null ? { values: [current] } : emptyPagedResult;
164
171
  }
@@ -375,14 +382,20 @@ export class BranchesGitSubProvider {
375
382
  if (branch == null)
376
383
  return undefined;
377
384
  mergeTarget = `${branch.remoteName}/${pr.refs.base.branch}`;
378
- // Store for future reuse turns the next call into a Tier 1 path. Skip the
379
- // `branchOverviews` invalidation that the write would otherwise trigger: we're
380
- // about to populate `${ref}|${mergeTarget}` with this exact target, so evicting
381
- // it would just force the next caller to redo the body. (gk-merge-target writes
382
- // do not affect `baseBranchName`, so no need to skip that target.)
383
- void this.storeMergeTargetBranchName(repoPath, ref, mergeTarget, {
384
- skipInvalidation: ['branchOverviews'],
385
- });
385
+ // A stacked PR's base is the layer below it, which is deleted when the stack merges.
386
+ // Storing it would leave a stored target naming a branch that no longer exists and
387
+ // a stored target beats every other tier, so it would keep winning until cleared by
388
+ // hand. Use it for this lookup, but don't persist it.
389
+ if (pr.stack == null) {
390
+ // Store for future reuse — turns the next call into a Tier 1 path. Skip the
391
+ // `branchOverviews` invalidation that the write would otherwise trigger: we're
392
+ // about to populate `${ref}|${mergeTarget}` with this exact target, so evicting
393
+ // it would just force the next caller to redo the body. (gk-merge-target writes
394
+ // do not affect `baseBranchName`, so no need to skip that target.)
395
+ void this.storeMergeTargetBranchName(repoPath, ref, mergeTarget, {
396
+ skipInvalidation: ['branchOverviews'],
397
+ });
398
+ }
386
399
  }
387
400
  }
388
401
  // Tier 3: Base branch, then default branch.
@@ -539,37 +552,62 @@ export class BranchesGitSubProvider {
539
552
  remote ??= 'origin';
540
553
  const priority = options?.priority;
541
554
  const priorityOpts = priority != null ? { priority: priority } : undefined;
542
- // Local-only: read just the existing local symref, never contacting the remote. Bypass the shared
543
- // cache entirelyit's keyed by repoPath+remote, so caching a local miss (or sharing this in-flight
544
- // promise) would starve a concurrent networked caller of the real default branch. The local read is
545
- // a cheap ref lookup, fine to repeat per call.
555
+ // Local-only: read just the existing local symref, never contacting the remote. Cached under a
556
+ // `${remote}:local` keya colon can't appear in a refname component, so it can never collide
557
+ // with a real remote name used by the networked variant below. Invalidated by the same
558
+ // remotes/branches cache cascades as the networked lookup.
546
559
  if (options?.local) {
547
- try {
548
- const result = await this.git.run({ cwd: this.cache.getCommonPath(repoPath), cancellation: cancellation, ...priorityOpts }, 'symbolic-ref', '--short', `refs/remotes/${remote}/HEAD`);
549
- return result.stdout.trim() || undefined;
550
- }
551
- catch (ex) {
552
- if (isCancellationError(ex))
553
- throw ex;
554
- return undefined;
555
- }
560
+ return this.cache.getDefaultBranchName(repoPath, `${remote}:local`, async (commonPath, cacheable, signal) => {
561
+ try {
562
+ // Bind to the aggregate `signal` (fires only when ALL current callers abort), not
563
+ // this-caller's `cancellation` — otherwise a superseded caller's abort would kill the
564
+ // shared command and reject concurrent riders that never cancelled.
565
+ const result = await this.git.run({ cwd: commonPath, cancellation: signal ?? cancellation, ...priorityOpts }, 'symbolic-ref', '--short', `refs/remotes/${remote}/HEAD`);
566
+ return result.stdout.trim() || undefined;
567
+ }
568
+ catch (ex) {
569
+ if (isCancellationError(ex))
570
+ throw ex;
571
+ // `symbolic-ref` on an absent `refs/remotes/<remote>/HEAD` exits fatal rather than
572
+ // empty, and that message matches no `GitWarnings` entry, so it arrives here as a
573
+ // throw. It is still an ANSWER — the ref genuinely isn't set — and the overwhelmingly
574
+ // common one, so cache it. Invalidating instead means every caller re-probes and the
575
+ // whole point of caching this lookup is lost.
576
+ const stderr = ex.stderr ?? '';
577
+ if (stderr.includes('is not a symbolic ref'))
578
+ return undefined;
579
+ // Anything else never produced an answer (spawn failure, queue rejection). This entry
580
+ // has no TTL, so caching that would pin "no default branch" for the session.
581
+ cacheable.invalidate();
582
+ return undefined;
583
+ }
584
+ }, cancellation);
556
585
  }
557
- return this.cache.getDefaultBranchName(repoPath, remote, async (commonPath) => {
586
+ return this.cache.getDefaultBranchName(repoPath, remote, async (commonPath, cacheable, signal) => {
558
587
  let retried = false;
559
588
  while (true) {
560
589
  try {
561
- const result = await this.git.run({ cwd: commonPath, cancellation: cancellation, ...priorityOpts }, 'symbolic-ref', '--short', `refs/remotes/${remote}/HEAD`);
590
+ const result = await this.git.run({ cwd: commonPath, cancellation: signal ?? cancellation, ...priorityOpts }, 'symbolic-ref', '--short', `refs/remotes/${remote}/HEAD`);
562
591
  return result.stdout.trim() || undefined;
563
592
  }
564
593
  catch (ex) {
594
+ // Only "no HEAD ref configured" is an ANSWER here; every other throw means the lookup
595
+ // never happened. This entry has no TTL, so caching a failure — an offline `ls-remote`
596
+ // being the likely one — would pin "no default branch" for the session.
597
+ let answered = false;
565
598
  if (/is not a symbolic ref/.test(ex.stderr)) {
566
599
  try {
567
600
  if (!retried) {
568
601
  retried = true;
569
- await this.git.run({ cwd: commonPath, cancellation: cancellation, ...priorityOpts }, 'remote', 'set-head', '-a', remote);
602
+ await this.git.run({ cwd: commonPath, cancellation: signal ?? cancellation, ...priorityOpts }, 'remote', 'set-head', '-a', remote);
603
+ // This just created `refs/remotes/<remote>/HEAD`, which the `local` variant
604
+ // may already have cached a miss for. The watcher would also catch the new
605
+ // ref, but evict directly so consumers of this package that don't wire up a
606
+ // watcher aren't stuck with that miss.
607
+ this.cache.deleteDefaultBranchName(commonPath, `${remote}:local`);
570
608
  continue;
571
609
  }
572
- const result = await this.git.run({ cwd: commonPath, cancellation: cancellation, ...priorityOpts }, 'ls-remote', '--symref', remote, 'HEAD');
610
+ const result = await this.git.run({ cwd: commonPath, cancellation: signal ?? cancellation, ...priorityOpts }, 'ls-remote', '--symref', remote, 'HEAD');
573
611
  if (result.stdout) {
574
612
  const match = /ref:\s(\S+)\s+HEAD/m.exec(result.stdout);
575
613
  if (match != null) {
@@ -577,16 +615,21 @@ export class BranchesGitSubProvider {
577
615
  return `${remote}/${branch.substring('refs/heads/'.length).trim()}`;
578
616
  }
579
617
  }
618
+ // `ls-remote` ran and the remote advertised no HEAD — a real "none".
619
+ answered = true;
580
620
  }
581
621
  catch {
582
622
  if (isCancellationError(ex))
583
623
  throw ex;
584
624
  }
585
625
  }
626
+ if (!answered) {
627
+ cacheable.invalidate();
628
+ }
586
629
  return undefined;
587
630
  }
588
631
  }
589
- });
632
+ }, cancellation);
590
633
  }
591
634
  async createBranch(repoPath, name, ref, options) {
592
635
  const args = ['branch', name, ref];
@@ -595,6 +638,12 @@ export class BranchesGitSubProvider {
595
638
  }
596
639
  try {
597
640
  await this.git.run({ cwd: repoPath }, ...args);
641
+ // Evict the cached base so the new branch re-derives its own. The PERSISTED `branch.<ref>.gk-*`
642
+ // keys are left alone: they're dropped when GitLens deletes a branch, never when one is created.
643
+ // Removing them here would mean treating a reused name as proof of a different branch, which
644
+ // nothing can establish — so the trade is that a branch reusing a name deleted outside GitLens
645
+ // inherits the orphaned section.
646
+ this.cache.deleteBaseBranchName(repoPath, name);
598
647
  this.context.hooks?.cache?.onReset?.(repoPath, 'branches');
599
648
  this.context.hooks?.repository?.onChanged?.(repoPath, ['heads']);
600
649
  }
@@ -612,10 +661,30 @@ export class BranchesGitSubProvider {
612
661
  const args = ['branch', options?.force ? '-D' : '-d', ...branches];
613
662
  try {
614
663
  await this.git.run({ cwd: repoPath }, ...args);
664
+ for (const branch of branches) {
665
+ this.cache.deleteBaseBranchName(repoPath, branch);
666
+ // Drop the persisted gk metadata too, not just the cached resolution. `getBaseBranchName`
667
+ // reads `branch.<ref>.gk-merge-base` before falling back to the reflog, so leaving it behind
668
+ // would hand a later branch reusing this name its predecessor's base — evicting the cache
669
+ // alone just forces a re-derivation that reads the same stale value back.
670
+ await this.provider.config.removeGkConfigBranchSection(repoPath, branch);
671
+ }
615
672
  this.context.hooks?.cache?.onReset?.(repoPath, 'branches');
616
673
  this.context.hooks?.repository?.onChanged?.(repoPath, ['heads']);
617
674
  }
618
675
  catch (ex) {
676
+ // A multi-name delete partially succeeds: `git branch -d a bad c` removes `a` and `c`, refuses
677
+ // `bad`, and still exits non-zero — so the loop above never ran for the branches that did go.
678
+ // Clean those up here rather than leave persisted gk metadata behind for a name a later branch
679
+ // can reuse.
680
+ const anyDeleted = await this.cleanupDeletedBranchMetadata(repoPath, branches);
681
+ // The success path's notifications never ran, but some branches DID go — without these the
682
+ // branch caches stay stale and consumers wait on the filesystem watcher (which never fires
683
+ // for a packed ref).
684
+ if (anyDeleted) {
685
+ this.context.hooks?.cache?.onReset?.(repoPath, 'branches');
686
+ this.context.hooks?.repository?.onChanged?.(repoPath, ['heads']);
687
+ }
619
688
  if (ex instanceof BranchError) {
620
689
  throw ex.update({
621
690
  action: options?.force ? 'force delete' : 'delete',
@@ -630,6 +699,54 @@ export class BranchesGitSubProvider {
630
699
  }, ex);
631
700
  }
632
701
  }
702
+ /**
703
+ * Drops the cached and persisted per-branch metadata for whichever of `names` no longer exists. Only
704
+ * for the partial-failure path — one extra `for-each-ref` is cheap while already handling an error,
705
+ * and keeps the success path free of it.
706
+ *
707
+ * Returns whether any of `names` is confirmed gone; `false` when the probe couldn't answer, so a
708
+ * spurious notification is never fired on an unrelated failure.
709
+ */
710
+ async cleanupDeletedBranchMetadata(repoPath, names) {
711
+ try {
712
+ const result = await this.git.run({ cwd: repoPath, errors: 'ignore' }, 'for-each-ref',
713
+ // Full refname, stripped here rather than by git. NOT `%(refname:short)`: that yields the
714
+ // shortest UNAMBIGUOUS name, so a branch with a same-named tag comes back as `heads/<name>`,
715
+ // never matches the requested name, and would read as deleted — wiping a live branch's
716
+ // metadata. `%(refname:strip=2)` also solves that, but `%(refname)` is the oldest, plainest
717
+ // field and needs no assumption about which git version gained `strip`.
718
+ '--format=%(refname)', ...names.map(n => `refs/heads/${n}`));
719
+ // `errors: 'ignore'` means a failed probe RESOLVES empty, which is indistinguishable from "none
720
+ // of them survived" — and would delete every name's metadata, including user-owned keys like
721
+ // `gk-disposition` and `gk-associated-issues`. Only act on a genuine answer.
722
+ //
723
+ // BOTH conditions: `exitCode !== 0` catches a probe git ran and rejected, `status !== 'exited'`
724
+ // catches one that never produced an answer — a spawn failure or queue rejection (no exit code at
725
+ // all), or a swallowed warning (which carries git's real code, so the exit-code test alone can
726
+ // still read it as "none survived").
727
+ if (result.completion.status !== 'exited' || result.exitCode !== 0)
728
+ return false;
729
+ const prefix = 'refs/heads/';
730
+ const surviving = new Set(result.stdout
731
+ .split('\n')
732
+ .map(l => l.trim())
733
+ .filter(l => l.startsWith(prefix))
734
+ .map(l => l.substring(prefix.length)));
735
+ let anyDeleted = false;
736
+ for (const name of names) {
737
+ if (surviving.has(name))
738
+ continue;
739
+ anyDeleted = true;
740
+ this.cache.deleteBaseBranchName(repoPath, name);
741
+ await this.provider.config.removeGkConfigBranchSection(repoPath, name);
742
+ }
743
+ return anyDeleted;
744
+ }
745
+ catch {
746
+ // Best-effort bookkeeping while already unwinding a failure — never mask the original error.
747
+ return false;
748
+ }
749
+ }
633
750
  async deleteRemoteBranch(repoPath, names, remote) {
634
751
  const branches = ensureArray(names);
635
752
  const args = ['push', '-d', remote, ...branches];
@@ -831,7 +948,7 @@ export class BranchesGitSubProvider {
831
948
  return { status: 'clean' };
832
949
  const mergeConflict = parseMergeTreeConflict(data);
833
950
  if (!mergeConflict.conflicts.length)
834
- return { status: 'clean' };
951
+ return { status: 'clean', treeOid: mergeConflict.treeOid };
835
952
  return {
836
953
  status: 'conflicts',
837
954
  conflict: {
@@ -841,9 +958,99 @@ export class BranchesGitSubProvider {
841
958
  files: mergeConflict.conflicts,
842
959
  shas: undefined, // Merge conflicts don't have a specific commit SHA
843
960
  },
961
+ treeOid: mergeConflict.treeOid,
962
+ };
963
+ }, { cancellation: cancellation });
964
+ }
965
+ /** Detects conflicts when an autostash is reapplied onto the tree an integration produced */
966
+ async getPotentialStashReapplyConflicts(repoPath, ontoTreeOid, cancellation) {
967
+ // Requires Git v2.38+ for --write-tree with 3-arg form
968
+ if (!(await this.git.supports('git:merge-tree:write-tree'))) {
969
+ return createConflictDetectionError('unsupported');
970
+ }
971
+ // git stash create writes commit objects but touches neither the working tree nor the stash ref, and
972
+ // captures tracked changes only — which is exactly what autostash does
973
+ let stashSha;
974
+ try {
975
+ const result = await this.git.run({ cwd: repoPath, cancellation: cancellation, errors: 'throw' }, 'stash', 'create');
976
+ stashSha = result.stdout.trim();
977
+ }
978
+ catch (ex) {
979
+ if (isCancellationError(ex))
980
+ throw ex;
981
+ return createConflictDetectionError('other');
982
+ }
983
+ if (!stashSha)
984
+ return { status: 'clean' };
985
+ // Key on what the simulation actually consumes — the stash's tree and its first parent (the HEAD it was
986
+ // taken against, and so the merge base). The stash SHA itself embeds a commit timestamp, so two runs over
987
+ // an identical tree produce different SHAs and a SHA-keyed entry could never be hit.
988
+ let stashTree;
989
+ let stashParent;
990
+ try {
991
+ const result = await this.git.run({ cwd: repoPath, cancellation: cancellation, errors: 'throw' }, 'rev-parse', `${stashSha}^{tree}`, `${stashSha}^`);
992
+ [stashTree, stashParent] = result.stdout.trim().split('\n');
993
+ }
994
+ catch (ex) {
995
+ if (isCancellationError(ex))
996
+ throw ex;
997
+ return createConflictDetectionError('other');
998
+ }
999
+ const cacheKey = `reapply:${ontoTreeOid}:${stashTree}.${stashParent}`;
1000
+ return this.cache.conflictDetection.getOrCreate(repoPath, cacheKey, async (_cacheable, signal) => {
1001
+ // The stash commit's first parent is the HEAD it was taken against, which is the correct merge
1002
+ // base for a reapply — ours is the post-integration tree, theirs is the stash
1003
+ const step = await this.runMergeTreeStep(repoPath, `${stashSha}^`, ontoTreeOid, stashSha, signal);
1004
+ if (!step.ok)
1005
+ return createConflictDetectionError(step.reason);
1006
+ if (!step.result.conflicts.length)
1007
+ return { status: 'clean', treeOid: step.result.treeOid };
1008
+ return {
1009
+ status: 'conflicts',
1010
+ conflict: {
1011
+ repoPath: repoPath,
1012
+ branch: stashSha,
1013
+ target: ontoTreeOid,
1014
+ files: step.result.conflicts,
1015
+ shas: undefined,
1016
+ },
1017
+ treeOid: step.result.treeOid,
844
1018
  };
845
1019
  }, { cancellation: cancellation });
846
1020
  }
1021
+ async runMergeTreeStep(repoPath, base, ours, theirs, cancellation) {
1022
+ const scope = getScopedLogger();
1023
+ let data;
1024
+ try {
1025
+ const result = await this.git.run({ cwd: repoPath, cancellation: cancellation, errors: 'throw' }, 'merge-tree', '--write-tree', '-z', '--name-only', '--no-messages', `--merge-base=${base}`, ours, theirs);
1026
+ data = result.stdout;
1027
+ }
1028
+ catch (ex) {
1029
+ if (isCancellationError(ex))
1030
+ throw ex;
1031
+ const msg = ex?.toString() ?? '';
1032
+ if (GitErrors.notAValidObjectName.test(msg)) {
1033
+ scope?.error(ex, `'${ours}' or '${theirs}' not found - ensure the branches/commits exist`);
1034
+ return { ok: false, reason: 'refNotFound' };
1035
+ }
1036
+ else if (GitErrors.badRevision.test(msg)) {
1037
+ scope?.error(ex, `Invalid revision: ${msg.slice(msg.indexOf("'"))}`);
1038
+ return { ok: false, reason: 'refNotFound' };
1039
+ }
1040
+ else if (GitErrors.noMergeBase.test(msg)) {
1041
+ scope?.error(ex, `Unable to merge '${ours}' and '${theirs}' as they have no common ancestor`);
1042
+ return { ok: false, reason: 'noMergeBase' };
1043
+ }
1044
+ else if (ex instanceof GitError) {
1045
+ data = ex.stdout;
1046
+ }
1047
+ else {
1048
+ scope?.error(ex, 'Failed to execute merge-tree for conflict check');
1049
+ return { ok: false, reason: 'other' };
1050
+ }
1051
+ }
1052
+ return { ok: true, result: parseMergeTreeConflict(data ?? '') };
1053
+ }
847
1054
  async checkForPotentialConflicts(repoPath, commits, targetBranch, cancellation, options) {
848
1055
  const scope = getScopedLogger();
849
1056
  // Requires Git v2.38+ for --write-tree with 3-arg form
@@ -867,44 +1074,16 @@ export class BranchesGitSubProvider {
867
1074
  for (const commit of commits) {
868
1075
  if (cancellation?.aborted)
869
1076
  throw new CancellationError();
870
- let data;
871
- try {
872
- // Use merge-tree --write-tree with --merge-base to simulate cherry-pick:
873
- // merge-tree --write-tree --merge-base=<commit-parent> <current-tree> <commit>
874
- // This performs a 3-way merge where:
875
- // - base = commit.parent (where the commit started)
876
- // - ours = currentTreeOid (current state of the target)
877
- // - theirs = commit.sha (what we're cherry-picking)
878
- const result = await this.git.run({ cwd: repoPath, cancellation: cancellation, errors: 'throw' }, 'merge-tree', '--write-tree', '-z', '--name-only', '--no-messages', `--merge-base=${commit.parent}`, currentTreeOid, commit.sha);
879
- data = result.stdout;
880
- }
881
- catch (ex) {
882
- if (isCancellationError(ex))
883
- throw ex;
884
- const msg = ex?.toString() ?? '';
885
- if (GitErrors.notAValidObjectName.test(msg)) {
886
- scope?.error(ex, `'${targetBranch}' or '${commit.sha}' not found - ensure the branches/commits exist`);
887
- return createConflictDetectionError('refNotFound');
888
- }
889
- else if (GitErrors.badRevision.test(msg)) {
890
- scope?.error(ex, `Invalid revision: ${msg.slice(msg.indexOf("'"))}`);
891
- return createConflictDetectionError('refNotFound');
892
- }
893
- else if (GitErrors.noMergeBase.test(msg)) {
894
- scope?.error(ex, `Unable to merge '${commit.sha}' and '${targetBranch}' as they have no common ancestor`);
895
- return createConflictDetectionError('noMergeBase');
896
- }
897
- else if (ex instanceof GitError) {
898
- data = ex.stdout;
899
- }
900
- else {
901
- scope?.error(ex, 'Failed to execute merge-tree for conflict check');
902
- return createConflictDetectionError('other');
903
- }
904
- }
905
- if (!data)
906
- continue;
907
- const mergeConflict = parseMergeTreeConflict(data);
1077
+ // Use merge-tree --write-tree with --merge-base to simulate cherry-pick:
1078
+ // merge-tree --write-tree --merge-base=<commit-parent> <current-tree> <commit>
1079
+ // This performs a 3-way merge where:
1080
+ // - base = commit.parent (where the commit started)
1081
+ // - ours = currentTreeOid (current state of the target)
1082
+ // - theirs = commit.sha (what we're cherry-picking)
1083
+ const step = await this.runMergeTreeStep(repoPath, commit.parent, currentTreeOid, commit.sha, cancellation);
1084
+ if (!step.ok)
1085
+ return createConflictDetectionError(step.reason);
1086
+ const mergeConflict = step.result;
908
1087
  if (mergeConflict.conflicts.length) {
909
1088
  conflictingShas.push(commit.sha);
910
1089
  for (const file of mergeConflict.conflicts) {
@@ -921,6 +1100,7 @@ export class BranchesGitSubProvider {
921
1100
  shas: [commit.sha],
922
1101
  },
923
1102
  stoppedOnFirstConflict: true,
1103
+ treeOid: mergeConflict.treeOid || undefined,
924
1104
  };
925
1105
  }
926
1106
  }
@@ -941,14 +1121,15 @@ export class BranchesGitSubProvider {
941
1121
  shas: conflictingShas,
942
1122
  },
943
1123
  stoppedOnFirstConflict: false,
1124
+ treeOid: currentTreeOid,
944
1125
  };
945
1126
  }
946
- return { status: 'clean' };
1127
+ return { status: 'clean', treeOid: currentTreeOid };
947
1128
  }
948
1129
  getBaseBranchName(repoPath, ref, options, cancellation) {
949
1130
  const priority = options?.priority;
950
1131
  const priorityOpts = priority != null ? { priority: priority } : undefined;
951
- return this.cache.getBaseBranchName(repoPath, ref, async (commonPath, signal) => {
1132
+ return this.cache.getBaseBranchName(repoPath, ref, async (commonPath, cacheable, signal) => {
952
1133
  try {
953
1134
  // getGkConfig has built-in fallback to regular config for backward compatibility
954
1135
  let mergeBase = await this.provider.config.getGkConfig(commonPath, `branch.${ref}.gk-merge-base`);
@@ -974,17 +1155,27 @@ export class BranchesGitSubProvider {
974
1155
  }
975
1156
  }
976
1157
  catch { }
977
- const branch = await this.getBaseBranchFromReflog(commonPath, ref, { upstream: true, priority: priority }, signal);
978
- if (branch != null) {
1158
+ const reflog = await this.getBaseBranchFromReflog(commonPath, ref, { upstream: true, priority: priority }, signal);
1159
+ if (reflog.branch != null) {
979
1160
  // Self-write: see comment on the migration path above.
980
- void this.storeBaseBranchName(commonPath, ref, branch, {
1161
+ void this.storeBaseBranchName(commonPath, ref, reflog.branch, {
981
1162
  skipInvalidation: ['branchOverviews', 'baseBranchName'],
982
1163
  });
983
- return branch;
1164
+ return reflog.branch;
1165
+ }
1166
+ // This entry has no TTL and the `'branches'` cascade no longer clears it (a tip move can't
1167
+ // change a base), so a failed read cached as `undefined` would read as "no base branch" for
1168
+ // the rest of the session. Only a reflog that actually RAN and found nothing is an answer.
1169
+ if (reflog.failed) {
1170
+ cacheable.invalidate();
984
1171
  }
985
1172
  return undefined;
986
1173
  }, cancellation);
987
1174
  }
1175
+ /**
1176
+ * `failed` separates "the reflog ran and had no answer" from "the read never happened" — the caller
1177
+ * caches the former forever and must not cache the latter.
1178
+ */
988
1179
  async getBaseBranchFromReflog(repoPath, ref, options, cancellation) {
989
1180
  const priority = options?.priority;
990
1181
  const priorityOpts = priority != null ? { priority: priority } : undefined;
@@ -992,7 +1183,7 @@ export class BranchesGitSubProvider {
992
1183
  let result = await this.git.run({ cwd: repoPath, cancellation: cancellation, ...priorityOpts }, 'reflog', ref, '--grep-reflog=branch: Created from *.');
993
1184
  let entries = result.stdout.split('\n').filter(entry => Boolean(entry));
994
1185
  if (entries.length !== 1)
995
- return undefined;
1186
+ return { branch: undefined, failed: false };
996
1187
  // Check if branch created from an explicit branch
997
1188
  let match = entries[0].match(/branch: Created from (.*)$/);
998
1189
  if (match?.length === 2) {
@@ -1001,36 +1192,39 @@ export class BranchesGitSubProvider {
1001
1192
  if (options?.upstream) {
1002
1193
  const upstream = await this.provider.refs.getSymbolicReferenceName(repoPath, `${name}@{u}`, priorityOpts);
1003
1194
  if (upstream)
1004
- return upstream;
1195
+ return { branch: upstream, failed: false };
1005
1196
  }
1006
1197
  name = await this.provider.refs.getSymbolicReferenceName(repoPath, name, priorityOpts);
1007
1198
  if (name)
1008
- return name;
1199
+ return { branch: name, failed: false };
1009
1200
  }
1010
1201
  }
1011
1202
  // Check if branch was created from HEAD
1012
1203
  result = await this.git.run({ cwd: repoPath, cancellation: cancellation, ...priorityOpts }, 'reflog', 'HEAD', `--grep-reflog=checkout: moving from .* to ${ref.replace('refs/heads/', '')}`);
1013
1204
  entries = result.stdout.split('\n').filter(entry => Boolean(entry));
1014
1205
  if (!entries.length)
1015
- return undefined;
1206
+ return { branch: undefined, failed: false };
1016
1207
  match = entries.at(-1).match(/checkout: moving from ([^\s]+)\s/);
1017
1208
  if (match?.length === 2) {
1018
1209
  let name = match[1];
1019
1210
  if (options?.upstream) {
1020
1211
  const upstream = await this.provider.refs.getSymbolicReferenceName(repoPath, `${name}@{u}`, priorityOpts);
1021
1212
  if (upstream)
1022
- return upstream;
1213
+ return { branch: upstream, failed: false };
1023
1214
  }
1024
1215
  name = await this.provider.refs.getSymbolicReferenceName(repoPath, name, priorityOpts);
1025
1216
  if (name)
1026
- return name;
1217
+ return { branch: name, failed: false };
1027
1218
  }
1028
1219
  }
1029
1220
  catch (ex) {
1030
1221
  if (isCancellationError(ex))
1031
1222
  throw ex;
1223
+ // The reads use default error handling, so a spawn failure, queue rejection or swallowed
1224
+ // `GitWarnings` match lands here rather than producing an empty answer.
1225
+ return { branch: undefined, failed: true };
1032
1226
  }
1033
- return undefined;
1227
+ return { branch: undefined, failed: false };
1034
1228
  }
1035
1229
  async getStoredMergeTargetBranchName(repoPath, ref) {
1036
1230
  const target = (await this.getStoredUserMergeTargetBranchName?.(repoPath, ref)) ??
@@ -1073,6 +1267,13 @@ export class BranchesGitSubProvider {
1073
1267
  const args = ['branch', '-m', oldName, newName];
1074
1268
  try {
1075
1269
  await this.git.run({ cwd: repoPath }, ...args);
1270
+ // The branch keeps its identity across a rename, so move its stored gk metadata with it —
1271
+ // otherwise the new name re-derives a base it already had, and the old name is left holding
1272
+ // metadata for a branch that no longer exists under it.
1273
+ await this.provider.config.renameGkConfigBranchSection(repoPath, oldName, newName);
1274
+ // Old name's entry is now orphaned; the new name must not inherit a predecessor's base
1275
+ this.cache.deleteBaseBranchName(repoPath, oldName);
1276
+ this.cache.deleteBaseBranchName(repoPath, newName);
1076
1277
  this.context.hooks?.cache?.onReset?.(repoPath, 'branches');
1077
1278
  this.context.hooks?.repository?.onChanged?.(repoPath, ['heads']);
1078
1279
  }
@@ -1214,6 +1415,9 @@ __decorate([
1214
1415
  __decorate([
1215
1416
  debug()
1216
1417
  ], BranchesGitSubProvider.prototype, "getPotentialMergeConflicts", null);
1418
+ __decorate([
1419
+ debug()
1420
+ ], BranchesGitSubProvider.prototype, "getPotentialStashReapplyConflicts", null);
1217
1421
  __decorate([
1218
1422
  debug({ exit: true })
1219
1423
  ], BranchesGitSubProvider.prototype, "getBaseBranchName", null);