difit 3.1.18 → 4.0.1
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 +44 -16
- package/README.ko.md +44 -16
- package/README.md +44 -16
- package/README.zh.md +44 -16
- package/dist/cli/github.d.ts +65 -0
- package/dist/cli/github.js +296 -0
- package/dist/cli/github.test.d.ts +1 -0
- package/dist/cli/github.test.js +341 -0
- package/dist/cli/index.js +42 -1
- package/dist/cli/index.test.js +330 -4
- package/dist/cli/utils.d.ts +2 -8
- package/dist/cli/utils.js +4 -43
- package/dist/cli/utils.test.js +50 -67
- package/dist/client/assets/{_basePickBy-DyiQWUmK.js → _basePickBy-ChXFkTMC.js} +1 -1
- package/dist/client/assets/{_baseUniq-DivSZEOF.js → _baseUniq-Mj_sFFQW.js} +1 -1
- package/dist/client/assets/{arc-c0kacVOL.js → arc-BMA6S9F1.js} +1 -1
- package/dist/client/assets/{architectureDiagram-2XIMDMQ5-ubymLNEe.js → architectureDiagram-2XIMDMQ5-0uiM_v5K.js} +1 -1
- package/dist/client/assets/{blockDiagram-WCTKOSBZ-F9D8w4_S.js → blockDiagram-WCTKOSBZ-CM7ZLL6F.js} +1 -1
- package/dist/client/assets/{c4Diagram-IC4MRINW-JE9Kx4yQ.js → c4Diagram-IC4MRINW-DKtCnVwn.js} +1 -1
- package/dist/client/assets/channel-D057yzDp.js +1 -0
- package/dist/client/assets/{chunk-4BX2VUAB-CYOCoDMc.js → chunk-4BX2VUAB-Wsl8DxEB.js} +1 -1
- package/dist/client/assets/{chunk-55IACEB6-PRBuiJg9.js → chunk-55IACEB6-CHm9X5i7.js} +1 -1
- package/dist/client/assets/{chunk-FMBD7UC4-C0eJ7JsI.js → chunk-FMBD7UC4-BSa8SHgd.js} +1 -1
- package/dist/client/assets/{chunk-JSJVCQXG-QZotPSqo.js → chunk-JSJVCQXG-Cpk76oJ3.js} +1 -1
- package/dist/client/assets/{chunk-KX2RTZJC-B8du3tt8.js → chunk-KX2RTZJC-D8YvfZVu.js} +1 -1
- package/dist/client/assets/{chunk-NQ4KR5QH-B10ldi5m.js → chunk-NQ4KR5QH-BogviJOv.js} +1 -1
- package/dist/client/assets/{chunk-QZHKN3VN-CpwW9rUQ.js → chunk-QZHKN3VN-DwLJYu26.js} +1 -1
- package/dist/client/assets/{chunk-WL4C6EOR-DwKPHpbL.js → chunk-WL4C6EOR-BFDpGxW2.js} +1 -1
- package/dist/client/assets/classDiagram-VBA2DB6C---D4iOts.js +1 -0
- package/dist/client/assets/classDiagram-v2-RAHNMMFH---D4iOts.js +1 -0
- package/dist/client/assets/clone-xSR3otEf.js +1 -0
- package/dist/client/assets/{cose-bilkent-S5V4N54A-p76yal75.js → cose-bilkent-S5V4N54A-oEosZ_5y.js} +1 -1
- package/dist/client/assets/{dagre-KLK3FWXG-CdDyed3V.js → dagre-KLK3FWXG-gFld4u1H.js} +1 -1
- package/dist/client/assets/{diagram-E7M64L7V-BaC8dXuW.js → diagram-E7M64L7V-gJq3kSrf.js} +1 -1
- package/dist/client/assets/{diagram-IFDJBPK2-BGf8xwJI.js → diagram-IFDJBPK2-BsUm_q22.js} +1 -1
- package/dist/client/assets/{diagram-P4PSJMXO-D3j16gBZ.js → diagram-P4PSJMXO-juB-sfcR.js} +1 -1
- package/dist/client/assets/{erDiagram-INFDFZHY-DFpDdocf.js → erDiagram-INFDFZHY-Dn77qXAt.js} +1 -1
- package/dist/client/assets/{flowDiagram-PKNHOUZH-Cz4mb4IF.js → flowDiagram-PKNHOUZH-DtmvDYdN.js} +1 -1
- package/dist/client/assets/{ganttDiagram-A5KZAMGK-CNzY9ua5.js → ganttDiagram-A5KZAMGK-BlDaKLbQ.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-K3NZZRJ6-DCSxL8EQ.js → gitGraphDiagram-K3NZZRJ6-DeAAeuMS.js} +1 -1
- package/dist/client/assets/{graph-BC2BV1-T.js → graph-NX9gBP47.js} +1 -1
- package/dist/client/assets/index-VxkpzDXr.css +1 -0
- package/dist/client/assets/index-kJdw4DY-.js +98 -0
- package/dist/client/assets/{infoDiagram-LFFYTUFH-BKSspZbH.js → infoDiagram-LFFYTUFH-CAaX023c.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-PHBUUO56-DZ2IRYwc.js → ishikawaDiagram-PHBUUO56-CmiTQStv.js} +1 -1
- package/dist/client/assets/{journeyDiagram-4ABVD52K-BrjXAkii.js → journeyDiagram-4ABVD52K-B0SHC7mz.js} +1 -1
- package/dist/client/assets/{kanban-definition-K7BYSVSG-B1mfOekw.js → kanban-definition-K7BYSVSG-IfRdhzz7.js} +1 -1
- package/dist/client/assets/{layout-CWTG02uT.js → layout-l3OdNQhJ.js} +1 -1
- package/dist/client/assets/{linear-CGgOKp1d.js → linear-CQ0hx5Qs.js} +1 -1
- package/dist/client/assets/{mermaid.core-DTPtVBG7.js → mermaid.core-DqlPTabt.js} +4 -4
- package/dist/client/assets/{mindmap-definition-YRQLILUH-DByVRPFT.js → mindmap-definition-YRQLILUH-DIgSmG_f.js} +1 -1
- package/dist/client/assets/{pieDiagram-SKSYHLDU-DEgvAxAy.js → pieDiagram-SKSYHLDU-FzM5qoIB.js} +1 -1
- package/dist/client/assets/{prism-csharp-DqTrHqwJ.js → prism-csharp-DCfUUOUs.js} +1 -1
- package/dist/client/assets/{prism-elixir-DEJaM00V.js → prism-elixir-riuOL1mm.js} +1 -1
- package/dist/client/assets/{prism-hcl-HvJ0aPiH.js → prism-hcl-CizuX1s4.js} +1 -1
- package/dist/client/assets/{prism-java-DDUFERTh.js → prism-java-DYCKrDUh.js} +1 -1
- package/dist/client/assets/{prism-perl-CNA3SNC9.js → prism-perl-BJwBYR3Y.js} +1 -1
- package/dist/client/assets/{prism-php-hBQuhE2A.js → prism-php-BMhFuA7y.js} +1 -1
- package/dist/client/assets/{prism-ruby-BKap8imy.js → prism-ruby-Bcu0cDEh.js} +1 -1
- package/dist/client/assets/{prism-solidity-DHc7LZHq.js → prism-solidity-DDDs3w-w.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-337W2JSQ-DTtikTvc.js → quadrantDiagram-337W2JSQ-BBrApyD7.js} +1 -1
- package/dist/client/assets/{requirementDiagram-Z7DCOOCP-B34R-xD0.js → requirementDiagram-Z7DCOOCP-CLXiwUaA.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-WA2Y5GQK-Dts1ZXRC.js → sankeyDiagram-WA2Y5GQK-9Y3Ly5qe.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-2WXFIKYE-DzM3WhEY.js → sequenceDiagram-2WXFIKYE-DEpX1BA5.js} +1 -1
- package/dist/client/assets/{stateDiagram-RAJIS63D-B2dF8YnK.js → stateDiagram-RAJIS63D-Ck3ullwA.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-FVOUBMTO-X6UiDsar.js +1 -0
- package/dist/client/assets/{timeline-definition-YZTLITO2-BO4OtcEm.js → timeline-definition-YZTLITO2-CMezf3XV.js} +1 -1
- package/dist/client/assets/{treemap-KZPCXAKY-DaXnvVRH.js → treemap-KZPCXAKY-DqrcV0gQ.js} +1 -1
- package/dist/client/assets/{vennDiagram-LZ73GAT5-AIMhd8Js.js → vennDiagram-LZ73GAT5-eQg945Fz.js} +1 -1
- package/dist/client/assets/{xychartDiagram-JWTSCODW-Ch6W1f7P.js → xychartDiagram-JWTSCODW-_hqdXeX1.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/server/generated-file-check.js +113 -58
- package/dist/server/generated-file-check.test.js +2 -0
- package/dist/server/git-diff-tui.d.ts +1 -1
- package/dist/server/git-diff-tui.js +7 -5
- package/dist/server/git-diff-tui.test.d.ts +1 -0
- package/dist/server/git-diff-tui.test.js +60 -0
- package/dist/server/git-diff.d.ts +4 -1
- package/dist/server/git-diff.js +73 -9
- package/dist/server/git-diff.test.js +46 -0
- package/dist/server/server.d.ts +3 -0
- package/dist/server/server.js +111 -37
- package/dist/server/server.test.js +152 -0
- package/dist/tui/App.d.ts +1 -0
- package/dist/tui/App.js +2 -2
- package/dist/types/diff.d.ts +74 -14
- package/dist/utils/commentFormatting.d.ts +4 -2
- package/dist/utils/commentFormatting.js +57 -19
- package/dist/utils/commentImports.d.ts +9 -0
- package/dist/utils/commentImports.js +264 -0
- package/dist/utils/commentImports.test.d.ts +1 -0
- package/dist/utils/commentImports.test.js +197 -0
- package/package.json +1 -1
- package/dist/client/assets/channel-Ca4c0q8d.js +0 -1
- package/dist/client/assets/classDiagram-VBA2DB6C-CJLw9sK7.js +0 -1
- package/dist/client/assets/classDiagram-v2-RAHNMMFH-CJLw9sK7.js +0 -1
- package/dist/client/assets/clone-D0mDLEir.js +0 -1
- package/dist/client/assets/index-DHt9OwVU.css +0 -1
- package/dist/client/assets/index-mE8CA51x.js +0 -95
- package/dist/client/assets/stateDiagram-v2-FVOUBMTO-ReD0hBzH.js +0 -1
package/dist/server/server.js
CHANGED
|
@@ -7,6 +7,7 @@ import open from 'open';
|
|
|
7
7
|
const __filename = fileURLToPath(import.meta.url);
|
|
8
8
|
const __dirname = dirname(__filename);
|
|
9
9
|
import { formatCommentsOutput } from '../utils/commentFormatting.js';
|
|
10
|
+
import { serializeCommentImports } from '../utils/commentImports.js';
|
|
10
11
|
import { normalizeDiffViewMode } from '../utils/diffMode.js';
|
|
11
12
|
import { resolveEditorOption } from '../utils/editorOptions.js';
|
|
12
13
|
import { getFileExtension } from '../utils/fileUtils.js';
|
|
@@ -17,6 +18,12 @@ export async function startServer(options) {
|
|
|
17
18
|
const app = express();
|
|
18
19
|
const repositoryPath = resolve(options.repoPath ?? process.cwd());
|
|
19
20
|
const repositoryId = createHash('sha256').update(repositoryPath).digest('hex');
|
|
21
|
+
const initialCommentImports = options.commentImports || [];
|
|
22
|
+
const initialBaseCommitish = options.baseCommitish ?? '';
|
|
23
|
+
const initialTargetCommitish = options.targetCommitish ?? '';
|
|
24
|
+
const commentImportId = initialCommentImports.length > 0
|
|
25
|
+
? createHash('sha256').update(serializeCommentImports(initialCommentImports)).digest('hex')
|
|
26
|
+
: undefined;
|
|
20
27
|
const parser = new GitDiffParser(repositoryPath);
|
|
21
28
|
const fileWatcher = new FileWatcherService();
|
|
22
29
|
const generatedStatusCache = new Map();
|
|
@@ -44,7 +51,7 @@ export async function startServer(options) {
|
|
|
44
51
|
diffDataCache = parser.parseStdinDiff(options.stdinDiff);
|
|
45
52
|
}
|
|
46
53
|
else {
|
|
47
|
-
diffDataCache = await parser.parseDiff(options.targetCommitish ?? '', options.baseCommitish ?? '', currentIgnoreWhitespace);
|
|
54
|
+
diffDataCache = await parser.parseDiff(options.targetCommitish ?? '', options.baseCommitish ?? '', currentIgnoreWhitespace, options.contextLines);
|
|
48
55
|
}
|
|
49
56
|
// Function to invalidate cache when file changes are detected
|
|
50
57
|
const invalidateCache = () => {
|
|
@@ -55,10 +62,28 @@ export async function startServer(options) {
|
|
|
55
62
|
// Track current revisions for cache invalidation
|
|
56
63
|
let currentBaseCommitish = options.baseCommitish ?? '';
|
|
57
64
|
let currentTargetCommitish = options.targetCommitish ?? '';
|
|
65
|
+
function parseRepositoryRelativePath(filepath) {
|
|
66
|
+
if (typeof filepath !== 'string' || filepath.length === 0) {
|
|
67
|
+
return { ok: false, error: 'Invalid file path' };
|
|
68
|
+
}
|
|
69
|
+
const normalizedFilepath = filepath.replace(/\\/g, '/');
|
|
70
|
+
const hasParentTraversal = normalizedFilepath.split('/').some((segment) => segment === '..');
|
|
71
|
+
if (isAbsolute(filepath) || normalizedFilepath.startsWith('/') || hasParentTraversal) {
|
|
72
|
+
return { ok: false, error: 'File path outside repository' };
|
|
73
|
+
}
|
|
74
|
+
const resolvedPath = resolve(repositoryPath, normalizedFilepath);
|
|
75
|
+
if (resolvedPath !== repositoryPath && !resolvedPath.startsWith(`${repositoryPath}${sep}`)) {
|
|
76
|
+
return { ok: false, error: 'File path outside repository' };
|
|
77
|
+
}
|
|
78
|
+
return { ok: true, path: normalizedFilepath };
|
|
79
|
+
}
|
|
58
80
|
app.get('/api/diff', async (req, res) => {
|
|
59
81
|
const ignoreWhitespace = req.query.ignoreWhitespace === 'true';
|
|
60
82
|
const requestedBase = req.query.base || options.baseCommitish || '';
|
|
61
83
|
const requestedTarget = req.query.target || options.targetCommitish || '';
|
|
84
|
+
const shouldIncludeCommentImports = initialCommentImports.length > 0 &&
|
|
85
|
+
(Boolean(options.stdinDiff) ||
|
|
86
|
+
(requestedBase === initialBaseCommitish && requestedTarget === initialTargetCommitish));
|
|
62
87
|
// Check if revisions or whitespace setting changed
|
|
63
88
|
const revisionsChanged = requestedBase !== currentBaseCommitish || requestedTarget !== currentTargetCommitish;
|
|
64
89
|
const whitespaceChanged = ignoreWhitespace !== currentIgnoreWhitespace;
|
|
@@ -67,7 +92,7 @@ export async function startServer(options) {
|
|
|
67
92
|
currentIgnoreWhitespace = ignoreWhitespace;
|
|
68
93
|
currentBaseCommitish = requestedBase;
|
|
69
94
|
currentTargetCommitish = requestedTarget;
|
|
70
|
-
diffDataCache = await parser.parseDiff(requestedTarget, requestedBase, ignoreWhitespace);
|
|
95
|
+
diffDataCache = await parser.parseDiff(requestedTarget, requestedBase, ignoreWhitespace, options.contextLines);
|
|
71
96
|
generatedStatusCache.clear();
|
|
72
97
|
}
|
|
73
98
|
// Resolve symbolic refs like HEAD/HEAD^ to actual hashes for the UI
|
|
@@ -106,6 +131,8 @@ export async function startServer(options) {
|
|
|
106
131
|
requestedTargetCommitish,
|
|
107
132
|
clearComments: options.clearComments,
|
|
108
133
|
repositoryId,
|
|
134
|
+
commentImports: shouldIncludeCommentImports ? initialCommentImports : undefined,
|
|
135
|
+
commentImportId: shouldIncludeCommentImports ? commentImportId : undefined,
|
|
109
136
|
});
|
|
110
137
|
});
|
|
111
138
|
app.get(/^\/api\/generated-status\/(.*)$/, async (req, res) => {
|
|
@@ -114,22 +141,12 @@ export async function startServer(options) {
|
|
|
114
141
|
return;
|
|
115
142
|
}
|
|
116
143
|
try {
|
|
117
|
-
const
|
|
118
|
-
if (
|
|
119
|
-
res.status(400).json({ error:
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
const normalizedFilepath = filepath.replace(/\\/g, '/');
|
|
123
|
-
const hasParentTraversal = normalizedFilepath.split('/').some((segment) => segment === '..');
|
|
124
|
-
if (isAbsolute(filepath) || normalizedFilepath.startsWith('/') || hasParentTraversal) {
|
|
125
|
-
res.status(400).json({ error: 'File path outside repository' });
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
const resolvedPath = resolve(repositoryPath, normalizedFilepath);
|
|
129
|
-
if (resolvedPath !== repositoryPath && !resolvedPath.startsWith(`${repositoryPath}${sep}`)) {
|
|
130
|
-
res.status(400).json({ error: 'File path outside repository' });
|
|
144
|
+
const filepathResult = parseRepositoryRelativePath(req.params[0]);
|
|
145
|
+
if (!filepathResult.ok) {
|
|
146
|
+
res.status(400).json({ error: filepathResult.error });
|
|
131
147
|
return;
|
|
132
148
|
}
|
|
149
|
+
const normalizedFilepath = filepathResult.path;
|
|
133
150
|
const ref = req.query.ref || currentTargetCommitish || 'HEAD';
|
|
134
151
|
const cacheKey = `${ref}:${normalizedFilepath}`;
|
|
135
152
|
const now = Date.now();
|
|
@@ -162,7 +179,7 @@ export async function startServer(options) {
|
|
|
162
179
|
return;
|
|
163
180
|
}
|
|
164
181
|
try {
|
|
165
|
-
const { branches, commits, resolvedBase, resolvedTarget } = await parser.getRevisionOptions(currentBaseCommitish, currentTargetCommitish);
|
|
182
|
+
const { branches, commits, originDefaultBranch, resolvedBase, resolvedTarget } = await parser.getRevisionOptions(currentBaseCommitish, currentTargetCommitish);
|
|
166
183
|
const response = {
|
|
167
184
|
specialOptions: [
|
|
168
185
|
{ value: '.', label: 'All Uncommitted Changes' },
|
|
@@ -171,6 +188,7 @@ export async function startServer(options) {
|
|
|
171
188
|
],
|
|
172
189
|
branches,
|
|
173
190
|
commits,
|
|
191
|
+
originDefaultBranch,
|
|
174
192
|
resolvedBase,
|
|
175
193
|
resolvedTarget,
|
|
176
194
|
};
|
|
@@ -187,10 +205,22 @@ export async function startServer(options) {
|
|
|
187
205
|
res.status(404).json({ error: 'Line count not available for stdin diff' });
|
|
188
206
|
return;
|
|
189
207
|
}
|
|
190
|
-
const
|
|
208
|
+
const filepathResult = parseRepositoryRelativePath(req.params[0]);
|
|
209
|
+
if (!filepathResult.ok) {
|
|
210
|
+
res.status(400).json({ error: filepathResult.error });
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const filepath = filepathResult.path;
|
|
191
214
|
const oldRef = req.query.oldRef;
|
|
215
|
+
const oldPathResult = req.query.oldPath
|
|
216
|
+
? parseRepositoryRelativePath(req.query.oldPath)
|
|
217
|
+
: { ok: true, path: filepath };
|
|
218
|
+
if (!oldPathResult.ok) {
|
|
219
|
+
res.status(400).json({ error: oldPathResult.error });
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
192
222
|
const newRef = req.query.newRef;
|
|
193
|
-
const oldPath =
|
|
223
|
+
const oldPath = oldPathResult.path;
|
|
194
224
|
const result = {};
|
|
195
225
|
if (oldRef) {
|
|
196
226
|
try {
|
|
@@ -222,7 +252,12 @@ export async function startServer(options) {
|
|
|
222
252
|
res.status(404).json({ error: 'Blob content not available for stdin diff' });
|
|
223
253
|
return;
|
|
224
254
|
}
|
|
225
|
-
const
|
|
255
|
+
const filepathResult = parseRepositoryRelativePath(req.params[0]);
|
|
256
|
+
if (!filepathResult.ok) {
|
|
257
|
+
res.status(400).json({ error: filepathResult.error });
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
const filepath = filepathResult.path;
|
|
226
261
|
const ref = req.query.ref || 'HEAD';
|
|
227
262
|
const blob = await parser.getBlobContent(filepath, ref);
|
|
228
263
|
// Determine content type based on file extension
|
|
@@ -254,18 +289,59 @@ export async function startServer(options) {
|
|
|
254
289
|
res.status(404).json({ error: 'File not found' });
|
|
255
290
|
}
|
|
256
291
|
});
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
292
|
+
let finalThreads = [];
|
|
293
|
+
function normalizeComment(comment) {
|
|
294
|
+
return {
|
|
295
|
+
id: comment.id,
|
|
296
|
+
file: comment.file,
|
|
297
|
+
line: comment.line,
|
|
298
|
+
side: comment.side,
|
|
299
|
+
createdAt: comment.timestamp,
|
|
300
|
+
updatedAt: comment.timestamp,
|
|
301
|
+
codeContent: comment.codeContent,
|
|
302
|
+
messages: [
|
|
303
|
+
{
|
|
304
|
+
id: comment.id,
|
|
305
|
+
body: comment.body,
|
|
306
|
+
author: comment.author,
|
|
307
|
+
createdAt: comment.timestamp,
|
|
308
|
+
updatedAt: comment.timestamp,
|
|
309
|
+
},
|
|
310
|
+
],
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
function normalizeThreadPayload(thread) {
|
|
314
|
+
if ('file' in thread && 'line' in thread) {
|
|
315
|
+
return thread;
|
|
316
|
+
}
|
|
317
|
+
return {
|
|
318
|
+
id: thread.id,
|
|
319
|
+
file: thread.filePath,
|
|
320
|
+
line: typeof thread.position.line === 'number'
|
|
321
|
+
? thread.position.line
|
|
322
|
+
: [thread.position.line.start, thread.position.line.end],
|
|
323
|
+
side: thread.position.side,
|
|
324
|
+
createdAt: thread.createdAt,
|
|
325
|
+
updatedAt: thread.updatedAt,
|
|
326
|
+
codeContent: thread.codeSnapshot?.content,
|
|
327
|
+
messages: thread.messages,
|
|
328
|
+
};
|
|
329
|
+
}
|
|
260
330
|
function parseCommentsPayload(body) {
|
|
261
331
|
const payload = typeof body === 'string'
|
|
262
332
|
? JSON.parse(body)
|
|
263
333
|
: body;
|
|
264
|
-
|
|
334
|
+
if (Array.isArray(payload.threads)) {
|
|
335
|
+
return payload.threads.map(normalizeThreadPayload);
|
|
336
|
+
}
|
|
337
|
+
if (Array.isArray(payload.comments)) {
|
|
338
|
+
return payload.comments.map(normalizeComment);
|
|
339
|
+
}
|
|
340
|
+
return [];
|
|
265
341
|
}
|
|
266
342
|
app.post('/api/comments', (req, res) => {
|
|
267
343
|
try {
|
|
268
|
-
|
|
344
|
+
finalThreads = parseCommentsPayload(req.body);
|
|
269
345
|
res.json({ success: true });
|
|
270
346
|
}
|
|
271
347
|
catch (error) {
|
|
@@ -274,8 +350,9 @@ export async function startServer(options) {
|
|
|
274
350
|
}
|
|
275
351
|
});
|
|
276
352
|
app.get('/api/comments-output', (_req, res) => {
|
|
277
|
-
|
|
278
|
-
|
|
353
|
+
res.type('text/plain');
|
|
354
|
+
if (finalThreads.length > 0) {
|
|
355
|
+
const output = formatCommentsOutput(finalThreads);
|
|
279
356
|
res.send(output);
|
|
280
357
|
}
|
|
281
358
|
else {
|
|
@@ -292,12 +369,12 @@ export async function startServer(options) {
|
|
|
292
369
|
res.status(400).json({ error: 'Invalid request payload' });
|
|
293
370
|
return;
|
|
294
371
|
}
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
res.status(400).json({ error: 'File path outside repository' });
|
|
372
|
+
const filepathResult = parseRepositoryRelativePath(filePath);
|
|
373
|
+
if (!filepathResult.ok) {
|
|
374
|
+
res.status(400).json({ error: filepathResult.error });
|
|
299
375
|
return;
|
|
300
376
|
}
|
|
377
|
+
const resolvedPath = resolve(repositoryPath, filepathResult.path);
|
|
301
378
|
const editorInput = typeof editor === 'string' ? editor : (process.env.DIFIT_EDITOR ?? process.env.EDITOR);
|
|
302
379
|
const resolvedEditor = resolveEditorOption(editorInput);
|
|
303
380
|
if (resolvedEditor.protocol === null) {
|
|
@@ -324,7 +401,7 @@ export async function startServer(options) {
|
|
|
324
401
|
else {
|
|
325
402
|
args.push(resolvedPath);
|
|
326
403
|
}
|
|
327
|
-
args.push(
|
|
404
|
+
args.push(repositoryPath);
|
|
328
405
|
return await new Promise((resolvePromise) => {
|
|
329
406
|
const child = spawn(resolvedEditor.cliCommand, args, { stdio: 'ignore', detached: true });
|
|
330
407
|
child.once('error', (error) => {
|
|
@@ -357,8 +434,8 @@ export async function startServer(options) {
|
|
|
357
434
|
});
|
|
358
435
|
// Function to output comments when server shuts down
|
|
359
436
|
function outputFinalComments() {
|
|
360
|
-
if (
|
|
361
|
-
console.log(formatCommentsOutput(
|
|
437
|
+
if (finalThreads.length > 0) {
|
|
438
|
+
console.log(formatCommentsOutput(finalThreads));
|
|
362
439
|
}
|
|
363
440
|
}
|
|
364
441
|
// SSE endpoint for file watching
|
|
@@ -413,9 +490,6 @@ export async function startServer(options) {
|
|
|
413
490
|
// Find client files relative to the CLI executable location
|
|
414
491
|
const distPath = join(__dirname, '..', 'client');
|
|
415
492
|
app.use(express.static(distPath));
|
|
416
|
-
app.get('/{*splat}', (_req, res) => {
|
|
417
|
-
res.sendFile(join(distPath, 'index.html'));
|
|
418
|
-
});
|
|
419
493
|
}
|
|
420
494
|
else {
|
|
421
495
|
app.get('/', (_req, res) => {
|
|
@@ -5,6 +5,7 @@ import { startServer } from './server.js';
|
|
|
5
5
|
// Add fetch polyfill for Node.js test environment
|
|
6
6
|
const { fetch } = await import('undici');
|
|
7
7
|
globalThis.fetch = fetch;
|
|
8
|
+
const parserInstances = vi.hoisted(() => []);
|
|
8
9
|
// Helper function to get available port
|
|
9
10
|
async function getAvailablePort(preferredPort) {
|
|
10
11
|
let port = preferredPort;
|
|
@@ -25,6 +26,9 @@ async function getAvailablePort(preferredPort) {
|
|
|
25
26
|
// Mock GitDiffParser
|
|
26
27
|
vi.mock('./git-diff.js', () => {
|
|
27
28
|
class GitDiffParserMock {
|
|
29
|
+
constructor() {
|
|
30
|
+
parserInstances.push(this);
|
|
31
|
+
}
|
|
28
32
|
validateCommit = vi.fn().mockResolvedValue(true);
|
|
29
33
|
parseDiff = vi.fn().mockResolvedValue({
|
|
30
34
|
targetCommit: 'abc123',
|
|
@@ -59,6 +63,7 @@ vi.mock('./git-diff.js', () => {
|
|
|
59
63
|
isEmpty: false,
|
|
60
64
|
});
|
|
61
65
|
getBlobContent = vi.fn().mockResolvedValue(Buffer.from('mock image data'));
|
|
66
|
+
getLineCount = vi.fn().mockResolvedValue(42);
|
|
62
67
|
getGeneratedStatus = vi.fn().mockResolvedValue({
|
|
63
68
|
isGenerated: true,
|
|
64
69
|
source: 'content',
|
|
@@ -67,6 +72,7 @@ vi.mock('./git-diff.js', () => {
|
|
|
67
72
|
getRevisionOptions = vi.fn().mockResolvedValue({
|
|
68
73
|
branches: [{ name: 'main', current: true }],
|
|
69
74
|
commits: [{ hash: 'abc1234', shortHash: 'abc1234', message: 'Test commit' }],
|
|
75
|
+
originDefaultBranch: 'origin/main',
|
|
70
76
|
resolvedBase: 'abc1234',
|
|
71
77
|
resolvedTarget: 'def5678',
|
|
72
78
|
});
|
|
@@ -166,6 +172,7 @@ describe('Server Integration Tests', () => {
|
|
|
166
172
|
// Mock process.exit to prevent tests from actually exiting
|
|
167
173
|
originalProcessExit = process.exit;
|
|
168
174
|
process.exit = vi.fn();
|
|
175
|
+
parserInstances.length = 0;
|
|
169
176
|
});
|
|
170
177
|
afterEach(async () => {
|
|
171
178
|
// Restore process.exit
|
|
@@ -225,6 +232,17 @@ describe('Server Integration Tests', () => {
|
|
|
225
232
|
servers.push(result.server);
|
|
226
233
|
expect(result.url).toContain('http://localhost:'); // Display host conversion
|
|
227
234
|
});
|
|
235
|
+
it('passes context lines to the initial diff load', async () => {
|
|
236
|
+
const result = await startServer({
|
|
237
|
+
targetCommitish: 'HEAD',
|
|
238
|
+
baseCommitish: 'HEAD^',
|
|
239
|
+
preferredPort: 9025,
|
|
240
|
+
contextLines: 4,
|
|
241
|
+
});
|
|
242
|
+
servers.push(result.server);
|
|
243
|
+
const parser = parserInstances.at(-1);
|
|
244
|
+
expect(parser?.parseDiff).toHaveBeenCalledWith('HEAD', 'HEAD^', false, 4);
|
|
245
|
+
});
|
|
228
246
|
});
|
|
229
247
|
describe('API endpoints', () => {
|
|
230
248
|
let port;
|
|
@@ -255,6 +273,88 @@ describe('Server Integration Tests', () => {
|
|
|
255
273
|
expect(response.ok).toBe(true);
|
|
256
274
|
expect(data).toHaveProperty('ignoreWhitespace', true);
|
|
257
275
|
});
|
|
276
|
+
it('GET /api/diff preserves context lines when recalculating revisions', async () => {
|
|
277
|
+
const result = await startServer({
|
|
278
|
+
targetCommitish: 'HEAD',
|
|
279
|
+
baseCommitish: 'HEAD^',
|
|
280
|
+
preferredPort: 9031,
|
|
281
|
+
contextLines: 2,
|
|
282
|
+
});
|
|
283
|
+
servers.push(result.server);
|
|
284
|
+
const parser = parserInstances.at(-1);
|
|
285
|
+
parser?.parseDiff.mockClear();
|
|
286
|
+
const response = await fetch(`http://localhost:${result.port}/api/diff?base=main&target=feature&ignoreWhitespace=true`);
|
|
287
|
+
expect(response.ok).toBe(true);
|
|
288
|
+
expect(parser?.parseDiff).toHaveBeenCalledWith('feature', 'main', true, 2);
|
|
289
|
+
});
|
|
290
|
+
it('GET /api/diff returns comment import payload when configured', async () => {
|
|
291
|
+
const importedComments = [
|
|
292
|
+
{
|
|
293
|
+
type: 'thread',
|
|
294
|
+
filePath: 'test.js',
|
|
295
|
+
position: { side: 'new', line: 10 },
|
|
296
|
+
body: 'Imported comment',
|
|
297
|
+
},
|
|
298
|
+
];
|
|
299
|
+
const importServer = await startServer({
|
|
300
|
+
targetCommitish: 'HEAD',
|
|
301
|
+
baseCommitish: 'HEAD^',
|
|
302
|
+
preferredPort: 9034,
|
|
303
|
+
commentImports: importedComments,
|
|
304
|
+
});
|
|
305
|
+
servers.push(importServer.server);
|
|
306
|
+
const response = await fetch(`http://localhost:${importServer.port}/api/diff`);
|
|
307
|
+
const data = (await response.json());
|
|
308
|
+
expect(response.ok).toBe(true);
|
|
309
|
+
expect(data.commentImports).toEqual(importedComments);
|
|
310
|
+
expect(data.commentImportId).toEqual(expect.any(String));
|
|
311
|
+
});
|
|
312
|
+
it('GET /api/diff returns clearComments together with comment import payload', async () => {
|
|
313
|
+
const importedComments = [
|
|
314
|
+
{
|
|
315
|
+
type: 'thread',
|
|
316
|
+
filePath: 'test.js',
|
|
317
|
+
position: { side: 'new', line: 10 },
|
|
318
|
+
body: 'Imported comment',
|
|
319
|
+
},
|
|
320
|
+
];
|
|
321
|
+
const importServer = await startServer({
|
|
322
|
+
targetCommitish: 'HEAD',
|
|
323
|
+
baseCommitish: 'HEAD^',
|
|
324
|
+
preferredPort: 9037,
|
|
325
|
+
clearComments: true,
|
|
326
|
+
commentImports: importedComments,
|
|
327
|
+
});
|
|
328
|
+
servers.push(importServer.server);
|
|
329
|
+
const response = await fetch(`http://localhost:${importServer.port}/api/diff`);
|
|
330
|
+
const data = (await response.json());
|
|
331
|
+
expect(response.ok).toBe(true);
|
|
332
|
+
expect(data.clearComments).toBe(true);
|
|
333
|
+
expect(data.commentImports).toEqual(importedComments);
|
|
334
|
+
expect(data.commentImportId).toEqual(expect.any(String));
|
|
335
|
+
});
|
|
336
|
+
it('GET /api/diff omits comment import payload after revision changes', async () => {
|
|
337
|
+
const importedComments = [
|
|
338
|
+
{
|
|
339
|
+
type: 'thread',
|
|
340
|
+
filePath: 'test.js',
|
|
341
|
+
position: { side: 'new', line: 10 },
|
|
342
|
+
body: 'Imported comment',
|
|
343
|
+
},
|
|
344
|
+
];
|
|
345
|
+
const importServer = await startServer({
|
|
346
|
+
targetCommitish: 'HEAD',
|
|
347
|
+
baseCommitish: 'HEAD^',
|
|
348
|
+
preferredPort: 9038,
|
|
349
|
+
commentImports: importedComments,
|
|
350
|
+
});
|
|
351
|
+
servers.push(importServer.server);
|
|
352
|
+
const response = await fetch(`http://localhost:${importServer.port}/api/diff?base=main&target=feature`);
|
|
353
|
+
const data = (await response.json());
|
|
354
|
+
expect(response.ok).toBe(true);
|
|
355
|
+
expect(data.commentImports).toBeUndefined();
|
|
356
|
+
expect(data.commentImportId).toBeUndefined();
|
|
357
|
+
});
|
|
258
358
|
it('GET /api/generated-status/* returns generated status', async () => {
|
|
259
359
|
const response = await fetch(`http://localhost:${port}/api/generated-status/src/query.ts?ref=HEAD`);
|
|
260
360
|
const data = (await response.json());
|
|
@@ -329,6 +429,7 @@ describe('Server Integration Tests', () => {
|
|
|
329
429
|
const response = await fetch(`http://localhost:${port}/api/comments-output`);
|
|
330
430
|
const output = await response.text();
|
|
331
431
|
expect(response.ok).toBe(true);
|
|
432
|
+
expect(response.headers.get('Content-Type')).toContain('text/plain');
|
|
332
433
|
expect(output).toContain('Comments from review session');
|
|
333
434
|
expect(output).toContain('test.js:L10');
|
|
334
435
|
expect(output).toContain('First comment');
|
|
@@ -427,6 +528,17 @@ describe('Server Integration Tests', () => {
|
|
|
427
528
|
// But the server should not crash
|
|
428
529
|
expect([200, 404]).toContain(response.status);
|
|
429
530
|
});
|
|
531
|
+
it('returns 404 for unknown paths in production mode', async () => {
|
|
532
|
+
process.env.NODE_ENV = 'production';
|
|
533
|
+
const result = await startServer({
|
|
534
|
+
targetCommitish: 'HEAD',
|
|
535
|
+
baseCommitish: 'HEAD^',
|
|
536
|
+
preferredPort: 9055,
|
|
537
|
+
});
|
|
538
|
+
servers.push(result.server);
|
|
539
|
+
const response = await fetch(`http://localhost:${result.port}/not-a-route`);
|
|
540
|
+
expect(response.status).toBe(404);
|
|
541
|
+
});
|
|
430
542
|
});
|
|
431
543
|
describe('Mode option handling', () => {
|
|
432
544
|
it('accepts mode option in server configuration', async () => {
|
|
@@ -484,6 +596,7 @@ describe('Server Integration Tests', () => {
|
|
|
484
596
|
expect(data.commits).toEqual([
|
|
485
597
|
{ hash: 'abc1234', shortHash: 'abc1234', message: 'Test commit' },
|
|
486
598
|
]);
|
|
599
|
+
expect(data.originDefaultBranch).toBe('origin/main');
|
|
487
600
|
expect(data.resolvedBase).toBe('abc1234');
|
|
488
601
|
expect(data.resolvedTarget).toBe('def5678');
|
|
489
602
|
});
|
|
@@ -529,6 +642,39 @@ describe('Server Integration Tests', () => {
|
|
|
529
642
|
expect(response.headers.get('Access-Control-Allow-Headers')).toBe('Origin, X-Requested-With, Content-Type, Accept');
|
|
530
643
|
});
|
|
531
644
|
});
|
|
645
|
+
describe('Line count API', () => {
|
|
646
|
+
let port;
|
|
647
|
+
beforeEach(async () => {
|
|
648
|
+
const result = await startServer({
|
|
649
|
+
targetCommitish: 'HEAD',
|
|
650
|
+
baseCommitish: 'HEAD^',
|
|
651
|
+
preferredPort: 9050,
|
|
652
|
+
});
|
|
653
|
+
servers.push(result.server);
|
|
654
|
+
port = result.port;
|
|
655
|
+
});
|
|
656
|
+
it('returns line counts for repository files', async () => {
|
|
657
|
+
const response = await fetch(`http://localhost:${port}/api/line-count/src%2Findex.ts?oldRef=HEAD~1&newRef=HEAD`);
|
|
658
|
+
const data = (await response.json());
|
|
659
|
+
expect(response.ok).toBe(true);
|
|
660
|
+
expect(data).toEqual({
|
|
661
|
+
oldLineCount: 42,
|
|
662
|
+
newLineCount: 42,
|
|
663
|
+
});
|
|
664
|
+
});
|
|
665
|
+
it('rejects paths outside repository', async () => {
|
|
666
|
+
const response = await fetch(`http://localhost:${port}/api/line-count/..%2Foutside.txt`);
|
|
667
|
+
const data = (await response.json());
|
|
668
|
+
expect(response.status).toBe(400);
|
|
669
|
+
expect(data).toHaveProperty('error', 'File path outside repository');
|
|
670
|
+
});
|
|
671
|
+
it('rejects oldPath values outside repository', async () => {
|
|
672
|
+
const response = await fetch(`http://localhost:${port}/api/line-count/src%2Findex.ts?oldRef=HEAD~1&oldPath=..%2Foutside.txt`);
|
|
673
|
+
const data = (await response.json());
|
|
674
|
+
expect(response.status).toBe(400);
|
|
675
|
+
expect(data).toHaveProperty('error', 'File path outside repository');
|
|
676
|
+
});
|
|
677
|
+
});
|
|
532
678
|
describe('Blob API endpoints', () => {
|
|
533
679
|
let port;
|
|
534
680
|
beforeEach(async () => {
|
|
@@ -609,6 +755,12 @@ describe('Server Integration Tests', () => {
|
|
|
609
755
|
expect(response.ok).toBe(true);
|
|
610
756
|
}
|
|
611
757
|
});
|
|
758
|
+
it('rejects paths outside repository', async () => {
|
|
759
|
+
const response = await fetch(`http://localhost:${port}/api/blob/..%2Foutside.txt?ref=HEAD`);
|
|
760
|
+
const data = (await response.json());
|
|
761
|
+
expect(response.status).toBe(400);
|
|
762
|
+
expect(data).toHaveProperty('error', 'File path outside repository');
|
|
763
|
+
});
|
|
612
764
|
});
|
|
613
765
|
describe('Keep-alive option', () => {
|
|
614
766
|
it('accepts keepAlive option without error', async () => {
|
package/dist/tui/App.d.ts
CHANGED
package/dist/tui/App.js
CHANGED
|
@@ -6,7 +6,7 @@ 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 = ({ targetCommitish, baseCommitish, mode, repoPath }) => {
|
|
9
|
+
const App = ({ targetCommitish, baseCommitish, mode, repoPath, contextLines, }) => {
|
|
10
10
|
const [files, setFiles] = useState([]);
|
|
11
11
|
const [selectedFileIndex, setSelectedFileIndex] = useState(0);
|
|
12
12
|
const [loading, setLoading] = useState(true);
|
|
@@ -17,7 +17,7 @@ const App = ({ targetCommitish, baseCommitish, mode, repoPath }) => {
|
|
|
17
17
|
setLoading(true);
|
|
18
18
|
setError(null);
|
|
19
19
|
try {
|
|
20
|
-
const fileDiffs = await loadGitDiff(targetCommitish, baseCommitish, repoPath);
|
|
20
|
+
const fileDiffs = await loadGitDiff(targetCommitish, baseCommitish, repoPath, contextLines);
|
|
21
21
|
setFiles(fileDiffs);
|
|
22
22
|
setLoading(false);
|
|
23
23
|
}
|
package/dist/types/diff.d.ts
CHANGED
|
@@ -34,6 +34,18 @@ export interface ParsedDiff {
|
|
|
34
34
|
export type DiffViewMode = 'split' | 'unified';
|
|
35
35
|
export type LegacyDiffViewMode = 'side-by-side' | 'inline';
|
|
36
36
|
export type DiffSide = 'old' | 'new';
|
|
37
|
+
export type DiffLineRange = number | {
|
|
38
|
+
start: number;
|
|
39
|
+
end: number;
|
|
40
|
+
};
|
|
41
|
+
export interface DiffCommentPosition {
|
|
42
|
+
side: DiffSide;
|
|
43
|
+
line: DiffLineRange;
|
|
44
|
+
}
|
|
45
|
+
export interface DiffCommentCodeSnapshot {
|
|
46
|
+
content: string;
|
|
47
|
+
language?: string;
|
|
48
|
+
}
|
|
37
49
|
export interface DiffResponse {
|
|
38
50
|
commit: string;
|
|
39
51
|
files: DiffFile[];
|
|
@@ -47,6 +59,8 @@ export interface DiffResponse {
|
|
|
47
59
|
requestedTargetCommitish?: string;
|
|
48
60
|
clearComments?: boolean;
|
|
49
61
|
repositoryId?: string;
|
|
62
|
+
commentImports?: CommentImport[];
|
|
63
|
+
commentImportId?: string;
|
|
50
64
|
}
|
|
51
65
|
export interface GeneratedStatusResponse {
|
|
52
66
|
path: string;
|
|
@@ -69,38 +83,82 @@ export interface LineSelection {
|
|
|
69
83
|
side: DiffSide;
|
|
70
84
|
lineNumber: number;
|
|
71
85
|
}
|
|
72
|
-
export interface
|
|
86
|
+
export interface LegacyDiffComment {
|
|
73
87
|
id: string;
|
|
74
88
|
filePath: string;
|
|
75
89
|
body: string;
|
|
76
90
|
author?: string;
|
|
77
91
|
createdAt: string;
|
|
78
92
|
updatedAt: string;
|
|
79
|
-
position:
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
93
|
+
position: DiffCommentPosition;
|
|
94
|
+
codeSnapshot?: DiffCommentCodeSnapshot;
|
|
95
|
+
}
|
|
96
|
+
export interface DiffCommentMessage {
|
|
97
|
+
id: string;
|
|
98
|
+
body: string;
|
|
99
|
+
author?: string;
|
|
100
|
+
createdAt: string;
|
|
101
|
+
updatedAt: string;
|
|
102
|
+
}
|
|
103
|
+
export interface DiffCommentThread {
|
|
104
|
+
id: string;
|
|
105
|
+
filePath: string;
|
|
106
|
+
createdAt: string;
|
|
107
|
+
updatedAt: string;
|
|
108
|
+
position: DiffCommentPosition;
|
|
109
|
+
codeSnapshot?: DiffCommentCodeSnapshot;
|
|
110
|
+
messages: DiffCommentMessage[];
|
|
111
|
+
}
|
|
112
|
+
interface CommentImportBase {
|
|
113
|
+
id?: string;
|
|
114
|
+
filePath: string;
|
|
115
|
+
position: DiffCommentPosition;
|
|
116
|
+
body: string;
|
|
117
|
+
author?: string;
|
|
118
|
+
createdAt?: string;
|
|
119
|
+
updatedAt?: string;
|
|
120
|
+
codeSnapshot?: DiffCommentCodeSnapshot;
|
|
90
121
|
}
|
|
122
|
+
export interface ThreadCommentImport extends CommentImportBase {
|
|
123
|
+
type: 'thread';
|
|
124
|
+
}
|
|
125
|
+
export interface ReplyCommentImport extends CommentImportBase {
|
|
126
|
+
type: 'reply';
|
|
127
|
+
}
|
|
128
|
+
export type CommentImport = ThreadCommentImport | ReplyCommentImport;
|
|
91
129
|
export interface ViewedFileRecord {
|
|
92
130
|
filePath: string;
|
|
93
131
|
viewedAt: string;
|
|
94
132
|
diffContentHash: string;
|
|
95
133
|
}
|
|
96
|
-
export interface
|
|
134
|
+
export interface LegacyDiffContextStorage {
|
|
97
135
|
version: 1;
|
|
98
136
|
baseCommitish: string;
|
|
99
137
|
targetCommitish: string;
|
|
100
138
|
createdAt: string;
|
|
101
139
|
lastModifiedAt: string;
|
|
102
|
-
comments:
|
|
140
|
+
comments: LegacyDiffComment[];
|
|
141
|
+
viewedFiles: ViewedFileRecord[];
|
|
142
|
+
}
|
|
143
|
+
export interface DiffContextStorage {
|
|
144
|
+
version: 2;
|
|
145
|
+
baseCommitish: string;
|
|
146
|
+
targetCommitish: string;
|
|
147
|
+
createdAt: string;
|
|
148
|
+
lastModifiedAt: string;
|
|
149
|
+
threads: DiffCommentThread[];
|
|
103
150
|
viewedFiles: ViewedFileRecord[];
|
|
151
|
+
appliedCommentImportIds: string[];
|
|
152
|
+
}
|
|
153
|
+
export interface CommentThread {
|
|
154
|
+
id: string;
|
|
155
|
+
file: string;
|
|
156
|
+
line: LineNumber;
|
|
157
|
+
side?: DiffSide;
|
|
158
|
+
createdAt: string;
|
|
159
|
+
updatedAt: string;
|
|
160
|
+
codeContent?: string;
|
|
161
|
+
messages: DiffCommentMessage[];
|
|
104
162
|
}
|
|
105
163
|
export interface RevisionOption {
|
|
106
164
|
value: string;
|
|
@@ -119,6 +177,7 @@ export interface RevisionsResponse {
|
|
|
119
177
|
specialOptions: RevisionOption[];
|
|
120
178
|
branches: BranchInfo[];
|
|
121
179
|
commits: CommitInfo[];
|
|
180
|
+
originDefaultBranch?: string;
|
|
122
181
|
resolvedBase?: string;
|
|
123
182
|
resolvedTarget?: string;
|
|
124
183
|
}
|
|
@@ -140,3 +199,4 @@ export interface ExpandedRange {
|
|
|
140
199
|
export interface ExpandedLine extends DiffLine {
|
|
141
200
|
isExpanded?: boolean;
|
|
142
201
|
}
|
|
202
|
+
export {};
|