difit 4.0.2 → 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.
- 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/client/assets/architecture-PBZL5I3N-DUNTzy9d.js +1 -0
- package/dist/client/assets/{architectureDiagram-2XIMDMQ5-CXJTJFYJ.js → architectureDiagram-2XIMDMQ5-BOmef_aT.js} +1 -1
- package/dist/client/assets/{blockDiagram-WCTKOSBZ-B60owdAn.js → blockDiagram-WCTKOSBZ-CuovjbLp.js} +1 -1
- package/dist/client/assets/{c4Diagram-IC4MRINW-4tg2D_Vt.js → c4Diagram-IC4MRINW-l2hgU0UG.js} +1 -1
- package/dist/client/assets/channel-BBMOf_Bn.js +1 -0
- package/dist/client/assets/{chunk-4BX2VUAB-CW45MZFx.js → chunk-4BX2VUAB-Bh2XMPGo.js} +1 -1
- package/dist/client/assets/{chunk-55IACEB6-Busc3sfI.js → chunk-55IACEB6-r9BRoqNs.js} +1 -1
- package/dist/client/assets/{chunk-7E7YKBS2-BVR-8Pma.js → chunk-7E7YKBS2-BUy3or4g.js} +1 -1
- package/dist/client/assets/{chunk-7R4GIKGN-DneC7PwP.js → chunk-7R4GIKGN-C7ClNgvP.js} +1 -1
- package/dist/client/assets/{chunk-C72U2L5F-CJr98gus.js → chunk-C72U2L5F-_P9RrDdo.js} +1 -1
- package/dist/client/assets/{chunk-EGIJ26TM-iD_CSqpR.js → chunk-EGIJ26TM-D-xQ2sZ-.js} +1 -1
- package/dist/client/assets/{chunk-FMBD7UC4-BSsJVlRg.js → chunk-FMBD7UC4-OuP8NjEM.js} +1 -1
- package/dist/client/assets/{chunk-GEFDOKGD-eDUrsRgt.js → chunk-GEFDOKGD-noJ9o8LM.js} +1 -1
- package/dist/client/assets/chunk-GLR3WWYH-DVK9OjRZ.js +2 -0
- package/dist/client/assets/chunk-HHEYEP7N-C1QyKuQs.js +1 -0
- package/dist/client/assets/{chunk-JSJVCQXG-UCJub_Eo.js → chunk-JSJVCQXG-2AjhqYcu.js} +1 -1
- package/dist/client/assets/{chunk-KX2RTZJC-DrhxxMOx.js → chunk-KX2RTZJC-tMPTaDcx.js} +1 -1
- package/dist/client/assets/{chunk-KYZI473N-Brv52ZeO.js → chunk-KYZI473N-CGwu81pT.js} +1 -1
- package/dist/client/assets/{chunk-L3YUKLVL-BkBigLhQ.js → chunk-L3YUKLVL-DFNfIVVw.js} +1 -1
- package/dist/client/assets/{chunk-MX3YWQON-DHRoNbgW.js → chunk-MX3YWQON-BnSlIBhe.js} +1 -1
- package/dist/client/assets/{chunk-NQ4KR5QH-BZ86r2qK.js → chunk-NQ4KR5QH-CpfTlpaZ.js} +1 -1
- package/dist/client/assets/{chunk-O4XLMI2P-Sr33dk8c.js → chunk-O4XLMI2P-BDGKGscp.js} +1 -1
- package/dist/client/assets/{chunk-OZEHJAEY-3F2ff7sj.js → chunk-OZEHJAEY-3OAEqm17.js} +1 -1
- package/dist/client/assets/{chunk-PQ6SQG4A-C9acTu_E.js → chunk-PQ6SQG4A-DfQjNfPX.js} +1 -1
- package/dist/client/assets/{chunk-PU5JKC2W-PQmA4K_y.js → chunk-PU5JKC2W-DRiL1iN6.js} +1 -1
- package/dist/client/assets/chunk-QZHKN3VN-DBD5yPlw.js +1 -0
- package/dist/client/assets/{chunk-R5LLSJPH-ChexuO_S.js → chunk-R5LLSJPH-dkcbq1pR.js} +1 -1
- package/dist/client/assets/{chunk-WL4C6EOR-oxNV_hhM.js → chunk-WL4C6EOR-BeCB6d6F.js} +1 -1
- package/dist/client/assets/{chunk-XIRO2GV7-C9gOnffv.js → chunk-XIRO2GV7-BxmEO1Vi.js} +1 -1
- package/dist/client/assets/{chunk-XPW4576I-CcqR6BsE.js → chunk-XPW4576I-jm7TiixU.js} +1 -1
- package/dist/client/assets/{chunk-XZSTWKYB-C5JJ0TZR.js → chunk-XZSTWKYB-BGKYCy46.js} +1 -1
- package/dist/client/assets/{chunk-YBOYWFTD-B6kAkNgH.js → chunk-YBOYWFTD-C9faLjdm.js} +1 -1
- package/dist/client/assets/classDiagram-VBA2DB6C-Depk8rxx.js +1 -0
- package/dist/client/assets/classDiagram-v2-RAHNMMFH-DHvQPm8y.js +1 -0
- package/dist/client/assets/{cose-bilkent-S5V4N54A-hlDud6Ym.js → cose-bilkent-S5V4N54A-BWD5TWFn.js} +1 -1
- package/dist/client/assets/{dagre-BwDYerGQ.js → dagre-Dd1VxucU.js} +1 -1
- package/dist/client/assets/{dagre-KLK3FWXG-KnkMUlUE.js → dagre-KLK3FWXG-BJFTyMud.js} +1 -1
- package/dist/client/assets/{diagram-E7M64L7V-DcTCIFUG.js → diagram-E7M64L7V-eWdHIl72.js} +1 -1
- package/dist/client/assets/{diagram-IFDJBPK2-COcDQunj.js → diagram-IFDJBPK2-C1-sqK0o.js} +1 -1
- package/dist/client/assets/{diagram-P4PSJMXO-DmgET9pD.js → diagram-P4PSJMXO-DHeUNvSg.js} +1 -1
- package/dist/client/assets/{dist-v55TM3-O.js → dist-FLbYR5UU.js} +1 -1
- package/dist/client/assets/{erDiagram-INFDFZHY-ByL02DP-.js → erDiagram-INFDFZHY-CX8FAWmU.js} +1 -1
- package/dist/client/assets/{flowDiagram-PKNHOUZH-CW-lseYE.js → flowDiagram-PKNHOUZH-DgBnUaHH.js} +1 -1
- package/dist/client/assets/{ganttDiagram-A5KZAMGK-BxLjKRld.js → ganttDiagram-A5KZAMGK-C331HQ-y.js} +1 -1
- package/dist/client/assets/gitGraph-HDMCJU4V-DPGoIMlm.js +1 -0
- package/dist/client/assets/{gitGraphDiagram-K3NZZRJ6-DLEDjokx.js → gitGraphDiagram-K3NZZRJ6-zEXLThxN.js} +1 -1
- package/dist/client/assets/index-BGPkswtu.js +79 -0
- package/dist/client/assets/{index-Cn4K2uvR.css → index-Cq_APK7Y.css} +1 -1
- package/dist/client/assets/info-3K5VOQVL-CYdIfRwG.js +1 -0
- package/dist/client/assets/{infoDiagram-LFFYTUFH-CnmYkyCb.js → infoDiagram-LFFYTUFH-CKx11_2a.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-PHBUUO56-zycn1mVK.js → ishikawaDiagram-PHBUUO56-BlZMQgOe.js} +1 -1
- package/dist/client/assets/{journeyDiagram-4ABVD52K-aRoH36nV.js → journeyDiagram-4ABVD52K-C3p_p4rn.js} +1 -1
- package/dist/client/assets/{kanban-definition-K7BYSVSG-BGtGv5yb.js → kanban-definition-K7BYSVSG-4XJPQF50.js} +1 -1
- package/dist/client/assets/{line-Cm3ZuldI.js → line-Bb6xn3n_.js} +1 -1
- package/dist/client/assets/{linear-HJOLPv7E.js → linear-BPttYRJr.js} +1 -1
- package/dist/client/assets/{mermaid-parser.core-BvMqHn4b.js → mermaid-parser.core-CjY9NqXx.js} +2 -2
- package/dist/client/assets/{mermaid.core-C4SvQTx9.js → mermaid.core-B0ynITdC.js} +3 -3
- package/dist/client/assets/{mindmap-definition-YRQLILUH-B8jMe7ir.js → mindmap-definition-YRQLILUH-Dya2e4tr.js} +1 -1
- package/dist/client/assets/packet-RMMSAZCW-D7vTTuAT.js +1 -0
- package/dist/client/assets/pie-UPGHQEXC-ptFuye_f.js +1 -0
- package/dist/client/assets/{pieDiagram-SKSYHLDU-CGWbtgxq.js → pieDiagram-SKSYHLDU-MZ74L9cN.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-337W2JSQ-CQ1QKsru.js → quadrantDiagram-337W2JSQ-Da_T39nG.js} +1 -1
- package/dist/client/assets/radar-KQ55EAFF-BR1_ZPLF.js +1 -0
- package/dist/client/assets/{requirementDiagram-Z7DCOOCP-Co1LyL5T.js → requirementDiagram-Z7DCOOCP-DzlKWGt3.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-WA2Y5GQK-BQVbT6bS.js → sankeyDiagram-WA2Y5GQK-BPketwK-.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-2WXFIKYE-DGIEkdPm.js → sequenceDiagram-2WXFIKYE-geDrMLZ_.js} +1 -1
- package/dist/client/assets/{src-DsmFf7gO.js → src-BuTVwZtT.js} +1 -1
- package/dist/client/assets/{stateDiagram-RAJIS63D-DgjKbXnG.js → stateDiagram-RAJIS63D-DOLTjnid.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-FVOUBMTO-BIjVI5d6.js +1 -0
- package/dist/client/assets/{timeline-definition-YZTLITO2-Dz2dVWjY.js → timeline-definition-YZTLITO2-Cy6Qm4Pd.js} +1 -1
- package/dist/client/assets/treemap-KZPCXAKY-Bw93Vsua.js +1 -0
- package/dist/client/assets/{vennDiagram-LZ73GAT5-IIH5S1B6.js → vennDiagram-LZ73GAT5-UUQN9akd.js} +1 -1
- package/dist/client/assets/{xychartDiagram-JWTSCODW-DeYZhM2j.js → xychartDiagram-JWTSCODW-DiTicxdS.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/server/git-diff-tui.d.ts +2 -2
- package/dist/server/git-diff-tui.js +12 -7
- package/dist/server/git-diff-tui.test.js +18 -2
- package/dist/server/git-diff.d.ts +3 -2
- package/dist/server/git-diff.js +29 -6
- package/dist/server/git-diff.test.js +52 -3
- 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 +1 -1
- package/dist/client/assets/architecture-PBZL5I3N-DFdrPtRG.js +0 -1
- package/dist/client/assets/channel-DogeU0Wo.js +0 -1
- 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-QZHKN3VN-DMRW-mur.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/gitGraph-HDMCJU4V-CjAGJiCH.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/packet-RMMSAZCW-CzbC-tXD.js +0 -1
- package/dist/client/assets/pie-UPGHQEXC-CmhYIo8p.js +0 -1
- package/dist/client/assets/radar-KQ55EAFF-BCa9lsCc.js +0 -1
- package/dist/client/assets/stateDiagram-v2-FVOUBMTO-gPrpjL74.js +0 -1
- package/dist/client/assets/treemap-KZPCXAKY-DXiPfAB6.js +0 -1
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) => {
|
|
@@ -33,6 +33,11 @@ vi.mock('./git-diff.js', () => {
|
|
|
33
33
|
parseDiff = vi.fn().mockResolvedValue({
|
|
34
34
|
targetCommit: 'abc123',
|
|
35
35
|
baseCommit: 'def456',
|
|
36
|
+
baseCommitish: 'def4567',
|
|
37
|
+
targetCommitish: 'abc1234',
|
|
38
|
+
requestedBaseCommitish: 'HEAD^',
|
|
39
|
+
requestedTargetCommitish: 'HEAD',
|
|
40
|
+
requestedBaseMode: undefined,
|
|
36
41
|
targetMessage: 'Test commit',
|
|
37
42
|
baseMessage: 'Previous commit',
|
|
38
43
|
files: [
|
|
@@ -192,8 +197,7 @@ describe('Server Integration Tests', () => {
|
|
|
192
197
|
// Use a high port number to avoid conflicts
|
|
193
198
|
const preferredPort = 9000;
|
|
194
199
|
const result = await startServer({
|
|
195
|
-
targetCommitish: 'HEAD',
|
|
196
|
-
baseCommitish: 'HEAD^',
|
|
200
|
+
selection: { targetCommitish: 'HEAD', baseCommitish: 'HEAD^' },
|
|
197
201
|
preferredPort,
|
|
198
202
|
});
|
|
199
203
|
servers.push(result.server); // Track for cleanup
|
|
@@ -206,15 +210,13 @@ describe('Server Integration Tests', () => {
|
|
|
206
210
|
const preferredPort = 9010;
|
|
207
211
|
// Start server on port 9010
|
|
208
212
|
const firstServer = await startServer({
|
|
209
|
-
targetCommitish: 'HEAD',
|
|
210
|
-
baseCommitish: 'HEAD^',
|
|
213
|
+
selection: { targetCommitish: 'HEAD', baseCommitish: 'HEAD^' },
|
|
211
214
|
preferredPort,
|
|
212
215
|
});
|
|
213
216
|
servers.push(firstServer.server);
|
|
214
217
|
// Try to start another server on the same port
|
|
215
218
|
const secondServer = await startServer({
|
|
216
|
-
targetCommitish: 'HEAD',
|
|
217
|
-
baseCommitish: 'HEAD^',
|
|
219
|
+
selection: { targetCommitish: 'HEAD', baseCommitish: 'HEAD^' },
|
|
218
220
|
preferredPort,
|
|
219
221
|
});
|
|
220
222
|
servers.push(secondServer.server);
|
|
@@ -224,8 +226,7 @@ describe('Server Integration Tests', () => {
|
|
|
224
226
|
});
|
|
225
227
|
it('binds to specified host', async () => {
|
|
226
228
|
const result = await startServer({
|
|
227
|
-
targetCommitish: 'HEAD',
|
|
228
|
-
baseCommitish: 'HEAD^',
|
|
229
|
+
selection: { targetCommitish: 'HEAD', baseCommitish: 'HEAD^' },
|
|
229
230
|
host: '0.0.0.0',
|
|
230
231
|
preferredPort: 9020,
|
|
231
232
|
});
|
|
@@ -234,22 +235,20 @@ describe('Server Integration Tests', () => {
|
|
|
234
235
|
});
|
|
235
236
|
it('passes context lines to the initial diff load', async () => {
|
|
236
237
|
const result = await startServer({
|
|
237
|
-
targetCommitish: 'HEAD',
|
|
238
|
-
baseCommitish: 'HEAD^',
|
|
238
|
+
selection: { targetCommitish: 'HEAD', baseCommitish: 'HEAD^' },
|
|
239
239
|
preferredPort: 9025,
|
|
240
240
|
contextLines: 4,
|
|
241
241
|
});
|
|
242
242
|
servers.push(result.server);
|
|
243
243
|
const parser = parserInstances.at(-1);
|
|
244
|
-
expect(parser?.parseDiff).toHaveBeenCalledWith('HEAD', 'HEAD^', false, 4);
|
|
244
|
+
expect(parser?.parseDiff).toHaveBeenCalledWith({ targetCommitish: 'HEAD', baseCommitish: 'HEAD^' }, false, 4);
|
|
245
245
|
});
|
|
246
246
|
});
|
|
247
247
|
describe('API endpoints', () => {
|
|
248
248
|
let port;
|
|
249
249
|
beforeEach(async () => {
|
|
250
250
|
const result = await startServer({
|
|
251
|
-
targetCommitish: 'HEAD',
|
|
252
|
-
baseCommitish: 'HEAD^',
|
|
251
|
+
selection: { targetCommitish: 'HEAD', baseCommitish: 'HEAD^' },
|
|
253
252
|
preferredPort: 9030,
|
|
254
253
|
});
|
|
255
254
|
servers.push(result.server);
|
|
@@ -266,6 +265,8 @@ describe('Server Integration Tests', () => {
|
|
|
266
265
|
expect(data.files[0]).toHaveProperty('path', 'test.js');
|
|
267
266
|
expect(data).toHaveProperty('ignoreWhitespace', false);
|
|
268
267
|
expect(data).toHaveProperty('openInEditorAvailable', true);
|
|
268
|
+
expect(data).toHaveProperty('requestedBaseCommitish', 'HEAD^');
|
|
269
|
+
expect(data).toHaveProperty('requestedTargetCommitish', 'HEAD');
|
|
269
270
|
});
|
|
270
271
|
it('GET /api/diff?ignoreWhitespace=true handles whitespace ignore', async () => {
|
|
271
272
|
const response = await fetch(`http://localhost:${port}/api/diff?ignoreWhitespace=true`);
|
|
@@ -275,8 +276,7 @@ describe('Server Integration Tests', () => {
|
|
|
275
276
|
});
|
|
276
277
|
it('GET /api/diff preserves context lines when recalculating revisions', async () => {
|
|
277
278
|
const result = await startServer({
|
|
278
|
-
targetCommitish: 'HEAD',
|
|
279
|
-
baseCommitish: 'HEAD^',
|
|
279
|
+
selection: { targetCommitish: 'HEAD', baseCommitish: 'HEAD^' },
|
|
280
280
|
preferredPort: 9031,
|
|
281
281
|
contextLines: 2,
|
|
282
282
|
});
|
|
@@ -285,7 +285,78 @@ describe('Server Integration Tests', () => {
|
|
|
285
285
|
parser?.parseDiff.mockClear();
|
|
286
286
|
const response = await fetch(`http://localhost:${result.port}/api/diff?base=main&target=feature&ignoreWhitespace=true`);
|
|
287
287
|
expect(response.ok).toBe(true);
|
|
288
|
-
expect(parser?.parseDiff).toHaveBeenCalledWith('feature', 'main', true, 2);
|
|
288
|
+
expect(parser?.parseDiff).toHaveBeenCalledWith({ targetCommitish: 'feature', baseCommitish: 'main' }, true, 2);
|
|
289
|
+
});
|
|
290
|
+
it('GET /api/diff passes baseMode through to the parser', async () => {
|
|
291
|
+
const parser = parserInstances.at(-1);
|
|
292
|
+
parser?.parseDiff.mockClear();
|
|
293
|
+
parser?.parseDiff.mockResolvedValueOnce({
|
|
294
|
+
targetCommit: 'abc123',
|
|
295
|
+
baseCommit: 'def456',
|
|
296
|
+
baseCommitish: 'fedcba9',
|
|
297
|
+
targetCommitish: '.',
|
|
298
|
+
requestedBaseCommitish: 'origin/main',
|
|
299
|
+
requestedTargetCommitish: '.',
|
|
300
|
+
requestedBaseMode: 'merge-base',
|
|
301
|
+
files: [],
|
|
302
|
+
isEmpty: true,
|
|
303
|
+
});
|
|
304
|
+
const response = await fetch(`http://localhost:${port}/api/diff?base=origin%2Fmain&target=.&baseMode=merge-base`);
|
|
305
|
+
const data = (await response.json());
|
|
306
|
+
expect(response.ok).toBe(true);
|
|
307
|
+
expect(parser?.parseDiff).toHaveBeenCalledWith({
|
|
308
|
+
targetCommitish: '.',
|
|
309
|
+
baseCommitish: 'origin/main',
|
|
310
|
+
baseMode: 'merge-base',
|
|
311
|
+
}, false, undefined);
|
|
312
|
+
expect(data.requestedBaseMode).toBe('merge-base');
|
|
313
|
+
expect(data.baseCommitish).toBe('fedcba9');
|
|
314
|
+
expect(data.requestedBaseCommitish).toBe('origin/main');
|
|
315
|
+
});
|
|
316
|
+
it('GET /api/diff caches results per revision pair instead of reusing the last request', async () => {
|
|
317
|
+
const result = await startServer({
|
|
318
|
+
selection: { targetCommitish: 'HEAD', baseCommitish: 'HEAD^' },
|
|
319
|
+
preferredPort: 9032,
|
|
320
|
+
});
|
|
321
|
+
servers.push(result.server);
|
|
322
|
+
const parser = parserInstances.at(-1);
|
|
323
|
+
parser?.parseDiff.mockClear();
|
|
324
|
+
const firstResponse = await fetch(`http://localhost:${result.port}/api/diff?base=main&target=feature`);
|
|
325
|
+
expect(firstResponse.ok).toBe(true);
|
|
326
|
+
const secondResponse = await fetch(`http://localhost:${result.port}/api/diff?base=HEAD%5E&target=HEAD`);
|
|
327
|
+
expect(secondResponse.ok).toBe(true);
|
|
328
|
+
const thirdResponse = await fetch(`http://localhost:${result.port}/api/diff?base=main&target=feature`);
|
|
329
|
+
expect(thirdResponse.ok).toBe(true);
|
|
330
|
+
expect(parser?.parseDiff).toHaveBeenCalledTimes(1);
|
|
331
|
+
expect(parser?.parseDiff).toHaveBeenNthCalledWith(1, { targetCommitish: 'feature', baseCommitish: 'main' }, false, undefined);
|
|
332
|
+
});
|
|
333
|
+
it('GET /api/diff evicts least recently used cached diff responses', async () => {
|
|
334
|
+
const result = await startServer({
|
|
335
|
+
selection: { targetCommitish: 'HEAD', baseCommitish: 'HEAD^' },
|
|
336
|
+
preferredPort: 9033,
|
|
337
|
+
});
|
|
338
|
+
servers.push(result.server);
|
|
339
|
+
const parser = parserInstances.at(-1);
|
|
340
|
+
parser?.parseDiff.mockClear();
|
|
341
|
+
const revisionPairs = [
|
|
342
|
+
['base-a', 'target-a'],
|
|
343
|
+
['base-b', 'target-b'],
|
|
344
|
+
['base-c', 'target-c'],
|
|
345
|
+
['base-d', 'target-d'],
|
|
346
|
+
['base-e', 'target-e'],
|
|
347
|
+
['base-f', 'target-f'],
|
|
348
|
+
['base-g', 'target-g'],
|
|
349
|
+
['base-h', 'target-h'],
|
|
350
|
+
['base-i', 'target-i'],
|
|
351
|
+
];
|
|
352
|
+
for (const [base, target] of revisionPairs) {
|
|
353
|
+
const response = await fetch(`http://localhost:${result.port}/api/diff?base=${base}&target=${target}`);
|
|
354
|
+
expect(response.ok).toBe(true);
|
|
355
|
+
}
|
|
356
|
+
const revisitedResponse = await fetch(`http://localhost:${result.port}/api/diff?base=base-a&target=target-a`);
|
|
357
|
+
expect(revisitedResponse.ok).toBe(true);
|
|
358
|
+
expect(parser?.parseDiff).toHaveBeenCalledTimes(10);
|
|
359
|
+
expect(parser?.parseDiff).toHaveBeenLastCalledWith({ targetCommitish: 'target-a', baseCommitish: 'base-a' }, false, undefined);
|
|
289
360
|
});
|
|
290
361
|
it('GET /api/diff returns comment import payload when configured', async () => {
|
|
291
362
|
const importedComments = [
|
|
@@ -297,8 +368,7 @@ describe('Server Integration Tests', () => {
|
|
|
297
368
|
},
|
|
298
369
|
];
|
|
299
370
|
const importServer = await startServer({
|
|
300
|
-
targetCommitish: 'HEAD',
|
|
301
|
-
baseCommitish: 'HEAD^',
|
|
371
|
+
selection: { targetCommitish: 'HEAD', baseCommitish: 'HEAD^' },
|
|
302
372
|
preferredPort: 9034,
|
|
303
373
|
commentImports: importedComments,
|
|
304
374
|
});
|
|
@@ -319,8 +389,7 @@ describe('Server Integration Tests', () => {
|
|
|
319
389
|
},
|
|
320
390
|
];
|
|
321
391
|
const importServer = await startServer({
|
|
322
|
-
targetCommitish: 'HEAD',
|
|
323
|
-
baseCommitish: 'HEAD^',
|
|
392
|
+
selection: { targetCommitish: 'HEAD', baseCommitish: 'HEAD^' },
|
|
324
393
|
preferredPort: 9037,
|
|
325
394
|
clearComments: true,
|
|
326
395
|
commentImports: importedComments,
|
|
@@ -343,8 +412,7 @@ describe('Server Integration Tests', () => {
|
|
|
343
412
|
},
|
|
344
413
|
];
|
|
345
414
|
const importServer = await startServer({
|
|
346
|
-
targetCommitish: 'HEAD',
|
|
347
|
-
baseCommitish: 'HEAD^',
|
|
415
|
+
selection: { targetCommitish: 'HEAD', baseCommitish: 'HEAD^' },
|
|
348
416
|
preferredPort: 9038,
|
|
349
417
|
commentImports: importedComments,
|
|
350
418
|
});
|
|
@@ -502,8 +570,7 @@ describe('Server Integration Tests', () => {
|
|
|
502
570
|
it('serves dev mode HTML in development', async () => {
|
|
503
571
|
process.env.NODE_ENV = 'development';
|
|
504
572
|
const result = await startServer({
|
|
505
|
-
targetCommitish: 'HEAD',
|
|
506
|
-
baseCommitish: 'HEAD^',
|
|
573
|
+
selection: { targetCommitish: 'HEAD', baseCommitish: 'HEAD^' },
|
|
507
574
|
preferredPort: 9040,
|
|
508
575
|
});
|
|
509
576
|
servers.push(result.server);
|
|
@@ -516,8 +583,7 @@ describe('Server Integration Tests', () => {
|
|
|
516
583
|
it('serves static files in production mode', async () => {
|
|
517
584
|
process.env.NODE_ENV = 'production';
|
|
518
585
|
const result = await startServer({
|
|
519
|
-
targetCommitish: 'HEAD',
|
|
520
|
-
baseCommitish: 'HEAD^',
|
|
586
|
+
selection: { targetCommitish: 'HEAD', baseCommitish: 'HEAD^' },
|
|
521
587
|
preferredPort: 9050,
|
|
522
588
|
});
|
|
523
589
|
servers.push(result.server);
|
|
@@ -531,8 +597,7 @@ describe('Server Integration Tests', () => {
|
|
|
531
597
|
it('returns 404 for unknown paths in production mode', async () => {
|
|
532
598
|
process.env.NODE_ENV = 'production';
|
|
533
599
|
const result = await startServer({
|
|
534
|
-
targetCommitish: 'HEAD',
|
|
535
|
-
baseCommitish: 'HEAD^',
|
|
600
|
+
selection: { targetCommitish: 'HEAD', baseCommitish: 'HEAD^' },
|
|
536
601
|
preferredPort: 9055,
|
|
537
602
|
});
|
|
538
603
|
servers.push(result.server);
|
|
@@ -544,8 +609,7 @@ describe('Server Integration Tests', () => {
|
|
|
544
609
|
it('accepts mode option in server configuration', async () => {
|
|
545
610
|
// Test that mode option is accepted without error
|
|
546
611
|
const result = await startServer({
|
|
547
|
-
targetCommitish: 'HEAD',
|
|
548
|
-
baseCommitish: 'HEAD^',
|
|
612
|
+
selection: { targetCommitish: 'HEAD', baseCommitish: 'HEAD^' },
|
|
549
613
|
mode: 'unified',
|
|
550
614
|
});
|
|
551
615
|
servers.push(result.server);
|
|
@@ -554,14 +618,12 @@ describe('Server Integration Tests', () => {
|
|
|
554
618
|
});
|
|
555
619
|
it('accepts different mode values', async () => {
|
|
556
620
|
const inlineResult = await startServer({
|
|
557
|
-
targetCommitish: 'HEAD',
|
|
558
|
-
baseCommitish: 'HEAD^',
|
|
621
|
+
selection: { targetCommitish: 'HEAD', baseCommitish: 'HEAD^' },
|
|
559
622
|
mode: 'unified',
|
|
560
623
|
});
|
|
561
624
|
servers.push(inlineResult.server);
|
|
562
625
|
const sideBySideResult = await startServer({
|
|
563
|
-
targetCommitish: 'HEAD',
|
|
564
|
-
baseCommitish: 'HEAD^',
|
|
626
|
+
selection: { targetCommitish: 'HEAD', baseCommitish: 'HEAD^' },
|
|
565
627
|
mode: 'split',
|
|
566
628
|
});
|
|
567
629
|
servers.push(sideBySideResult.server);
|
|
@@ -570,8 +632,7 @@ describe('Server Integration Tests', () => {
|
|
|
570
632
|
});
|
|
571
633
|
it('mode option should be included in diff response', async () => {
|
|
572
634
|
const result = await startServer({
|
|
573
|
-
targetCommitish: 'HEAD',
|
|
574
|
-
baseCommitish: 'HEAD^',
|
|
635
|
+
selection: { targetCommitish: 'HEAD', baseCommitish: 'HEAD^' },
|
|
575
636
|
mode: 'inline',
|
|
576
637
|
});
|
|
577
638
|
servers.push(result.server);
|
|
@@ -584,14 +645,14 @@ describe('Server Integration Tests', () => {
|
|
|
584
645
|
describe('Revision options API', () => {
|
|
585
646
|
it('returns available revisions', async () => {
|
|
586
647
|
const result = await startServer({
|
|
587
|
-
targetCommitish: 'HEAD',
|
|
588
|
-
baseCommitish: 'HEAD^',
|
|
648
|
+
selection: { targetCommitish: 'HEAD', baseCommitish: 'HEAD^' },
|
|
589
649
|
});
|
|
590
650
|
servers.push(result.server);
|
|
591
651
|
const response = await fetch(`http://localhost:${result.port}/api/revisions`);
|
|
592
652
|
const data = (await response.json());
|
|
593
653
|
expect(response.ok).toBe(true);
|
|
594
654
|
expect(data.specialOptions).toHaveLength(3);
|
|
655
|
+
expect(data.specialOptions).not.toContainEqual({ value: 'merge-base', label: 'Merge Base' });
|
|
595
656
|
expect(data.branches).toEqual([{ name: 'main', current: true }]);
|
|
596
657
|
expect(data.commits).toEqual([
|
|
597
658
|
{ hash: 'abc1234', shortHash: 'abc1234', message: 'Test commit' },
|
|
@@ -609,8 +670,7 @@ describe('Server Integration Tests', () => {
|
|
|
609
670
|
});
|
|
610
671
|
it('handles malformed comment data', async () => {
|
|
611
672
|
const result = await startServer({
|
|
612
|
-
targetCommitish: 'HEAD',
|
|
613
|
-
baseCommitish: 'HEAD^',
|
|
673
|
+
selection: { targetCommitish: 'HEAD', baseCommitish: 'HEAD^' },
|
|
614
674
|
});
|
|
615
675
|
servers.push(result.server);
|
|
616
676
|
const response = await fetch(`http://localhost:${result.port}/api/comments`, {
|
|
@@ -632,8 +692,7 @@ describe('Server Integration Tests', () => {
|
|
|
632
692
|
describe('CORS configuration', () => {
|
|
633
693
|
it('sets correct CORS headers', async () => {
|
|
634
694
|
const result = await startServer({
|
|
635
|
-
targetCommitish: 'HEAD',
|
|
636
|
-
baseCommitish: 'HEAD^',
|
|
695
|
+
selection: { targetCommitish: 'HEAD', baseCommitish: 'HEAD^' },
|
|
637
696
|
});
|
|
638
697
|
servers.push(result.server);
|
|
639
698
|
const response = await fetch(`http://localhost:${result.port}/api/diff`);
|
|
@@ -646,8 +705,7 @@ describe('Server Integration Tests', () => {
|
|
|
646
705
|
let port;
|
|
647
706
|
beforeEach(async () => {
|
|
648
707
|
const result = await startServer({
|
|
649
|
-
targetCommitish: 'HEAD',
|
|
650
|
-
baseCommitish: 'HEAD^',
|
|
708
|
+
selection: { targetCommitish: 'HEAD', baseCommitish: 'HEAD^' },
|
|
651
709
|
preferredPort: 9050,
|
|
652
710
|
});
|
|
653
711
|
servers.push(result.server);
|
|
@@ -679,8 +737,7 @@ describe('Server Integration Tests', () => {
|
|
|
679
737
|
let port;
|
|
680
738
|
beforeEach(async () => {
|
|
681
739
|
const result = await startServer({
|
|
682
|
-
targetCommitish: 'HEAD',
|
|
683
|
-
baseCommitish: 'HEAD^',
|
|
740
|
+
selection: { targetCommitish: 'HEAD', baseCommitish: 'HEAD^' },
|
|
684
741
|
preferredPort: 9060,
|
|
685
742
|
});
|
|
686
743
|
servers.push(result.server);
|
|
@@ -765,8 +822,7 @@ describe('Server Integration Tests', () => {
|
|
|
765
822
|
describe('Keep-alive option', () => {
|
|
766
823
|
it('accepts keepAlive option without error', async () => {
|
|
767
824
|
const { port, server } = await startServer({
|
|
768
|
-
targetCommitish: 'HEAD',
|
|
769
|
-
baseCommitish: 'HEAD^',
|
|
825
|
+
selection: { targetCommitish: 'HEAD', baseCommitish: 'HEAD^' },
|
|
770
826
|
keepAlive: true,
|
|
771
827
|
});
|
|
772
828
|
servers.push(server);
|
|
@@ -774,16 +830,14 @@ describe('Server Integration Tests', () => {
|
|
|
774
830
|
});
|
|
775
831
|
it('starts normally without keepAlive option', async () => {
|
|
776
832
|
const { port, server } = await startServer({
|
|
777
|
-
targetCommitish: 'HEAD',
|
|
778
|
-
baseCommitish: 'HEAD^',
|
|
833
|
+
selection: { targetCommitish: 'HEAD', baseCommitish: 'HEAD^' },
|
|
779
834
|
});
|
|
780
835
|
servers.push(server);
|
|
781
836
|
expect(port).toBeGreaterThanOrEqual(4966);
|
|
782
837
|
});
|
|
783
838
|
it('does not call process.exit on client disconnect when keepAlive is true', async () => {
|
|
784
839
|
const { port, server } = await startServer({
|
|
785
|
-
targetCommitish: 'HEAD',
|
|
786
|
-
baseCommitish: 'HEAD^',
|
|
840
|
+
selection: { targetCommitish: 'HEAD', baseCommitish: 'HEAD^' },
|
|
787
841
|
keepAlive: true,
|
|
788
842
|
preferredPort: 9070,
|
|
789
843
|
});
|
|
@@ -811,8 +865,7 @@ describe('Server Integration Tests', () => {
|
|
|
811
865
|
});
|
|
812
866
|
it('calls process.exit on client disconnect when keepAlive is false', async () => {
|
|
813
867
|
const { port, server } = await startServer({
|
|
814
|
-
targetCommitish: 'HEAD',
|
|
815
|
-
baseCommitish: 'HEAD^',
|
|
868
|
+
selection: { targetCommitish: 'HEAD', baseCommitish: 'HEAD^' },
|
|
816
869
|
keepAlive: false,
|
|
817
870
|
preferredPort: 9080,
|
|
818
871
|
});
|
|
@@ -841,8 +894,7 @@ describe('Server Integration Tests', () => {
|
|
|
841
894
|
describe('Clear Comments functionality', () => {
|
|
842
895
|
it('includes clearComments flag in diff response when provided', async () => {
|
|
843
896
|
const { port, server } = await startServer({
|
|
844
|
-
targetCommitish: 'HEAD',
|
|
845
|
-
baseCommitish: 'HEAD^',
|
|
897
|
+
selection: { targetCommitish: 'HEAD', baseCommitish: 'HEAD^' },
|
|
846
898
|
clearComments: true,
|
|
847
899
|
});
|
|
848
900
|
servers.push(server);
|
|
@@ -853,8 +905,7 @@ describe('Server Integration Tests', () => {
|
|
|
853
905
|
});
|
|
854
906
|
it('does not include clearComments flag when not provided', async () => {
|
|
855
907
|
const { port, server } = await startServer({
|
|
856
|
-
targetCommitish: 'HEAD',
|
|
857
|
-
baseCommitish: 'HEAD^',
|
|
908
|
+
selection: { targetCommitish: 'HEAD', baseCommitish: 'HEAD^' },
|
|
858
909
|
});
|
|
859
910
|
servers.push(server);
|
|
860
911
|
const response = await fetch(`http://localhost:${port}/api/diff`);
|
|
@@ -864,8 +915,7 @@ describe('Server Integration Tests', () => {
|
|
|
864
915
|
});
|
|
865
916
|
it('preserves clearComments flag across diff requests', async () => {
|
|
866
917
|
const { port, server } = await startServer({
|
|
867
|
-
targetCommitish: 'HEAD',
|
|
868
|
-
baseCommitish: 'HEAD^',
|
|
918
|
+
selection: { targetCommitish: 'HEAD', baseCommitish: 'HEAD^' },
|
|
869
919
|
clearComments: true,
|
|
870
920
|
});
|
|
871
921
|
servers.push(server);
|
package/dist/tui/App.d.ts
CHANGED
package/dist/tui/App.js
CHANGED
|
@@ -6,7 +6,8 @@ import DiffViewer from './components/DiffViewer.js';
|
|
|
6
6
|
import FileList from './components/FileList.js';
|
|
7
7
|
import SideBySideDiffViewer from './components/SideBySideDiffViewer.js';
|
|
8
8
|
import StatusBar from './components/StatusBar.js';
|
|
9
|
-
const App = ({
|
|
9
|
+
const App = ({ selection, mode, repoPath, contextLines }) => {
|
|
10
|
+
const { targetCommitish, baseCommitish } = selection;
|
|
10
11
|
const [files, setFiles] = useState([]);
|
|
11
12
|
const [selectedFileIndex, setSelectedFileIndex] = useState(0);
|
|
12
13
|
const [loading, setLoading] = useState(true);
|
|
@@ -17,7 +18,7 @@ const App = ({ targetCommitish, baseCommitish, mode, repoPath, contextLines, })
|
|
|
17
18
|
setLoading(true);
|
|
18
19
|
setError(null);
|
|
19
20
|
try {
|
|
20
|
-
const fileDiffs = await loadGitDiff(
|
|
21
|
+
const fileDiffs = await loadGitDiff(selection, repoPath, contextLines);
|
|
21
22
|
setFiles(fileDiffs);
|
|
22
23
|
setLoading(false);
|
|
23
24
|
}
|
|
@@ -30,7 +31,7 @@ const App = ({ targetCommitish, baseCommitish, mode, repoPath, contextLines, })
|
|
|
30
31
|
// oxlint-disable-next-line react-hooks-js/set-state-in-effect -- intentional: trigger initial diff load when revisions change
|
|
31
32
|
void loadDiff();
|
|
32
33
|
// oxlint-disable-next-line react/exhaustive-deps
|
|
33
|
-
}, [
|
|
34
|
+
}, [baseCommitish, targetCommitish]);
|
|
34
35
|
useInput((input, key) => {
|
|
35
36
|
if (input === 'q' || (key.ctrl && input === 'c')) {
|
|
36
37
|
exit();
|