@gitkraken/core-gitlens 0.3.0 → 0.4.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 (378) hide show
  1. package/CHANGELOG.md +20 -1
  2. package/README.md +13 -10
  3. package/dist/git/cache.d.ts +12 -6
  4. package/dist/git/cache.d.ts.map +1 -1
  5. package/dist/git/cache.js +40 -39
  6. package/dist/git/cache.js.map +1 -1
  7. package/dist/git/context.d.ts +23 -3
  8. package/dist/git/context.d.ts.map +1 -1
  9. package/dist/git/context.js.map +1 -1
  10. package/dist/git/features.d.ts +1 -1
  11. package/dist/git/features.d.ts.map +1 -1
  12. package/dist/git/features.js +1 -0
  13. package/dist/git/features.js.map +1 -1
  14. package/dist/git/models/graph.d.ts +96 -0
  15. package/dist/git/models/graph.d.ts.map +1 -1
  16. package/dist/git/models/graph.js +24 -1
  17. package/dist/git/models/graph.js.map +1 -1
  18. package/dist/git/models/remoteProvider.d.ts +1 -0
  19. package/dist/git/models/remoteProvider.d.ts.map +1 -1
  20. package/dist/git/models/remoteProvider.js.map +1 -1
  21. package/dist/git/models/repository.d.ts +21 -12
  22. package/dist/git/models/repository.d.ts.map +1 -1
  23. package/dist/git/models/repository.js +70 -84
  24. package/dist/git/models/repository.js.map +1 -1
  25. package/dist/git/models/statusFile.d.ts +9 -0
  26. package/dist/git/models/statusFile.d.ts.map +1 -1
  27. package/dist/git/models/statusFile.js +11 -0
  28. package/dist/git/models/statusFile.js.map +1 -1
  29. package/dist/git/providers/commits.d.ts +4 -0
  30. package/dist/git/providers/commits.d.ts.map +1 -1
  31. package/dist/git/providers/config.d.ts +1 -3
  32. package/dist/git/providers/config.d.ts.map +1 -1
  33. package/dist/git/providers/operations.d.ts +40 -13
  34. package/dist/git/providers/operations.d.ts.map +1 -1
  35. package/dist/git/providers/worktrees.d.ts +2 -0
  36. package/dist/git/providers/worktrees.d.ts.map +1 -1
  37. package/dist/git/remotes/custom.d.ts +2 -0
  38. package/dist/git/remotes/custom.d.ts.map +1 -1
  39. package/dist/git/remotes/custom.js +20 -0
  40. package/dist/git/remotes/custom.js.map +1 -1
  41. package/dist/git/utils/conflictResolution.utils.d.ts +32 -0
  42. package/dist/git/utils/conflictResolution.utils.d.ts.map +1 -1
  43. package/dist/git/utils/conflictResolution.utils.js +95 -0
  44. package/dist/git/utils/conflictResolution.utils.js.map +1 -1
  45. package/dist/git/utils/contributor.utils.d.ts +3 -0
  46. package/dist/git/utils/contributor.utils.d.ts.map +1 -1
  47. package/dist/git/utils/contributor.utils.js +22 -0
  48. package/dist/git/utils/contributor.utils.js.map +1 -1
  49. package/dist/git/utils/reachability.utils.d.ts +37 -0
  50. package/dist/git/utils/reachability.utils.d.ts.map +1 -0
  51. package/dist/git/utils/reachability.utils.js +96 -0
  52. package/dist/git/utils/reachability.utils.js.map +1 -0
  53. package/dist/git/watching/watchService.d.ts +4 -2
  54. package/dist/git/watching/watchService.d.ts.map +1 -1
  55. package/dist/git/watching/watchService.js +8 -3
  56. package/dist/git/watching/watchService.js.map +1 -1
  57. package/dist/git/watching/watchSession.d.ts +14 -12
  58. package/dist/git/watching/watchSession.d.ts.map +1 -1
  59. package/dist/git/watching/watchSession.js +56 -55
  60. package/dist/git/watching/watchSession.js.map +1 -1
  61. package/dist/git-cli/providers/branches.d.ts.map +1 -1
  62. package/dist/git-cli/providers/branches.js +1 -0
  63. package/dist/git-cli/providers/branches.js.map +1 -1
  64. package/dist/git-cli/providers/commits.d.ts +1 -0
  65. package/dist/git-cli/providers/commits.d.ts.map +1 -1
  66. package/dist/git-cli/providers/commits.js +12 -0
  67. package/dist/git-cli/providers/commits.js.map +1 -1
  68. package/dist/git-cli/providers/config.d.ts +8 -4
  69. package/dist/git-cli/providers/config.d.ts.map +1 -1
  70. package/dist/git-cli/providers/config.js +62 -39
  71. package/dist/git-cli/providers/config.js.map +1 -1
  72. package/dist/git-cli/providers/diff.d.ts.map +1 -1
  73. package/dist/git-cli/providers/diff.js +1 -1
  74. package/dist/git-cli/providers/diff.js.map +1 -1
  75. package/dist/git-cli/providers/graph.d.ts.map +1 -1
  76. package/dist/git-cli/providers/graph.js +132 -14
  77. package/dist/git-cli/providers/graph.js.map +1 -1
  78. package/dist/git-cli/providers/operations.d.ts +7 -5
  79. package/dist/git-cli/providers/operations.d.ts.map +1 -1
  80. package/dist/git-cli/providers/operations.js +118 -95
  81. package/dist/git-cli/providers/operations.js.map +1 -1
  82. package/dist/git-cli/providers/stash.d.ts.map +1 -1
  83. package/dist/git-cli/providers/stash.js +20 -2
  84. package/dist/git-cli/providers/stash.js.map +1 -1
  85. package/dist/git-cli/providers/status.d.ts.map +1 -1
  86. package/dist/git-cli/providers/status.js +5 -2
  87. package/dist/git-cli/providers/status.js.map +1 -1
  88. package/dist/git-cli/providers/worktrees.d.ts +2 -0
  89. package/dist/git-cli/providers/worktrees.d.ts.map +1 -1
  90. package/dist/git-cli/providers/worktrees.js +3 -0
  91. package/dist/git-cli/providers/worktrees.js.map +1 -1
  92. package/dist/ipc/discovery.d.ts +22 -0
  93. package/dist/ipc/discovery.d.ts.map +1 -1
  94. package/dist/ipc/discovery.js +111 -2
  95. package/dist/ipc/discovery.js.map +1 -1
  96. package/dist/plus/agents/providers/claudeCodeProvider.d.ts +73 -20
  97. package/dist/plus/agents/providers/claudeCodeProvider.d.ts.map +1 -1
  98. package/dist/plus/agents/providers/claudeCodeProvider.js +394 -88
  99. package/dist/plus/agents/providers/claudeCodeProvider.js.map +1 -1
  100. package/dist/plus/agents/types.d.ts +62 -11
  101. package/dist/plus/agents/types.d.ts.map +1 -1
  102. package/dist/plus/ai/models/model.d.ts +1 -1
  103. package/dist/plus/ai/models/model.d.ts.map +1 -1
  104. package/dist/plus/ai/models/model.js.map +1 -1
  105. package/dist/plus/ai/models/provider.d.ts +3 -0
  106. package/dist/plus/ai/models/provider.d.ts.map +1 -1
  107. package/dist/plus/ai/providers/gitkrakenProvider.d.ts +1 -1
  108. package/dist/plus/ai/providers/gitkrakenProvider.d.ts.map +1 -1
  109. package/dist/plus/ai/providers/gitkrakenProvider.js +4 -1
  110. package/dist/plus/ai/providers/gitkrakenProvider.js.map +1 -1
  111. package/dist/plus/ai/providers/openAICompatibleProviderBase.d.ts +4 -3
  112. package/dist/plus/ai/providers/openAICompatibleProviderBase.d.ts.map +1 -1
  113. package/dist/plus/ai/providers/openAICompatibleProviderBase.js +9 -6
  114. package/dist/plus/ai/providers/openAICompatibleProviderBase.js.map +1 -1
  115. package/dist/plus/ai/utils/ai.utils.d.ts.map +1 -1
  116. package/dist/plus/ai/utils/ai.utils.js +6 -4
  117. package/dist/plus/ai/utils/ai.utils.js.map +1 -1
  118. package/dist/plus/git-github/api/github.d.ts +8 -13
  119. package/dist/plus/git-github/api/github.d.ts.map +1 -1
  120. package/dist/plus/git-github/api/github.js +96 -14
  121. package/dist/plus/git-github/api/github.js.map +1 -1
  122. package/dist/plus/git-github/providers/github/refs.js +1 -1
  123. package/dist/plus/integrations/authentication/cloudIntegrationService.d.ts +11 -0
  124. package/dist/plus/integrations/authentication/cloudIntegrationService.d.ts.map +1 -0
  125. package/dist/plus/integrations/authentication/cloudIntegrationService.js +90 -0
  126. package/dist/plus/integrations/authentication/cloudIntegrationService.js.map +1 -0
  127. package/dist/plus/integrations/authentication/configuredIntegrationService.d.ts +67 -0
  128. package/dist/plus/integrations/authentication/configuredIntegrationService.d.ts.map +1 -0
  129. package/dist/plus/integrations/authentication/configuredIntegrationService.js +295 -0
  130. package/dist/plus/integrations/authentication/configuredIntegrationService.js.map +1 -0
  131. package/dist/plus/integrations/authentication/integrationAuthenticationProvider.d.ts +92 -0
  132. package/dist/plus/integrations/authentication/integrationAuthenticationProvider.d.ts.map +1 -0
  133. package/dist/plus/integrations/authentication/integrationAuthenticationProvider.js +185 -0
  134. package/dist/plus/integrations/authentication/integrationAuthenticationProvider.js.map +1 -0
  135. package/dist/plus/integrations/authentication/integrationAuthenticationService.d.ts +62 -0
  136. package/dist/plus/integrations/authentication/integrationAuthenticationService.d.ts.map +1 -0
  137. package/dist/plus/integrations/authentication/integrationAuthenticationService.js +122 -0
  138. package/dist/plus/integrations/authentication/integrationAuthenticationService.js.map +1 -0
  139. package/dist/plus/integrations/authentication/manualTokenProvider.d.ts +15 -0
  140. package/dist/plus/integrations/authentication/manualTokenProvider.d.ts.map +1 -0
  141. package/dist/plus/integrations/authentication/manualTokenProvider.js +49 -0
  142. package/dist/plus/integrations/authentication/manualTokenProvider.js.map +1 -0
  143. package/dist/plus/integrations/authentication/models.d.ts +87 -0
  144. package/dist/plus/integrations/authentication/models.d.ts.map +1 -0
  145. package/dist/plus/integrations/authentication/models.js +64 -0
  146. package/dist/plus/integrations/authentication/models.js.map +1 -0
  147. package/dist/plus/integrations/constants.d.ts +47 -0
  148. package/dist/plus/integrations/constants.d.ts.map +1 -0
  149. package/dist/plus/integrations/constants.js +128 -0
  150. package/dist/plus/integrations/constants.js.map +1 -0
  151. package/dist/plus/integrations/context.d.ts +354 -0
  152. package/dist/plus/integrations/context.d.ts.map +1 -0
  153. package/dist/plus/integrations/context.js +2 -0
  154. package/dist/plus/integrations/context.js.map +1 -0
  155. package/dist/plus/integrations/errors.d.ts +14 -0
  156. package/dist/plus/integrations/errors.d.ts.map +1 -0
  157. package/dist/plus/integrations/errors.js +19 -0
  158. package/dist/plus/integrations/errors.js.map +1 -0
  159. package/dist/plus/integrations/index.d.ts +32 -0
  160. package/dist/plus/integrations/index.d.ts.map +1 -0
  161. package/dist/plus/integrations/index.js +62 -0
  162. package/dist/plus/integrations/index.js.map +1 -0
  163. package/dist/plus/integrations/integrationService.d.ts +98 -0
  164. package/dist/plus/integrations/integrationService.d.ts.map +1 -0
  165. package/dist/plus/integrations/integrationService.js +627 -0
  166. package/dist/plus/integrations/integrationService.js.map +1 -0
  167. package/dist/plus/integrations/models/gitHostIntegration.d.ts +82 -0
  168. package/dist/plus/integrations/models/gitHostIntegration.d.ts.map +1 -0
  169. package/dist/plus/integrations/models/gitHostIntegration.js +522 -0
  170. package/dist/plus/integrations/models/gitHostIntegration.js.map +1 -0
  171. package/dist/plus/integrations/models/integration.d.ts +127 -0
  172. package/dist/plus/integrations/models/integration.d.ts.map +1 -0
  173. package/dist/plus/integrations/models/integration.js +456 -0
  174. package/dist/plus/integrations/models/integration.js.map +1 -0
  175. package/dist/plus/integrations/models/issuesIntegration.d.ts +28 -0
  176. package/dist/plus/integrations/models/issuesIntegration.d.ts.map +1 -0
  177. package/dist/plus/integrations/models/issuesIntegration.js +101 -0
  178. package/dist/plus/integrations/models/issuesIntegration.js.map +1 -0
  179. package/dist/plus/integrations/providers/apiClients.d.ts +25 -0
  180. package/dist/plus/integrations/providers/apiClients.d.ts.map +1 -0
  181. package/dist/plus/integrations/providers/apiClients.js +39 -0
  182. package/dist/plus/integrations/providers/apiClients.js.map +1 -0
  183. package/dist/plus/integrations/providers/azure/azure.d.ts +47 -0
  184. package/dist/plus/integrations/providers/azure/azure.d.ts.map +1 -0
  185. package/dist/plus/integrations/providers/azure/azure.js +444 -0
  186. package/dist/plus/integrations/providers/azure/azure.js.map +1 -0
  187. package/dist/plus/integrations/providers/azure/models.d.ts +286 -0
  188. package/dist/plus/integrations/providers/azure/models.d.ts.map +1 -0
  189. package/dist/plus/integrations/providers/azure/models.js +203 -0
  190. package/dist/plus/integrations/providers/azure/models.js.map +1 -0
  191. package/dist/plus/integrations/providers/azureDevOps.d.ts +95 -0
  192. package/dist/plus/integrations/providers/azureDevOps.d.ts.map +1 -0
  193. package/dist/plus/integrations/providers/azureDevOps.js +432 -0
  194. package/dist/plus/integrations/providers/azureDevOps.js.map +1 -0
  195. package/dist/plus/integrations/providers/bitbucket/bitbucket.d.ts +42 -0
  196. package/dist/plus/integrations/providers/bitbucket/bitbucket.d.ts.map +1 -0
  197. package/dist/plus/integrations/providers/bitbucket/bitbucket.js +513 -0
  198. package/dist/plus/integrations/providers/bitbucket/bitbucket.js.map +1 -0
  199. package/dist/plus/integrations/providers/bitbucket/models.d.ts +222 -0
  200. package/dist/plus/integrations/providers/bitbucket/models.d.ts.map +1 -0
  201. package/dist/plus/integrations/providers/bitbucket/models.js +158 -0
  202. package/dist/plus/integrations/providers/bitbucket/models.js.map +1 -0
  203. package/dist/plus/integrations/providers/bitbucket-server/models.d.ts +120 -0
  204. package/dist/plus/integrations/providers/bitbucket-server/models.d.ts.map +1 -0
  205. package/dist/plus/integrations/providers/bitbucket-server/models.js +111 -0
  206. package/dist/plus/integrations/providers/bitbucket-server/models.js.map +1 -0
  207. package/dist/plus/integrations/providers/bitbucket-server.d.ts +60 -0
  208. package/dist/plus/integrations/providers/bitbucket-server.d.ts.map +1 -0
  209. package/dist/plus/integrations/providers/bitbucket-server.js +138 -0
  210. package/dist/plus/integrations/providers/bitbucket-server.js.map +1 -0
  211. package/dist/plus/integrations/providers/bitbucket.d.ts +50 -0
  212. package/dist/plus/integrations/providers/bitbucket.d.ts.map +1 -0
  213. package/dist/plus/integrations/providers/bitbucket.js +181 -0
  214. package/dist/plus/integrations/providers/bitbucket.js.map +1 -0
  215. package/dist/plus/integrations/providers/github/github.d.ts +5 -0
  216. package/dist/plus/integrations/providers/github/github.d.ts.map +1 -0
  217. package/dist/plus/integrations/providers/github/github.js +47 -0
  218. package/dist/plus/integrations/providers/github/github.js.map +1 -0
  219. package/dist/plus/integrations/providers/github/github.utils.d.ts +11 -0
  220. package/dist/plus/integrations/providers/github/github.utils.d.ts.map +1 -0
  221. package/dist/plus/integrations/providers/github/github.utils.js +6 -0
  222. package/dist/plus/integrations/providers/github/github.utils.js.map +1 -0
  223. package/dist/plus/integrations/providers/github/models.d.ts +3 -0
  224. package/dist/plus/integrations/providers/github/models.d.ts.map +1 -0
  225. package/dist/plus/integrations/providers/github/models.js +2 -0
  226. package/dist/plus/integrations/providers/github/models.js.map +1 -0
  227. package/dist/plus/integrations/providers/github.d.ts +73 -0
  228. package/dist/plus/integrations/providers/github.d.ts.map +1 -0
  229. package/dist/plus/integrations/providers/github.js +160 -0
  230. package/dist/plus/integrations/providers/github.js.map +1 -0
  231. package/dist/plus/integrations/providers/gitlab/gitlab.d.ts +63 -0
  232. package/dist/plus/integrations/providers/gitlab/gitlab.d.ts.map +1 -0
  233. package/dist/plus/integrations/providers/gitlab/gitlab.js +828 -0
  234. package/dist/plus/integrations/providers/gitlab/gitlab.js.map +1 -0
  235. package/dist/plus/integrations/providers/gitlab/gitlab.utils.d.ts +21 -0
  236. package/dist/plus/integrations/providers/gitlab/gitlab.utils.d.ts.map +1 -0
  237. package/dist/plus/integrations/providers/gitlab/gitlab.utils.js +45 -0
  238. package/dist/plus/integrations/providers/gitlab/gitlab.utils.js.map +1 -0
  239. package/dist/plus/integrations/providers/gitlab/models.d.ts +128 -0
  240. package/dist/plus/integrations/providers/gitlab/models.d.ts.map +1 -0
  241. package/dist/plus/integrations/providers/gitlab/models.js +96 -0
  242. package/dist/plus/integrations/providers/gitlab/models.js.map +1 -0
  243. package/dist/plus/integrations/providers/gitlab.d.ts +79 -0
  244. package/dist/plus/integrations/providers/gitlab.d.ts.map +1 -0
  245. package/dist/plus/integrations/providers/gitlab.js +253 -0
  246. package/dist/plus/integrations/providers/gitlab.js.map +1 -0
  247. package/dist/plus/integrations/providers/jira.d.ts +46 -0
  248. package/dist/plus/integrations/providers/jira.d.ts.map +1 -0
  249. package/dist/plus/integrations/providers/jira.js +255 -0
  250. package/dist/plus/integrations/providers/jira.js.map +1 -0
  251. package/dist/plus/integrations/providers/linear.d.ts +47 -0
  252. package/dist/plus/integrations/providers/linear.d.ts.map +1 -0
  253. package/dist/plus/integrations/providers/linear.js +198 -0
  254. package/dist/plus/integrations/providers/linear.js.map +1 -0
  255. package/dist/plus/integrations/providers/models.d.ts +433 -0
  256. package/dist/plus/integrations/providers/models.d.ts.map +1 -0
  257. package/dist/plus/integrations/providers/models.js +602 -0
  258. package/dist/plus/integrations/providers/models.js.map +1 -0
  259. package/dist/plus/integrations/providers/providersApi.d.ts +129 -0
  260. package/dist/plus/integrations/providers/providersApi.d.ts.map +1 -0
  261. package/dist/plus/integrations/providers/providersApi.js +602 -0
  262. package/dist/plus/integrations/providers/providersApi.js.map +1 -0
  263. package/dist/plus/integrations/providers/utils.d.ts +54 -0
  264. package/dist/plus/integrations/providers/utils.d.ts.map +1 -0
  265. package/dist/plus/integrations/providers/utils.js +262 -0
  266. package/dist/plus/integrations/providers/utils.js.map +1 -0
  267. package/dist/plus/integrations/telemetry.d.ts +26 -0
  268. package/dist/plus/integrations/telemetry.d.ts.map +1 -0
  269. package/dist/plus/integrations/telemetry.js +11 -0
  270. package/dist/plus/integrations/telemetry.js.map +1 -0
  271. package/dist/plus/integrations/utils/integration.utils.d.ts +14 -0
  272. package/dist/plus/integrations/utils/integration.utils.d.ts.map +1 -0
  273. package/dist/plus/integrations/utils/integration.utils.js +111 -0
  274. package/dist/plus/integrations/utils/integration.utils.js.map +1 -0
  275. package/dist/utils/date.d.ts.map +1 -1
  276. package/dist/utils/date.js +6 -0
  277. package/dist/utils/date.js.map +1 -1
  278. package/dist/utils/debounce.js +1 -1
  279. package/dist/utils/disposable.d.ts +15 -2
  280. package/dist/utils/disposable.d.ts.map +1 -1
  281. package/dist/utils/disposable.js.map +1 -1
  282. package/dist/utils/iterable.d.ts +7 -1
  283. package/dist/utils/iterable.d.ts.map +1 -1
  284. package/dist/utils/iterable.js +12 -4
  285. package/dist/utils/iterable.js.map +1 -1
  286. package/dist/utils/object.d.ts +22 -1
  287. package/dist/utils/object.d.ts.map +1 -1
  288. package/dist/utils/object.js +56 -3
  289. package/dist/utils/object.js.map +1 -1
  290. package/dist/utils/paging.js +14 -15
  291. package/dist/utils/paging.js.map +1 -1
  292. package/dist/utils/promiseCache.d.ts.map +1 -1
  293. package/dist/utils/promiseCache.js +12 -4
  294. package/dist/utils/promiseCache.js.map +1 -1
  295. package/dist/utils/string.js +1 -1
  296. package/package.json +43 -2
  297. package/src/git/cache.ts +58 -70
  298. package/src/git/context.ts +25 -3
  299. package/src/git/features.ts +2 -0
  300. package/src/git/models/graph.ts +97 -0
  301. package/src/git/models/remoteProvider.ts +1 -0
  302. package/src/git/models/repository.ts +84 -93
  303. package/src/git/models/statusFile.ts +12 -0
  304. package/src/git/providers/commits.ts +4 -0
  305. package/src/git/providers/config.ts +1 -5
  306. package/src/git/providers/operations.ts +42 -22
  307. package/src/git/providers/worktrees.ts +14 -2
  308. package/src/git/remotes/custom.ts +23 -0
  309. package/src/git/utils/conflictResolution.utils.ts +126 -0
  310. package/src/git/utils/contributor.utils.ts +24 -0
  311. package/src/git/utils/reachability.utils.ts +109 -0
  312. package/src/git/watching/watchService.ts +9 -4
  313. package/src/git/watching/watchSession.ts +61 -55
  314. package/src/git-cli/providers/branches.ts +1 -0
  315. package/src/git-cli/providers/commits.ts +24 -0
  316. package/src/git-cli/providers/config.ts +63 -49
  317. package/src/git-cli/providers/diff.ts +2 -1
  318. package/src/git-cli/providers/graph.ts +145 -14
  319. package/src/git-cli/providers/operations.ts +138 -130
  320. package/src/git-cli/providers/stash.ts +20 -2
  321. package/src/git-cli/providers/status.ts +11 -3
  322. package/src/git-cli/providers/worktrees.ts +17 -2
  323. package/src/ipc/discovery.ts +132 -2
  324. package/src/plus/agents/providers/claudeCodeProvider.ts +430 -90
  325. package/src/plus/agents/types.ts +59 -11
  326. package/src/plus/ai/models/model.ts +1 -0
  327. package/src/plus/ai/models/provider.ts +7 -1
  328. package/src/plus/ai/providers/gitkrakenProvider.ts +4 -0
  329. package/src/plus/ai/providers/openAICompatibleProviderBase.ts +21 -4
  330. package/src/plus/ai/utils/ai.utils.ts +6 -4
  331. package/src/plus/git-github/api/github.ts +131 -31
  332. package/src/plus/git-github/providers/github/refs.ts +1 -1
  333. package/src/plus/integrations/authentication/cloudIntegrationService.ts +120 -0
  334. package/src/plus/integrations/authentication/configuredIntegrationService.ts +414 -0
  335. package/src/plus/integrations/authentication/integrationAuthenticationProvider.ts +289 -0
  336. package/src/plus/integrations/authentication/integrationAuthenticationService.ts +144 -0
  337. package/src/plus/integrations/authentication/manualTokenProvider.ts +72 -0
  338. package/src/plus/integrations/authentication/models.ts +168 -0
  339. package/src/plus/integrations/constants.ts +169 -0
  340. package/src/plus/integrations/context.ts +383 -0
  341. package/src/plus/integrations/errors.ts +35 -0
  342. package/src/plus/integrations/index.ts +119 -0
  343. package/src/plus/integrations/integrationService.ts +924 -0
  344. package/src/plus/integrations/models/gitHostIntegration.ts +738 -0
  345. package/src/plus/integrations/models/integration.ts +657 -0
  346. package/src/plus/integrations/models/issuesIntegration.ts +124 -0
  347. package/src/plus/integrations/providers/apiClients.ts +72 -0
  348. package/src/plus/integrations/providers/azure/azure.ts +691 -0
  349. package/src/plus/integrations/providers/azure/models.ts +605 -0
  350. package/src/plus/integrations/providers/azureDevOps.ts +677 -0
  351. package/src/plus/integrations/providers/bitbucket/bitbucket.ts +774 -0
  352. package/src/plus/integrations/providers/bitbucket/models.ts +445 -0
  353. package/src/plus/integrations/providers/bitbucket-server/models.ts +252 -0
  354. package/src/plus/integrations/providers/bitbucket-server.ts +282 -0
  355. package/src/plus/integrations/providers/bitbucket.ts +350 -0
  356. package/src/plus/integrations/providers/github/github.ts +64 -0
  357. package/src/plus/integrations/providers/github/github.utils.ts +23 -0
  358. package/src/plus/integrations/providers/github/models.ts +29 -0
  359. package/src/plus/integrations/providers/github.ts +366 -0
  360. package/src/plus/integrations/providers/gitlab/gitlab.ts +1210 -0
  361. package/src/plus/integrations/providers/gitlab/gitlab.utils.ts +71 -0
  362. package/src/plus/integrations/providers/gitlab/models.ts +257 -0
  363. package/src/plus/integrations/providers/gitlab.ts +473 -0
  364. package/src/plus/integrations/providers/jira.ts +359 -0
  365. package/src/plus/integrations/providers/linear.ts +275 -0
  366. package/src/plus/integrations/providers/models.ts +1114 -0
  367. package/src/plus/integrations/providers/providersApi.ts +1126 -0
  368. package/src/plus/integrations/providers/utils.ts +359 -0
  369. package/src/plus/integrations/telemetry.ts +29 -0
  370. package/src/plus/integrations/utils/integration.utils.ts +138 -0
  371. package/src/utils/date.ts +5 -0
  372. package/src/utils/debounce.ts +1 -1
  373. package/src/utils/disposable.ts +15 -2
  374. package/src/utils/iterable.ts +14 -3
  375. package/src/utils/object.ts +55 -4
  376. package/src/utils/paging.ts +14 -14
  377. package/src/utils/promiseCache.ts +24 -16
  378. package/src/utils/string.ts +1 -1
@@ -7,7 +7,6 @@ import type { UnifiedDisposable } from '../../utils/disposable.js';
7
7
  import { createDisposable } from '../../utils/disposable.js';
8
8
  import type { Event } from '../../utils/event.js';
9
9
  import { Emitter } from '../../utils/event.js';
10
- import { getScopedLogger } from '../../utils/logger.scoped.js';
11
10
  import { basename, normalizePath } from '../../utils/path.js';
12
11
  import type { Uri } from '../../utils/uri.js';
13
12
  import { fileUri } from '../../utils/uri.js';
@@ -84,8 +83,6 @@ export interface RepositoryInit {
84
83
  readonly gitDir: GitDir | undefined;
85
84
  readonly index: number;
86
85
  readonly root: boolean;
87
- readonly closed?: boolean;
88
- readonly suspended?: boolean;
89
86
  readonly watchService: RepositoryWatchService;
90
87
  }
91
88
 
@@ -118,19 +115,21 @@ export class Repository {
118
115
  readonly root: boolean;
119
116
 
120
117
  protected readonly _gitDir: GitDir | undefined;
121
- protected _pendingResumeTimer: ReturnType<typeof setTimeout> | undefined;
122
118
  protected _repoSubscription: RepositorySubscription | undefined;
123
- protected _suspended: boolean;
124
119
  protected _watchHandle: WatchHandle | undefined;
125
120
  protected readonly _watchService: RepositoryWatchService;
126
121
 
127
122
  private _pendingWorkingTreeChange?: RepositoryWorkingTreeChangeEvent;
128
123
  private _pendingWorkingTreeFlush = false;
129
124
  private _repoChangeListener: UnifiedDisposable | undefined;
125
+ /** Outstanding leases on the shared {@link _watchHandle} across `watch()` + `watchWorkingTree()`. */
126
+ private _watchHandleRefs = 0;
127
+ /** Outstanding `watch()` (repo-change) leases; drives the single shared bridge subscription. */
128
+ private _watchRefs = 0;
129
+ private _disposed = false;
130
130
 
131
131
  constructor(init: RepositoryInit) {
132
132
  ({
133
- closed: this._closed = init.closed ?? false,
134
133
  id: this.id,
135
134
  index: this.index,
136
135
  gitDir: this._gitDir,
@@ -138,7 +137,6 @@ export class Repository {
138
137
  path: this.path,
139
138
  provider: this.provider,
140
139
  root: this.root,
141
- suspended: this._suspended = init.suspended ?? false,
142
140
  uri: this.uri,
143
141
  watchService: this._watchService,
144
142
  } = init);
@@ -156,45 +154,25 @@ export class Repository {
156
154
  this._name = `${prefix}${this._name}`;
157
155
  }
158
156
  }
159
-
160
- this.setupWatching();
161
157
  }
162
158
 
163
159
  dispose(): void {
164
- clearTimeout(this._pendingResumeTimer);
160
+ this._disposed = true;
165
161
  this._repoChangeListener?.dispose();
166
162
  this._repoChangeListener = undefined;
167
163
  this._repoSubscription?.dispose();
168
164
  this._repoSubscription = undefined;
169
165
  this._watchHandle?.dispose();
170
166
  this._watchHandle = undefined;
167
+ this._watchHandleRefs = 0;
168
+ this._watchRefs = 0;
171
169
  this._onDidChange.dispose();
172
170
  this._onDidChangeWorkingTree.dispose();
173
171
  }
174
172
 
175
- protected _closed: boolean;
176
- get closed(): boolean {
177
- return this._closed;
178
- }
179
- set closed(value: boolean) {
180
- const changed = this._closed !== value;
181
- this._closed = value;
182
- if (changed) {
183
- if (this._closed) {
184
- // When closing, fire immediately even if suspended
185
- this.fireChange('closed', true);
186
-
187
- this._repoChangeListener?.dispose();
188
- this._repoChangeListener = undefined;
189
- this._repoSubscription?.dispose();
190
- this._repoSubscription = undefined;
191
- } else {
192
- this.setupWatching();
193
- this.fireChange('opened');
194
- }
195
-
196
- this.onClosedChanged(value);
197
- }
173
+ /** Indicates whether this repository currently has an active `watch()` (repo-change) lease. */
174
+ get watching(): boolean {
175
+ return this._watchRefs > 0;
198
176
  }
199
177
 
200
178
  get etag(): number {
@@ -249,41 +227,6 @@ export class Repository {
249
227
  return this.provider.virtual;
250
228
  }
251
229
 
252
- @trace({ onlyExit: true })
253
- suspend(): void {
254
- this._suspended = true;
255
- this._watchHandle?.session.suspend();
256
-
257
- if (this._pendingResumeTimer != null) {
258
- clearTimeout(this._pendingResumeTimer);
259
- this._pendingResumeTimer = undefined;
260
- }
261
- }
262
-
263
- @trace({ onlyExit: true })
264
- resume(delayMs?: number): void {
265
- if (delayMs) {
266
- if (this._pendingResumeTimer != null) {
267
- clearTimeout(this._pendingResumeTimer);
268
- }
269
- this._pendingResumeTimer = setTimeout(() => {
270
- this._pendingResumeTimer = undefined;
271
- this.resume();
272
- }, delayMs);
273
- return;
274
- }
275
-
276
- const scope = getScopedLogger();
277
-
278
- if (!this._suspended) {
279
- scope?.addExitInfo('ignored; not suspended');
280
- return;
281
- }
282
-
283
- this._suspended = false;
284
- this._watchHandle?.session.resume();
285
- }
286
-
287
230
  waitForRepoChange(timeoutMs: number): Promise<boolean> {
288
231
  let timeoutId: ReturnType<typeof setTimeout> | undefined;
289
232
  let listener: UnifiedDisposable | undefined;
@@ -313,17 +256,57 @@ export class Repository {
313
256
  ]);
314
257
  }
315
258
 
259
+ /**
260
+ * Starts watching this repository's `.git` directory for repo-change events (delivered via
261
+ * {@link onDidChange}) and returns a lease. Ref-counted: the underlying watch handle is acquired
262
+ * on the first lease (across `watch()` and {@link watchWorkingTree}) and released when the last
263
+ * is disposed. The model is inert until watched. No-ops (returns a disposable that does nothing)
264
+ * for repositories without a git directory (e.g. virtual repos).
265
+ */
266
+ @trace({ onlyExit: true })
267
+ watch(): UnifiedDisposable {
268
+ const acquired = this.acquireWatchHandle();
269
+ if (acquired == null) return createDisposable(() => {});
270
+
271
+ if (this._watchRefs++ === 0) {
272
+ const sub = acquired.handle.session.subscribe();
273
+ this._repoSubscription = sub;
274
+ this._repoChangeListener = sub.onDidChange(e => this.onSessionRepoChange(e));
275
+ }
276
+
277
+ return createDisposable(
278
+ () => {
279
+ // Repo already torn down — its handle/subscriptions are gone; skip to avoid a negative refcount
280
+ if (this._disposed) return;
281
+
282
+ if (--this._watchRefs === 0) {
283
+ this._repoChangeListener?.dispose();
284
+ this._repoChangeListener = undefined;
285
+ this._repoSubscription?.dispose();
286
+ this._repoSubscription = undefined;
287
+ }
288
+ acquired.lease.dispose();
289
+ },
290
+ { once: true },
291
+ );
292
+ }
293
+
316
294
  @trace({ onlyExit: true })
317
295
  watchWorkingTree(delay: number = 2500): UnifiedDisposable {
318
- const sub = this._watchHandle?.session.subscribeToWorkingTree({ delayMs: delay });
319
- if (sub == null) return createDisposable(() => {});
296
+ const acquired = this.acquireWatchHandle();
297
+ if (acquired == null) return createDisposable(() => {});
320
298
 
299
+ const sub = acquired.handle.session.subscribeToWorkingTree({ delayMs: delay });
321
300
  const listener = sub.onDidChangeWorkingTree(e => this.onWorkingTreeChanged(e.paths));
322
301
 
323
- return createDisposable(() => {
324
- listener.dispose();
325
- sub.dispose();
326
- });
302
+ return createDisposable(
303
+ () => {
304
+ listener.dispose();
305
+ sub.dispose();
306
+ acquired.lease.dispose();
307
+ },
308
+ { once: true },
309
+ );
327
310
  }
328
311
 
329
312
  /**
@@ -338,7 +321,7 @@ export class Repository {
338
321
  protected fireChange(change: RepositoryChange, force: boolean): void;
339
322
  @trace()
340
323
  protected fireChange(...args: RepositoryChange[] | [RepositoryChange, boolean]): void {
341
- const lastArg = args[args.length - 1];
324
+ const lastArg = args.at(-1);
342
325
  const force = typeof lastArg === 'boolean' ? lastArg : false;
343
326
  const changes = (force ? args.slice(0, -1) : args) as RepositoryChange[];
344
327
 
@@ -353,9 +336,6 @@ export class Repository {
353
336
  this._watchHandle?.session.fireChange(...changes);
354
337
  }
355
338
 
356
- /** Called when the closed state changes. Override to add behavior (e.g., branch access tracking). */
357
- protected onClosedChanged(_closed: boolean): void {}
358
-
359
339
  /** Called when the watch service notifies of FETCH_HEAD changes. */
360
340
  protected onFetchHeadChanged(): void {
361
341
  // Don't reset `_lastFetched` here: the FS watcher can fire even when the on-disk mtime
@@ -414,39 +394,50 @@ export class Repository {
414
394
  (this._pendingWorkingTreeChange.uris as Set<Uri>).add(fileUri(p));
415
395
  }
416
396
 
417
- if (this._suspended) return;
418
397
  if (this._pendingWorkingTreeFlush) return;
419
398
 
420
399
  this._pendingWorkingTreeFlush = true;
421
400
  queueMicrotask(() => {
422
401
  this._pendingWorkingTreeFlush = false;
423
402
  const e = this._pendingWorkingTreeChange;
424
- if (e == null || this._suspended) return;
403
+ if (e == null) return;
425
404
 
426
405
  this._pendingWorkingTreeChange = undefined;
427
406
  this._onDidChangeWorkingTree.fire(e);
428
407
  });
429
408
  }
430
409
 
431
- @trace({ onlyExit: true })
432
- private setupWatching(): void {
433
- if (this._gitDir == null || this._closed) return;
434
- if (this._repoSubscription != null) return;
435
-
436
- const scope = getScopedLogger();
410
+ /**
411
+ * Lazily acquires (and ref-counts) the shared {@link _watchHandle} for this repository, returning
412
+ * the handle plus a one-shot lease whose disposal decrements the ref count (releasing the handle
413
+ * when the last lease is gone). Returns `undefined` for repositories without a git directory.
414
+ */
415
+ private acquireWatchHandle(): { handle: WatchHandle; lease: UnifiedDisposable } | undefined {
416
+ if (this._gitDir == null || this._disposed) return undefined;
437
417
 
438
418
  this._watchHandle ??= this._watchService.watch(this.path, this._gitDir, {
439
419
  onFetchHeadChanged: () => this.onFetchHeadChanged(),
440
420
  onGitIgnoreChanged: () => this.onGitIgnoreChanged(),
441
421
  onIgnoresChanged: () => this.onIgnoresChanged(),
442
422
  } satisfies WatchHooks);
443
- if (this._watchHandle == null) return;
444
-
445
- const sub = this._watchHandle.session.subscribe();
446
- this._repoSubscription = sub;
447
- this._repoChangeListener = sub.onDidChange(e => this.onSessionRepoChange(e));
448
-
449
- scope?.trace(`subscribed to repo changes for '${this.uri.toString()}'`);
423
+ const handle = this._watchHandle;
424
+ if (handle == null) return undefined;
425
+
426
+ this._watchHandleRefs++;
427
+ const lease = createDisposable(
428
+ () => {
429
+ // Repo already torn down — the handle is gone; skip to avoid a negative refcount
430
+ if (this._disposed) return;
431
+
432
+ if (--this._watchHandleRefs === 0) {
433
+ this._watchHandle?.dispose();
434
+ this._watchHandle = undefined;
435
+ }
436
+ },
437
+ { once: true },
438
+ );
439
+
440
+ return { handle: handle, lease: lease };
450
441
  }
451
442
 
452
443
  static is(repository: unknown): repository is Repository {
@@ -94,6 +94,18 @@ export class GitStatusFile implements GitFile {
94
94
  return this.conflictStatus != null;
95
95
  }
96
96
 
97
+ /**
98
+ * True when the file has both staged (index) and unstaged (working-tree) changes and is not
99
+ * conflicted. Used by the host-side discard flows to apply partial-discard semantics — drop the
100
+ * working-tree delta while preserving the staged portion. Centralized here so those host paths
101
+ * agree on what "mixed" means. (The webview button predicate can't consume this getter — it
102
+ * runs against the wire `GitFileChangeShape` and infers the same state from the duplicate
103
+ * staged/unstaged rows the WIP feed emits per mixed path.)
104
+ */
105
+ get mixed(): boolean {
106
+ return this.indexStatus != null && this.workingTreeStatus != null && this.conflictStatus == null;
107
+ }
108
+
97
109
  get staged(): boolean {
98
110
  return this.indexStatus != null;
99
111
  }
@@ -78,6 +78,10 @@ export interface GitCommitReachability {
78
78
  export interface GitCommitsSubProvider {
79
79
  getCommit(repoPath: string, rev: string, cancellation?: AbortSignal): Promise<GitCommit | undefined>;
80
80
  getCommitCount(repoPath: string, rev: string, cancellation?: AbortSignal): Promise<number | undefined>;
81
+ /** Whether `rev` has any commits not reachable from any remote-tracking ref (i.e. unpushed/unpublished).
82
+ * Cheap early-exit probe (`rev-list --not --remotes <rev> -n 1`) — it does NOT count them. Returns
83
+ * `undefined` when it can't be determined. */
84
+ hasUnpublishedCommits?(repoPath: string, rev: string, cancellation?: AbortSignal): Promise<boolean | undefined>;
81
85
  /** Cheap author/committer date lookup for a single revision. Skips the full commit parse (no files, parents, or message) — use when only the dates are needed. Pass a full SHA so caching stays correct (commits are immutable; refs are not). */
82
86
  getCommitDates?(
83
87
  repoPath: string,
@@ -88,11 +88,7 @@ export interface GitConfigSubProvider {
88
88
  | []
89
89
  >;
90
90
 
91
- getGkConfig?(
92
- repoPath: string,
93
- key: GkConfigKeys | DeprecatedGkConfigKeys,
94
- options?: { type?: GitConfigType },
95
- ): Promise<string | undefined>;
91
+ getGkConfig?(repoPath: string, key: GkConfigKeys | DeprecatedGkConfigKeys): Promise<string | undefined>;
96
92
  getGkConfigRegex?(repoPath: string, pattern: string): Promise<Map<string, string>>;
97
93
  setGkConfig?(
98
94
  repoPath: string,
@@ -21,31 +21,14 @@ export interface GitOperationResult {
21
21
  export type GitOperationRunOptions = Pick<GitRunOptions, 'env' | 'cancellation' | 'timeout'>;
22
22
 
23
23
  export interface GitOperationsSubProvider {
24
- checkout(
25
- repoPath: string,
26
- ref: string,
27
- options?: { createBranch?: string | undefined } | { path?: string | undefined },
28
- runOptions?: GitOperationRunOptions,
29
- ): Promise<void>;
30
24
  /**
31
- * Checks out one side of a conflicted path during a paused merge/rebase/cherry-pick.
32
- * Executes `git checkout --ours|--theirs -- <path>`, leaving the file unstaged.
25
+ * Switches the repository to a branch or ref. Pure branch/ref switching — does not accept
26
+ * a path. For path-level operations use {@link restore}.
33
27
  */
34
- checkoutConflictedPath(
35
- repoPath: string,
36
- path: string,
37
- side: 'ours' | 'theirs',
38
- runOptions?: GitOperationRunOptions,
39
- ): Promise<void>;
40
- /**
41
- * Checks out one side for multiple conflicted paths during a paused merge/rebase/cherry-pick.
42
- * Batches paths into `git checkout --ours|--theirs -- <paths...>`, chunked to stay under the
43
- * CLI length limit. Leaves files unstaged.
44
- */
45
- checkoutConflictedPaths(
28
+ checkout(
46
29
  repoPath: string,
47
- paths: string[],
48
- side: 'ours' | 'theirs',
30
+ ref: string,
31
+ options?: { createBranch?: string | undefined; noTracking?: boolean },
49
32
  runOptions?: GitOperationRunOptions,
50
33
  ): Promise<void>;
51
34
  cherryPick(
@@ -110,8 +93,24 @@ export interface GitOperationsSubProvider {
110
93
  options?: {
111
94
  autoStash?: boolean;
112
95
  branch?: string;
96
+ /** Command set as `sequence.editor` — edits the interactive rebase todo list. */
113
97
  editor?: string;
114
98
  interactive?: boolean;
99
+ /**
100
+ * Set when the `editor` is a script that rewrites the todo by command word + SHA (e.g. the
101
+ * Commit Graph's headless squash/drop/reword) rather than a human. Forces git to emit a plain,
102
+ * natural-order todo by disabling `rebase.autosquash` (which would reorder commits and rewrite
103
+ * `pick`→`fixup` for `fixup!`/`squash!` commits) and `rebase.abbreviateCommands` (which would
104
+ * emit `p` instead of `pick`). Both honor the user's git config otherwise.
105
+ */
106
+ programmaticEditor?: boolean;
107
+ /**
108
+ * Command git uses to edit per-commit messages (the combined message a `squash` produces, or a
109
+ * `reword`). Applied as `GIT_EDITOR` — which git's interactive-rebase `reword`/`squash` step honors,
110
+ * unlike `core.editor` — with `core.editor` also set as a fallback. When omitted, git falls back to
111
+ * the user's configured editor.
112
+ */
113
+ messageEditor?: string;
115
114
  onto?: string;
116
115
  updateRefs?: boolean;
117
116
  source?: unknown;
@@ -124,6 +123,27 @@ export interface GitOperationsSubProvider {
124
123
  options?: { mode?: 'hard' | 'keep' | 'merge' | 'mixed' | 'soft' },
125
124
  runOptions?: GitOperationRunOptions,
126
125
  ): Promise<void>;
126
+ /**
127
+ * Restores the working-tree copy of one or more paths. Single path or array; arrays are
128
+ * chunked under the CLI length limit and dispatched as one git invocation per chunk
129
+ * (typically one for normal-sized batches).
130
+ *
131
+ * Source selection — `options` is mutually exclusive (`side` wins if both are supplied):
132
+ *
133
+ * - **No options** (`git checkout -- <paths>`): copies the index to the working tree, leaving
134
+ * the index untouched. Use to drop unstaged changes (mixed-file partial discard preserves
135
+ * the staged portion this way).
136
+ * - **`{ ref }`** (`git checkout <ref> -- <paths>`): resets BOTH the index and the working
137
+ * tree of the path(s) to `<ref>`. Use to fully revert files to HEAD, a specific commit, etc.
138
+ * - **`{ side }`** (`git checkout --ours|--theirs -- <paths>`): conflict-resolution
139
+ * shortcut, valid only during a paused merge/rebase/cherry-pick. Leaves files unstaged.
140
+ */
141
+ restore(
142
+ repoPath: string,
143
+ path: string | string[],
144
+ options?: { ref?: string; side?: 'ours' | 'theirs' },
145
+ runOptions?: GitOperationRunOptions,
146
+ ): Promise<void>;
127
147
  revert(
128
148
  repoPath: string,
129
149
  refs: string[],
@@ -5,12 +5,24 @@ export interface GitWorktreesSubProvider {
5
5
  createWorktree(
6
6
  repoPath: string,
7
7
  path: string,
8
- options?: { commitish?: string; createBranch?: string; detach?: boolean; force?: boolean },
8
+ options?: {
9
+ commitish?: string;
10
+ createBranch?: string;
11
+ detach?: boolean;
12
+ force?: boolean;
13
+ noTracking?: boolean;
14
+ },
9
15
  ): Promise<void>;
10
16
  createWorktreeWithResult(
11
17
  repoPath: string,
12
18
  path: string,
13
- options?: { commitish?: string; createBranch?: string; detach?: boolean; force?: boolean },
19
+ options?: {
20
+ commitish?: string;
21
+ createBranch?: string;
22
+ detach?: boolean;
23
+ force?: boolean;
24
+ noTracking?: boolean;
25
+ },
14
26
  ): Promise<GitWorktree | undefined>;
15
27
  getWorktree(
16
28
  repoPath: string,
@@ -35,6 +35,29 @@ export class CustomRemoteProvider extends RemoteProvider {
35
35
  return [];
36
36
  }
37
37
 
38
+ get avatarUrlTemplate(): string | undefined {
39
+ return this.urls.avatar;
40
+ }
41
+
42
+ getUrlForAvatar(email: string, size: number): string | undefined {
43
+ if (this.urls.avatar == null) return undefined;
44
+
45
+ // Split on the last `@` so local-parts that contain `@` are preserved (per RFC 5322)
46
+ const at = email.lastIndexOf('@');
47
+ const emailName = at === -1 ? email : email.slice(0, at);
48
+ const domain = at === -1 ? '' : email.slice(at + 1);
49
+
50
+ // Component-encode identity values — commit emails are attacker-controllable and
51
+ // must never be able to inject URL-structural characters (`/`, `?`, `#`, ...) into the
52
+ // resulting URL
53
+ return interpolate(this.urls.avatar, {
54
+ email: encodeURIComponent(email),
55
+ emailName: encodeURIComponent(emailName),
56
+ domain: encodeURIComponent(domain),
57
+ size: String(size),
58
+ });
59
+ }
60
+
38
61
  protected override getUrlForRepository(): string {
39
62
  return this.getUrl(this.urls.repository, this.getContext());
40
63
  }
@@ -2,6 +2,71 @@ import type { GitFileConflictStatus } from '../models/fileStatus.js';
2
2
 
3
3
  export type ConflictResolutionAction = 'take-ours' | 'take-theirs' | 'delete' | 'unsupported';
4
4
 
5
+ /**
6
+ * A richer description of a conflict than the raw XY status code — used to label conflicts the
7
+ * AI text resolver can't parse (binary, symlink, submodule, mode-only, add/add) and to surface
8
+ * rename conflicts. Derived by {@link classifyConflictKind} from the XY status plus, when
9
+ * available, the per-stage file modes/oids and rename/binary hints the caller computes.
10
+ */
11
+ export type ConflictKind =
12
+ | 'text'
13
+ | 'binary'
14
+ | 'symlink'
15
+ | 'submodule'
16
+ | 'mode-only'
17
+ | 'add-add'
18
+ | 'delete-modify'
19
+ | 'both-deleted'
20
+ | 'rename-rename'
21
+ | 'rename-delete'
22
+ | 'rename-modify'
23
+ | 'unknown';
24
+
25
+ export type ConflictRenameKind = 'rename-rename' | 'rename-delete' | 'rename-modify';
26
+
27
+ const symlinkMode = '120000';
28
+ const submoduleMode = '160000';
29
+
30
+ /**
31
+ * Classifies a conflict into a {@link ConflictKind}. Pure — all detection that needs git/IO
32
+ * (rename correlation, binary sniffing) is computed by the caller and passed via `hints`. When
33
+ * `modes`/`oids` are omitted (e.g. a webview caller that only has the XY status), it still returns
34
+ * a useful coarse kind; pass the per-stage data for the fine-grained symlink/submodule/mode-only split.
35
+ */
36
+ export function classifyConflictKind(
37
+ status: GitFileConflictStatus,
38
+ modes?: { base?: string; current?: string; incoming?: string },
39
+ oids?: { base?: string; current?: string; incoming?: string },
40
+ hints?: { binary?: boolean; rename?: ConflictRenameKind },
41
+ ): ConflictKind {
42
+ if (hints?.rename != null) return hints.rename;
43
+
44
+ if (status === 'DD') return 'both-deleted';
45
+ if (status === 'UD' || status === 'DU') return 'delete-modify';
46
+
47
+ const presentModes = [modes?.current, modes?.incoming, modes?.base].filter((m): m is string => m != null);
48
+ if (presentModes.includes(submoduleMode)) return 'submodule';
49
+ if (presentModes.includes(symlinkMode)) return 'symlink';
50
+
51
+ if (hints?.binary) return 'binary';
52
+
53
+ // Both sides present with identical content (same oid) but different mode → only the file mode
54
+ // conflicts (e.g. one side flipped the executable bit).
55
+ if (
56
+ modes?.current != null &&
57
+ modes?.incoming != null &&
58
+ modes.current !== modes.incoming &&
59
+ oids?.current != null &&
60
+ oids.current === oids?.incoming
61
+ ) {
62
+ return 'mode-only';
63
+ }
64
+
65
+ if (status === 'AA' || status === 'AU' || status === 'UA') return 'add-add';
66
+
67
+ return 'text';
68
+ }
69
+
5
70
  export function classifyConflictAction(
6
71
  status: GitFileConflictStatus,
7
72
  resolution: 'current' | 'incoming',
@@ -29,3 +94,64 @@ export function canStageCurrent(status: GitFileConflictStatus): boolean {
29
94
  export function canStageIncoming(status: GitFileConflictStatus): boolean {
30
95
  return status !== 'AU' && status !== 'DD';
31
96
  }
97
+
98
+ /** A short label + one-line description for a {@link ConflictKind}, used to explain conflicts the AI
99
+ * resolver can't auto-merge (and rename conflicts) wherever they're surfaced. */
100
+ export function getConflictKindLabel(kind: ConflictKind, renameOf?: string): { label: string; description: string } {
101
+ const named = renameOf ? `"${renameOf}"` : 'The file';
102
+ switch (kind) {
103
+ case 'binary':
104
+ return {
105
+ label: 'Binary conflict',
106
+ description: 'Binary file changed on both sides — choose a side to keep',
107
+ };
108
+ case 'symlink':
109
+ return {
110
+ label: 'Symlink conflict',
111
+ description: 'Symbolic link changed on both sides — choose a side to keep',
112
+ };
113
+ case 'submodule':
114
+ return {
115
+ label: 'Submodule conflict',
116
+ description: 'Submodule reference changed on both sides — choose a side to keep',
117
+ };
118
+ case 'mode-only':
119
+ return {
120
+ label: 'File mode conflict',
121
+ description: 'Only the file mode differs (e.g. the executable bit) — choose a side to keep',
122
+ };
123
+ case 'add-add':
124
+ // Covers AA (added on both sides) as well as AU/UA (added on one side) — keep the wording
125
+ // accurate for all three rather than asserting "both sides".
126
+ return {
127
+ label: 'Add conflict',
128
+ description: 'Conflicting file additions — choose a side to keep',
129
+ };
130
+ case 'delete-modify':
131
+ return {
132
+ label: 'Modified and deleted',
133
+ description: 'Deleted on one side and modified on the other — keep the file or delete it',
134
+ };
135
+ case 'both-deleted':
136
+ return { label: 'Deleted on both sides', description: 'Deleted on both sides — confirm the deletion' };
137
+ case 'rename-rename':
138
+ return {
139
+ label: 'Renamed differently',
140
+ description: `${named} was renamed differently on each side — choose which name to keep`,
141
+ };
142
+ case 'rename-delete':
143
+ return {
144
+ label: 'Renamed and deleted',
145
+ description: `${named} was renamed on one side and deleted on the other — keep the file or delete it`,
146
+ };
147
+ case 'rename-modify':
148
+ return {
149
+ label: 'Renamed and modified',
150
+ description: `${named} was renamed on one side and modified on the other`,
151
+ };
152
+ case 'text':
153
+ return { label: 'Text conflict', description: 'Conflicting changes on both sides' };
154
+ default:
155
+ return { label: 'Conflict', description: 'Resolve this conflict manually' };
156
+ }
157
+ }
@@ -79,3 +79,27 @@ export function calculateDistribution<T extends string>(
79
79
  export function matchContributor(c: GitContributor, user: GitUser): boolean {
80
80
  return c.name === user.name && c.email === user.email && c.username === user.username;
81
81
  }
82
+
83
+ /** Appends `Co-authored-by:` trailers for the given coauthors to a commit message, replacing
84
+ * any existing trailer block. Each coauthor is the `Name <email>` form (GitContributor.coauthor). */
85
+ export function appendCoauthorsToMessage(message: string, coauthors: Iterable<string>): string {
86
+ const index = message.indexOf('Co-authored-by: ');
87
+ if (index !== -1) {
88
+ message = message.substring(0, index - 1).trimEnd();
89
+ }
90
+
91
+ for (const coauthor of coauthors) {
92
+ let newlines;
93
+ if (message.includes('Co-authored-by: ')) {
94
+ newlines = '\n';
95
+ } else if (message.length !== 0 && message.endsWith('\n')) {
96
+ newlines = '\n\n';
97
+ } else {
98
+ newlines = '\n\n\n';
99
+ }
100
+
101
+ message += `${newlines}Co-authored-by: ${coauthor}`;
102
+ }
103
+
104
+ return message;
105
+ }