aws-runtime-bridge 1.7.35 → 1.7.37
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/dist/routes/git.d.ts +30 -1
- package/dist/routes/git.d.ts.map +1 -1
- package/dist/routes/git.js +127 -39
- package/dist/routes/git.test.js +60 -1
- package/package.json +1 -1
package/dist/routes/git.d.ts
CHANGED
|
@@ -18,7 +18,21 @@ interface GitRepositoryContext {
|
|
|
18
18
|
relativeWorkspacePath: string;
|
|
19
19
|
}
|
|
20
20
|
export declare function assertGitWorkspacePathAccessible(workspacePath: string): Promise<void>;
|
|
21
|
+
interface GitStashItem {
|
|
22
|
+
ref: string;
|
|
23
|
+
stashName?: string;
|
|
24
|
+
message: string;
|
|
25
|
+
branch: string;
|
|
26
|
+
createdAt: string | null;
|
|
27
|
+
}
|
|
21
28
|
type GitDiffFileStatus = 'modified' | 'added' | 'deleted' | 'renamed';
|
|
29
|
+
interface GitDiffSummaryFile {
|
|
30
|
+
path: string;
|
|
31
|
+
status: GitDiffFileStatus;
|
|
32
|
+
additions: number;
|
|
33
|
+
deletions: number;
|
|
34
|
+
staged: boolean;
|
|
35
|
+
}
|
|
22
36
|
/**
|
|
23
37
|
* 判断 Git diff 汇总行是否应该展示在差异树中。
|
|
24
38
|
* 普通 modified 且文本增删均为 0 通常是 filemode/元数据噪声,避免显示为“无差异内容”。
|
|
@@ -37,17 +51,32 @@ export declare function createMissingInitialCommitSnapshotError(): string;
|
|
|
37
51
|
export declare function isGitStashNoChangesOutput(output: string): boolean;
|
|
38
52
|
interface LatestGitStash {
|
|
39
53
|
ref: string;
|
|
54
|
+
stashName: string;
|
|
40
55
|
message: string;
|
|
41
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* 解析 git stash list --format 输出行。
|
|
59
|
+
* 主流程:优先返回稳定 stash commit hash 作为 ref,同时保留 stash@{n} 供界面展示。
|
|
60
|
+
*/
|
|
61
|
+
export declare function parseGitStashListLine(line: string): GitStashItem | null;
|
|
42
62
|
/**
|
|
43
63
|
* 从 git stash list -n 1 输出解析最近一次 stash。
|
|
44
|
-
*
|
|
64
|
+
* 主流程:提取稳定 stash commit hash,供无变更快照复用最近快照引用。
|
|
45
65
|
*/
|
|
46
66
|
export declare function parseLatestGitStash(output: string): LatestGitStash | null;
|
|
47
67
|
/**
|
|
48
68
|
* 解析 git diff 的 name-status 与 numstat 输出,生成前端文件树需要的汇总数据。
|
|
49
69
|
*/
|
|
50
70
|
export declare function parseGitStatusStagedPaths(output: string): Set<string>;
|
|
71
|
+
/**
|
|
72
|
+
* 解析 git diff 的 name-status 与 numstat 输出,生成前端文件树需要的汇总数据。
|
|
73
|
+
*/
|
|
74
|
+
export declare function parseGitDiffSummary(nameStatusOutput: string, numstatOutput: string, stagedPaths?: Set<string>, forceStaged?: boolean): GitDiffSummaryFile[];
|
|
75
|
+
/**
|
|
76
|
+
* 合并工作区与暂存区差异汇总。
|
|
77
|
+
* 主流程:以路径去重,保留工作区展示顺序;同一路径若也存在暂存变更则标记为 staged 并累加统计。
|
|
78
|
+
*/
|
|
79
|
+
export declare function mergeGitDiffSummaryFiles(unstagedFiles: GitDiffSummaryFile[], stagedFiles: GitDiffSummaryFile[]): GitDiffSummaryFile[];
|
|
51
80
|
export declare function normalizeGitCommitMessage(message: unknown): string;
|
|
52
81
|
export declare function createScopedGitPathspecArgs(context: Pick<GitRepositoryContext, 'relativeWorkspacePath'>): string[];
|
|
53
82
|
export declare function createScopedFilePathspecArgs(filePath: string, context: Pick<GitRepositoryContext, 'relativeWorkspacePath'>): string[];
|
package/dist/routes/git.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../../src/routes/git.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../../src/routes/git.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAUH,eAAO,MAAM,SAAS,4CAAW,CAAC;AAIlC;;;GAGG;AACH,wBAAgB,0BAA0B,IAAI,MAAM,CAOnD;AAED,UAAU,oBAAoB;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,aAAa,EAAE,OAAO,CAAC;IACvB,SAAS,EAAE,OAAO,CAAC;IACnB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,qBAAqB,EAAE,MAAM,CAAC;CAC/B;AAED,wBAAsB,gCAAgC,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAe3F;AAED,UAAU,YAAY;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,KAAK,iBAAiB,GAAG,UAAU,GAAG,OAAO,GAAG,SAAS,GAAG,SAAS,CAAC;AAEtE,UAAU,kBAAkB;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,iBAAiB,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC;CACjB;AAUD;;;GAGG;AACH,wBAAgB,+BAA+B,CAC7C,MAAM,EAAE,iBAAiB,EACzB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,OAAO,GACpB,OAAO,CAMT;AAED;;;GAGG;AACH,wBAAgB,mCAAmC,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAK3E;AAED,wBAAgB,uCAAuC,IAAI,MAAM,CAEhE;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAKjE;AAED,UAAU,cAAc;IACtB,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAmCvE;AAkBD;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAYzE;AA+KD;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAmBrE;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,gBAAgB,EAAE,MAAM,EACxB,aAAa,EAAE,MAAM,EACrB,WAAW,GAAE,GAAG,CAAC,MAAM,CAAa,EACpC,WAAW,UAAQ,GAClB,kBAAkB,EAAE,CAuCtB;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CACtC,aAAa,EAAE,kBAAkB,EAAE,EACnC,WAAW,EAAE,kBAAkB,EAAE,GAChC,kBAAkB,EAAE,CAwBtB;AAMD,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAElE;AAED,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,IAAI,CAAC,oBAAoB,EAAE,uBAAuB,CAAC,GAAG,MAAM,EAAE,CAElH;AAED,wBAAgB,4BAA4B,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,oBAAoB,EAAE,uBAAuB,CAAC,GAAG,MAAM,EAAE,CAErI"}
|
package/dist/routes/git.js
CHANGED
|
@@ -71,20 +71,73 @@ export function isGitStashNoChangesOutput(output) {
|
|
|
71
71
|
|| normalized.includes('no changes to save')
|
|
72
72
|
|| normalized.includes('没有本地变更需要保存');
|
|
73
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* 解析 git stash list --format 输出行。
|
|
76
|
+
* 主流程:优先返回稳定 stash commit hash 作为 ref,同时保留 stash@{n} 供界面展示。
|
|
77
|
+
*/
|
|
78
|
+
export function parseGitStashListLine(line) {
|
|
79
|
+
const rawLine = String(line || '').trim();
|
|
80
|
+
if (!rawLine)
|
|
81
|
+
return null;
|
|
82
|
+
const fields = rawLine.split('\0');
|
|
83
|
+
if (fields.length >= 3) {
|
|
84
|
+
const stableRef = fields[0]?.trim();
|
|
85
|
+
const stashName = fields[1]?.trim();
|
|
86
|
+
const subject = fields[2]?.trim() || stashName;
|
|
87
|
+
const createdAt = fields[3]?.trim() || null;
|
|
88
|
+
if (!stableRef || !stashName)
|
|
89
|
+
return null;
|
|
90
|
+
const parsedSubject = parseGitStashSubject(subject || '');
|
|
91
|
+
return {
|
|
92
|
+
ref: stableRef,
|
|
93
|
+
stashName,
|
|
94
|
+
message: parsedSubject.message,
|
|
95
|
+
branch: parsedSubject.branch,
|
|
96
|
+
createdAt,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
const match = rawLine.match(/^(stash@\{\d+\})(?::\s*)?(.*)$/);
|
|
100
|
+
if (!match)
|
|
101
|
+
return null;
|
|
102
|
+
const stashName = match[1];
|
|
103
|
+
const parsedSubject = parseGitStashSubject(match[2]?.trim() || stashName);
|
|
104
|
+
return {
|
|
105
|
+
ref: stashName,
|
|
106
|
+
stashName,
|
|
107
|
+
message: parsedSubject.message,
|
|
108
|
+
branch: parsedSubject.branch,
|
|
109
|
+
createdAt: null,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* 从 stash subject 中拆出分支和用户消息。
|
|
114
|
+
*/
|
|
115
|
+
function parseGitStashSubject(subject) {
|
|
116
|
+
const normalized = String(subject || '').trim();
|
|
117
|
+
const branchMatch = normalized.match(/^(?:On|WIP on)\s+([^:]+):\s*(.*)$/);
|
|
118
|
+
if (branchMatch) {
|
|
119
|
+
return {
|
|
120
|
+
branch: branchMatch[1].trim(),
|
|
121
|
+
message: branchMatch[2].trim() || normalized,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
return { branch: '', message: normalized };
|
|
125
|
+
}
|
|
74
126
|
/**
|
|
75
127
|
* 从 git stash list -n 1 输出解析最近一次 stash。
|
|
76
|
-
*
|
|
128
|
+
* 主流程:提取稳定 stash commit hash,供无变更快照复用最近快照引用。
|
|
77
129
|
*/
|
|
78
130
|
export function parseLatestGitStash(output) {
|
|
79
131
|
const firstLine = String(output || '').split('\n').find(line => line.trim());
|
|
80
132
|
if (!firstLine)
|
|
81
133
|
return null;
|
|
82
|
-
const
|
|
83
|
-
if (!
|
|
134
|
+
const parsed = parseGitStashListLine(firstLine);
|
|
135
|
+
if (!parsed)
|
|
84
136
|
return null;
|
|
85
137
|
return {
|
|
86
|
-
ref:
|
|
87
|
-
|
|
138
|
+
ref: parsed.ref,
|
|
139
|
+
stashName: parsed.stashName || parsed.ref,
|
|
140
|
+
message: parsed.message || parsed.ref,
|
|
88
141
|
};
|
|
89
142
|
}
|
|
90
143
|
/**
|
|
@@ -255,7 +308,7 @@ export function parseGitStatusStagedPaths(output) {
|
|
|
255
308
|
/**
|
|
256
309
|
* 解析 git diff 的 name-status 与 numstat 输出,生成前端文件树需要的汇总数据。
|
|
257
310
|
*/
|
|
258
|
-
function parseGitDiffSummary(nameStatusOutput, numstatOutput, stagedPaths = new Set()) {
|
|
311
|
+
export function parseGitDiffSummary(nameStatusOutput, numstatOutput, stagedPaths = new Set(), forceStaged = false) {
|
|
259
312
|
const statusMap = new Map();
|
|
260
313
|
const nameStatusLines = nameStatusOutput.split('\n').filter(line => line.trim());
|
|
261
314
|
for (const line of nameStatusLines) {
|
|
@@ -285,10 +338,35 @@ function parseGitDiffSummary(nameStatusOutput, numstatOutput, stagedPaths = new
|
|
|
285
338
|
if (!filePath || !shouldIncludeGitDiffSummaryFile(status, additions, deletions, isBinaryDiff)) {
|
|
286
339
|
continue;
|
|
287
340
|
}
|
|
288
|
-
files.push({ path: filePath, status, additions, deletions, staged: stagedPaths.has(filePath) });
|
|
341
|
+
files.push({ path: filePath, status, additions, deletions, staged: forceStaged || stagedPaths.has(filePath) });
|
|
289
342
|
}
|
|
290
343
|
return files;
|
|
291
344
|
}
|
|
345
|
+
/**
|
|
346
|
+
* 合并工作区与暂存区差异汇总。
|
|
347
|
+
* 主流程:以路径去重,保留工作区展示顺序;同一路径若也存在暂存变更则标记为 staged 并累加统计。
|
|
348
|
+
*/
|
|
349
|
+
export function mergeGitDiffSummaryFiles(unstagedFiles, stagedFiles) {
|
|
350
|
+
const fileMap = new Map();
|
|
351
|
+
for (const file of unstagedFiles) {
|
|
352
|
+
fileMap.set(file.path, { ...file });
|
|
353
|
+
}
|
|
354
|
+
for (const stagedFile of stagedFiles) {
|
|
355
|
+
const existingFile = fileMap.get(stagedFile.path);
|
|
356
|
+
if (!existingFile) {
|
|
357
|
+
fileMap.set(stagedFile.path, { ...stagedFile, staged: true });
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
fileMap.set(stagedFile.path, {
|
|
361
|
+
...existingFile,
|
|
362
|
+
status: existingFile.status === 'modified' ? stagedFile.status : existingFile.status,
|
|
363
|
+
additions: existingFile.additions + stagedFile.additions,
|
|
364
|
+
deletions: existingFile.deletions + stagedFile.deletions,
|
|
365
|
+
staged: true,
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
return [...fileMap.values()];
|
|
369
|
+
}
|
|
292
370
|
function normalizeCommitHash(commitHash) {
|
|
293
371
|
return String(commitHash || '').trim();
|
|
294
372
|
}
|
|
@@ -374,13 +452,14 @@ gitRouter.post('/git/stash/create', validateToken, async (req, res) => {
|
|
|
374
452
|
: defaultMessage;
|
|
375
453
|
const result = await execGitCommand(normalizedPath, ['stash', 'push', '-m', stashMessage]);
|
|
376
454
|
if (isGitStashNoChangesOutput(`${result.stdout}\n${result.stderr}`)) {
|
|
377
|
-
const latestResult = await execGitCommand(normalizedPath, ['stash', 'list', '-n', '1']);
|
|
455
|
+
const latestResult = await execGitCommand(normalizedPath, ['stash', 'list', '-n', '1', '--format=%H%x00%gd%x00%gs%x00%cr']);
|
|
378
456
|
const latestStash = latestResult.exitCode === 0 ? parseLatestGitStash(latestResult.stdout) : null;
|
|
379
457
|
res.json({
|
|
380
458
|
ok: true,
|
|
381
459
|
stashRef: latestStash?.ref ?? null,
|
|
460
|
+
stashName: latestStash?.stashName ?? null,
|
|
382
461
|
message: latestStash
|
|
383
|
-
? `没有本地变更需要保存,已复用最近快照 ${latestStash.
|
|
462
|
+
? `没有本地变更需要保存,已复用最近快照 ${latestStash.stashName}`
|
|
384
463
|
: '没有本地变更需要保存',
|
|
385
464
|
noChanges: true,
|
|
386
465
|
reusedLatestStash: Boolean(latestStash),
|
|
@@ -396,13 +475,13 @@ gitRouter.post('/git/stash/create', validateToken, async (req, res) => {
|
|
|
396
475
|
res.status(400).json({ error: result.stderr || 'git stash push failed' });
|
|
397
476
|
return;
|
|
398
477
|
}
|
|
399
|
-
const listResult = await execGitCommand(normalizedPath, ['stash', 'list', '-n', '1']);
|
|
400
|
-
const
|
|
401
|
-
const
|
|
402
|
-
const stashRef = match ? match[1] : 'stash@{0}';
|
|
478
|
+
const listResult = await execGitCommand(normalizedPath, ['stash', 'list', '-n', '1', '--format=%H%x00%gd%x00%gs%x00%cr']);
|
|
479
|
+
const latestStash = listResult.exitCode === 0 ? parseLatestGitStash(listResult.stdout) : null;
|
|
480
|
+
const stashRef = latestStash?.ref || 'stash@{0}';
|
|
403
481
|
res.json({
|
|
404
482
|
ok: true,
|
|
405
483
|
stashRef,
|
|
484
|
+
stashName: latestStash?.stashName ?? null,
|
|
406
485
|
message: stashMessage,
|
|
407
486
|
workspacePath: normalizedPath
|
|
408
487
|
});
|
|
@@ -425,33 +504,15 @@ gitRouter.post('/git/stash/list', validateToken, async (req, res) => {
|
|
|
425
504
|
try {
|
|
426
505
|
const normalizedPath = path.resolve(String(workspacePath).trim());
|
|
427
506
|
const stashLimit = Math.min(Math.max(1, Number(limit) || 20), 100);
|
|
428
|
-
const result = await execGitCommand(normalizedPath, ['stash', 'list',
|
|
507
|
+
const result = await execGitCommand(normalizedPath, ['stash', 'list', '-n', String(stashLimit), '--format=%H%x00%gd%x00%gs%x00%cr']);
|
|
429
508
|
if (result.exitCode !== 0) {
|
|
430
509
|
res.status(400).json({ error: result.stderr || 'git stash list failed' });
|
|
431
510
|
return;
|
|
432
511
|
}
|
|
433
|
-
const stashes =
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
if (!refMatch)
|
|
438
|
-
continue;
|
|
439
|
-
const ref = refMatch[1];
|
|
440
|
-
const restOfLine = line.slice(ref.length + 2);
|
|
441
|
-
let branch = '';
|
|
442
|
-
let message = restOfLine;
|
|
443
|
-
const branchMatch = restOfLine.match(/^(?:On|WIP on)\s+([^:]+):\s*(.*)$/);
|
|
444
|
-
if (branchMatch) {
|
|
445
|
-
branch = branchMatch[1].trim();
|
|
446
|
-
message = branchMatch[2].trim();
|
|
447
|
-
}
|
|
448
|
-
stashes.push({
|
|
449
|
-
ref,
|
|
450
|
-
message,
|
|
451
|
-
branch,
|
|
452
|
-
createdAt: null,
|
|
453
|
-
});
|
|
454
|
-
}
|
|
512
|
+
const stashes = result.stdout
|
|
513
|
+
.split('\n')
|
|
514
|
+
.map(parseGitStashListLine)
|
|
515
|
+
.filter((stash) => stash !== null);
|
|
455
516
|
res.json({
|
|
456
517
|
ok: true,
|
|
457
518
|
stashes,
|
|
@@ -594,12 +655,23 @@ gitRouter.post('/git/diff', validateToken, async (req, res) => {
|
|
|
594
655
|
res.status(400).json({ error: result.stderr || 'git diff failed' });
|
|
595
656
|
return;
|
|
596
657
|
}
|
|
658
|
+
const stagedNameStatusResult = await execGitCommand(context.repositoryRootPath, createScopedGitArgs(['diff', '--cached', '--name-status'], context));
|
|
659
|
+
if (stagedNameStatusResult.exitCode !== 0) {
|
|
660
|
+
res.status(400).json({ error: stagedNameStatusResult.stderr || 'git staged diff failed' });
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
const stagedNumstatResult = await execGitCommand(context.repositoryRootPath, createScopedGitArgs(['diff', '--cached', '--numstat'], context));
|
|
664
|
+
if (stagedNumstatResult.exitCode !== 0) {
|
|
665
|
+
res.status(400).json({ error: stagedNumstatResult.stderr || 'git staged diff failed' });
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
597
668
|
const statusResult = await execGitCommand(context.repositoryRootPath, createScopedGitArgs(['status', '--porcelain=v1'], context));
|
|
598
669
|
if (statusResult.exitCode !== 0) {
|
|
599
670
|
res.status(400).json({ error: statusResult.stderr || 'git status failed' });
|
|
600
671
|
return;
|
|
601
672
|
}
|
|
602
|
-
const
|
|
673
|
+
const stagedPaths = parseGitStatusStagedPaths(statusResult.stdout);
|
|
674
|
+
const files = mergeGitDiffSummaryFiles(parseGitDiffSummary(nameStatusResult.stdout, result.stdout, stagedPaths), parseGitDiffSummary(stagedNameStatusResult.stdout, stagedNumstatResult.stdout, stagedPaths, true));
|
|
603
675
|
res.json({
|
|
604
676
|
ok: true,
|
|
605
677
|
isGitRepo: context.isGitRepo,
|
|
@@ -832,12 +904,28 @@ gitRouter.post('/git/diff-file', validateToken, async (req, res) => {
|
|
|
832
904
|
const normalizedCommitHash = normalizeCommitHash(commitHash);
|
|
833
905
|
const gitArgs = normalizedCommitHash
|
|
834
906
|
? ['show', '--format=', '--no-ext-diff', normalizedCommitHash, '--', repositoryRelativeFilePath]
|
|
835
|
-
: ['diff', '--', repositoryRelativeFilePath];
|
|
836
|
-
|
|
907
|
+
: ['diff', 'HEAD', '--', repositoryRelativeFilePath];
|
|
908
|
+
let result = await execGitCommand(context.repositoryRootPath, gitArgs);
|
|
909
|
+
if (!normalizedCommitHash && result.exitCode !== 0) {
|
|
910
|
+
result = await execGitCommand(context.repositoryRootPath, ['diff', '--', repositoryRelativeFilePath]);
|
|
911
|
+
}
|
|
837
912
|
if (result.exitCode !== 0) {
|
|
838
913
|
res.status(400).json({ error: result.stderr || 'git diff file failed' });
|
|
839
914
|
return;
|
|
840
915
|
}
|
|
916
|
+
if (!normalizedCommitHash && !result.stdout.trim()) {
|
|
917
|
+
const stagedResult = await execGitCommand(context.repositoryRootPath, [
|
|
918
|
+
'diff',
|
|
919
|
+
'--cached',
|
|
920
|
+
'--',
|
|
921
|
+
repositoryRelativeFilePath,
|
|
922
|
+
]);
|
|
923
|
+
if (stagedResult.exitCode !== 0) {
|
|
924
|
+
res.status(400).json({ error: stagedResult.stderr || 'git staged diff file failed' });
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
result = stagedResult;
|
|
928
|
+
}
|
|
841
929
|
res.json({
|
|
842
930
|
ok: true,
|
|
843
931
|
filePath: normalizedFilePath,
|
package/dist/routes/git.test.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { createScopedFilePathspecArgs, createScopedGitPathspecArgs, normalizeGitCommitMessage, parseGitStatusStagedPaths, } from './git.js';
|
|
2
|
+
import { createScopedFilePathspecArgs, createScopedGitPathspecArgs, mergeGitDiffSummaryFiles, normalizeGitCommitMessage, parseGitDiffSummary, parseGitStashListLine, parseGitStatusStagedPaths, parseLatestGitStash, } from './git.js';
|
|
3
3
|
describe('git commit helpers', () => {
|
|
4
4
|
it('normalizes commit messages before executing git commit', () => {
|
|
5
5
|
expect(normalizeGitCommitMessage(' chore: update dashboard ')).toBe('chore: update dashboard');
|
|
@@ -20,4 +20,63 @@ describe('git commit helpers', () => {
|
|
|
20
20
|
const stagedPaths = parseGitStatusStagedPaths('M staged.ts\n M unstaged.ts\nA added.ts\n?? untracked.ts\nR old.ts -> renamed.ts\n');
|
|
21
21
|
expect([...stagedPaths].sort()).toEqual(['added.ts', 'renamed.ts', 'staged.ts']);
|
|
22
22
|
});
|
|
23
|
+
it('marks cached diff files as staged when parsing staged summaries', () => {
|
|
24
|
+
const files = parseGitDiffSummary('M\tsrc/staged.ts\n', '2\t1\tsrc/staged.ts\n', new Set(), true);
|
|
25
|
+
expect(files).toEqual([
|
|
26
|
+
{
|
|
27
|
+
path: 'src/staged.ts',
|
|
28
|
+
status: 'modified',
|
|
29
|
+
additions: 2,
|
|
30
|
+
deletions: 1,
|
|
31
|
+
staged: true,
|
|
32
|
+
},
|
|
33
|
+
]);
|
|
34
|
+
});
|
|
35
|
+
it('merges unstaged and staged summaries while preserving staged-only files', () => {
|
|
36
|
+
const unstagedFiles = parseGitDiffSummary('M\tsrc/mixed.ts\nM\tsrc/unstaged.ts\n', '1\t0\tsrc/mixed.ts\n3\t1\tsrc/unstaged.ts\n', new Set(['src/mixed.ts']));
|
|
37
|
+
const stagedFiles = parseGitDiffSummary('M\tsrc/mixed.ts\nA\tsrc/staged-only.ts\n', '2\t1\tsrc/mixed.ts\n5\t0\tsrc/staged-only.ts\n', new Set(['src/mixed.ts', 'src/staged-only.ts']), true);
|
|
38
|
+
expect(mergeGitDiffSummaryFiles(unstagedFiles, stagedFiles)).toEqual([
|
|
39
|
+
{
|
|
40
|
+
path: 'src/mixed.ts',
|
|
41
|
+
status: 'modified',
|
|
42
|
+
additions: 3,
|
|
43
|
+
deletions: 1,
|
|
44
|
+
staged: true,
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
path: 'src/unstaged.ts',
|
|
48
|
+
status: 'modified',
|
|
49
|
+
additions: 3,
|
|
50
|
+
deletions: 1,
|
|
51
|
+
staged: false,
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
path: 'src/staged-only.ts',
|
|
55
|
+
status: 'added',
|
|
56
|
+
additions: 5,
|
|
57
|
+
deletions: 0,
|
|
58
|
+
staged: true,
|
|
59
|
+
},
|
|
60
|
+
]);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
describe('git stash reference helpers', () => {
|
|
64
|
+
it('parses stable stash object hashes while preserving the display selector', () => {
|
|
65
|
+
const stash = parseGitStashListLine('0123456789abcdef0123456789abcdef01234567\u0000stash@{3}\u0000On main: 快照: 任务线-2026-06-02 10:00:00\u00002 hours ago');
|
|
66
|
+
expect(stash).toEqual({
|
|
67
|
+
ref: '0123456789abcdef0123456789abcdef01234567',
|
|
68
|
+
stashName: 'stash@{3}',
|
|
69
|
+
message: '快照: 任务线-2026-06-02 10:00:00',
|
|
70
|
+
branch: 'main',
|
|
71
|
+
createdAt: '2 hours ago',
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
it('uses the stable stash object hash for the latest no-change snapshot reuse', () => {
|
|
75
|
+
const latest = parseLatestGitStash('fedcba9876543210fedcba9876543210fedcba98\u0000stash@{0}\u0000On master: 快照: latest\u0000just now\n');
|
|
76
|
+
expect(latest).toEqual({
|
|
77
|
+
ref: 'fedcba9876543210fedcba9876543210fedcba98',
|
|
78
|
+
stashName: 'stash@{0}',
|
|
79
|
+
message: '快照: latest',
|
|
80
|
+
});
|
|
81
|
+
});
|
|
23
82
|
});
|