difit 4.0.2 → 4.0.4
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.
- package/README.ja.md +1 -0
- package/README.ko.md +1 -0
- package/README.md +1 -0
- package/README.zh.md +1 -0
- package/dist/cli/index.js +38 -28
- package/dist/cli/index.test.js +162 -77
- package/dist/cli/utils.js +4 -0
- package/dist/cli/utils.test.js +6 -1
- package/dist/client/assets/_baseFor-DKyA49xd.js +1 -0
- package/dist/client/assets/arc-COOp7iVe.js +1 -0
- package/dist/client/assets/architecture-YZFGNWBL-Cs2Q6RQP.js +1 -0
- package/dist/client/assets/architectureDiagram-Q4EWVU46-BO4dVPUA.js +36 -0
- package/dist/client/assets/blockDiagram-DXYQGD6D-CtNJnEWN.js +132 -0
- package/dist/client/assets/c4Diagram-AHTNJAMY-BqG-1m6C.js +10 -0
- package/dist/client/assets/channel-_xDT1u3-.js +1 -0
- package/dist/client/assets/chunk-2KRD3SAO-DqP2NJNd.js +1 -0
- package/dist/client/assets/chunk-336JU56O-D1qa7Qzb.js +2 -0
- package/dist/client/assets/chunk-426QAEUC-6J_A_wvD.js +1 -0
- package/dist/client/assets/{chunk-4BX2VUAB-CW45MZFx.js → chunk-4BX2VUAB-BT78EnQ6.js} +1 -1
- package/dist/client/assets/chunk-4TB4RGXK-C4w_Bwzw.js +206 -0
- package/dist/client/assets/{chunk-55IACEB6-Busc3sfI.js → chunk-55IACEB6-z3MQSTaj.js} +1 -1
- package/dist/client/assets/chunk-5FUZZQ4R-Chei69aj.js +62 -0
- package/dist/client/assets/chunk-5PVQY5BW-HgRiIs0X.js +2 -0
- package/dist/client/assets/chunk-67CJDMHE-B2q10-fp.js +1 -0
- package/dist/client/assets/chunk-7N4EOEYR-DPgxysWq.js +1 -0
- package/dist/client/assets/chunk-AA7GKIK3-BqmVmKLq.js +1 -0
- package/dist/client/assets/chunk-BSJP7CBP-CaIgleFn.js +1 -0
- package/dist/client/assets/chunk-CFjPhJqf.js +1 -0
- package/dist/client/assets/chunk-CIAEETIT-ByD-tlNF.js +1 -0
- package/dist/client/assets/{chunk-KX2RTZJC-DrhxxMOx.js → chunk-EDXVE4YY-d3RUKKAj.js} +1 -1
- package/dist/client/assets/chunk-ENJZ2VHE-CNq5Qmg9.js +10 -0
- package/dist/client/assets/{chunk-FMBD7UC4-BSsJVlRg.js → chunk-FMBD7UC4-DYfHJ6MV.js} +1 -1
- package/dist/client/assets/chunk-FOC6F5B3-BRpSWlZj.js +1 -0
- package/dist/client/assets/chunk-ICPOFSXX-B_MThwG6.js +122 -0
- package/dist/client/assets/chunk-K5T4RW27-DmamW1Ds.js +94 -0
- package/dist/client/assets/chunk-KGLVRYIC-CRbg4c4z.js +1 -0
- package/dist/client/assets/chunk-LIHQZDEY-CHQPSdB3.js +1 -0
- package/dist/client/assets/chunk-ORNJ4GCN-CIsQ4Zi4.js +1 -0
- package/dist/client/assets/{chunk-NQ4KR5QH-BZ86r2qK.js → chunk-OYMX7WX6-Cxi0kdGg.js} +25 -14
- package/dist/client/assets/chunk-QZHKN3VN-C0QzfgZ8.js +1 -0
- package/dist/client/assets/{chunk-PU5JKC2W-PQmA4K_y.js → chunk-U2HBQHQK-V_hneCfR.js} +6 -6
- package/dist/client/assets/{chunk-PQ6SQG4A-C9acTu_E.js → chunk-X2U36JSP-De4pvO-I.js} +1 -1
- package/dist/client/assets/{chunk-XPW4576I-CcqR6BsE.js → chunk-XPW4576I-B_osXKp6.js} +2 -2
- package/dist/client/assets/{chunk-JSJVCQXG-UCJub_Eo.js → chunk-YZCP3GAM-C_kqXssD.js} +1 -1
- package/dist/client/assets/chunk-ZZ45TVLE-B_xtlma5.js +1 -0
- package/dist/client/assets/classDiagram-6PBFFD2Q-5XrS-DAQ.js +1 -0
- package/dist/client/assets/classDiagram-v2-HSJHXN6E-Covl2vKy.js +1 -0
- package/dist/client/assets/clone-rhRH8pyW.js +1 -0
- package/dist/client/assets/cose-bilkent-S5V4N54A-BvXFc7Rr.js +1 -0
- package/dist/client/assets/cytoscape.esm-DRReFUEO.js +321 -0
- package/dist/client/assets/dagre-DU-XBdcU.js +1 -0
- package/dist/client/assets/dagre-KV5264BT-BWYGReXF.js +4 -0
- package/dist/client/assets/diagram-5BDNPKRD-DpUUhvWz.js +10 -0
- package/dist/client/assets/diagram-G4DWMVQ6-BJoTrUAx.js +24 -0
- package/dist/client/assets/diagram-MMDJMWI5-CAk1GW5g.js +43 -0
- package/dist/client/assets/diagram-TYMM5635-Cct6g7FA.js +24 -0
- package/dist/client/assets/dist-61sCfOmN.js +1 -0
- package/dist/client/assets/{erDiagram-INFDFZHY-ByL02DP-.js → erDiagram-SMLLAGMA-DHs2bXUj.js} +33 -18
- package/dist/client/assets/flatten-mnWyE-RB.js +1 -0
- package/dist/client/assets/flowDiagram-DWJPFMVM-DLu-6dfC.js +162 -0
- package/dist/client/assets/ganttDiagram-T4ZO3ILL-CMIzlKAR.js +292 -0
- package/dist/client/assets/gitGraph-7Q5UKJZL-A_wWsXju.js +1 -0
- package/dist/client/assets/gitGraphDiagram-UUTBAWPF-Bc_rL3_k.js +106 -0
- package/dist/client/assets/graphlib-BVMK0xYE.js +1 -0
- package/dist/client/assets/{index-Cn4K2uvR.css → index-Cq_APK7Y.css} +1 -1
- package/dist/client/assets/index-RcU838Ah.js +79 -0
- package/dist/client/assets/info-OMHHGYJF-Bv3kK2Bb.js +1 -0
- package/dist/client/assets/{infoDiagram-LFFYTUFH-CnmYkyCb.js → infoDiagram-42DDH7IO-Cf8u4jgP.js} +1 -1
- package/dist/client/assets/isEmpty-CiiIHfXR.js +1 -0
- package/dist/client/assets/ishikawaDiagram-UXIWVN3A-7n7DvfEb.js +70 -0
- package/dist/client/assets/{journeyDiagram-4ABVD52K-aRoH36nV.js → journeyDiagram-VCZTEJTY-BMkeQqJb.js} +2 -2
- package/dist/client/assets/{kanban-definition-K7BYSVSG-BGtGv5yb.js → kanban-definition-6JOO6SKY-B8KkeZLS.js} +2 -2
- package/dist/client/assets/katex-FOM3xZj7.js +257 -0
- package/dist/client/assets/line-CVpcI6kj.js +1 -0
- package/dist/client/assets/{linear-HJOLPv7E.js → linear-DmhiOOKU.js} +1 -1
- package/dist/client/assets/mermaid-parser.core-CnJ9Tv8l.js +4 -0
- package/dist/client/assets/mermaid.core-R7nXpPx-.js +11 -0
- package/dist/client/assets/{mindmap-definition-YRQLILUH-B8jMe7ir.js → mindmap-definition-QFDTVHPH-CwcHocMZ.js} +39 -11
- package/dist/client/assets/{ordinal-DIg8h6NI.js → ordinal-k--hYEme.js} +1 -1
- package/dist/client/assets/packet-4T2RLAQJ-D2q3-9ae.js +1 -0
- package/dist/client/assets/pie-ZZUOXDRM-GivlQcUF.js +1 -0
- package/dist/client/assets/pieDiagram-DEJITSTG-BVAn8Lmr.js +30 -0
- package/dist/client/assets/preload-helper-DSXbuxSR.js +1 -0
- package/dist/client/assets/prism-haskell-BP3SRvzt.js +1 -0
- package/dist/client/assets/prism-nix-CO4UPu3E.js +1 -0
- package/dist/client/assets/{quadrantDiagram-337W2JSQ-CQ1QKsru.js → quadrantDiagram-34T5L4WZ-C2XZ_zxa.js} +1 -1
- package/dist/client/assets/radar-PYXPWWZC-C9pD6VNR.js +1 -0
- package/dist/client/assets/reduce-BTlHjXna.js +1 -0
- package/dist/client/assets/requirementDiagram-MS252O5E-CfO16pkI.js +84 -0
- package/dist/client/assets/{sankeyDiagram-WA2Y5GQK-BQVbT6bS.js → sankeyDiagram-XADWPNL6-D_4_234M.js} +1 -1
- package/dist/client/assets/sequenceDiagram-FGHM5R23-B-yHKMuK.js +157 -0
- package/dist/client/assets/src-CjDs0_Ij.js +1 -0
- package/dist/client/assets/stateDiagram-FHFEXIEX-BeG2di4I.js +1 -0
- package/dist/client/assets/stateDiagram-v2-QKLJ7IA2-DvcSq7KE.js +1 -0
- package/dist/client/assets/timeline-definition-GMOUNBTQ-DhtnMGcE.js +120 -0
- package/dist/client/assets/treeView-SZITEDCU-BSNk8_yV.js +1 -0
- package/dist/client/assets/treemap-W4RFUUIX-ym4zQztE.js +1 -0
- package/dist/client/assets/vennDiagram-DHZGUBPP-CBn69TcQ.js +34 -0
- package/dist/client/assets/wardley-RL74JXVD-B02H6ReJ.js +1 -0
- package/dist/client/assets/wardleyDiagram-NUSXRM2D-CEoSJmN1.js +20 -0
- package/dist/client/assets/{xychartDiagram-JWTSCODW-DeYZhM2j.js → xychartDiagram-5P7HB3ND-BZ_X9tkn.js} +6 -6
- package/dist/client/index.html +4 -2
- package/dist/server/git-diff-tui.d.ts +2 -2
- package/dist/server/git-diff-tui.js +14 -11
- package/dist/server/git-diff-tui.test.js +20 -4
- package/dist/server/git-diff.d.ts +3 -2
- package/dist/server/git-diff.js +30 -7
- package/dist/server/git-diff.test.js +60 -5
- package/dist/server/server.d.ts +2 -3
- package/dist/server/server.js +80 -55
- package/dist/server/server.test.js +110 -60
- package/dist/tui/App.d.ts +2 -2
- package/dist/tui/App.js +4 -3
- package/dist/types/diff.d.ts +8 -0
- package/dist/utils/diffSelection.d.ts +6 -0
- package/dist/utils/diffSelection.js +30 -0
- package/package.json +9 -9
- package/dist/client/assets/_basePickBy-hOr-yGsE.js +0 -1
- package/dist/client/assets/_baseUniq-b7bzdUSn.js +0 -1
- package/dist/client/assets/arc-D65wG9gm.js +0 -1
- package/dist/client/assets/architecture-PBZL5I3N-DFdrPtRG.js +0 -1
- package/dist/client/assets/architectureDiagram-2XIMDMQ5-CXJTJFYJ.js +0 -36
- package/dist/client/assets/blockDiagram-WCTKOSBZ-B60owdAn.js +0 -132
- package/dist/client/assets/c4Diagram-IC4MRINW-4tg2D_Vt.js +0 -10
- package/dist/client/assets/channel-DogeU0Wo.js +0 -1
- package/dist/client/assets/chunk-7E7YKBS2-BVR-8Pma.js +0 -1
- package/dist/client/assets/chunk-7R4GIKGN-DneC7PwP.js +0 -80
- package/dist/client/assets/chunk-C72U2L5F-CJr98gus.js +0 -1
- package/dist/client/assets/chunk-EGIJ26TM-iD_CSqpR.js +0 -1
- package/dist/client/assets/chunk-GEFDOKGD-eDUrsRgt.js +0 -2
- package/dist/client/assets/chunk-GLR3WWYH-NUOKNaxd.js +0 -2
- package/dist/client/assets/chunk-HHEYEP7N-DhuxpkmW.js +0 -1
- package/dist/client/assets/chunk-KYZI473N-Brv52ZeO.js +0 -53
- package/dist/client/assets/chunk-L3YUKLVL-BkBigLhQ.js +0 -1
- package/dist/client/assets/chunk-MX3YWQON-DHRoNbgW.js +0 -1
- package/dist/client/assets/chunk-O4XLMI2P-Sr33dk8c.js +0 -7
- package/dist/client/assets/chunk-OZEHJAEY-3F2ff7sj.js +0 -1
- package/dist/client/assets/chunk-QZHKN3VN-DMRW-mur.js +0 -1
- package/dist/client/assets/chunk-R5LLSJPH-ChexuO_S.js +0 -1
- package/dist/client/assets/chunk-WL4C6EOR-oxNV_hhM.js +0 -189
- package/dist/client/assets/chunk-XIRO2GV7-C9gOnffv.js +0 -1
- package/dist/client/assets/chunk-XZSTWKYB-C5JJ0TZR.js +0 -94
- package/dist/client/assets/chunk-YBOYWFTD-B6kAkNgH.js +0 -1
- package/dist/client/assets/classDiagram-VBA2DB6C-DlDUg6JI.js +0 -1
- package/dist/client/assets/classDiagram-v2-RAHNMMFH-BxzJfV1S.js +0 -1
- package/dist/client/assets/clone-DuY6BQEm.js +0 -1
- package/dist/client/assets/cose-bilkent-S5V4N54A-hlDud6Ym.js +0 -1
- package/dist/client/assets/cytoscape.esm-B3gzQ1NF.js +0 -321
- package/dist/client/assets/dagre-BwDYerGQ.js +0 -1
- package/dist/client/assets/dagre-KLK3FWXG-KnkMUlUE.js +0 -4
- package/dist/client/assets/diagram-E7M64L7V-DcTCIFUG.js +0 -24
- package/dist/client/assets/diagram-IFDJBPK2-COcDQunj.js +0 -43
- package/dist/client/assets/diagram-P4PSJMXO-DmgET9pD.js +0 -24
- package/dist/client/assets/dist-v55TM3-O.js +0 -1
- package/dist/client/assets/flowDiagram-PKNHOUZH-CW-lseYE.js +0 -162
- package/dist/client/assets/ganttDiagram-A5KZAMGK-BxLjKRld.js +0 -292
- package/dist/client/assets/gitGraph-HDMCJU4V-CjAGJiCH.js +0 -1
- package/dist/client/assets/gitGraphDiagram-K3NZZRJ6-DLEDjokx.js +0 -65
- package/dist/client/assets/graphlib-WkJoBgka.js +0 -1
- package/dist/client/assets/index-CizZxdOT.js +0 -79
- package/dist/client/assets/info-3K5VOQVL-CB6KpH1K.js +0 -1
- package/dist/client/assets/isArrayLikeObject-icl0H0jo.js +0 -1
- package/dist/client/assets/isEmpty-Du8sNmkE.js +0 -1
- package/dist/client/assets/ishikawaDiagram-PHBUUO56-zycn1mVK.js +0 -70
- package/dist/client/assets/katex-BJrMXEjr.js +0 -261
- package/dist/client/assets/line-Cm3ZuldI.js +0 -1
- package/dist/client/assets/math-CNhlSIO3.js +0 -1
- package/dist/client/assets/mermaid-parser.core-BvMqHn4b.js +0 -4
- package/dist/client/assets/mermaid.core-C4SvQTx9.js +0 -11
- package/dist/client/assets/packet-RMMSAZCW-CzbC-tXD.js +0 -1
- package/dist/client/assets/pie-UPGHQEXC-CmhYIo8p.js +0 -1
- package/dist/client/assets/pieDiagram-SKSYHLDU-CGWbtgxq.js +0 -30
- package/dist/client/assets/radar-KQ55EAFF-BCa9lsCc.js +0 -1
- package/dist/client/assets/requirementDiagram-Z7DCOOCP-Co1LyL5T.js +0 -73
- package/dist/client/assets/sequenceDiagram-2WXFIKYE-DGIEkdPm.js +0 -145
- package/dist/client/assets/src-DsmFf7gO.js +0 -1
- package/dist/client/assets/stateDiagram-RAJIS63D-DgjKbXnG.js +0 -1
- package/dist/client/assets/stateDiagram-v2-FVOUBMTO-gPrpjL74.js +0 -1
- package/dist/client/assets/timeline-definition-YZTLITO2-Dz2dVWjY.js +0 -61
- package/dist/client/assets/treemap-KZPCXAKY-DXiPfAB6.js +0 -1
- package/dist/client/assets/vennDiagram-LZ73GAT5-IIH5S1B6.js +0 -34
- /package/dist/client/assets/{array-DOVTz2Mq.js → array-BNor45A1.js} +0 -0
- /package/dist/client/assets/{defaultLocale-Ck2Xxk-C.js → defaultLocale-DPzUsThw.js} +0 -0
- /package/dist/client/assets/{init-Bft5Ffpj.js → init-C0L3woqb.js} +0 -0
- /package/dist/client/assets/{path-DfRbCp9y.js → path-sMK4d_s9.js} +0 -0
- /package/dist/client/assets/{prism-bash-6uMTC0Q2.js → prism-bash-iQBez6et.js} +0 -0
- /package/dist/client/assets/{prism-csharp-Dkc2OSmh.js → prism-csharp-C1RDHXRk.js} +0 -0
- /package/dist/client/assets/{prism-dart-iZy_wlz-.js → prism-dart-nIH9vDUM.js} +0 -0
- /package/dist/client/assets/{prism-elixir-BIzI9WJK.js → prism-elixir-DUMUOd7H.js} +0 -0
- /package/dist/client/assets/{prism-hcl-Bx2FGBKG.js → prism-hcl-C-ZHJGEE.js} +0 -0
- /package/dist/client/assets/{prism-java-DBXf7fH0.js → prism-java-scuShSv5.js} +0 -0
- /package/dist/client/assets/{prism-markup-templating-DS0ksKLt.js → prism-markup-templating-BFXREXfb.js} +0 -0
- /package/dist/client/assets/{prism-perl-BlhPiMfT.js → prism-perl-BBDKnHRR.js} +0 -0
- /package/dist/client/assets/{prism-php-DVtOAJsW.js → prism-php-DjIafOi_.js} +0 -0
- /package/dist/client/assets/{prism-protobuf-BUsrNVvv.js → prism-protobuf-BE1MoFmZ.js} +0 -0
- /package/dist/client/assets/{prism-ruby-Saes64I6.js → prism-ruby-CZ-lrXfL.js} +0 -0
- /package/dist/client/assets/{prism-scala-ANOINMog.js → prism-scala-DgnxHuDn.js} +0 -0
- /package/dist/client/assets/{prism-solidity-C5Mx5y66.js → prism-solidity-5fSUcW9Y.js} +0 -0
- /package/dist/client/assets/{prism-sql-D5pwK0Dp.js → prism-sql-CKkohPI_.js} +0 -0
- /package/dist/client/assets/{prism-vim-BSZSu-gX.js → prism-vim-CkRmxTmK.js} +0 -0
- /package/dist/client/assets/{rough.esm-KjoEK0it.js → rough.esm-DeLgKbOI.js} +0 -0
package/dist/client/index.html
CHANGED
|
@@ -7,8 +7,10 @@
|
|
|
7
7
|
<link rel="icon" href="/favicon-white.svg" media="(prefers-color-scheme: dark)" />
|
|
8
8
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
9
9
|
<title>difit - Git Diff Viewer</title>
|
|
10
|
-
<script type="module" crossorigin src="/assets/index-
|
|
11
|
-
<link rel="
|
|
10
|
+
<script type="module" crossorigin src="/assets/index-RcU838Ah.js"></script>
|
|
11
|
+
<link rel="modulepreload" crossorigin href="/assets/chunk-CFjPhJqf.js">
|
|
12
|
+
<link rel="modulepreload" crossorigin href="/assets/preload-helper-DSXbuxSR.js">
|
|
13
|
+
<link rel="stylesheet" crossorigin href="/assets/index-Cq_APK7Y.css">
|
|
12
14
|
</head>
|
|
13
15
|
<body>
|
|
14
16
|
<div id="root"></div>
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import type { FileDiff } from '../types/diff.js';
|
|
2
|
-
export declare function loadGitDiff(
|
|
1
|
+
import type { DiffSelection, FileDiff } from '../types/diff.js';
|
|
2
|
+
export declare function loadGitDiff(selection: DiffSelection, repoPath?: string, contextLines?: number): Promise<FileDiff[]>;
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import simpleGit from 'simple-git';
|
|
2
|
-
import { validateDiffArguments
|
|
3
|
-
|
|
2
|
+
import { validateDiffArguments } from '../cli/utils.js';
|
|
3
|
+
import { getMergeBaseTargetRef, normalizeBaseMode } from '../utils/diffSelection.js';
|
|
4
|
+
export async function loadGitDiff(selection, repoPath, contextLines) {
|
|
5
|
+
const { targetCommitish, baseCommitish } = selection;
|
|
4
6
|
// Validate arguments
|
|
5
7
|
const validation = validateDiffArguments(targetCommitish, baseCommitish);
|
|
6
8
|
if (!validation.valid) {
|
|
7
9
|
throw new Error(validation.error);
|
|
8
10
|
}
|
|
9
11
|
const git = simpleGit(repoPath);
|
|
12
|
+
const effectiveBaseCommitish = normalizeBaseMode(selection.baseMode) === 'merge-base'
|
|
13
|
+
? (await git.raw(['merge-base', getMergeBaseTargetRef(targetCommitish), baseCommitish])).trim()
|
|
14
|
+
: baseCommitish;
|
|
10
15
|
let diff;
|
|
11
16
|
// Handle target special chars (base is always a regular commit)
|
|
12
17
|
if (targetCommitish === 'working') {
|
|
@@ -15,18 +20,15 @@ export async function loadGitDiff(targetCommitish, baseCommitish, repoPath, cont
|
|
|
15
20
|
}
|
|
16
21
|
else if (targetCommitish === 'staged') {
|
|
17
22
|
// Show staged changes against base commit
|
|
18
|
-
diff = await git.diff(['--cached',
|
|
23
|
+
diff = await git.diff(['--cached', effectiveBaseCommitish, '--name-status']);
|
|
19
24
|
}
|
|
20
25
|
else if (targetCommitish === '.') {
|
|
21
26
|
// Show all uncommitted changes against base commit
|
|
22
|
-
diff = await git.diff([
|
|
27
|
+
diff = await git.diff([effectiveBaseCommitish, '--name-status']);
|
|
23
28
|
}
|
|
24
29
|
else {
|
|
25
30
|
// Both are regular commits: standard commit-to-commit comparison
|
|
26
|
-
diff = await git.diff([
|
|
27
|
-
createCommitRangeString(baseCommitish, targetCommitish),
|
|
28
|
-
'--name-status',
|
|
29
|
-
]);
|
|
31
|
+
diff = await git.diff([effectiveBaseCommitish, targetCommitish, '--name-status']);
|
|
30
32
|
if (!diff.trim()) {
|
|
31
33
|
// Try without parent (for initial commit)
|
|
32
34
|
const diffInitial = await git.diff([targetCommitish, '--name-status']);
|
|
@@ -55,17 +57,18 @@ export async function loadGitDiff(targetCommitish, baseCommitish, repoPath, cont
|
|
|
55
57
|
}
|
|
56
58
|
else if (targetCommitish === 'staged') {
|
|
57
59
|
// Show staged changes against base commit
|
|
58
|
-
fileDiff = await git.diff(['--cached',
|
|
60
|
+
fileDiff = await git.diff(['--cached', effectiveBaseCommitish, ...contextArgs, '--', path]);
|
|
59
61
|
}
|
|
60
62
|
else if (targetCommitish === '.') {
|
|
61
63
|
// Show all uncommitted changes against base commit
|
|
62
|
-
fileDiff = await git.diff([
|
|
64
|
+
fileDiff = await git.diff([effectiveBaseCommitish, ...contextArgs, '--', path]);
|
|
63
65
|
}
|
|
64
66
|
else {
|
|
65
67
|
try {
|
|
66
68
|
// Both are regular commits: standard commit-to-commit comparison
|
|
67
69
|
fileDiff = await git.diff([
|
|
68
|
-
|
|
70
|
+
effectiveBaseCommitish,
|
|
71
|
+
targetCommitish,
|
|
69
72
|
...contextArgs,
|
|
70
73
|
'--',
|
|
71
74
|
path,
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
2
|
import { loadGitDiff } from './git-diff-tui.js';
|
|
3
3
|
const mockDiff = vi.hoisted(() => vi.fn());
|
|
4
|
-
const
|
|
4
|
+
const mockRaw = vi.hoisted(() => vi.fn());
|
|
5
|
+
const mockSimpleGit = vi.hoisted(() => vi.fn(() => ({ diff: mockDiff, raw: mockRaw })));
|
|
5
6
|
vi.mock('simple-git', () => ({
|
|
6
7
|
default: mockSimpleGit,
|
|
7
8
|
}));
|
|
8
9
|
describe('loadGitDiff', () => {
|
|
9
10
|
beforeEach(() => {
|
|
10
11
|
mockDiff.mockReset();
|
|
12
|
+
mockRaw.mockReset();
|
|
11
13
|
mockSimpleGit.mockClear();
|
|
12
14
|
});
|
|
13
15
|
it.each([
|
|
@@ -36,14 +38,14 @@ describe('loadGitDiff', () => {
|
|
|
36
38
|
name: 'commit comparisons',
|
|
37
39
|
targetCommitish: 'HEAD',
|
|
38
40
|
baseCommitish: 'HEAD^',
|
|
39
|
-
expectedListArgs: ['HEAD
|
|
40
|
-
expectedFileArgs: ['HEAD
|
|
41
|
+
expectedListArgs: ['HEAD^', 'HEAD', '--name-status'],
|
|
42
|
+
expectedFileArgs: ['HEAD^', 'HEAD', '-U5', '--', 'src/file.ts'],
|
|
41
43
|
},
|
|
42
44
|
])('passes context lines for $name', async ({ targetCommitish, baseCommitish, expectedListArgs, expectedFileArgs }) => {
|
|
43
45
|
mockDiff
|
|
44
46
|
.mockResolvedValueOnce('M\tsrc/file.ts')
|
|
45
47
|
.mockResolvedValueOnce('@@ -1 +1 @@\n-old line\n+new line\n');
|
|
46
|
-
const result = await loadGitDiff(targetCommitish, baseCommitish, '/repo', 5);
|
|
48
|
+
const result = await loadGitDiff({ targetCommitish, baseCommitish }, '/repo', 5);
|
|
47
49
|
expect(mockSimpleGit).toHaveBeenCalledWith('/repo');
|
|
48
50
|
expect(mockDiff).toHaveBeenNthCalledWith(1, expectedListArgs);
|
|
49
51
|
expect(mockDiff).toHaveBeenNthCalledWith(2, expectedFileArgs);
|
|
@@ -57,4 +59,18 @@ describe('loadGitDiff', () => {
|
|
|
57
59
|
},
|
|
58
60
|
]);
|
|
59
61
|
});
|
|
62
|
+
it('uses merge-base for merge-base selections', async () => {
|
|
63
|
+
mockRaw.mockResolvedValue('mergebase123\n');
|
|
64
|
+
mockDiff
|
|
65
|
+
.mockResolvedValueOnce('M\tsrc/file.ts')
|
|
66
|
+
.mockResolvedValueOnce('@@ -1 +1 @@\n-old line\n+new line\n');
|
|
67
|
+
await loadGitDiff({
|
|
68
|
+
targetCommitish: '.',
|
|
69
|
+
baseCommitish: 'origin/main',
|
|
70
|
+
baseMode: 'merge-base',
|
|
71
|
+
}, '/repo', 5);
|
|
72
|
+
expect(mockRaw).toHaveBeenCalledWith(['merge-base', 'HEAD', 'origin/main']);
|
|
73
|
+
expect(mockDiff).toHaveBeenNthCalledWith(1, ['mergebase123', '--name-status']);
|
|
74
|
+
expect(mockDiff).toHaveBeenNthCalledWith(2, ['mergebase123', '-U5', '--', 'src/file.ts']);
|
|
75
|
+
});
|
|
60
76
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type DiffResponse } from '../types/diff.js';
|
|
1
|
+
import { type DiffResponse, type DiffSelection } from '../types/diff.js';
|
|
2
2
|
export declare class GitDiffParser {
|
|
3
3
|
private git;
|
|
4
4
|
private repoPath;
|
|
@@ -7,7 +7,8 @@ export declare class GitDiffParser {
|
|
|
7
7
|
private static readonly GENERATED_HEADER_SCAN_BYTES;
|
|
8
8
|
constructor(repoPath?: string);
|
|
9
9
|
private normalizeRepositoryRelativePath;
|
|
10
|
-
|
|
10
|
+
private resolveBaseCommitish;
|
|
11
|
+
parseDiff(selection: DiffSelection, ignoreWhitespace?: boolean, contextLines?: number): Promise<DiffResponse>;
|
|
11
12
|
private parseUnifiedDiff;
|
|
12
13
|
private decodeGitPath;
|
|
13
14
|
private extractPathFromLine;
|
package/dist/server/git-diff.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { simpleGit } from 'simple-git';
|
|
2
2
|
import { isAbsolute, resolve, sep } from 'path';
|
|
3
3
|
import { validateDiffArguments, shortHash, createCommitRangeString } from '../cli/utils.js';
|
|
4
|
+
import { getMergeBaseTargetRef, normalizeBaseMode } from '../utils/diffSelection.js';
|
|
4
5
|
import { isGeneratedFile } from './generated-file-check.js';
|
|
5
6
|
export class GitDiffParser {
|
|
6
7
|
git;
|
|
@@ -28,15 +29,28 @@ export class GitDiffParser {
|
|
|
28
29
|
}
|
|
29
30
|
return normalizedFilepath;
|
|
30
31
|
}
|
|
31
|
-
async
|
|
32
|
+
async resolveBaseCommitish(selection) {
|
|
33
|
+
if (normalizeBaseMode(selection.baseMode) !== 'merge-base') {
|
|
34
|
+
return selection.baseCommitish;
|
|
35
|
+
}
|
|
36
|
+
const targetRef = getMergeBaseTargetRef(selection.targetCommitish);
|
|
37
|
+
const mergeBase = await this.git.raw(['merge-base', targetRef, selection.baseCommitish]);
|
|
38
|
+
return mergeBase.trim();
|
|
39
|
+
}
|
|
40
|
+
async parseDiff(selection, ignoreWhitespace = false, contextLines) {
|
|
41
|
+
const { targetCommitish, baseCommitish } = selection;
|
|
42
|
+
const requestedBaseMode = normalizeBaseMode(selection.baseMode) === 'merge-base' ? 'merge-base' : undefined;
|
|
32
43
|
try {
|
|
33
44
|
// Validate arguments
|
|
34
45
|
const validation = validateDiffArguments(targetCommitish, baseCommitish);
|
|
35
46
|
if (!validation.valid) {
|
|
36
47
|
throw new Error(validation.error);
|
|
37
48
|
}
|
|
49
|
+
const effectiveBaseCommitish = await this.resolveBaseCommitish(selection);
|
|
38
50
|
let resolvedCommit;
|
|
39
51
|
let diffArgs;
|
|
52
|
+
let resolvedBaseCommitish = effectiveBaseCommitish;
|
|
53
|
+
let resolvedTargetCommitish = targetCommitish;
|
|
40
54
|
// Handle target special chars (base is always a regular commit)
|
|
41
55
|
if (targetCommitish === 'working') {
|
|
42
56
|
// Show unstaged changes (working vs staged)
|
|
@@ -45,22 +59,26 @@ export class GitDiffParser {
|
|
|
45
59
|
}
|
|
46
60
|
else if (targetCommitish === 'staged') {
|
|
47
61
|
// Show staged changes against base commit
|
|
48
|
-
const baseHash = await this.git.revparse([
|
|
62
|
+
const baseHash = await this.git.revparse([effectiveBaseCommitish]);
|
|
49
63
|
resolvedCommit = `${shortHash(baseHash)} vs Staging Area (staged changes)`;
|
|
50
|
-
|
|
64
|
+
resolvedBaseCommitish = shortHash(baseHash);
|
|
65
|
+
diffArgs = ['--cached', effectiveBaseCommitish];
|
|
51
66
|
}
|
|
52
67
|
else if (targetCommitish === '.') {
|
|
53
68
|
// Show all uncommitted changes against base commit
|
|
54
|
-
const baseHash = await this.git.revparse([
|
|
69
|
+
const baseHash = await this.git.revparse([effectiveBaseCommitish]);
|
|
55
70
|
resolvedCommit = `${shortHash(baseHash)} vs Working Directory (all uncommitted changes)`;
|
|
56
|
-
|
|
71
|
+
resolvedBaseCommitish = shortHash(baseHash);
|
|
72
|
+
diffArgs = [effectiveBaseCommitish];
|
|
57
73
|
}
|
|
58
74
|
else {
|
|
59
75
|
// Both are regular commits: standard commit-to-commit comparison
|
|
60
76
|
const targetHash = await this.git.revparse([targetCommitish]);
|
|
61
|
-
const baseHash = await this.git.revparse([
|
|
77
|
+
const baseHash = await this.git.revparse([effectiveBaseCommitish]);
|
|
62
78
|
resolvedCommit = createCommitRangeString(shortHash(baseHash), shortHash(targetHash));
|
|
63
|
-
|
|
79
|
+
resolvedBaseCommitish = shortHash(baseHash);
|
|
80
|
+
resolvedTargetCommitish = shortHash(targetHash);
|
|
81
|
+
diffArgs = [baseHash, targetHash];
|
|
64
82
|
}
|
|
65
83
|
if (ignoreWhitespace) {
|
|
66
84
|
diffArgs.push('-w');
|
|
@@ -78,6 +96,11 @@ export class GitDiffParser {
|
|
|
78
96
|
commit: resolvedCommit,
|
|
79
97
|
files,
|
|
80
98
|
isEmpty: files.length === 0,
|
|
99
|
+
baseCommitish: resolvedBaseCommitish,
|
|
100
|
+
targetCommitish: resolvedTargetCommitish,
|
|
101
|
+
requestedBaseCommitish: baseCommitish,
|
|
102
|
+
requestedTargetCommitish: targetCommitish,
|
|
103
|
+
requestedBaseMode,
|
|
81
104
|
};
|
|
82
105
|
}
|
|
83
106
|
catch (error) {
|
|
@@ -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(
|
|
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,9 +1082,13 @@ index abc123..def456 100644
|
|
|
1078
1082
|
.mockResolvedValueOnce('1234567890abcdef1234567890abcdef12345678')
|
|
1079
1083
|
.mockResolvedValueOnce('abcdef1234567890abcdef1234567890abcdef12');
|
|
1080
1084
|
gitDiff.mockResolvedValue('');
|
|
1081
|
-
const response = await parser.parseDiff(
|
|
1085
|
+
const response = await parser.parseDiff({
|
|
1086
|
+
targetCommitish: 'HEAD',
|
|
1087
|
+
baseCommitish: 'HEAD~1',
|
|
1088
|
+
}, false, 5);
|
|
1082
1089
|
expect(gitDiff).toHaveBeenCalledWith([
|
|
1083
|
-
'
|
|
1090
|
+
'abcdef1234567890abcdef1234567890abcdef12',
|
|
1091
|
+
'1234567890abcdef1234567890abcdef12345678',
|
|
1084
1092
|
'-U5',
|
|
1085
1093
|
'--no-ext-diff',
|
|
1086
1094
|
'--color=never',
|
|
@@ -1089,6 +1097,11 @@ index abc123..def456 100644
|
|
|
1089
1097
|
commit: 'abcdef1...1234567',
|
|
1090
1098
|
files: [],
|
|
1091
1099
|
isEmpty: true,
|
|
1100
|
+
baseCommitish: 'abcdef1',
|
|
1101
|
+
targetCommitish: '1234567',
|
|
1102
|
+
requestedBaseCommitish: 'HEAD~1',
|
|
1103
|
+
requestedTargetCommitish: 'HEAD',
|
|
1104
|
+
requestedBaseMode: undefined,
|
|
1092
1105
|
});
|
|
1093
1106
|
});
|
|
1094
1107
|
it('accepts branch refs with revision suffixes', async () => {
|
|
@@ -1098,14 +1111,56 @@ index abc123..def456 100644
|
|
|
1098
1111
|
.mockResolvedValueOnce('1234567890abcdef1234567890abcdef12345678')
|
|
1099
1112
|
.mockResolvedValueOnce('abcdef1234567890abcdef1234567890abcdef12');
|
|
1100
1113
|
gitDiff.mockResolvedValue('');
|
|
1101
|
-
const response = await parser.parseDiff(
|
|
1114
|
+
const response = await parser.parseDiff({
|
|
1115
|
+
targetCommitish: 'codex/comment-thread',
|
|
1116
|
+
baseCommitish: 'codex/comment-thread^',
|
|
1117
|
+
});
|
|
1102
1118
|
expect(gitRevparse).toHaveBeenNthCalledWith(1, ['codex/comment-thread']);
|
|
1103
1119
|
expect(gitRevparse).toHaveBeenNthCalledWith(2, ['codex/comment-thread^']);
|
|
1104
|
-
expect(gitDiff).toHaveBeenCalledWith([
|
|
1120
|
+
expect(gitDiff).toHaveBeenCalledWith([
|
|
1121
|
+
'abcdef1234567890abcdef1234567890abcdef12',
|
|
1122
|
+
'1234567890abcdef1234567890abcdef12345678',
|
|
1123
|
+
'--no-ext-diff',
|
|
1124
|
+
'--color=never',
|
|
1125
|
+
]);
|
|
1105
1126
|
expect(response).toEqual({
|
|
1106
1127
|
commit: 'abcdef1...1234567',
|
|
1107
1128
|
files: [],
|
|
1108
1129
|
isEmpty: true,
|
|
1130
|
+
baseCommitish: 'abcdef1',
|
|
1131
|
+
targetCommitish: '1234567',
|
|
1132
|
+
requestedBaseCommitish: 'codex/comment-thread^',
|
|
1133
|
+
requestedTargetCommitish: 'codex/comment-thread',
|
|
1134
|
+
requestedBaseMode: undefined,
|
|
1135
|
+
});
|
|
1136
|
+
});
|
|
1137
|
+
it('uses merge-base when baseMode is merge-base', async () => {
|
|
1138
|
+
const gitDiff = parser.git.diff;
|
|
1139
|
+
const gitRevparse = parser.git.revparse;
|
|
1140
|
+
const gitRaw = parser.git.raw;
|
|
1141
|
+
gitRaw.mockResolvedValue('fedcba9876543210fedcba9876543210fedcba98\n');
|
|
1142
|
+
gitRevparse.mockResolvedValueOnce('fedcba9876543210fedcba9876543210fedcba98');
|
|
1143
|
+
gitDiff.mockResolvedValue('');
|
|
1144
|
+
const response = await parser.parseDiff({
|
|
1145
|
+
targetCommitish: '.',
|
|
1146
|
+
baseCommitish: 'origin/main',
|
|
1147
|
+
baseMode: 'merge-base',
|
|
1148
|
+
});
|
|
1149
|
+
expect(gitRaw).toHaveBeenCalledWith(['merge-base', 'HEAD', 'origin/main']);
|
|
1150
|
+
expect(gitDiff).toHaveBeenCalledWith([
|
|
1151
|
+
'fedcba9876543210fedcba9876543210fedcba98',
|
|
1152
|
+
'--no-ext-diff',
|
|
1153
|
+
'--color=never',
|
|
1154
|
+
]);
|
|
1155
|
+
expect(response).toEqual({
|
|
1156
|
+
commit: 'fedcba9 vs Working Directory (all uncommitted changes)',
|
|
1157
|
+
files: [],
|
|
1158
|
+
isEmpty: true,
|
|
1159
|
+
baseCommitish: 'fedcba9',
|
|
1160
|
+
targetCommitish: '.',
|
|
1161
|
+
requestedBaseCommitish: 'origin/main',
|
|
1162
|
+
requestedTargetCommitish: '.',
|
|
1163
|
+
requestedBaseMode: 'merge-base',
|
|
1109
1164
|
});
|
|
1110
1165
|
});
|
|
1111
1166
|
});
|
package/dist/server/server.d.ts
CHANGED
|
@@ -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
|
-
|
|
6
|
-
baseCommitish?: string;
|
|
5
|
+
selection?: DiffSelection;
|
|
7
6
|
stdinDiff?: string;
|
|
8
7
|
preferredPort?: number;
|
|
9
8
|
host?: string;
|
package/dist/server/server.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
31
|
-
|
|
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(
|
|
76
|
+
const isValidCommit = await parser.validateCommit(initialSelection.targetCommitish);
|
|
44
77
|
if (!isValidCommit) {
|
|
45
|
-
throw new Error(`Invalid or non-existent commit: ${
|
|
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
|
-
|
|
85
|
+
initialDiffData = parser.parseStdinDiff(options.stdinDiff);
|
|
52
86
|
}
|
|
53
87
|
else {
|
|
54
|
-
|
|
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
|
|
93
|
+
diffDataCache.clear();
|
|
59
94
|
generatedStatusCache.clear();
|
|
60
95
|
parser.clearResolvedCommitCache();
|
|
61
96
|
};
|
|
62
97
|
// Track current revisions for cache invalidation
|
|
63
|
-
let
|
|
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
|
|
83
|
-
const
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
|
122
|
-
const
|
|
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
|
-
...
|
|
148
|
+
...responseDiffData,
|
|
125
149
|
ignoreWhitespace,
|
|
126
150
|
mode: diffMode,
|
|
127
151
|
openInEditorAvailable: !options.stdinDiff,
|
|
128
|
-
baseCommitish
|
|
129
|
-
targetCommitish
|
|
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 ||
|
|
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(
|
|
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 (
|
|
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:
|
|
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) => {
|