@gitkraken/core-gitlens 0.1.0 → 0.2.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 (223) hide show
  1. package/CHANGELOG.md +22 -1
  2. package/dist/git/cache.d.ts +21 -5
  3. package/dist/git/cache.d.ts.map +1 -1
  4. package/dist/git/cache.js +102 -6
  5. package/dist/git/cache.js.map +1 -1
  6. package/dist/git/context.d.ts +11 -2
  7. package/dist/git/context.d.ts.map +1 -1
  8. package/dist/git/context.js.map +1 -1
  9. package/dist/git/models/branch.d.ts +1 -0
  10. package/dist/git/models/branch.d.ts.map +1 -1
  11. package/dist/git/models/branch.js.map +1 -1
  12. package/dist/git/models/commit.d.ts +1 -9
  13. package/dist/git/models/commit.d.ts.map +1 -1
  14. package/dist/git/models/commit.js +4 -9
  15. package/dist/git/models/commit.js.map +1 -1
  16. package/dist/git/models/graph.d.ts +2 -4
  17. package/dist/git/models/graph.d.ts.map +1 -1
  18. package/dist/git/models/repository.d.ts +12 -3
  19. package/dist/git/models/repository.d.ts.map +1 -1
  20. package/dist/git/models/repository.js +25 -4
  21. package/dist/git/models/repository.js.map +1 -1
  22. package/dist/git/models/worktree.d.ts +1 -1
  23. package/dist/git/models/worktree.d.ts.map +1 -1
  24. package/dist/git/models/worktree.js +2 -2
  25. package/dist/git/models/worktree.js.map +1 -1
  26. package/dist/git/parsers/diffParser.d.ts +0 -1
  27. package/dist/git/parsers/diffParser.d.ts.map +1 -1
  28. package/dist/git/parsers/diffParser.js +22 -61
  29. package/dist/git/parsers/diffParser.js.map +1 -1
  30. package/dist/git/providers/branches.d.ts +3 -0
  31. package/dist/git/providers/branches.d.ts.map +1 -1
  32. package/dist/git/providers/commits.d.ts +6 -0
  33. package/dist/git/providers/commits.d.ts.map +1 -1
  34. package/dist/git/providers/config.d.ts +2 -0
  35. package/dist/git/providers/config.d.ts.map +1 -1
  36. package/dist/git/providers/diff.d.ts +2 -0
  37. package/dist/git/providers/diff.d.ts.map +1 -1
  38. package/dist/git/providers/operations.d.ts +6 -0
  39. package/dist/git/providers/operations.d.ts.map +1 -1
  40. package/dist/git/providers/revision.d.ts +1 -1
  41. package/dist/git/providers/revision.d.ts.map +1 -1
  42. package/dist/git/providers/staging.d.ts +2 -0
  43. package/dist/git/providers/staging.d.ts.map +1 -1
  44. package/dist/git/utils/sorting.d.ts +6 -0
  45. package/dist/git/utils/sorting.d.ts.map +1 -1
  46. package/dist/git/utils/sorting.js +19 -0
  47. package/dist/git/utils/sorting.js.map +1 -1
  48. package/dist/git-cli/cliGitProvider.js +1 -1
  49. package/dist/git-cli/cliGitProvider.js.map +1 -1
  50. package/dist/git-cli/exec/git.d.ts +29 -4
  51. package/dist/git-cli/exec/git.d.ts.map +1 -1
  52. package/dist/git-cli/exec/git.js +70 -9
  53. package/dist/git-cli/exec/git.js.map +1 -1
  54. package/dist/git-cli/exec/gitQueue.d.ts +12 -4
  55. package/dist/git-cli/exec/gitQueue.d.ts.map +1 -1
  56. package/dist/git-cli/exec/gitQueue.js +49 -12
  57. package/dist/git-cli/exec/gitQueue.js.map +1 -1
  58. package/dist/git-cli/parsers/logParser.d.ts +1 -0
  59. package/dist/git-cli/parsers/logParser.d.ts.map +1 -1
  60. package/dist/git-cli/parsers/logParser.js +4 -0
  61. package/dist/git-cli/parsers/logParser.js.map +1 -1
  62. package/dist/git-cli/providers/blame.js +1 -1
  63. package/dist/git-cli/providers/blame.js.map +1 -1
  64. package/dist/git-cli/providers/branches.d.ts +2 -0
  65. package/dist/git-cli/providers/branches.d.ts.map +1 -1
  66. package/dist/git-cli/providers/branches.js +176 -123
  67. package/dist/git-cli/providers/branches.js.map +1 -1
  68. package/dist/git-cli/providers/commits.d.ts +4 -0
  69. package/dist/git-cli/providers/commits.d.ts.map +1 -1
  70. package/dist/git-cli/providers/commits.js +58 -17
  71. package/dist/git-cli/providers/commits.js.map +1 -1
  72. package/dist/git-cli/providers/config.d.ts.map +1 -1
  73. package/dist/git-cli/providers/config.js +9 -9
  74. package/dist/git-cli/providers/config.js.map +1 -1
  75. package/dist/git-cli/providers/contributors.d.ts.map +1 -1
  76. package/dist/git-cli/providers/contributors.js +7 -4
  77. package/dist/git-cli/providers/contributors.js.map +1 -1
  78. package/dist/git-cli/providers/diff.d.ts +2 -0
  79. package/dist/git-cli/providers/diff.d.ts.map +1 -1
  80. package/dist/git-cli/providers/diff.js +89 -19
  81. package/dist/git-cli/providers/diff.js.map +1 -1
  82. package/dist/git-cli/providers/graph.d.ts.map +1 -1
  83. package/dist/git-cli/providers/graph.js +43 -27
  84. package/dist/git-cli/providers/graph.js.map +1 -1
  85. package/dist/git-cli/providers/operations.d.ts +17 -0
  86. package/dist/git-cli/providers/operations.d.ts.map +1 -1
  87. package/dist/git-cli/providers/operations.js +47 -14
  88. package/dist/git-cli/providers/operations.js.map +1 -1
  89. package/dist/git-cli/providers/patch.d.ts.map +1 -1
  90. package/dist/git-cli/providers/patch.js +15 -30
  91. package/dist/git-cli/providers/patch.js.map +1 -1
  92. package/dist/git-cli/providers/pausedOperations.js +6 -6
  93. package/dist/git-cli/providers/pausedOperations.js.map +1 -1
  94. package/dist/git-cli/providers/refs.d.ts.map +1 -1
  95. package/dist/git-cli/providers/refs.js +26 -7
  96. package/dist/git-cli/providers/refs.js.map +1 -1
  97. package/dist/git-cli/providers/remotes.js +4 -4
  98. package/dist/git-cli/providers/remotes.js.map +1 -1
  99. package/dist/git-cli/providers/revision.d.ts +1 -1
  100. package/dist/git-cli/providers/revision.d.ts.map +1 -1
  101. package/dist/git-cli/providers/revision.js +15 -11
  102. package/dist/git-cli/providers/revision.js.map +1 -1
  103. package/dist/git-cli/providers/staging.d.ts +2 -0
  104. package/dist/git-cli/providers/staging.d.ts.map +1 -1
  105. package/dist/git-cli/providers/staging.js +20 -8
  106. package/dist/git-cli/providers/staging.js.map +1 -1
  107. package/dist/git-cli/providers/stash.d.ts +0 -8
  108. package/dist/git-cli/providers/stash.d.ts.map +1 -1
  109. package/dist/git-cli/providers/stash.js +63 -119
  110. package/dist/git-cli/providers/stash.js.map +1 -1
  111. package/dist/git-cli/providers/status.js +6 -6
  112. package/dist/git-cli/providers/status.js.map +1 -1
  113. package/dist/git-cli/providers/tags.js +4 -4
  114. package/dist/git-cli/providers/tags.js.map +1 -1
  115. package/dist/git-cli/providers/worktrees.js +3 -3
  116. package/dist/git-cli/providers/worktrees.js.map +1 -1
  117. package/dist/plus/ai/constants.d.ts +2 -1
  118. package/dist/plus/ai/constants.d.ts.map +1 -1
  119. package/dist/plus/ai/constants.js +7 -0
  120. package/dist/plus/ai/constants.js.map +1 -1
  121. package/dist/plus/ai/models/model.d.ts +1 -1
  122. package/dist/plus/ai/models/model.d.ts.map +1 -1
  123. package/dist/plus/ai/models/model.js.map +1 -1
  124. package/dist/plus/ai/models/promptTemplates.d.ts +19 -2
  125. package/dist/plus/ai/models/promptTemplates.d.ts.map +1 -1
  126. package/dist/plus/ai/models/results.d.ts +28 -0
  127. package/dist/plus/ai/models/results.d.ts.map +1 -1
  128. package/dist/plus/ai/prompts.d.ts +3 -0
  129. package/dist/plus/ai/prompts.d.ts.map +1 -1
  130. package/dist/plus/ai/prompts.js +126 -0
  131. package/dist/plus/ai/prompts.js.map +1 -1
  132. package/dist/plus/ai/utils/ai.utils.d.ts.map +1 -1
  133. package/dist/plus/ai/utils/ai.utils.js +2 -0
  134. package/dist/plus/ai/utils/ai.utils.js.map +1 -1
  135. package/dist/plus/ai/utils/results.utils.d.ts +3 -0
  136. package/dist/plus/ai/utils/results.utils.d.ts.map +1 -1
  137. package/dist/plus/ai/utils/results.utils.js +75 -0
  138. package/dist/plus/ai/utils/results.utils.js.map +1 -1
  139. package/dist/plus/ai/utils/truncation.utils.d.ts +1 -1
  140. package/dist/plus/ai/utils/truncation.utils.d.ts.map +1 -1
  141. package/dist/plus/ai/utils/truncation.utils.js.map +1 -1
  142. package/dist/plus/git-github/providers/github/branches.d.ts.map +1 -1
  143. package/dist/plus/git-github/providers/github/branches.js +16 -1
  144. package/dist/plus/git-github/providers/github/branches.js.map +1 -1
  145. package/dist/plus/git-github/providers/github/diff.d.ts +2 -0
  146. package/dist/plus/git-github/providers/github/diff.d.ts.map +1 -1
  147. package/dist/plus/git-github/providers/github/diff.js.map +1 -1
  148. package/dist/utils/decorators/log.js +1 -1
  149. package/dist/utils/decorators/log.js.map +1 -1
  150. package/dist/utils/decorators/sequentialize.d.ts.map +1 -1
  151. package/dist/utils/decorators/sequentialize.js +4 -2
  152. package/dist/utils/decorators/sequentialize.js.map +1 -1
  153. package/dist/utils/env/node/base64.js.map +1 -1
  154. package/dist/utils/env/node/exec.js.map +1 -1
  155. package/dist/utils/formatter.d.ts.map +1 -1
  156. package/dist/utils/formatter.js +4 -10
  157. package/dist/utils/formatter.js.map +1 -1
  158. package/dist/utils/iterable.js.map +1 -1
  159. package/dist/utils/lruMap.d.ts +40 -0
  160. package/dist/utils/lruMap.d.ts.map +1 -0
  161. package/dist/utils/lruMap.js +81 -0
  162. package/dist/utils/lruMap.js.map +1 -0
  163. package/dist/utils/version.d.ts +14 -0
  164. package/dist/utils/version.d.ts.map +1 -1
  165. package/dist/utils/version.js +48 -0
  166. package/dist/utils/version.js.map +1 -1
  167. package/package.json +1 -1
  168. package/src/git/cache.ts +179 -13
  169. package/src/git/context.ts +11 -2
  170. package/src/git/models/branch.ts +1 -0
  171. package/src/git/models/commit.ts +1 -17
  172. package/src/git/models/graph.ts +2 -4
  173. package/src/git/models/repository.ts +26 -6
  174. package/src/git/models/worktree.ts +2 -2
  175. package/src/git/parsers/diffParser.ts +7 -44
  176. package/src/git/providers/branches.ts +3 -0
  177. package/src/git/providers/commits.ts +7 -0
  178. package/src/git/providers/config.ts +2 -0
  179. package/src/git/providers/diff.ts +8 -2
  180. package/src/git/providers/operations.ts +10 -3
  181. package/src/git/providers/revision.ts +5 -1
  182. package/src/git/providers/staging.ts +2 -0
  183. package/src/git/utils/sorting.ts +21 -0
  184. package/src/git-cli/cliGitProvider.ts +1 -1
  185. package/src/git-cli/exec/git.ts +70 -18
  186. package/src/git-cli/exec/gitQueue.ts +57 -13
  187. package/src/git-cli/parsers/logParser.ts +5 -1
  188. package/src/git-cli/providers/blame.ts +1 -1
  189. package/src/git-cli/providers/branches.ts +207 -133
  190. package/src/git-cli/providers/commits.ts +71 -18
  191. package/src/git-cli/providers/config.ts +9 -14
  192. package/src/git-cli/providers/contributors.ts +7 -12
  193. package/src/git-cli/providers/diff.ts +100 -19
  194. package/src/git-cli/providers/graph.ts +47 -27
  195. package/src/git-cli/providers/operations.ts +77 -18
  196. package/src/git-cli/providers/patch.ts +15 -37
  197. package/src/git-cli/providers/pausedOperations.ts +6 -6
  198. package/src/git-cli/providers/refs.ts +27 -8
  199. package/src/git-cli/providers/remotes.ts +4 -4
  200. package/src/git-cli/providers/revision.ts +20 -13
  201. package/src/git-cli/providers/staging.ts +18 -8
  202. package/src/git-cli/providers/stash.ts +67 -134
  203. package/src/git-cli/providers/status.ts +6 -6
  204. package/src/git-cli/providers/tags.ts +4 -4
  205. package/src/git-cli/providers/worktrees.ts +3 -3
  206. package/src/plus/ai/constants.ts +8 -0
  207. package/src/plus/ai/models/model.ts +1 -0
  208. package/src/plus/ai/models/promptTemplates.ts +29 -0
  209. package/src/plus/ai/models/results.ts +30 -0
  210. package/src/plus/ai/prompts.ts +129 -0
  211. package/src/plus/ai/utils/ai.utils.ts +2 -0
  212. package/src/plus/ai/utils/results.utils.ts +99 -0
  213. package/src/plus/ai/utils/truncation.utils.ts +3 -1
  214. package/src/plus/git-github/providers/github/branches.ts +17 -5
  215. package/src/plus/git-github/providers/github/diff.ts +8 -2
  216. package/src/utils/decorators/log.ts +1 -1
  217. package/src/utils/decorators/sequentialize.ts +8 -6
  218. package/src/utils/env/node/base64.ts +1 -1
  219. package/src/utils/env/node/exec.ts +2 -2
  220. package/src/utils/formatter.ts +4 -11
  221. package/src/utils/iterable.ts +1 -1
  222. package/src/utils/lruMap.ts +87 -0
  223. package/src/utils/version.ts +48 -0
@@ -24,6 +24,7 @@ import type {
24
24
  import type { GitRemote } from '../../git/models/remote.js';
25
25
  import type { SearchQuery } from '../../git/models/search.js';
26
26
  import type { GitWorktree } from '../../git/models/worktree.js';
27
+ import type { GitCommitReachability } from '../../git/providers/commits.js';
27
28
  import type { GitGraphSubProvider } from '../../git/providers/graph.js';
28
29
  import {
29
30
  getBranchId,
@@ -33,6 +34,7 @@ import {
33
34
  import { getChangedFilesCount } from '../../git/utils/commit.utils.js';
34
35
  import { isUncommitted } from '../../git/utils/revision.utils.js';
35
36
  import { getSearchQueryComparisonKey, parseSearchQueryGitCommand } from '../../git/utils/search.utils.js';
37
+ import { compareReachableRefs } from '../../git/utils/sorting.js';
36
38
  import { getTagId } from '../../git/utils/tag.utils.js';
37
39
  import { isUserMatch } from '../../git/utils/user.utils.js';
38
40
  import { getWorktreeId, groupWorktreesByBranch } from '../../git/utils/worktree.utils.js';
@@ -56,6 +58,8 @@ import { convertStashesToStdin } from './stash.js';
56
58
 
57
59
  const progressiveSearchResultsBatchTimeMs = 500; // Send updates every 500ms (2 updates/second)
58
60
 
61
+ type ReachableRef = GitCommitReachability['refs'][number];
62
+
59
63
  export class GraphGitSubProvider implements GitGraphSubProvider {
60
64
  constructor(
61
65
  private readonly context: GitServiceContext,
@@ -87,7 +91,7 @@ export class GraphGitSubProvider implements GitGraphSubProvider {
87
91
  const [shaResult, stashResult, branchesResult, remotesResult, currentUserResult, worktreesResult] =
88
92
  await Promise.allSettled([
89
93
  !isUncommitted(rev, true)
90
- ? this.git.exec(
94
+ ? this.git.run(
91
95
  { cwd: repoPath, configs: gitConfigsLog },
92
96
  'log',
93
97
  ...shaParser.arguments,
@@ -96,7 +100,7 @@ export class GraphGitSubProvider implements GitGraphSubProvider {
96
100
  '--',
97
101
  )
98
102
  : undefined,
99
- this.provider.stash?.getStash(repoPath, undefined, cancellation),
103
+ this.provider.stash?.getStash(repoPath, { includeFiles: false }, cancellation),
100
104
  this.provider.branches.getBranches(repoPath, undefined, cancellation),
101
105
  this.provider.remotes.getRemotes(repoPath, undefined, cancellation),
102
106
  this.provider.config.getCurrentUser(repoPath),
@@ -138,7 +142,9 @@ export class GraphGitSubProvider implements GitGraphSubProvider {
138
142
  const avatars = new Map<string, string>();
139
143
  const ids = new Set<string>();
140
144
  const reachableFromHEAD = new Set<string>();
141
- const reachableFromBranches = new Map<string, Set<string>>();
145
+
146
+ // Map<sha, Map<refKey, ref>> — inner map deduplicates refs during propagation
147
+ const reachableRefs = new Map<string, Map<string, ReachableRef>>();
142
148
  const rowStats: GitGraphRowsStats = new Map<string, GitGraphRowStats>();
143
149
  let pendingRowsStatsCount = 0;
144
150
  let iterations = 0;
@@ -218,7 +224,6 @@ export class GraphGitSubProvider implements GitGraphSubProvider {
218
224
  branchIdOfMainWorktree: branchIdOfMainWorktree,
219
225
  stashes: gitStash?.stashes,
220
226
  reachableFromHEAD: reachableFromHEAD,
221
- reachableFromBranches: reachableFromBranches,
222
227
  avatars: avatars,
223
228
  }
224
229
  : undefined;
@@ -340,34 +345,46 @@ export class GraphGitSubProvider implements GitGraphSubProvider {
340
345
  }
341
346
  }
342
347
 
343
- if (refHeads.length > 0) {
344
- let branches = reachableFromBranches.get(shaOrRemapped);
345
- if (branches == null) {
346
- branches = new Set<string>();
347
- reachableFromBranches.set(shaOrRemapped, branches);
348
+ // Seed reachability from all ref types on this commit
349
+ if (refHeads.length > 0 || refRemoteHeads.length > 0 || refTags.length > 0) {
350
+ let refs = reachableRefs.get(shaOrRemapped);
351
+ if (refs == null) {
352
+ refs = new Map<string, ReachableRef>();
353
+ reachableRefs.set(shaOrRemapped, refs);
354
+ }
355
+ for (const h of refHeads) {
356
+ const key = `b:${h.name}`;
357
+ refs.set(key, { refType: 'branch', name: h.name, remote: false, current: h.isCurrentHead });
358
+ }
359
+ for (const r of refRemoteHeads) {
360
+ const name = `${r.owner}/${r.name}`;
361
+ refs.set(`r:${name}`, { refType: 'branch', name: name, remote: true });
348
362
  }
349
- for (const refHead of refHeads) {
350
- branches.add(refHead.name);
363
+ for (const t of refTags) {
364
+ refs.set(`t:${t.name}`, { refType: 'tag', name: t.name });
351
365
  }
352
366
  }
353
367
 
354
- const currentBranches = reachableFromBranches.get(shaOrRemapped);
355
- if (currentBranches != null && currentBranches.size > 0) {
368
+ // Propagate reachability to parents
369
+ const currentRefs = reachableRefs.get(shaOrRemapped);
370
+ if (currentRefs != null && currentRefs.size > 0) {
356
371
  for (parent of parents) {
357
- let parentBranches = reachableFromBranches.get(parent);
358
- if (parentBranches == null) {
359
- parentBranches = new Set<string>();
360
- reachableFromBranches.set(parent, parentBranches);
372
+ let parentRefs = reachableRefs.get(parent);
373
+ if (parentRefs == null) {
374
+ parentRefs = new Map<string, ReachableRef>();
375
+ reachableRefs.set(parent, parentRefs);
361
376
  }
362
- for (const branchName of currentBranches) {
363
- parentBranches.add(branchName);
377
+ for (const [key, ref] of currentRefs) {
378
+ if (!parentRefs.has(key)) {
379
+ parentRefs.set(key, ref);
380
+ }
364
381
  }
365
382
  }
366
383
  }
367
384
 
368
385
  stash = gitStash?.stashes.get(shaOrRemapped);
369
386
  if (stash != null) {
370
- const branches = reachableFromBranches.get(shaOrRemapped);
387
+ const refs = reachableRefs.get(shaOrRemapped);
371
388
  const row: GitGraphRow = {
372
389
  sha: shaOrRemapped,
373
390
  // Always only return the first parent for stashes, as it is a Git implementation for the index and untracked files
@@ -381,7 +398,9 @@ export class GraphGitSubProvider implements GitGraphSubProvider {
381
398
  heads: refHeads,
382
399
  remotes: refRemoteHeads,
383
400
  tags: refTags,
384
- reachableFromBranches: branches ? [...branches] : undefined,
401
+ reachability: refs?.size
402
+ ? { partial: true, refs: [...refs.values()].sort(compareReachableRefs) }
403
+ : undefined,
385
404
  isCurrentUser: true,
386
405
  };
387
406
  rowProcessor?.processRow(row, graphCtx!);
@@ -397,7 +416,7 @@ export class GraphGitSubProvider implements GitGraphSubProvider {
397
416
  } else {
398
417
  isCurrentUser = isUserMatch(currentUser, commit.author, commit.authorEmail);
399
418
 
400
- const branches = reachableFromBranches.get(shaOrRemapped);
419
+ const refs = reachableRefs.get(shaOrRemapped);
401
420
  const row: GitGraphRow = {
402
421
  sha: shaOrRemapped,
403
422
  parents: onlyFollowFirstParent ? parents.slice(0, 1) : parents,
@@ -410,7 +429,9 @@ export class GraphGitSubProvider implements GitGraphSubProvider {
410
429
  heads: refHeads,
411
430
  remotes: refRemoteHeads,
412
431
  tags: refTags,
413
- reachableFromBranches: branches ? [...branches] : undefined,
432
+ reachability: refs?.size
433
+ ? { partial: true, refs: [...refs.values()].sort(compareReachableRefs) }
434
+ : undefined,
414
435
  isCurrentUser: isCurrentUser || undefined,
415
436
  };
416
437
  rowProcessor?.processRow(row, graphCtx!);
@@ -442,7 +463,7 @@ export class GraphGitSubProvider implements GitGraphSubProvider {
442
463
  }
443
464
  args.push(`--${ordering}-order`, '--all');
444
465
 
445
- const statsResult = await this.git.exec(
466
+ const statsResult = await this.git.run(
446
467
  { cwd: repoPath, configs: gitConfigsLog, stdin: stdin },
447
468
  'log',
448
469
  stdin ? '--stdin' : undefined,
@@ -455,7 +476,7 @@ export class GraphGitSubProvider implements GitGraphSubProvider {
455
476
  for (const stat of statsParser.parse(statsResult.stdout)) {
456
477
  statShaOrRemapped = remappedIds.get(stat.sha) ?? stat.sha;
457
478
 
458
- // If we already have the stats for this sha, skip it (e.g. stashes)
479
+ // Don't overwrite stats already populated for this sha
459
480
  if (rowStats.has(statShaOrRemapped)) continue;
460
481
 
461
482
  rowStats.set(statShaOrRemapped, stat.stats);
@@ -485,7 +506,6 @@ export class GraphGitSubProvider implements GitGraphSubProvider {
485
506
  worktrees: worktrees,
486
507
  worktreesByBranch: worktreesByBranch,
487
508
  reachableFromHEAD: reachableFromHEAD,
488
- reachableFromBranches: reachableFromBranches,
489
509
  rows: rows,
490
510
  id: sha ?? rev,
491
511
  rowsStats: rowStats,
@@ -595,7 +615,7 @@ export class GraphGitSubProvider implements GitGraphSubProvider {
595
615
  // Don't include stashes when using ref: filter, as they would add unrelated commits
596
616
  // There *HAS* to be a better way to get git log to return stashes, but this is the best we've found
597
617
  ({ stdin, stashes, remappedIds } = convertStashesToStdin(
598
- await this.provider.stash?.getStash(repoPath, undefined, cancellation),
618
+ await this.provider.stash?.getStash(repoPath, { includeFiles: false }, cancellation),
599
619
  ));
600
620
  } else {
601
621
  remappedIds = new Map();
@@ -11,8 +11,10 @@ import {
11
11
  RebaseError,
12
12
  ResetError,
13
13
  RevertError,
14
+ SigningError,
14
15
  } from '../../git/errors.js';
15
16
  import type { GitBranchReference, GitReference } from '../../git/models/reference.js';
17
+ import type { SigningFormat } from '../../git/models/signature.js';
16
18
  import type { GitConflictFile } from '../../git/models/staging.js';
17
19
  import type { GitOperationResult, GitOperationsSubProvider } from '../../git/providers/operations.js';
18
20
  import { getBranchNameAndRemote, getBranchTrackingWithoutRemote } from '../../git/utils/branch.utils.js';
@@ -24,7 +26,13 @@ import { getScopedLogger } from '../../utils/logger.scoped.js';
24
26
  import { normalizePath, splitPath } from '../../utils/path.js';
25
27
  import type { CliGitProviderInternal } from '../cliGitProvider.js';
26
28
  import type { Git, PushForceOptions } from '../exec/git.js';
27
- import { getGitCommandError, gitConfigsPull, GitErrors } from '../exec/git.js';
29
+ import {
30
+ classifySigningError,
31
+ getGitCommandError,
32
+ gitConfigsPull,
33
+ GitErrors,
34
+ inferSigningFormatFromError,
35
+ } from '../exec/git.js';
28
36
 
29
37
  export class OperationsGitSubProvider implements GitOperationsSubProvider {
30
38
  constructor(
@@ -70,7 +78,7 @@ export class OperationsGitSubProvider implements GitOperationsSubProvider {
70
78
  }
71
79
 
72
80
  try {
73
- await this.git.exec({ cwd: repoPath }, ...params);
81
+ await this.git.run({ cwd: repoPath }, ...params);
74
82
  } catch (ex) {
75
83
  throw getGitCommandError(
76
84
  'checkout',
@@ -89,7 +97,7 @@ export class OperationsGitSubProvider implements GitOperationsSubProvider {
89
97
  async cherryPick(
90
98
  repoPath: string,
91
99
  revs: string[],
92
- options?: { edit?: boolean; noCommit?: boolean },
100
+ options?: { edit?: boolean; noCommit?: boolean; source?: unknown },
93
101
  ): Promise<GitOperationResult> {
94
102
  const scope = getScopedLogger();
95
103
 
@@ -104,7 +112,7 @@ export class OperationsGitSubProvider implements GitOperationsSubProvider {
104
112
  if (revs.length > 1) {
105
113
  // Sort commits in topological order (oldest ancestor first) so cherry-pick applies them correctly
106
114
  const revsSet = new Set(revs);
107
- const result = await this.git.exec({ cwd: repoPath }, 'rev-list', '--topo-order', '--reverse', ...revs);
115
+ const result = await this.git.run({ cwd: repoPath }, 'rev-list', '--topo-order', '--reverse', ...revs);
108
116
 
109
117
  const ordered: string[] = [];
110
118
  for (const sha of result.stdout.trim().split('\n')) {
@@ -123,12 +131,13 @@ export class OperationsGitSubProvider implements GitOperationsSubProvider {
123
131
  args.push(...revs);
124
132
 
125
133
  try {
126
- await this.git.exec({ cwd: repoPath, errors: 'throw' }, ...args);
134
+ await this.git.run({ cwd: repoPath, errors: 'throw' }, ...args);
127
135
  this.context.hooks?.cache?.onReset?.(repoPath, 'branches', 'status');
128
136
  this.context.hooks?.repository?.onChanged?.(repoPath, ['head', 'heads', 'index']);
129
137
  return { conflicted: false };
130
138
  } catch (ex) {
131
139
  scope?.error(ex);
140
+ await this.throwIfSigningError(repoPath, args, ex, options?.source);
132
141
  const mapped = getGitCommandError(
133
142
  'cherry-pick',
134
143
  ex,
@@ -157,6 +166,7 @@ export class OperationsGitSubProvider implements GitOperationsSubProvider {
157
166
  author?: string;
158
167
  date?: string;
159
168
  signoff?: boolean;
169
+ source?: unknown;
160
170
  },
161
171
  ): Promise<void> {
162
172
  const scope = getScopedLogger();
@@ -184,11 +194,12 @@ export class OperationsGitSubProvider implements GitOperationsSubProvider {
184
194
  params.push('-F', '-');
185
195
 
186
196
  try {
187
- await this.git.exec({ cwd: repoPath, stdin: message, stdinEncoding: 'utf8', errors: 'throw' }, ...params);
197
+ await this.git.run({ cwd: repoPath, stdin: message, stdinEncoding: 'utf8', errors: 'throw' }, ...params);
188
198
  this.context.hooks?.cache?.onReset?.(repoPath, 'branches', 'status');
189
199
  this.context.hooks?.repository?.onChanged?.(repoPath, ['head', 'heads', 'index']);
190
200
  } catch (ex) {
191
201
  scope?.error(ex);
202
+ await this.throwIfSigningError(repoPath, params, ex, options?.source);
192
203
  throw getGitCommandError(
193
204
  'commit',
194
205
  ex,
@@ -265,7 +276,7 @@ export class OperationsGitSubProvider implements GitOperationsSubProvider {
265
276
  }
266
277
 
267
278
  try {
268
- await this.git.exec({ cwd: repoPath }, ...params);
279
+ await this.git.run({ cwd: repoPath }, ...params);
269
280
  } catch (ex) {
270
281
  throw getGitCommandError(
271
282
  'fetch',
@@ -289,7 +300,7 @@ export class OperationsGitSubProvider implements GitOperationsSubProvider {
289
300
  async merge(
290
301
  repoPath: string,
291
302
  ref: string,
292
- options?: { fastForward?: boolean | 'only'; noCommit?: boolean; squash?: boolean },
303
+ options?: { fastForward?: boolean | 'only'; noCommit?: boolean; squash?: boolean; source?: unknown },
293
304
  ): Promise<GitOperationResult> {
294
305
  const scope = getScopedLogger();
295
306
 
@@ -312,7 +323,7 @@ export class OperationsGitSubProvider implements GitOperationsSubProvider {
312
323
  args.push(ref);
313
324
 
314
325
  try {
315
- await this.git.exec(
326
+ await this.git.run(
316
327
  // Avoid a timeout since merges can take a long time (set to 0 to disable)
317
328
  { cwd: repoPath, errors: 'throw', timeout: 0 },
318
329
  ...args,
@@ -323,6 +334,7 @@ export class OperationsGitSubProvider implements GitOperationsSubProvider {
323
334
  return { conflicted: false };
324
335
  } catch (ex) {
325
336
  scope?.error(ex);
337
+ await this.throwIfSigningError(repoPath, args, ex, options?.source);
326
338
  const mapped = getGitCommandError(
327
339
  'merge',
328
340
  ex,
@@ -343,7 +355,7 @@ export class OperationsGitSubProvider implements GitOperationsSubProvider {
343
355
  @debug()
344
356
  async pull(
345
357
  repoPath: string,
346
- options?: { branch?: GitBranchReference; rebase?: boolean; tags?: boolean },
358
+ options?: { branch?: GitBranchReference; rebase?: boolean; tags?: boolean; source?: unknown },
347
359
  ): Promise<void> {
348
360
  const scope = getScopedLogger();
349
361
 
@@ -360,6 +372,7 @@ export class OperationsGitSubProvider implements GitOperationsSubProvider {
360
372
  await this.pullCore(normalizePath(worktree.uri.fsPath), {
361
373
  rebase: options?.rebase,
362
374
  tags: options?.tags,
375
+ source: options?.source,
363
376
  });
364
377
  this.context.hooks?.cache?.onReset?.(repoPath, 'branches', 'status', 'tags');
365
378
  this.context.hooks?.repository?.onChanged?.(repoPath, ['head', 'heads', 'remotes', 'index']);
@@ -373,6 +386,7 @@ export class OperationsGitSubProvider implements GitOperationsSubProvider {
373
386
  await this.pullCore(repoPath, {
374
387
  rebase: options?.rebase,
375
388
  tags: options?.tags,
389
+ source: options?.source,
376
390
  });
377
391
 
378
392
  this.context.hooks?.cache?.onReset?.(repoPath, 'branches', 'status', 'tags');
@@ -383,7 +397,10 @@ export class OperationsGitSubProvider implements GitOperationsSubProvider {
383
397
  }
384
398
  }
385
399
 
386
- private async pullCore(repoPath: string, options: { rebase?: boolean; tags?: boolean }): Promise<void> {
400
+ private async pullCore(
401
+ repoPath: string,
402
+ options: { rebase?: boolean; tags?: boolean; source?: unknown },
403
+ ): Promise<void> {
387
404
  const params = ['pull'];
388
405
 
389
406
  if (options.tags) {
@@ -395,8 +412,9 @@ export class OperationsGitSubProvider implements GitOperationsSubProvider {
395
412
  }
396
413
 
397
414
  try {
398
- await this.git.exec({ cwd: repoPath, configs: gitConfigsPull }, ...params);
415
+ await this.git.run({ cwd: repoPath, configs: gitConfigsPull }, ...params);
399
416
  } catch (ex) {
417
+ await this.throwIfSigningError(repoPath, params, ex, options.source);
400
418
  throw getGitCommandError(
401
419
  'pull',
402
420
  ex,
@@ -479,7 +497,7 @@ export class OperationsGitSubProvider implements GitOperationsSubProvider {
479
497
 
480
498
  // Since Git can't setup upstream tracking when publishing a new branch to a specific commit, do it now
481
499
  if (setUpstream != null) {
482
- await this.git.exec(
500
+ await this.git.run(
483
501
  { cwd: repoPath },
484
502
  'branch',
485
503
  '--set-upstream-to',
@@ -537,7 +555,7 @@ export class OperationsGitSubProvider implements GitOperationsSubProvider {
537
555
  }
538
556
 
539
557
  try {
540
- await this.git.exec({ cwd: repoPath }, ...params);
558
+ await this.git.run({ cwd: repoPath }, ...params);
541
559
  } catch (ex) {
542
560
  const error = getGitCommandError(
543
561
  'push',
@@ -609,6 +627,7 @@ export class OperationsGitSubProvider implements GitOperationsSubProvider {
609
627
  interactive?: boolean;
610
628
  onto?: string;
611
629
  updateRefs?: boolean;
630
+ source?: unknown;
612
631
  },
613
632
  ): Promise<GitOperationResult> {
614
633
  const scope = getScopedLogger();
@@ -643,7 +662,7 @@ export class OperationsGitSubProvider implements GitOperationsSubProvider {
643
662
  }
644
663
 
645
664
  try {
646
- await this.git.exec(
665
+ await this.git.run(
647
666
  // Avoid a timeout since rebases can take a long time (set to 0 to disable)
648
667
  { cwd: repoPath, errors: 'throw', configs: configs, timeout: 0 },
649
668
  ...args,
@@ -653,6 +672,7 @@ export class OperationsGitSubProvider implements GitOperationsSubProvider {
653
672
  return { conflicted: false };
654
673
  } catch (ex) {
655
674
  scope?.error(ex);
675
+ await this.throwIfSigningError(repoPath, args, ex, options?.source);
656
676
  const mapped = getGitCommandError(
657
677
  'rebase',
658
678
  ex,
@@ -703,7 +723,7 @@ export class OperationsGitSubProvider implements GitOperationsSubProvider {
703
723
  params.push(rev, '--');
704
724
 
705
725
  try {
706
- await this.git.exec({ cwd: repoPath }, ...params);
726
+ await this.git.run({ cwd: repoPath }, ...params);
707
727
  } catch (ex) {
708
728
  throw getGitCommandError(
709
729
  'reset',
@@ -716,7 +736,11 @@ export class OperationsGitSubProvider implements GitOperationsSubProvider {
716
736
 
717
737
  @sequentialize({ getQueueKey: rp => rp })
718
738
  @debug()
719
- async revert(repoPath: string, refs: string[], options?: { editMessage?: boolean }): Promise<GitOperationResult> {
739
+ async revert(
740
+ repoPath: string,
741
+ refs: string[],
742
+ options?: { editMessage?: boolean; source?: unknown },
743
+ ): Promise<GitOperationResult> {
720
744
  const scope = getScopedLogger();
721
745
 
722
746
  const args = ['revert'];
@@ -730,7 +754,7 @@ export class OperationsGitSubProvider implements GitOperationsSubProvider {
730
754
  args.push(...refs);
731
755
 
732
756
  try {
733
- await this.git.exec(
757
+ await this.git.run(
734
758
  // Avoid a timeout since reverts can take a long time (set to 0 to disable)
735
759
  { cwd: repoPath, errors: 'throw', timeout: 0 },
736
760
  ...args,
@@ -741,6 +765,7 @@ export class OperationsGitSubProvider implements GitOperationsSubProvider {
741
765
  return { conflicted: false };
742
766
  } catch (ex) {
743
767
  scope?.error(ex);
768
+ await this.throwIfSigningError(repoPath, args, ex, options?.source);
744
769
 
745
770
  const mapped = getGitCommandError(
746
771
  'revert',
@@ -758,6 +783,40 @@ export class OperationsGitSubProvider implements GitOperationsSubProvider {
758
783
  }
759
784
  }
760
785
 
786
+ /**
787
+ * Classifies `ex` as a signing failure; if so, fires the `commits.onSigningFailed`
788
+ * hook and throws a typed {@link SigningError}. Callers should invoke this first
789
+ * in their catch blocks, before falling through to command-specific error mapping
790
+ * via {@link getGitCommandError}.
791
+ *
792
+ * `SigningFormat` is read opportunistically from `config.getSigningConfig`, with
793
+ * {@link inferSigningFormatFromError} as a stderr-based fallback and `'gpg'` as
794
+ * the final default.
795
+ */
796
+ private async throwIfSigningError(
797
+ repoPath: string,
798
+ args: readonly (string | undefined)[],
799
+ ex: unknown,
800
+ source?: unknown,
801
+ ): Promise<void> {
802
+ const reason = classifySigningError(ex);
803
+ if (reason == null) return;
804
+
805
+ let format: SigningFormat | undefined;
806
+ try {
807
+ format = (await this.provider.config.getSigningConfig?.(repoPath))?.format;
808
+ } catch {
809
+ // Fall through — config read failed; use stderr-inferred format or gpg default
810
+ }
811
+ format ??= inferSigningFormatFromError(ex) ?? 'gpg';
812
+
813
+ this.context.hooks?.commits?.onSigningFailed?.(reason, format, source);
814
+ throw new SigningError(
815
+ { reason: reason, gitCommand: { repoPath: repoPath, args: args } },
816
+ ex instanceof Error ? ex : undefined,
817
+ );
818
+ }
819
+
761
820
  private async createConflictResult(repoPath: string, command: GitConflictCommand): Promise<GitOperationResult> {
762
821
  let conflicts: GitConflictFile[] | undefined;
763
822
  try {
@@ -11,7 +11,7 @@ import { getSettledValue } from '../../utils/promise.js';
11
11
  import type { CliGitProviderInternal } from '../cliGitProvider.js';
12
12
  import { RunError } from '../exec/exec.errors.js';
13
13
  import type { Git } from '../exec/git.js';
14
- import { gitConfigsLog, GitError } from '../exec/git.js';
14
+ import { classifySigningError, gitConfigsLog, GitError } from '../exec/git.js';
15
15
 
16
16
  export class PatchGitSubProvider implements GitPatchSubProvider {
17
17
  constructor(
@@ -27,7 +27,7 @@ export class PatchGitSubProvider implements GitPatchSubProvider {
27
27
  if (options?.threeWay) {
28
28
  args.push('--3way');
29
29
  }
30
- await this.git.exec({ cwd: repoPath, stdin: patch }, ...args);
30
+ await this.git.run({ cwd: repoPath, stdin: patch }, ...args);
31
31
  }
32
32
 
33
33
  @debug()
@@ -209,7 +209,7 @@ export class PatchGitSubProvider implements GitPatchSubProvider {
209
209
  const [signingConfigResult, applyResult] = await Promise.allSettled([
210
210
  this.provider.config.getSigningConfig?.(repoPath),
211
211
  // Apply the patch to our temp index, without touching the working directory
212
- this.git.exec(
212
+ this.git.run(
213
213
  { cwd: repoPath, configs: gitConfigsLog, env: env, stdin: patch },
214
214
  'apply',
215
215
  '--cached',
@@ -224,7 +224,7 @@ export class PatchGitSubProvider implements GitPatchSubProvider {
224
224
  _signingFormat = signingConfig?.format ?? 'gpg';
225
225
 
226
226
  // Create a new tree from our patched index
227
- let result = await this.git.exec({ cwd: repoPath, env: env }, 'write-tree');
227
+ let result = await this.git.run({ cwd: repoPath, env: env }, 'write-tree');
228
228
  const tree = result.stdout.trim();
229
229
 
230
230
  // Set the author if provided
@@ -247,7 +247,7 @@ export class PatchGitSubProvider implements GitPatchSubProvider {
247
247
  args.push('-m', message);
248
248
 
249
249
  // Create new commit from the tree
250
- result = await this.git.exec({ cwd: repoPath, env: finalEnv }, ...args);
250
+ result = await this.git.run({ cwd: repoPath, env: finalEnv }, ...args);
251
251
  const sha = result.stdout.trim();
252
252
 
253
253
  if (shouldSign) {
@@ -260,33 +260,11 @@ export class PatchGitSubProvider implements GitPatchSubProvider {
260
260
 
261
261
  // Handle signing-specific errors
262
262
  if (shouldSign && ex instanceof Error) {
263
- const errorMessage = ex.message.toLowerCase();
264
- const gitCommand: GitCommandContext = { repoPath: repoPath, args: ['commit-tree'] };
265
- let signingError: SigningError | undefined;
266
-
267
- if (errorMessage.includes('gpg failed to sign') || errorMessage.includes('error: gpg')) {
268
- signingError = new SigningError({ reason: 'passphraseFailed', gitCommand: gitCommand }, ex);
269
- } else if (
270
- errorMessage.includes('secret key not available') ||
271
- errorMessage.includes('no secret key') ||
272
- errorMessage.includes('no signing key')
273
- ) {
274
- signingError = new SigningError({ reason: 'noKey', gitCommand: gitCommand }, ex);
275
- } else if (
276
- errorMessage.includes('gpg: command not found') ||
277
- (errorMessage.includes('gpg') && errorMessage.includes('not found'))
278
- ) {
279
- signingError = new SigningError({ reason: 'gpgNotFound', gitCommand: gitCommand }, ex);
280
- } else if (errorMessage.includes('ssh-keygen') && errorMessage.includes('not found')) {
281
- signingError = new SigningError({ reason: 'sshNotFound', gitCommand: gitCommand }, ex);
282
- }
283
-
284
- if (signingError != null) {
285
- this.context.hooks?.commits?.onSigningFailed?.(
286
- signingError.details.reason ?? 'unknown',
287
- _signingFormat,
288
- options?.source,
289
- );
263
+ const reason = classifySigningError(ex);
264
+ if (reason != null) {
265
+ const gitCommand: GitCommandContext = { repoPath: repoPath, args: ['commit-tree'] };
266
+ const signingError = new SigningError({ reason: reason, gitCommand: gitCommand }, ex);
267
+ this.context.hooks?.commits?.onSigningFailed?.(reason, _signingFormat, options?.source);
290
268
  throw signingError;
291
269
  }
292
270
  }
@@ -296,19 +274,19 @@ export class PatchGitSubProvider implements GitPatchSubProvider {
296
274
  }
297
275
 
298
276
  async createEmptyInitialCommit(repoPath: string): Promise<string> {
299
- const emptyTree = await this.git.exec({ cwd: repoPath, stdin: '' }, 'hash-object', '-t', 'tree', '--stdin');
300
- const result = await this.git.exec({ cwd: repoPath }, 'commit-tree', emptyTree.stdout.trim(), '-m', 'temp');
277
+ const emptyTree = await this.git.run({ cwd: repoPath, stdin: '' }, 'hash-object', '-t', 'tree', '--stdin');
278
+ const result = await this.git.run({ cwd: repoPath }, 'commit-tree', emptyTree.stdout.trim(), '-m', 'temp');
301
279
  // create refs/heads/main and point to it
302
- await this.git.exec({ cwd: repoPath }, 'update-ref', 'refs/heads/main', result.stdout.trim());
280
+ await this.git.run({ cwd: repoPath }, 'update-ref', 'refs/heads/main', result.stdout.trim());
303
281
  // point HEAD to the branch
304
- await this.git.exec({ cwd: repoPath }, 'symbolic-ref', 'HEAD', 'refs/heads/main');
282
+ await this.git.run({ cwd: repoPath }, 'symbolic-ref', 'HEAD', 'refs/heads/main');
305
283
  return result.stdout.trim();
306
284
  }
307
285
 
308
286
  @debug({ args: repoPath => ({ repoPath: repoPath }) })
309
287
  async validatePatch(repoPath: string | undefined, contents: string): Promise<boolean> {
310
288
  try {
311
- await this.git.exec({ cwd: repoPath, configs: gitConfigsLog, stdin: contents }, 'apply', '--check', '-');
289
+ await this.git.run({ cwd: repoPath, configs: gitConfigsLog, stdin: contents }, 'apply', '--check', '-');
312
290
  return true;
313
291
  } catch (ex) {
314
292
  if (ex instanceof RunError || ex instanceof GitError) {
@@ -109,7 +109,7 @@ export class PausedOperationsGitSubProvider implements GitPausedOperationsSubPro
109
109
  const operation = sortedOperations[0];
110
110
  switch (operation) {
111
111
  case 'cherry-pick': {
112
- const result = await this.git.exec(
112
+ const result = await this.git.run(
113
113
  { cwd: repoPath, cancellation: cancellation, errors: 'ignore' },
114
114
  'rev-parse',
115
115
  '--quiet',
@@ -145,7 +145,7 @@ export class PausedOperationsGitSubProvider implements GitPausedOperationsSubPro
145
145
  } satisfies GitCherryPickStatus;
146
146
  }
147
147
  case 'merge': {
148
- const result = await this.git.exec(
148
+ const result = await this.git.run(
149
149
  { cwd: repoPath, cancellation: cancellation, errors: 'ignore' },
150
150
  'rev-parse',
151
151
  '--quiet',
@@ -197,7 +197,7 @@ export class PausedOperationsGitSubProvider implements GitPausedOperationsSubPro
197
197
  } satisfies GitMergeStatus;
198
198
  }
199
199
  case 'revert': {
200
- const result = await this.git.exec(
200
+ const result = await this.git.run(
201
201
  { cwd: repoPath, cancellation: cancellation, errors: 'ignore' },
202
202
  'rev-parse',
203
203
  '--quiet',
@@ -249,7 +249,7 @@ export class PausedOperationsGitSubProvider implements GitPausedOperationsSubPro
249
249
  stepsMessageResult,
250
250
  isInteractiveResult,
251
251
  ] = await Promise.allSettled([
252
- this.git.exec(
252
+ this.git.run(
253
253
  { cwd: repoPath, cancellation: cancellation, errors: 'ignore' },
254
254
  'rev-parse',
255
255
  '--quiet',
@@ -479,7 +479,7 @@ export class PausedOperationsGitSubProvider implements GitPausedOperationsSubPro
479
479
  const args = [status.type, options?.quit ? '--quit' : '--abort'];
480
480
 
481
481
  try {
482
- await this.git.exec({ cwd: repoPath, errors: 'throw' }, ...args);
482
+ await this.git.run({ cwd: repoPath, errors: 'throw' }, ...args);
483
483
  this.context.hooks?.cache?.onReset?.(repoPath, 'branches', 'status');
484
484
  this.context.hooks?.repository?.onChanged?.(repoPath, ['head', 'heads', 'index']);
485
485
  } catch (ex) {
@@ -514,7 +514,7 @@ export class PausedOperationsGitSubProvider implements GitPausedOperationsSubPro
514
514
  const args = [status.type, options?.skip ? '--skip' : '--continue'];
515
515
 
516
516
  try {
517
- await this.git.exec({ cwd: repoPath, errors: 'throw' }, ...args);
517
+ await this.git.run({ cwd: repoPath, errors: 'throw' }, ...args);
518
518
  this.context.hooks?.cache?.onReset?.(repoPath, 'branches', 'status');
519
519
  this.context.hooks?.repository?.onChanged?.(repoPath, ['head', 'heads', 'index']);
520
520
  } catch (ex) {