difit 4.0.1 → 4.0.3

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 (200) hide show
  1. package/README.ja.md +2 -1
  2. package/README.ko.md +2 -1
  3. package/README.md +2 -1
  4. package/README.zh.md +2 -1
  5. package/dist/cli/index.js +40 -28
  6. package/dist/cli/index.test.js +187 -116
  7. package/dist/cli/tuiDeprecation.d.ts +3 -0
  8. package/dist/cli/tuiDeprecation.js +16 -0
  9. package/dist/cli/tuiDeprecation.test.d.ts +1 -0
  10. package/dist/cli/tuiDeprecation.test.js +16 -0
  11. package/dist/cli/utils.d.ts +1 -0
  12. package/dist/cli/utils.js +17 -0
  13. package/dist/client/assets/_basePickBy-hOr-yGsE.js +1 -0
  14. package/dist/client/assets/_baseUniq-b7bzdUSn.js +1 -0
  15. package/dist/client/assets/arc-D65wG9gm.js +1 -0
  16. package/dist/client/assets/architecture-PBZL5I3N-DUNTzy9d.js +1 -0
  17. package/dist/client/assets/architectureDiagram-2XIMDMQ5-BOmef_aT.js +36 -0
  18. package/dist/client/assets/array-DOVTz2Mq.js +1 -0
  19. package/dist/client/assets/blockDiagram-WCTKOSBZ-CuovjbLp.js +132 -0
  20. package/dist/client/assets/c4Diagram-IC4MRINW-l2hgU0UG.js +10 -0
  21. package/dist/client/assets/channel-BBMOf_Bn.js +1 -0
  22. package/dist/client/assets/chunk-4BX2VUAB-Bh2XMPGo.js +1 -0
  23. package/dist/client/assets/chunk-55IACEB6-r9BRoqNs.js +1 -0
  24. package/dist/client/assets/chunk-7E7YKBS2-BUy3or4g.js +1 -0
  25. package/dist/client/assets/chunk-7R4GIKGN-C7ClNgvP.js +80 -0
  26. package/dist/client/assets/chunk-C72U2L5F-_P9RrDdo.js +1 -0
  27. package/dist/client/assets/chunk-EGIJ26TM-D-xQ2sZ-.js +1 -0
  28. package/dist/client/assets/{chunk-FMBD7UC4-BSa8SHgd.js → chunk-FMBD7UC4-OuP8NjEM.js} +2 -2
  29. package/dist/client/assets/chunk-GEFDOKGD-noJ9o8LM.js +2 -0
  30. package/dist/client/assets/chunk-GLR3WWYH-DVK9OjRZ.js +2 -0
  31. package/dist/client/assets/chunk-HHEYEP7N-C1QyKuQs.js +1 -0
  32. package/dist/client/assets/chunk-JSJVCQXG-2AjhqYcu.js +1 -0
  33. package/dist/client/assets/chunk-KX2RTZJC-tMPTaDcx.js +1 -0
  34. package/dist/client/assets/chunk-KYZI473N-CGwu81pT.js +53 -0
  35. package/dist/client/assets/chunk-L3YUKLVL-DFNfIVVw.js +1 -0
  36. package/dist/client/assets/chunk-MX3YWQON-BnSlIBhe.js +1 -0
  37. package/dist/client/assets/chunk-NQ4KR5QH-CpfTlpaZ.js +220 -0
  38. package/dist/client/assets/chunk-O4XLMI2P-BDGKGscp.js +7 -0
  39. package/dist/client/assets/chunk-OZEHJAEY-3OAEqm17.js +1 -0
  40. package/dist/client/assets/chunk-PQ6SQG4A-DfQjNfPX.js +1 -0
  41. package/dist/client/assets/chunk-PU5JKC2W-DRiL1iN6.js +70 -0
  42. package/dist/client/assets/chunk-QZHKN3VN-DBD5yPlw.js +1 -0
  43. package/dist/client/assets/chunk-R5LLSJPH-dkcbq1pR.js +1 -0
  44. package/dist/client/assets/chunk-WL4C6EOR-BeCB6d6F.js +189 -0
  45. package/dist/client/assets/chunk-XIRO2GV7-BxmEO1Vi.js +1 -0
  46. package/dist/client/assets/chunk-XPW4576I-jm7TiixU.js +32 -0
  47. package/dist/client/assets/chunk-XZSTWKYB-BGKYCy46.js +94 -0
  48. package/dist/client/assets/chunk-YBOYWFTD-C9faLjdm.js +1 -0
  49. package/dist/client/assets/classDiagram-VBA2DB6C-Depk8rxx.js +1 -0
  50. package/dist/client/assets/classDiagram-v2-RAHNMMFH-DHvQPm8y.js +1 -0
  51. package/dist/client/assets/clone-DuY6BQEm.js +1 -0
  52. package/dist/client/assets/cose-bilkent-S5V4N54A-BWD5TWFn.js +1 -0
  53. package/dist/client/assets/cytoscape.esm-B3gzQ1NF.js +321 -0
  54. package/dist/client/assets/dagre-Dd1VxucU.js +1 -0
  55. package/dist/client/assets/dagre-KLK3FWXG-BJFTyMud.js +4 -0
  56. package/dist/client/assets/defaultLocale-Ck2Xxk-C.js +1 -0
  57. package/dist/client/assets/diagram-E7M64L7V-eWdHIl72.js +24 -0
  58. package/dist/client/assets/diagram-IFDJBPK2-C1-sqK0o.js +43 -0
  59. package/dist/client/assets/diagram-P4PSJMXO-DHeUNvSg.js +24 -0
  60. package/dist/client/assets/dist-FLbYR5UU.js +1 -0
  61. package/dist/client/assets/erDiagram-INFDFZHY-CX8FAWmU.js +70 -0
  62. package/dist/client/assets/flowDiagram-PKNHOUZH-DgBnUaHH.js +162 -0
  63. package/dist/client/assets/ganttDiagram-A5KZAMGK-C331HQ-y.js +292 -0
  64. package/dist/client/assets/gitGraph-HDMCJU4V-DPGoIMlm.js +1 -0
  65. package/dist/client/assets/gitGraphDiagram-K3NZZRJ6-zEXLThxN.js +65 -0
  66. package/dist/client/assets/graphlib-WkJoBgka.js +1 -0
  67. package/dist/client/assets/index-BGPkswtu.js +79 -0
  68. package/dist/client/assets/index-Cq_APK7Y.css +2 -0
  69. package/dist/client/assets/info-3K5VOQVL-CYdIfRwG.js +1 -0
  70. package/dist/client/assets/infoDiagram-LFFYTUFH-CKx11_2a.js +2 -0
  71. package/dist/client/assets/init-Bft5Ffpj.js +1 -0
  72. package/dist/client/assets/isArrayLikeObject-icl0H0jo.js +1 -0
  73. package/dist/client/assets/isEmpty-Du8sNmkE.js +1 -0
  74. package/dist/client/assets/ishikawaDiagram-PHBUUO56-BlZMQgOe.js +70 -0
  75. package/dist/client/assets/journeyDiagram-4ABVD52K-C3p_p4rn.js +139 -0
  76. package/dist/client/assets/kanban-definition-K7BYSVSG-4XJPQF50.js +89 -0
  77. package/dist/client/assets/katex-BJrMXEjr.js +261 -0
  78. package/dist/client/assets/line-Bb6xn3n_.js +1 -0
  79. package/dist/client/assets/linear-BPttYRJr.js +1 -0
  80. package/dist/client/assets/math-CNhlSIO3.js +1 -0
  81. package/dist/client/assets/mermaid-parser.core-CjY9NqXx.js +4 -0
  82. package/dist/client/assets/mermaid.core-B0ynITdC.js +11 -0
  83. package/dist/client/assets/mindmap-definition-YRQLILUH-Dya2e4tr.js +68 -0
  84. package/dist/client/assets/ordinal-DIg8h6NI.js +1 -0
  85. package/dist/client/assets/packet-RMMSAZCW-D7vTTuAT.js +1 -0
  86. package/dist/client/assets/path-DfRbCp9y.js +1 -0
  87. package/dist/client/assets/pie-UPGHQEXC-ptFuye_f.js +1 -0
  88. package/dist/client/assets/pieDiagram-SKSYHLDU-MZ74L9cN.js +30 -0
  89. package/dist/client/assets/{prism-bash-DTkDXsAh.js → prism-bash-6uMTC0Q2.js} +1 -1
  90. package/dist/client/assets/prism-csharp-Dkc2OSmh.js +1 -0
  91. package/dist/client/assets/prism-dart-iZy_wlz-.js +1 -0
  92. package/dist/client/assets/prism-elixir-BIzI9WJK.js +1 -0
  93. package/dist/client/assets/prism-hcl-Bx2FGBKG.js +1 -0
  94. package/dist/client/assets/prism-java-DBXf7fH0.js +1 -0
  95. package/dist/client/assets/prism-markup-templating-DS0ksKLt.js +1 -0
  96. package/dist/client/assets/prism-perl-BlhPiMfT.js +1 -0
  97. package/dist/client/assets/prism-php-DVtOAJsW.js +1 -0
  98. package/dist/client/assets/{prism-protobuf-DiQ_z8B5.js → prism-protobuf-BUsrNVvv.js} +1 -1
  99. package/dist/client/assets/prism-ruby-Saes64I6.js +1 -0
  100. package/dist/client/assets/{prism-scala-BjNo2HkN.js → prism-scala-ANOINMog.js} +1 -1
  101. package/dist/client/assets/prism-solidity-C5Mx5y66.js +1 -0
  102. package/dist/client/assets/{prism-sql-AgAyy5H_.js → prism-sql-D5pwK0Dp.js} +1 -1
  103. package/dist/client/assets/{prism-vim-uciLQ2PQ.js → prism-vim-BSZSu-gX.js} +1 -1
  104. package/dist/client/assets/quadrantDiagram-337W2JSQ-Da_T39nG.js +7 -0
  105. package/dist/client/assets/radar-KQ55EAFF-BR1_ZPLF.js +1 -0
  106. package/dist/client/assets/requirementDiagram-Z7DCOOCP-DzlKWGt3.js +73 -0
  107. package/dist/client/assets/rough.esm-KjoEK0it.js +1 -0
  108. package/dist/client/assets/sankeyDiagram-WA2Y5GQK-BPketwK-.js +10 -0
  109. package/dist/client/assets/sequenceDiagram-2WXFIKYE-geDrMLZ_.js +145 -0
  110. package/dist/client/assets/src-BuTVwZtT.js +1 -0
  111. package/dist/client/assets/stateDiagram-RAJIS63D-DOLTjnid.js +1 -0
  112. package/dist/client/assets/stateDiagram-v2-FVOUBMTO-BIjVI5d6.js +1 -0
  113. package/dist/client/assets/timeline-definition-YZTLITO2-Cy6Qm4Pd.js +61 -0
  114. package/dist/client/assets/treemap-KZPCXAKY-Bw93Vsua.js +1 -0
  115. package/dist/client/assets/vennDiagram-LZ73GAT5-UUQN9akd.js +34 -0
  116. package/dist/client/assets/xychartDiagram-JWTSCODW-DiTicxdS.js +7 -0
  117. package/dist/client/index.html +2 -2
  118. package/dist/server/git-diff-tui.d.ts +2 -2
  119. package/dist/server/git-diff-tui.js +12 -7
  120. package/dist/server/git-diff-tui.test.js +18 -2
  121. package/dist/server/git-diff.d.ts +3 -2
  122. package/dist/server/git-diff.js +29 -6
  123. package/dist/server/git-diff.test.js +52 -3
  124. package/dist/server/server.d.ts +2 -3
  125. package/dist/server/server.js +80 -55
  126. package/dist/server/server.test.js +110 -60
  127. package/dist/tui/App.d.ts +2 -2
  128. package/dist/tui/App.js +4 -3
  129. package/dist/types/diff.d.ts +8 -0
  130. package/dist/utils/commentImports.js +3 -2
  131. package/dist/utils/createId.d.ts +1 -0
  132. package/dist/utils/createId.js +5 -0
  133. package/dist/utils/createId.test.d.ts +1 -0
  134. package/dist/utils/createId.test.js +48 -0
  135. package/dist/utils/diffSelection.d.ts +6 -0
  136. package/dist/utils/diffSelection.js +30 -0
  137. package/package.json +5 -5
  138. package/dist/client/assets/_basePickBy-ChXFkTMC.js +0 -1
  139. package/dist/client/assets/_baseUniq-Mj_sFFQW.js +0 -1
  140. package/dist/client/assets/arc-BMA6S9F1.js +0 -1
  141. package/dist/client/assets/architectureDiagram-2XIMDMQ5-0uiM_v5K.js +0 -36
  142. package/dist/client/assets/blockDiagram-WCTKOSBZ-CM7ZLL6F.js +0 -132
  143. package/dist/client/assets/c4Diagram-IC4MRINW-DKtCnVwn.js +0 -10
  144. package/dist/client/assets/channel-D057yzDp.js +0 -1
  145. package/dist/client/assets/chunk-4BX2VUAB-Wsl8DxEB.js +0 -1
  146. package/dist/client/assets/chunk-55IACEB6-CHm9X5i7.js +0 -1
  147. package/dist/client/assets/chunk-JSJVCQXG-Cpk76oJ3.js +0 -1
  148. package/dist/client/assets/chunk-KX2RTZJC-D8YvfZVu.js +0 -1
  149. package/dist/client/assets/chunk-NQ4KR5QH-BogviJOv.js +0 -220
  150. package/dist/client/assets/chunk-QZHKN3VN-DwLJYu26.js +0 -1
  151. package/dist/client/assets/chunk-WL4C6EOR-BFDpGxW2.js +0 -189
  152. package/dist/client/assets/classDiagram-VBA2DB6C---D4iOts.js +0 -1
  153. package/dist/client/assets/classDiagram-v2-RAHNMMFH---D4iOts.js +0 -1
  154. package/dist/client/assets/clone-xSR3otEf.js +0 -1
  155. package/dist/client/assets/cose-bilkent-S5V4N54A-oEosZ_5y.js +0 -1
  156. package/dist/client/assets/cytoscape.esm-5J0xJHOV.js +0 -321
  157. package/dist/client/assets/dagre-KLK3FWXG-gFld4u1H.js +0 -4
  158. package/dist/client/assets/defaultLocale-DX6XiGOO.js +0 -1
  159. package/dist/client/assets/diagram-E7M64L7V-gJq3kSrf.js +0 -24
  160. package/dist/client/assets/diagram-IFDJBPK2-BsUm_q22.js +0 -43
  161. package/dist/client/assets/diagram-P4PSJMXO-juB-sfcR.js +0 -24
  162. package/dist/client/assets/erDiagram-INFDFZHY-Dn77qXAt.js +0 -70
  163. package/dist/client/assets/flowDiagram-PKNHOUZH-DtmvDYdN.js +0 -162
  164. package/dist/client/assets/ganttDiagram-A5KZAMGK-BlDaKLbQ.js +0 -292
  165. package/dist/client/assets/gitGraphDiagram-K3NZZRJ6-DeAAeuMS.js +0 -65
  166. package/dist/client/assets/graph-NX9gBP47.js +0 -1
  167. package/dist/client/assets/index-VxkpzDXr.css +0 -1
  168. package/dist/client/assets/index-kJdw4DY-.js +0 -98
  169. package/dist/client/assets/infoDiagram-LFFYTUFH-CAaX023c.js +0 -2
  170. package/dist/client/assets/init-Gi6I4Gst.js +0 -1
  171. package/dist/client/assets/ishikawaDiagram-PHBUUO56-CmiTQStv.js +0 -70
  172. package/dist/client/assets/journeyDiagram-4ABVD52K-B0SHC7mz.js +0 -139
  173. package/dist/client/assets/kanban-definition-K7BYSVSG-IfRdhzz7.js +0 -89
  174. package/dist/client/assets/katex-C-M49wc6.js +0 -261
  175. package/dist/client/assets/layout-l3OdNQhJ.js +0 -1
  176. package/dist/client/assets/linear-CQ0hx5Qs.js +0 -1
  177. package/dist/client/assets/mermaid.core-DqlPTabt.js +0 -249
  178. package/dist/client/assets/mindmap-definition-YRQLILUH-DIgSmG_f.js +0 -68
  179. package/dist/client/assets/ordinal-Cboi1Yqb.js +0 -1
  180. package/dist/client/assets/pieDiagram-SKSYHLDU-FzM5qoIB.js +0 -30
  181. package/dist/client/assets/prism-csharp-DCfUUOUs.js +0 -1
  182. package/dist/client/assets/prism-dart-MjriiaMt.js +0 -1
  183. package/dist/client/assets/prism-elixir-riuOL1mm.js +0 -1
  184. package/dist/client/assets/prism-hcl-CizuX1s4.js +0 -1
  185. package/dist/client/assets/prism-java-DYCKrDUh.js +0 -1
  186. package/dist/client/assets/prism-markup-templating-Ct1xsyfA.js +0 -1
  187. package/dist/client/assets/prism-perl-BJwBYR3Y.js +0 -1
  188. package/dist/client/assets/prism-php-BMhFuA7y.js +0 -1
  189. package/dist/client/assets/prism-ruby-Bcu0cDEh.js +0 -1
  190. package/dist/client/assets/prism-solidity-DDDs3w-w.js +0 -1
  191. package/dist/client/assets/quadrantDiagram-337W2JSQ-BBrApyD7.js +0 -7
  192. package/dist/client/assets/requirementDiagram-Z7DCOOCP-CLXiwUaA.js +0 -73
  193. package/dist/client/assets/sankeyDiagram-WA2Y5GQK-9Y3Ly5qe.js +0 -10
  194. package/dist/client/assets/sequenceDiagram-2WXFIKYE-DEpX1BA5.js +0 -145
  195. package/dist/client/assets/stateDiagram-RAJIS63D-Ck3ullwA.js +0 -1
  196. package/dist/client/assets/stateDiagram-v2-FVOUBMTO-X6UiDsar.js +0 -1
  197. package/dist/client/assets/timeline-definition-YZTLITO2-CMezf3XV.js +0 -61
  198. package/dist/client/assets/treemap-KZPCXAKY-DqrcV0gQ.js +0 -162
  199. package/dist/client/assets/vennDiagram-LZ73GAT5-eQg945Fz.js +0 -34
  200. package/dist/client/assets/xychartDiagram-JWTSCODW-_hqdXeX1.js +0 -7
@@ -5,6 +5,7 @@ vi.mock('simple-git', () => ({
5
5
  simpleGit: vi.fn(() => ({
6
6
  revparse: vi.fn(),
7
7
  diff: vi.fn(),
8
+ raw: vi.fn(),
8
9
  })),
9
10
  }));
10
11
  // Mock child_process
@@ -967,7 +968,10 @@ index abc123..def456 100644
967
968
  parser.git.revparse.mockResolvedValue('abc1234567890abcdef1234567890abcdef12');
968
969
  const getBlobContentSpy = vi.spyOn(parser, 'getBlobContent');
969
970
  getBlobContentSpy.mockResolvedValue(Buffer.from('// @generated\nconst x = 1;'));
970
- const response = await parser.parseDiff('HEAD', 'HEAD~1');
971
+ const response = await parser.parseDiff({
972
+ targetCommitish: 'HEAD',
973
+ baseCommitish: 'HEAD~1',
974
+ });
971
975
  expect(response.files[0].path).toBe(file);
972
976
  expect(response.files[0].isGenerated).toBe(false);
973
977
  expect(getBlobContentSpy).not.toHaveBeenCalled();
@@ -1078,7 +1082,10 @@ index abc123..def456 100644
1078
1082
  .mockResolvedValueOnce('1234567890abcdef1234567890abcdef12345678')
1079
1083
  .mockResolvedValueOnce('abcdef1234567890abcdef1234567890abcdef12');
1080
1084
  gitDiff.mockResolvedValue('');
1081
- const response = await parser.parseDiff('HEAD', 'HEAD~1', false, 5);
1085
+ const response = await parser.parseDiff({
1086
+ targetCommitish: 'HEAD',
1087
+ baseCommitish: 'HEAD~1',
1088
+ }, false, 5);
1082
1089
  expect(gitDiff).toHaveBeenCalledWith([
1083
1090
  'abcdef1...1234567',
1084
1091
  '-U5',
@@ -1089,6 +1096,11 @@ index abc123..def456 100644
1089
1096
  commit: 'abcdef1...1234567',
1090
1097
  files: [],
1091
1098
  isEmpty: true,
1099
+ baseCommitish: 'abcdef1',
1100
+ targetCommitish: '1234567',
1101
+ requestedBaseCommitish: 'HEAD~1',
1102
+ requestedTargetCommitish: 'HEAD',
1103
+ requestedBaseMode: undefined,
1092
1104
  });
1093
1105
  });
1094
1106
  it('accepts branch refs with revision suffixes', async () => {
@@ -1098,7 +1110,10 @@ index abc123..def456 100644
1098
1110
  .mockResolvedValueOnce('1234567890abcdef1234567890abcdef12345678')
1099
1111
  .mockResolvedValueOnce('abcdef1234567890abcdef1234567890abcdef12');
1100
1112
  gitDiff.mockResolvedValue('');
1101
- const response = await parser.parseDiff('codex/comment-thread', 'codex/comment-thread^');
1113
+ const response = await parser.parseDiff({
1114
+ targetCommitish: 'codex/comment-thread',
1115
+ baseCommitish: 'codex/comment-thread^',
1116
+ });
1102
1117
  expect(gitRevparse).toHaveBeenNthCalledWith(1, ['codex/comment-thread']);
1103
1118
  expect(gitRevparse).toHaveBeenNthCalledWith(2, ['codex/comment-thread^']);
1104
1119
  expect(gitDiff).toHaveBeenCalledWith(['abcdef1...1234567', '--no-ext-diff', '--color=never']);
@@ -1106,6 +1121,40 @@ index abc123..def456 100644
1106
1121
  commit: 'abcdef1...1234567',
1107
1122
  files: [],
1108
1123
  isEmpty: true,
1124
+ baseCommitish: 'abcdef1',
1125
+ targetCommitish: '1234567',
1126
+ requestedBaseCommitish: 'codex/comment-thread^',
1127
+ requestedTargetCommitish: 'codex/comment-thread',
1128
+ requestedBaseMode: undefined,
1129
+ });
1130
+ });
1131
+ it('uses merge-base when baseMode is merge-base', async () => {
1132
+ const gitDiff = parser.git.diff;
1133
+ const gitRevparse = parser.git.revparse;
1134
+ const gitRaw = parser.git.raw;
1135
+ gitRaw.mockResolvedValue('fedcba9876543210fedcba9876543210fedcba98\n');
1136
+ gitRevparse.mockResolvedValueOnce('fedcba9876543210fedcba9876543210fedcba98');
1137
+ gitDiff.mockResolvedValue('');
1138
+ const response = await parser.parseDiff({
1139
+ targetCommitish: '.',
1140
+ baseCommitish: 'origin/main',
1141
+ baseMode: 'merge-base',
1142
+ });
1143
+ expect(gitRaw).toHaveBeenCalledWith(['merge-base', 'HEAD', 'origin/main']);
1144
+ expect(gitDiff).toHaveBeenCalledWith([
1145
+ 'fedcba9876543210fedcba9876543210fedcba98',
1146
+ '--no-ext-diff',
1147
+ '--color=never',
1148
+ ]);
1149
+ expect(response).toEqual({
1150
+ commit: 'fedcba9 vs Working Directory (all uncommitted changes)',
1151
+ files: [],
1152
+ isEmpty: true,
1153
+ baseCommitish: 'fedcba9',
1154
+ targetCommitish: '.',
1155
+ requestedBaseCommitish: 'origin/main',
1156
+ requestedTargetCommitish: '.',
1157
+ requestedBaseMode: 'merge-base',
1109
1158
  });
1110
1159
  });
1111
1160
  });
@@ -1,9 +1,8 @@
1
1
  import { type Server } from 'http';
2
2
  import { type DiffMode } from '../types/watch.js';
3
- import { type CommentImport } from '@/types/diff.js';
3
+ import { type CommentImport, type DiffSelection } from '@/types/diff.js';
4
4
  interface ServerOptions {
5
- targetCommitish?: string;
6
- baseCommitish?: string;
5
+ selection?: DiffSelection;
7
6
  stdinDiff?: string;
8
7
  preferredPort?: number;
9
8
  host?: string;
@@ -13,23 +13,56 @@ import { resolveEditorOption } from '../utils/editorOptions.js';
13
13
  import { getFileExtension } from '../utils/fileUtils.js';
14
14
  import { FileWatcherService } from './file-watcher.js';
15
15
  import { GitDiffParser } from './git-diff.js';
16
+ import { createDiffSelection, diffSelectionsEqual, getDiffSelectionKey, } from '../utils/diffSelection.js';
16
17
  const GENERATED_STATUS_CACHE_TTL_MS = 60_000;
18
+ const MAX_DIFF_CACHE_ENTRIES = 8;
19
+ function createDiffCacheKey(selection, ignoreWhitespace) {
20
+ return `${getDiffSelectionKey(selection)}\u0000${ignoreWhitespace ? '1' : '0'}`;
21
+ }
22
+ function getCachedDiffResponse(cache, key) {
23
+ const cached = cache.get(key);
24
+ if (!cached) {
25
+ return undefined;
26
+ }
27
+ // Refresh insertion order to keep the most recently used entry.
28
+ cache.delete(key);
29
+ cache.set(key, cached);
30
+ return cached;
31
+ }
32
+ function setCachedDiffResponse(cache, key, value) {
33
+ if (cache.has(key)) {
34
+ cache.delete(key);
35
+ }
36
+ cache.set(key, value);
37
+ while (cache.size > MAX_DIFF_CACHE_ENTRIES) {
38
+ const oldestKey = cache.keys().next().value;
39
+ if (typeof oldestKey !== 'string') {
40
+ break;
41
+ }
42
+ cache.delete(oldestKey);
43
+ }
44
+ }
17
45
  export async function startServer(options) {
18
46
  const app = express();
19
47
  const repositoryPath = resolve(options.repoPath ?? process.cwd());
20
48
  const repositoryId = createHash('sha256').update(repositoryPath).digest('hex');
21
49
  const initialCommentImports = options.commentImports || [];
22
- const initialBaseCommitish = options.baseCommitish ?? '';
23
- const initialTargetCommitish = options.targetCommitish ?? '';
50
+ const initialSelection = options.selection ?? createDiffSelection('', '');
24
51
  const commentImportId = initialCommentImports.length > 0
25
52
  ? createHash('sha256').update(serializeCommentImports(initialCommentImports)).digest('hex')
26
53
  : undefined;
27
54
  const parser = new GitDiffParser(repositoryPath);
28
55
  const fileWatcher = new FileWatcherService();
29
56
  const generatedStatusCache = new Map();
30
- let diffDataCache = null;
31
- let currentIgnoreWhitespace = options.ignoreWhitespace || false;
57
+ const diffDataCache = new Map();
58
+ const initialIgnoreWhitespace = options.ignoreWhitespace || false;
32
59
  const diffMode = normalizeDiffViewMode(options.mode);
60
+ const parseBaseMode = (value) => {
61
+ if (value === 'merge-base') {
62
+ return 'merge-base';
63
+ }
64
+ return undefined;
65
+ };
33
66
  app.use(express.json());
34
67
  app.use(express.text()); // For sendBeacon text/plain requests
35
68
  app.use((_req, res, next) => {
@@ -40,28 +73,29 @@ export async function startServer(options) {
40
73
  });
41
74
  // Skip validation if using stdin diff
42
75
  if (!options.stdinDiff) {
43
- const isValidCommit = await parser.validateCommit(options.targetCommitish ?? '');
76
+ const isValidCommit = await parser.validateCommit(initialSelection.targetCommitish);
44
77
  if (!isValidCommit) {
45
- throw new Error(`Invalid or non-existent commit: ${options.targetCommitish}`);
78
+ throw new Error(`Invalid or non-existent commit: ${initialSelection.targetCommitish}`);
46
79
  }
47
80
  }
48
81
  // Generate initial diff data for isEmpty check
82
+ let initialDiffData;
49
83
  if (options.stdinDiff) {
50
84
  // Parse stdin diff directly
51
- diffDataCache = parser.parseStdinDiff(options.stdinDiff);
85
+ initialDiffData = parser.parseStdinDiff(options.stdinDiff);
52
86
  }
53
87
  else {
54
- diffDataCache = await parser.parseDiff(options.targetCommitish ?? '', options.baseCommitish ?? '', currentIgnoreWhitespace, options.contextLines);
88
+ initialDiffData = await parser.parseDiff(initialSelection, initialIgnoreWhitespace, options.contextLines);
89
+ setCachedDiffResponse(diffDataCache, createDiffCacheKey(initialSelection, initialIgnoreWhitespace), initialDiffData);
55
90
  }
56
91
  // Function to invalidate cache when file changes are detected
57
92
  const invalidateCache = () => {
58
- diffDataCache = null;
93
+ diffDataCache.clear();
59
94
  generatedStatusCache.clear();
60
95
  parser.clearResolvedCommitCache();
61
96
  };
62
97
  // Track current revisions for cache invalidation
63
- let currentBaseCommitish = options.baseCommitish ?? '';
64
- let currentTargetCommitish = options.targetCommitish ?? '';
98
+ let currentSelection = initialSelection;
65
99
  function parseRepositoryRelativePath(filepath) {
66
100
  if (typeof filepath !== 'string' || filepath.length === 0) {
67
101
  return { ok: false, error: 'Invalid file path' };
@@ -79,56 +113,47 @@ export async function startServer(options) {
79
113
  }
80
114
  app.get('/api/diff', async (req, res) => {
81
115
  const ignoreWhitespace = req.query.ignoreWhitespace === 'true';
82
- const requestedBase = req.query.base || options.baseCommitish || '';
83
- const requestedTarget = req.query.target || options.targetCommitish || '';
116
+ const hasBase = typeof req.query.base === 'string';
117
+ const hasTarget = typeof req.query.target === 'string';
118
+ const hasBaseMode = typeof req.query.baseMode === 'string';
119
+ const requestedSelection = createDiffSelection(hasBase ? req.query.base : currentSelection.baseCommitish, hasTarget ? req.query.target : currentSelection.targetCommitish, hasBaseMode
120
+ ? parseBaseMode(req.query.baseMode)
121
+ : hasBase || hasTarget
122
+ ? undefined
123
+ : currentSelection.baseMode);
84
124
  const shouldIncludeCommentImports = initialCommentImports.length > 0 &&
85
- (Boolean(options.stdinDiff) ||
86
- (requestedBase === initialBaseCommitish && requestedTarget === initialTargetCommitish));
87
- // Check if revisions or whitespace setting changed
88
- const revisionsChanged = requestedBase !== currentBaseCommitish || requestedTarget !== currentTargetCommitish;
89
- const whitespaceChanged = ignoreWhitespace !== currentIgnoreWhitespace;
90
- // Regenerate diff data if cache is invalid or settings changed
91
- if (!diffDataCache || ((revisionsChanged || whitespaceChanged) && !options.stdinDiff)) {
92
- currentIgnoreWhitespace = ignoreWhitespace;
93
- currentBaseCommitish = requestedBase;
94
- currentTargetCommitish = requestedTarget;
95
- diffDataCache = await parser.parseDiff(requestedTarget, requestedBase, ignoreWhitespace, options.contextLines);
96
- generatedStatusCache.clear();
97
- }
98
- // Resolve symbolic refs like HEAD/HEAD^ to actual hashes for the UI
99
- let resolvedBase = currentBaseCommitish || 'stdin';
100
- let resolvedTarget = currentTargetCommitish || 'stdin';
101
- if (!options.stdinDiff &&
102
- currentBaseCommitish &&
103
- !['working', 'staged', '.'].includes(currentBaseCommitish)) {
104
- try {
105
- resolvedBase = await parser.resolveCommitish(currentBaseCommitish);
106
- }
107
- catch {
108
- // If resolution fails, keep original value
125
+ (Boolean(options.stdinDiff) || diffSelectionsEqual(requestedSelection, initialSelection));
126
+ currentSelection = requestedSelection;
127
+ let responseDiffData = initialDiffData;
128
+ if (!options.stdinDiff) {
129
+ const cacheKey = createDiffCacheKey(requestedSelection, ignoreWhitespace);
130
+ const cached = getCachedDiffResponse(diffDataCache, cacheKey);
131
+ if (cached) {
132
+ responseDiffData = cached;
109
133
  }
110
- }
111
- if (!options.stdinDiff &&
112
- currentTargetCommitish &&
113
- !['working', 'staged', '.'].includes(currentTargetCommitish)) {
114
- try {
115
- resolvedTarget = await parser.resolveCommitish(currentTargetCommitish);
116
- }
117
- catch {
118
- // If resolution fails, keep original value
134
+ else {
135
+ responseDiffData = await parser.parseDiff(requestedSelection, ignoreWhitespace, options.contextLines);
136
+ setCachedDiffResponse(diffDataCache, cacheKey, responseDiffData);
137
+ generatedStatusCache.clear();
119
138
  }
120
139
  }
121
- const requestedBaseCommitish = currentBaseCommitish || 'stdin';
122
- const requestedTargetCommitish = currentTargetCommitish || 'stdin';
140
+ const baseCommitish = responseDiffData.baseCommitish ?? (options.stdinDiff ? 'stdin' : undefined);
141
+ const targetCommitish = responseDiffData.targetCommitish ?? (options.stdinDiff ? 'stdin' : undefined);
142
+ const requestedBaseCommitish = responseDiffData.requestedBaseCommitish ??
143
+ (requestedSelection.baseCommitish || (options.stdinDiff ? 'stdin' : undefined));
144
+ const requestedTargetCommitish = responseDiffData.requestedTargetCommitish ??
145
+ (requestedSelection.targetCommitish || (options.stdinDiff ? 'stdin' : undefined));
146
+ const requestedBaseMode = responseDiffData.requestedBaseMode ?? requestedSelection.baseMode;
123
147
  res.json({
124
- ...diffDataCache,
148
+ ...responseDiffData,
125
149
  ignoreWhitespace,
126
150
  mode: diffMode,
127
151
  openInEditorAvailable: !options.stdinDiff,
128
- baseCommitish: resolvedBase,
129
- targetCommitish: resolvedTarget,
152
+ baseCommitish,
153
+ targetCommitish,
130
154
  requestedBaseCommitish,
131
155
  requestedTargetCommitish,
156
+ requestedBaseMode,
132
157
  clearComments: options.clearComments,
133
158
  repositoryId,
134
159
  commentImports: shouldIncludeCommentImports ? initialCommentImports : undefined,
@@ -147,7 +172,7 @@ export async function startServer(options) {
147
172
  return;
148
173
  }
149
174
  const normalizedFilepath = filepathResult.path;
150
- const ref = req.query.ref || currentTargetCommitish || 'HEAD';
175
+ const ref = req.query.ref || currentSelection.targetCommitish || 'HEAD';
151
176
  const cacheKey = `${ref}:${normalizedFilepath}`;
152
177
  const now = Date.now();
153
178
  const cached = generatedStatusCache.get(cacheKey);
@@ -179,7 +204,7 @@ export async function startServer(options) {
179
204
  return;
180
205
  }
181
206
  try {
182
- const { branches, commits, originDefaultBranch, resolvedBase, resolvedTarget } = await parser.getRevisionOptions(currentBaseCommitish, currentTargetCommitish);
207
+ const { branches, commits, originDefaultBranch, resolvedBase, resolvedTarget } = await parser.getRevisionOptions(currentSelection.baseCommitish, currentSelection.targetCommitish);
183
208
  const response = {
184
209
  specialOptions: [
185
210
  { value: '.', label: 'All Uncommitted Changes' },
@@ -528,7 +553,7 @@ export async function startServer(options) {
528
553
  }
529
554
  }
530
555
  // Check if diff is empty and skip browser opening
531
- if (diffDataCache?.isEmpty) {
556
+ if (initialDiffData.isEmpty) {
532
557
  // Don't open browser if no differences found
533
558
  }
534
559
  else if (options.openBrowser) {
@@ -539,7 +564,7 @@ export async function startServer(options) {
539
564
  console.warn('Failed to open browser automatically');
540
565
  }
541
566
  }
542
- return { port, url, isEmpty: diffDataCache?.isEmpty || false, server };
567
+ return { port, url, isEmpty: initialDiffData.isEmpty || false, server };
543
568
  }
544
569
  async function startServerWithFallback(app, preferredPort, host) {
545
570
  return new Promise((resolve, reject) => {