@straiffi/archon 1.11.8 → 1.11.9

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 (112) hide show
  1. package/dist/client/assets/{ActivityView-BKLYfPXE.js → ActivityView-CgC2rnNq.js} +1 -1
  2. package/dist/client/assets/{GitHubActionsDialog-yqqwcP8z.js → GitHubActionsDialog-XXGwm-bw.js} +1 -1
  3. package/dist/client/assets/{VerificationDialog-DhyIDs1V.js → VerificationDialog-C8cDKxSG.js} +1 -1
  4. package/dist/client/assets/{architectureDiagram-3BPJPVTR-5krjaOU4.js → architectureDiagram-3BPJPVTR-BhdIU_o9.js} +1 -1
  5. package/dist/client/assets/{blockDiagram-GPEHLZMM-Duo2q2gn.js → blockDiagram-GPEHLZMM-CfKNdNso.js} +1 -1
  6. package/dist/client/assets/{c4Diagram-AAUBKEIU-D2lxLmwB.js → c4Diagram-AAUBKEIU-BYOg4iNZ.js} +1 -1
  7. package/dist/client/assets/channel-4wFtrIng.js +1 -0
  8. package/dist/client/assets/{chunk-2J33WTMH-Dbo5lbSs.js → chunk-2J33WTMH-JmBb9p2l.js} +1 -1
  9. package/dist/client/assets/{chunk-3OPIFGDE-mjZrNxCg.js → chunk-3OPIFGDE-CuCn8uDI.js} +1 -1
  10. package/dist/client/assets/{chunk-5ZQYHXKU-C2nUnqLh.js → chunk-5ZQYHXKU-DaGbZz7d.js} +1 -1
  11. package/dist/client/assets/{chunk-727SXJPM-7eUgm0cn.js → chunk-727SXJPM-D7UwchWL.js} +1 -1
  12. package/dist/client/assets/{chunk-AQP2D5EJ-DouWpaE0.js → chunk-AQP2D5EJ-CxYpis5S.js} +1 -1
  13. package/dist/client/assets/{chunk-CSCIHK7Q-DesC0Fd7.js → chunk-CSCIHK7Q-DF4Jo5lo.js} +1 -1
  14. package/dist/client/assets/{chunk-KSCS5N6A-DAsFowjn.js → chunk-KSCS5N6A-Bul_8QaO.js} +1 -1
  15. package/dist/client/assets/{chunk-L5ZTLDWV-w7V9r0Cw.js → chunk-L5ZTLDWV-BQvBZt_1.js} +1 -1
  16. package/dist/client/assets/{chunk-LZXEDZCA-BCnxdvaJ.js → chunk-LZXEDZCA-BPsejGww.js} +2 -2
  17. package/dist/client/assets/{chunk-ND2GUHAM-DO8HdZCX.js → chunk-ND2GUHAM-D8AX2HAv.js} +1 -1
  18. package/dist/client/assets/{chunk-NZK2D7GU-37Rbzvm9.js → chunk-NZK2D7GU-CE21a8CK.js} +1 -1
  19. package/dist/client/assets/{chunk-O5CBEL6O-DTjB0GGI.js → chunk-O5CBEL6O-vZQ8YCvm.js} +1 -1
  20. package/dist/client/assets/{chunk-WU5MYG2G-CAonpz-x.js → chunk-WU5MYG2G-BpBNiw0j.js} +1 -1
  21. package/dist/client/assets/classDiagram-4FO5ZUOK-DNAwGSF5.js +1 -0
  22. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-cskOKMdS.js +1 -0
  23. package/dist/client/assets/{dagre-BM42HDAG-DDET9pbe.js → dagre-BM42HDAG-BqEPuJwq.js} +1 -1
  24. package/dist/client/assets/{diagram-2AECGRRQ-r_bu5Cgo.js → diagram-2AECGRRQ-BWLSNrm2.js} +1 -1
  25. package/dist/client/assets/{diagram-5GNKFQAL-QlPCzkUJ.js → diagram-5GNKFQAL-CUNUlEy4.js} +1 -1
  26. package/dist/client/assets/{diagram-KO2AKTUF-D5SzFOzP.js → diagram-KO2AKTUF-CgZcDZ0J.js} +1 -1
  27. package/dist/client/assets/{diagram-LMA3HP47-BJcWw3_s.js → diagram-LMA3HP47-DTfMha7_.js} +1 -1
  28. package/dist/client/assets/{diagram-OG6HWLK6-CpsR9p94.js → diagram-OG6HWLK6-ClO5evmB.js} +1 -1
  29. package/dist/client/assets/{erDiagram-TEJ5UH35-Dq-K6YrY.js → erDiagram-TEJ5UH35-BRW1HE33.js} +1 -1
  30. package/dist/client/assets/{flowDiagram-I6XJVG4X-39hKkH4n.js → flowDiagram-I6XJVG4X-tC2xALm9.js} +1 -1
  31. package/dist/client/assets/{ganttDiagram-6RSMTGT7-DaeBYOLz.js → ganttDiagram-6RSMTGT7-D7Bu6hss.js} +1 -1
  32. package/dist/client/assets/{gitGraphDiagram-PVQCEYII-CCeo9Czy.js → gitGraphDiagram-PVQCEYII-Cx1uYcGX.js} +1 -1
  33. package/dist/client/assets/index-CArcyEjQ.js +287 -0
  34. package/dist/client/assets/index-D_brbG-0.css +2 -0
  35. package/dist/client/assets/{infoDiagram-5YYISTIA-jgHH1Ta7.js → infoDiagram-5YYISTIA-bKT-ZR4K.js} +1 -1
  36. package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-gYPsFHRd.js → ishikawaDiagram-YF4QCWOH-CrUBKkGR.js} +1 -1
  37. package/dist/client/assets/{journeyDiagram-JHISSGLW-C5m8h46L.js → journeyDiagram-JHISSGLW-CCmXkk8M.js} +1 -1
  38. package/dist/client/assets/{kanban-definition-UN3LZRKU-rag4UxAB.js → kanban-definition-UN3LZRKU-BmcV7QM-.js} +1 -1
  39. package/dist/client/assets/{line-CAWxQrG3.js → line-C5oH-IfR.js} +1 -1
  40. package/dist/client/assets/{mermaid-parser.core-C5Hj2V3U.js → mermaid-parser.core-BFyFUctK.js} +1 -1
  41. package/dist/client/assets/{mermaid.core-BxRoDLxv.js → mermaid.core-DXkHU1mX.js} +3 -3
  42. package/dist/client/assets/{mindmap-definition-RKZ34NQL-BUV37aab.js → mindmap-definition-RKZ34NQL-DhErnbAy.js} +1 -1
  43. package/dist/client/assets/{pieDiagram-4H26LBE5-CMwVVhpZ.js → pieDiagram-4H26LBE5-B8pkGeer.js} +1 -1
  44. package/dist/client/assets/{quadrantDiagram-W4KKPZXB-DYHRB097.js → quadrantDiagram-W4KKPZXB-BkYWz3t_.js} +1 -1
  45. package/dist/client/assets/{requirementDiagram-4Y6WPE33-BLtNTWtR.js → requirementDiagram-4Y6WPE33-DB6jC89I.js} +1 -1
  46. package/dist/client/assets/{sankeyDiagram-5OEKKPKP-Bcd-UVHZ.js → sankeyDiagram-5OEKKPKP-BeAslLcb.js} +1 -1
  47. package/dist/client/assets/{sequenceDiagram-3UESZ5HK-Die4-puP.js → sequenceDiagram-3UESZ5HK-BgXC1aKi.js} +1 -1
  48. package/dist/client/assets/{stateDiagram-AJRCARHV-TBbOyQST.js → stateDiagram-AJRCARHV-Dj3Qlk0A.js} +1 -1
  49. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-I1Ry2lsa.js +1 -0
  50. package/dist/client/assets/{timeline-definition-PNZ67QCA-CZ3E6e1X.js → timeline-definition-PNZ67QCA-C8ncLV_K.js} +1 -1
  51. package/dist/client/assets/{vennDiagram-CIIHVFJN-CY_kofMf.js → vennDiagram-CIIHVFJN-jgBSpqC1.js} +1 -1
  52. package/dist/client/assets/{wardleyDiagram-YWT4CUSO-TzBIV1Er.js → wardleyDiagram-YWT4CUSO-DfGD5Kys.js} +1 -1
  53. package/dist/client/assets/{xychartDiagram-2RQKCTM6-BTvKU2GO.js → xychartDiagram-2RQKCTM6-Dyk41E2n.js} +1 -1
  54. package/dist/client/index.html +2 -2
  55. package/dist/server/index.js +124 -63
  56. package/dist/server/index.js.map +1 -1
  57. package/dist/server/lib/agent.js +23 -7
  58. package/dist/server/lib/agent.js.map +1 -1
  59. package/dist/server/lib/agentProvider.js +62 -15
  60. package/dist/server/lib/agentProvider.js.map +1 -1
  61. package/dist/server/lib/codexAgentProvider.js +186 -123
  62. package/dist/server/lib/codexAgentProvider.js.map +1 -1
  63. package/dist/server/lib/diffExplanation.js +485 -0
  64. package/dist/server/lib/diffExplanation.js.map +1 -0
  65. package/dist/server/lib/diffExplanationJobs.js +605 -0
  66. package/dist/server/lib/diffExplanationJobs.js.map +1 -0
  67. package/dist/server/lib/diffExplanationRoutes.js +236 -0
  68. package/dist/server/lib/diffExplanationRoutes.js.map +1 -0
  69. package/dist/server/lib/dockerSandboxExecutionEnvironment.js +27 -4
  70. package/dist/server/lib/dockerSandboxExecutionEnvironment.js.map +1 -1
  71. package/dist/server/lib/dockerSandboxReleaseProfiles.js +1 -1
  72. package/dist/server/lib/dockerSandboxReleaseProfiles.js.map +1 -1
  73. package/dist/server/lib/executionTargets.js +1 -1
  74. package/dist/server/lib/executionTargets.js.map +1 -1
  75. package/dist/server/lib/git.js +205 -35
  76. package/dist/server/lib/git.js.map +1 -1
  77. package/dist/server/lib/lightweightPrompt.js +284 -52
  78. package/dist/server/lib/lightweightPrompt.js.map +1 -1
  79. package/dist/server/lib/oneShotCancellation.js +54 -0
  80. package/dist/server/lib/oneShotCancellation.js.map +1 -0
  81. package/dist/server/lib/opencodeExecutionReliability.js +46 -2
  82. package/dist/server/lib/opencodeExecutionReliability.js.map +1 -1
  83. package/dist/server/lib/projectMemoryScan.js +3 -46
  84. package/dist/server/lib/projectMemoryScan.js.map +1 -1
  85. package/dist/server/lib/promptDiffSafety.js +53 -0
  86. package/dist/server/lib/promptDiffSafety.js.map +1 -0
  87. package/dist/server/lib/repositorySecrets.js +69 -0
  88. package/dist/server/lib/repositorySecrets.js.map +1 -0
  89. package/dist/server/lib/tickets.js +3 -2
  90. package/dist/server/lib/tickets.js.map +1 -1
  91. package/dist/server/lib/types.js.map +1 -1
  92. package/dist/server/lib/unifiedDiff.js +459 -0
  93. package/dist/server/lib/unifiedDiff.js.map +1 -0
  94. package/dist/server/lib/workspaceDiff.js +156 -0
  95. package/dist/server/lib/workspaceDiff.js.map +1 -0
  96. package/dist/server/lib/workspaceDiffRoutes.js +63 -0
  97. package/dist/server/lib/workspaceDiffRoutes.js.map +1 -0
  98. package/dist/server/lib/workspaceTargets.js +88 -0
  99. package/dist/server/lib/workspaceTargets.js.map +1 -0
  100. package/dist/server/workers/chat.js +71 -1
  101. package/dist/server/workers/chat.js.map +1 -1
  102. package/dist/server/workers/plan.js +24 -2
  103. package/dist/server/workers/plan.js.map +1 -1
  104. package/dist/server/workers/review.js +19 -13
  105. package/dist/server/workers/review.js.map +1 -1
  106. package/package.json +1 -1
  107. package/dist/client/assets/channel-Cf2U7nU4.js +0 -1
  108. package/dist/client/assets/classDiagram-4FO5ZUOK-CK8VsU-L.js +0 -1
  109. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-RvySOEwt.js +0 -1
  110. package/dist/client/assets/index-B9-M1vEw.js +0 -287
  111. package/dist/client/assets/index-CbYT3N3U.css +0 -2
  112. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-BRotXz7h.js +0 -1
@@ -7,6 +7,7 @@ import { dirname, isAbsolute, join, relative, resolve } from 'path';
7
7
  import db from '../db.js';
8
8
  import { runBoundedProcess } from './boundedProcess.js';
9
9
  import { getProjectWorktreeSync } from './projects.js';
10
+ import { selectUnifiedDiffFiles } from './unifiedDiff.js';
10
11
  import { createWorkspaceProcessEnvironment } from './workspaceProcessEnvironment.js';
11
12
  export class UnpushedCommitDiffSelectionError extends Error {
12
13
  }
@@ -239,6 +240,39 @@ const parseNumstatOutput = (output) => {
239
240
  }];
240
241
  });
241
242
  };
243
+ const parseNulNumstatOutput = (output) => {
244
+ const records = output.split('\0');
245
+ const parsed = [];
246
+ for (let index = 0; index < records.length; index += 1) {
247
+ const record = records[index] ?? '';
248
+ if (!record) {
249
+ continue;
250
+ }
251
+ const firstTabIndex = record.indexOf('\t');
252
+ const secondTabIndex = firstTabIndex === -1 ? -1 : record.indexOf('\t', firstTabIndex + 1);
253
+ if (firstTabIndex === -1 || secondTabIndex === -1) {
254
+ continue;
255
+ }
256
+ const additionsToken = record.slice(0, firstTabIndex);
257
+ const deletionsToken = record.slice(firstTabIndex + 1, secondTabIndex);
258
+ let path = record.slice(secondTabIndex + 1);
259
+ if (!path) {
260
+ index += 2;
261
+ path = records[index] ?? '';
262
+ }
263
+ if (!path) {
264
+ continue;
265
+ }
266
+ const additions = additionsToken === '-' ? 0 : Number.parseInt(additionsToken, 10);
267
+ const deletions = deletionsToken === '-' ? 0 : Number.parseInt(deletionsToken, 10);
268
+ parsed.push({
269
+ path,
270
+ additions: Number.isFinite(additions) ? additions : 0,
271
+ deletions: Number.isFinite(deletions) ? deletions : 0,
272
+ });
273
+ }
274
+ return parsed;
275
+ };
242
276
  const normalizeNoIndexPath = (path) => {
243
277
  const renamedPrefix = '/dev/null => ';
244
278
  if (path.startsWith(renamedPrefix)) {
@@ -794,7 +828,25 @@ const parseUnpushedCommitsOutput = (output) => {
794
828
  return commits.filter(commit => commit.hash && commit.short_hash && commit.subject);
795
829
  };
796
830
  const resolveDefaultBranchRef = (cwd) => {
797
- const candidates = ['refs/remotes/origin/HEAD', 'refs/remotes/origin/main', 'refs/remotes/origin/master', 'refs/heads/main', 'refs/heads/master'];
831
+ try {
832
+ const remoteDefault = runGitText(['symbolic-ref', '--quiet', 'refs/remotes/origin/HEAD'], cwd).trim();
833
+ if (remoteDefault) {
834
+ return remoteDefault;
835
+ }
836
+ }
837
+ catch {
838
+ // Fall back to conventional branch names below.
839
+ }
840
+ try {
841
+ const resolvedRemoteDefault = runGitText(['rev-parse', '--symbolic-full-name', 'refs/remotes/origin/HEAD'], cwd).trim();
842
+ if (resolvedRemoteDefault && resolvedRemoteDefault !== 'refs/remotes/origin/HEAD') {
843
+ return resolvedRemoteDefault;
844
+ }
845
+ }
846
+ catch {
847
+ // Ignore direct or unavailable origin/HEAD refs.
848
+ }
849
+ const candidates = ['refs/remotes/origin/main', 'refs/remotes/origin/master', 'refs/heads/main', 'refs/heads/master'];
798
850
  for (const candidate of candidates) {
799
851
  try {
800
852
  const resolved = runGitText(['rev-parse', '--symbolic-full-name', candidate], cwd).trim();
@@ -826,6 +878,21 @@ const resolveNamedBranchRef = (branch, cwd) => {
826
878
  }
827
879
  return null;
828
880
  };
881
+ const resolveReviewBranchRef = (branch, cwd) => {
882
+ const candidates = [`refs/remotes/origin/${branch}`, `refs/heads/${branch}`];
883
+ for (const candidate of candidates) {
884
+ try {
885
+ const resolved = runGitText(['rev-parse', '--symbolic-full-name', '--verify', candidate], cwd).trim();
886
+ if (resolved) {
887
+ return resolved;
888
+ }
889
+ }
890
+ catch {
891
+ // Try the local branch only when its remote tracking ref is unavailable.
892
+ }
893
+ }
894
+ return null;
895
+ };
829
896
  const resolveCommitBaseFromBaseRef = (baseRef, cwd) => {
830
897
  if (!baseRef) {
831
898
  return null;
@@ -850,6 +917,34 @@ const resolveCommitBaseFromBaseRef = (baseRef, cwd) => {
850
917
  }
851
918
  return null;
852
919
  };
920
+ const branchNameFromRef = (ref) => ref
921
+ .replace(/^refs\/heads\//, '')
922
+ .replace(/^refs\/remotes\/[^/]+\//, '');
923
+ export const resolveGitHeadCommit = (cwd) => {
924
+ try {
925
+ return resolveHeadCommit(cwd);
926
+ }
927
+ catch {
928
+ return null;
929
+ }
930
+ };
931
+ export const resolveGitCommit = (cwd, revision) => {
932
+ const normalizedRevision = revision.trim();
933
+ return normalizedRevision ? resolveCommitSha(normalizedRevision, cwd) : null;
934
+ };
935
+ export const resolveGitReviewBaseForBranch = (cwd, baseBranch) => {
936
+ const normalizedBranch = baseBranch.trim();
937
+ const baseRef = resolveReviewBranchRef(normalizedBranch, cwd);
938
+ const baseCommit = resolveCommitBaseFromBaseRef(baseRef, cwd);
939
+ return baseCommit ? { baseBranch: normalizedBranch, baseCommit } : null;
940
+ };
941
+ export const resolveGitRepositoryDefaultReviewBase = (cwd) => {
942
+ const baseRef = resolveDefaultBranchRef(cwd);
943
+ const baseCommit = resolveCommitBaseFromBaseRef(baseRef, cwd);
944
+ return baseRef && baseCommit
945
+ ? { baseBranch: branchNameFromRef(baseRef), baseCommit }
946
+ : null;
947
+ };
853
948
  const resolveRecordedBaseCommit = (cwd, baseBranch = null, baseCommit = null) => {
854
949
  const baseBranchCommit = resolveCommitBaseFromBaseRef(resolveNamedBranchRef(baseBranch, cwd), cwd);
855
950
  if (baseBranchCommit) {
@@ -897,23 +992,20 @@ const resolveUnpushedCommitRange = (cwd, branch, baseBranch = null, baseCommit =
897
992
  baseCommit: null,
898
993
  };
899
994
  };
900
- export const resolveBundleReviewBase = ({ cwd, branch, project, pullRequestBaseBranch = null, }) => {
901
- const normalizedPullRequestBaseBranch = pullRequestBaseBranch?.trim() || null;
902
- if (normalizedPullRequestBaseBranch) {
903
- const baseCommit = resolveCommitBaseFromBaseRef(resolveNamedBranchRef(normalizedPullRequestBaseBranch, cwd), cwd);
904
- if (baseCommit) {
905
- return {
906
- baseBranch: normalizedPullRequestBaseBranch,
907
- baseCommit,
908
- source: 'pull_request',
909
- };
910
- }
911
- }
995
+ export const resolveBundleReviewBase = ({ cwd, branch, project, pullRequestBaseBranch: _pullRequestBaseBranch = null, }) => {
912
996
  const recordedBase = project ? resolveRecordedBaseForCwd(cwd, branch, project) : null;
913
997
  if (!recordedBase) {
914
998
  return null;
915
999
  }
1000
+ const recordedCommit = recordedBase.baseCommit?.trim() || null;
916
1001
  const baseBranch = recordedBase.baseBranch?.trim() || null;
1002
+ if (recordedCommit) {
1003
+ return {
1004
+ baseBranch,
1005
+ baseCommit: recordedCommit,
1006
+ source: 'worktree_base',
1007
+ };
1008
+ }
917
1009
  if (baseBranch) {
918
1010
  const baseCommit = resolveCommitBaseFromBaseRef(resolveNamedBranchRef(baseBranch, cwd), cwd);
919
1011
  if (baseCommit) {
@@ -924,24 +1016,54 @@ export const resolveBundleReviewBase = ({ cwd, branch, project, pullRequestBaseB
924
1016
  };
925
1017
  }
926
1018
  }
927
- const baseCommit = recordedBase.baseCommit?.trim() || null;
928
- if (!baseCommit) {
929
- return null;
1019
+ return null;
1020
+ };
1021
+ const getExactDiffPath = (path) => {
1022
+ const normalizedPath = path.trim();
1023
+ const segments = normalizedPath.split(/[\\/]/);
1024
+ if (!normalizedPath
1025
+ || normalizedPath.includes('\0')
1026
+ || isAbsolute(normalizedPath)
1027
+ || /^[A-Za-z]:[\\/]/.test(normalizedPath)
1028
+ || segments.some(segment => segment === '' || segment === '.' || segment === '..')) {
1029
+ throw new Error('Diff file path must be a valid repository-relative path');
1030
+ }
1031
+ return { path: normalizedPath };
1032
+ };
1033
+ const selectExactDiffData = (data, exactPath) => {
1034
+ if (!exactPath) {
1035
+ return data;
1036
+ }
1037
+ const selection = selectUnifiedDiffFiles(data.diff, [exactPath.path]);
1038
+ if (!selection.ok) {
1039
+ return { files: [], diff: '' };
930
1040
  }
1041
+ const aliases = new Set(selection.files.flatMap(file => file.aliases));
931
1042
  return {
932
- baseBranch,
933
- baseCommit,
934
- source: 'worktree_base',
1043
+ files: data.files.filter(file => aliases.has(file.path)),
1044
+ diff: selection.diff,
935
1045
  };
936
1046
  };
937
- const getDiffDataForBaseRef = (cwd, baseRef) => {
1047
+ const getDiffDataForBaseRef = (cwd, baseRef, exactFilePath = null, suppressErrors = true) => {
1048
+ const exactPath = exactFilePath === null ? null : getExactDiffPath(exactFilePath);
938
1049
  try {
939
- const trackedNumstat = parseNumstatOutput(runGitText(['diff', '--numstat', '--find-renames', '--no-ext-diff', baseRef], cwd));
940
- const untrackedPaths = listUntrackedFiles(cwd);
1050
+ const trackedNumstatOutput = runGitText([
1051
+ 'diff',
1052
+ '--numstat',
1053
+ ...(suppressErrors ? [] : ['-z']),
1054
+ '--find-renames',
1055
+ '--no-ext-diff',
1056
+ baseRef,
1057
+ ], cwd);
1058
+ const trackedNumstat = suppressErrors
1059
+ ? parseNumstatOutput(trackedNumstatOutput)
1060
+ : parseNulNumstatOutput(trackedNumstatOutput);
1061
+ const untrackedPaths = (suppressErrors ? listUntrackedFiles(cwd) : listUntrackedFilesNul(cwd))
1062
+ .filter(path => !exactPath || path === exactPath.path);
941
1063
  const untrackedNumstat = untrackedPaths
942
- .map(path => getUntrackedNumstatEntry(path, cwd))
1064
+ .map(path => suppressErrors ? getUntrackedNumstatEntry(path, cwd) : getUntrackedNumstatEntryNul(path, cwd))
943
1065
  .filter((entry) => entry !== null);
944
- return {
1066
+ return selectExactDiffData({
945
1067
  files: [...trackedNumstat, ...untrackedNumstat].map(entry => ({
946
1068
  path: entry.path,
947
1069
  additions: entry.additions,
@@ -951,10 +1073,10 @@ const getDiffDataForBaseRef = (cwd, baseRef) => {
951
1073
  runGitText(['diff', '-U3', '--find-renames', '--no-ext-diff', baseRef], cwd),
952
1074
  ...untrackedPaths.map(path => getUntrackedUnifiedDiff(path, cwd)),
953
1075
  ].join(''),
954
- };
1076
+ }, exactPath);
955
1077
  }
956
1078
  catch (error) {
957
- if (isGitOutputLimitError(error)) {
1079
+ if (!suppressErrors || isGitOutputLimitError(error)) {
958
1080
  throw error;
959
1081
  }
960
1082
  return {
@@ -981,25 +1103,41 @@ const getCommittedDiffData = (cwd, baseCommit, headCommit) => {
981
1103
  diff: runGitText(['diff', '-U3', '--find-renames', '--no-ext-diff', ...revisions], cwd),
982
1104
  };
983
1105
  };
984
- const getDiffDataForBaseRefAsync = async (cwd, baseRef) => {
1106
+ const getDiffDataForBaseRefAsync = async (cwd, baseRef, exactFilePath = null, suppressErrors = true) => {
1107
+ const exactPath = exactFilePath === null ? null : getExactDiffPath(exactFilePath);
985
1108
  try {
986
- const trackedNumstat = parseNumstatOutput((await runGitTextAsync(['diff', '--numstat', '--find-renames', '--no-ext-diff', baseRef], cwd)).stdout);
987
- const untrackedPaths = await listUntrackedFilesAsync(cwd);
988
- const untrackedNumstat = (await Promise.all(untrackedPaths.map(path => getUntrackedNumstatEntryAsync(path, cwd))))
1109
+ const trackedNumstatOutput = (await runGitTextAsync([
1110
+ 'diff',
1111
+ '--numstat',
1112
+ ...(suppressErrors ? [] : ['-z']),
1113
+ '--find-renames',
1114
+ '--no-ext-diff',
1115
+ baseRef,
1116
+ ], cwd)).stdout;
1117
+ const trackedNumstat = suppressErrors
1118
+ ? parseNumstatOutput(trackedNumstatOutput)
1119
+ : parseNulNumstatOutput(trackedNumstatOutput);
1120
+ const untrackedPaths = (suppressErrors ? await listUntrackedFilesAsync(cwd) : await listUntrackedFilesNulAsync(cwd))
1121
+ .filter(path => !exactPath || path === exactPath.path);
1122
+ const untrackedNumstat = (await mapWithConcurrency(untrackedPaths, MAX_CONCURRENT_DIFF_PROCESSES, path => suppressErrors
1123
+ ? getUntrackedNumstatEntryAsync(path, cwd)
1124
+ : getUntrackedNumstatEntryNulAsync(path, cwd)))
989
1125
  .filter((entry) => entry !== null);
990
- const trackedDiff = (await runGitTextAsync(['diff', '-U3', '--find-renames', '--no-ext-diff', baseRef], cwd)).stdout;
991
- const untrackedDiffs = await Promise.all(untrackedPaths.map(path => getUntrackedUnifiedDiffAsync(path, cwd)));
992
- return {
1126
+ const trackedDiff = (await runGitTextAsync([
1127
+ 'diff', '-U3', '--find-renames', '--no-ext-diff', baseRef,
1128
+ ], cwd)).stdout;
1129
+ const untrackedDiffs = await mapWithConcurrency(untrackedPaths, MAX_CONCURRENT_DIFF_PROCESSES, path => getUntrackedUnifiedDiffAsync(path, cwd));
1130
+ return selectExactDiffData({
993
1131
  files: [...trackedNumstat, ...untrackedNumstat].map(entry => ({
994
1132
  path: entry.path,
995
1133
  additions: entry.additions,
996
1134
  deletions: entry.deletions,
997
1135
  })),
998
1136
  diff: [trackedDiff, ...untrackedDiffs].join(''),
999
- };
1137
+ }, exactPath);
1000
1138
  }
1001
1139
  catch (error) {
1002
- if (isGitOutputLimitError(error)) {
1140
+ if (!suppressErrors || isGitOutputLimitError(error)) {
1003
1141
  throw error;
1004
1142
  }
1005
1143
  return {
@@ -1258,6 +1396,13 @@ const listUntrackedFiles = (cwd) => {
1258
1396
  .map(line => line.trim())
1259
1397
  .filter(Boolean);
1260
1398
  };
1399
+ const listUntrackedFilesNul = (cwd) => {
1400
+ const paths = runGitText(['ls-files', '--others', '--exclude-standard', '-z'], cwd).split('\0');
1401
+ if (paths.at(-1) === '') {
1402
+ paths.pop();
1403
+ }
1404
+ return paths.filter(Boolean);
1405
+ };
1261
1406
  export const listGitSnapshotPaths = (cwd) => {
1262
1407
  const output = runGitText(['ls-files', '--cached', '--others', '--exclude-standard', '-z'], cwd);
1263
1408
  const paths = output.split('\0');
@@ -1272,6 +1417,15 @@ const listUntrackedFilesAsync = async (cwd) => {
1272
1417
  .map(line => line.trim())
1273
1418
  .filter(Boolean);
1274
1419
  };
1420
+ const listUntrackedFilesNulAsync = async (cwd) => {
1421
+ const paths = (await runGitTextAsync([
1422
+ 'ls-files', '--others', '--exclude-standard', '-z',
1423
+ ], cwd, { timeoutMs: GIT_READ_TIMEOUT_MS })).stdout.split('\0');
1424
+ if (paths.at(-1) === '') {
1425
+ paths.pop();
1426
+ }
1427
+ return paths.filter(Boolean);
1428
+ };
1275
1429
  const getUntrackedNumstatEntry = (path, cwd) => {
1276
1430
  const output = runGitDiffText(['diff', '--no-index', '--numstat', '--', '/dev/null', path], cwd);
1277
1431
  const entry = parseNumstatOutput(output)[0];
@@ -1283,6 +1437,11 @@ const getUntrackedNumstatEntry = (path, cwd) => {
1283
1437
  path: normalizeNoIndexPath(entry.path),
1284
1438
  };
1285
1439
  };
1440
+ const getUntrackedNumstatEntryNul = (path, cwd) => {
1441
+ const output = runGitDiffText(['diff', '--no-index', '--numstat', '-z', '--', '/dev/null', path], cwd);
1442
+ const entry = parseNulNumstatOutput(output)[0];
1443
+ return entry ? { ...entry, path } : null;
1444
+ };
1286
1445
  const getUntrackedNumstatEntryAsync = async (path, cwd) => {
1287
1446
  const output = await runGitDiffTextAsync(['diff', '--no-index', '--numstat', '--', '/dev/null', path], cwd, { timeoutMs: GIT_READ_TIMEOUT_MS });
1288
1447
  const entry = parseNumstatOutput(output)[0];
@@ -1294,6 +1453,13 @@ const getUntrackedNumstatEntryAsync = async (path, cwd) => {
1294
1453
  path: normalizeNoIndexPath(entry.path),
1295
1454
  };
1296
1455
  };
1456
+ const getUntrackedNumstatEntryNulAsync = async (path, cwd) => {
1457
+ const output = await runGitDiffTextAsync([
1458
+ 'diff', '--no-index', '--numstat', '-z', '--', '/dev/null', path,
1459
+ ], cwd, { timeoutMs: GIT_READ_TIMEOUT_MS });
1460
+ const entry = parseNulNumstatOutput(output)[0];
1461
+ return entry ? { ...entry, path } : null;
1462
+ };
1297
1463
  const getUntrackedUnifiedDiff = (path, cwd) => {
1298
1464
  return runGitDiffText(['diff', '--no-index', '-U3', '--', '/dev/null', path], cwd);
1299
1465
  };
@@ -1994,6 +2160,10 @@ export const getGitDiffFiles = (cwd) => {
1994
2160
  export const getGitDiffData = (cwd) => {
1995
2161
  return getDiffDataForCwd(cwd);
1996
2162
  };
2163
+ export const getGitDiffDataAgainstBase = (cwd, baseCommit, exactFilePath = null) => getDiffDataForBaseRef(cwd, baseCommit, exactFilePath, false);
2164
+ export const getGitDiffDataAgainstBaseAsync = (cwd, baseCommit, exactFilePath = null) => getDiffDataForBaseRefAsync(cwd, baseCommit, exactFilePath, false);
2165
+ export const getGitUncommittedDiffData = (cwd, exactFilePath = null) => getDiffDataForBaseRef(cwd, 'HEAD', exactFilePath, false);
2166
+ export const getGitUncommittedDiffDataAsync = (cwd, exactFilePath = null) => getDiffDataForBaseRefAsync(cwd, 'HEAD', exactFilePath, false);
1997
2167
  export const getGitDiffDataAsync = (cwd) => {
1998
2168
  return getDiffDataForCwdAsync(cwd);
1999
2169
  };