mcp-perforce-server 2.1.0 → 2.1.2
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/MCP_CONFIG_EXAMPLES.md +214 -223
- package/README.md +192 -568
- package/dist/p4/config.d.ts.map +1 -1
- package/dist/p4/config.js +17 -3
- package/dist/p4/config.js.map +1 -1
- package/dist/p4/parse.d.ts +28 -0
- package/dist/p4/parse.d.ts.map +1 -1
- package/dist/p4/parse.js +341 -29
- package/dist/p4/parse.js.map +1 -1
- package/dist/p4/runner.d.ts.map +1 -1
- package/dist/p4/runner.js +223 -14
- package/dist/p4/runner.js.map +1 -1
- package/dist/p4/security.d.ts.map +1 -1
- package/dist/p4/security.js +15 -5
- package/dist/p4/security.js.map +1 -1
- package/dist/server.d.ts +15 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +390 -107
- package/dist/server.js.map +1 -1
- package/dist/tools/basic.d.ts +58 -0
- package/dist/tools/basic.d.ts.map +1 -1
- package/dist/tools/basic.js +450 -52
- package/dist/tools/basic.js.map +1 -1
- package/dist/tools/changelist.d.ts +1 -1
- package/dist/tools/changelist.d.ts.map +1 -1
- package/dist/tools/changelist.js +31 -22
- package/dist/tools/changelist.js.map +1 -1
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +8 -1
- package/dist/tools/index.js.map +1 -1
- package/package.json +1 -1
package/dist/p4/parse.js
CHANGED
|
@@ -10,6 +10,8 @@ exports.parseChangesOutput = parseChangesOutput;
|
|
|
10
10
|
exports.parseFilelogOutput = parseFilelogOutput;
|
|
11
11
|
exports.parseClientsOutput = parseClientsOutput;
|
|
12
12
|
exports.parseDiffOutput = parseDiffOutput;
|
|
13
|
+
exports.parseDiff2Output = parseDiff2Output;
|
|
14
|
+
exports.parseDescribeOutput = parseDescribeOutput;
|
|
13
15
|
exports.parseSyncOutput = parseSyncOutput;
|
|
14
16
|
exports.parseResolveOutput = parseResolveOutput;
|
|
15
17
|
exports.parseShelveOutput = parseShelveOutput;
|
|
@@ -31,6 +33,11 @@ exports.parseLabelOutput = parseLabelOutput;
|
|
|
31
33
|
exports.parseSizesOutput = parseSizesOutput;
|
|
32
34
|
exports.parseHaveOutput = parseHaveOutput;
|
|
33
35
|
exports.parseWhereOutput = parseWhereOutput;
|
|
36
|
+
exports.parseFstatOutput = parseFstatOutput;
|
|
37
|
+
exports.parseStreamsOutput = parseStreamsOutput;
|
|
38
|
+
exports.parseStreamOutput = parseStreamOutput;
|
|
39
|
+
exports.parsePrintOutput = parsePrintOutput;
|
|
40
|
+
exports.parseIntegrateOutput = parseIntegrateOutput;
|
|
34
41
|
/**
|
|
35
42
|
* Parse p4 -ztag output into structured data
|
|
36
43
|
*/
|
|
@@ -40,7 +47,7 @@ function parseZtagOutput(output) {
|
|
|
40
47
|
if (!output || typeof output !== 'string') {
|
|
41
48
|
return results;
|
|
42
49
|
}
|
|
43
|
-
const lines = output
|
|
50
|
+
const lines = getNormalizedLines(output);
|
|
44
51
|
let currentRecord = {};
|
|
45
52
|
for (const line of lines) {
|
|
46
53
|
const trimmedLine = line.trim();
|
|
@@ -74,7 +81,7 @@ function parseInfoOutput(output) {
|
|
|
74
81
|
if (!output || typeof output !== 'string') {
|
|
75
82
|
return result;
|
|
76
83
|
}
|
|
77
|
-
const lines = output
|
|
84
|
+
const lines = getNormalizedLines(output);
|
|
78
85
|
for (const line of lines) {
|
|
79
86
|
const trimmedLine = line.trim();
|
|
80
87
|
if (!trimmedLine)
|
|
@@ -99,7 +106,7 @@ function parseOpenedOutput(output) {
|
|
|
99
106
|
if (!output || typeof output !== 'string') {
|
|
100
107
|
return results;
|
|
101
108
|
}
|
|
102
|
-
const lines = output
|
|
109
|
+
const lines = getNormalizedLines(output, { removeEmpty: true });
|
|
103
110
|
for (const line of lines) {
|
|
104
111
|
// Format: "//depot/path#revision - action by user@client (change) type"
|
|
105
112
|
const match = line.match(/^(.+?)#(\d+)\s+-\s+(\w+)\s+by\s+(.+?)@(.+?)\s+\((.+?)\)(?:\s+(.+))?/);
|
|
@@ -134,10 +141,12 @@ function parseChangesOutput(output) {
|
|
|
134
141
|
if (!output || typeof output !== 'string') {
|
|
135
142
|
return results;
|
|
136
143
|
}
|
|
137
|
-
const lines = output
|
|
144
|
+
const lines = getNormalizedLines(output, { trim: true, removeEmpty: true });
|
|
138
145
|
for (const line of lines) {
|
|
146
|
+
// Remove "info: " prefix if present (from -s flag output)
|
|
147
|
+
const cleanLine = line.replace(/^info:\s*/, '');
|
|
139
148
|
// Format: "Change 12345 on 2023/01/01 by user@client 'Description...'"
|
|
140
|
-
const match =
|
|
149
|
+
const match = cleanLine.match(/^Change\s+(\d+)\s+on\s+(\S+)\s+by\s+(.+?)@(.+?)\s+'(.*)'/);
|
|
141
150
|
if (match) {
|
|
142
151
|
const [, change, date, user, client, description] = match;
|
|
143
152
|
results.push({
|
|
@@ -160,7 +169,7 @@ function parseFilelogOutput(output) {
|
|
|
160
169
|
if (!output || typeof output !== 'string') {
|
|
161
170
|
return results;
|
|
162
171
|
}
|
|
163
|
-
const lines = output
|
|
172
|
+
const lines = getNormalizedLines(output);
|
|
164
173
|
let currentFile = null;
|
|
165
174
|
let currentRevision = null;
|
|
166
175
|
for (const line of lines) {
|
|
@@ -211,7 +220,7 @@ function parseClientsOutput(output) {
|
|
|
211
220
|
if (!output || typeof output !== 'string') {
|
|
212
221
|
return results;
|
|
213
222
|
}
|
|
214
|
-
const lines = output
|
|
223
|
+
const lines = getNormalizedLines(output, { removeEmpty: true });
|
|
215
224
|
for (const line of lines) {
|
|
216
225
|
// Format: "Client clientname 2023/01/01 root /path/to/root 'Description...'"
|
|
217
226
|
const match = line.match(/^Client\s+(\S+)\s+(\S+)\s+root\s+(.+?)\s+'(.*)'/);
|
|
@@ -240,7 +249,7 @@ function parseDiffOutput(output) {
|
|
|
240
249
|
totalRemovedLines: 0
|
|
241
250
|
};
|
|
242
251
|
}
|
|
243
|
-
const lines = output
|
|
252
|
+
const lines = getNormalizedLines(output);
|
|
244
253
|
const files = [];
|
|
245
254
|
let currentFile = null;
|
|
246
255
|
let diffLines = [];
|
|
@@ -292,6 +301,163 @@ function parseDiffOutput(output) {
|
|
|
292
301
|
totalRemovedLines: files.reduce((sum, file) => sum + (file.removedLines || 0), 0),
|
|
293
302
|
};
|
|
294
303
|
}
|
|
304
|
+
/**
|
|
305
|
+
* Parse p4 diff2 output for depot-to-depot comparisons
|
|
306
|
+
*/
|
|
307
|
+
function parseDiff2Output(output, summaryOnly = true) {
|
|
308
|
+
const emptyResult = {
|
|
309
|
+
differences: [],
|
|
310
|
+
totalDifferences: 0,
|
|
311
|
+
summaryOnly,
|
|
312
|
+
};
|
|
313
|
+
if (!output || typeof output !== 'string') {
|
|
314
|
+
return emptyResult;
|
|
315
|
+
}
|
|
316
|
+
const lines = getNormalizedLines(output);
|
|
317
|
+
const differences = [];
|
|
318
|
+
let currentDiff = null;
|
|
319
|
+
let currentDiffLines = [];
|
|
320
|
+
const finalizeCurrentDiff = () => {
|
|
321
|
+
if (!currentDiff)
|
|
322
|
+
return;
|
|
323
|
+
if (!summaryOnly) {
|
|
324
|
+
const diffText = currentDiffLines.join('\n').trim();
|
|
325
|
+
if (diffText) {
|
|
326
|
+
currentDiff.diff = diffText;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
differences.push(currentDiff);
|
|
330
|
+
currentDiff = null;
|
|
331
|
+
currentDiffLines = [];
|
|
332
|
+
};
|
|
333
|
+
for (const line of lines) {
|
|
334
|
+
const trimmed = line.trim();
|
|
335
|
+
if (!trimmed) {
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
const headerMatch = trimmed.match(/^====\s+(.+?)\s+-\s+(.+?)\s+====(?:\s+(.*))?$/);
|
|
339
|
+
if (headerMatch) {
|
|
340
|
+
finalizeCurrentDiff();
|
|
341
|
+
const [, sourceRaw, targetRaw, differenceTypeRaw] = headerMatch;
|
|
342
|
+
const sourceInfo = parseDiff2Side(sourceRaw);
|
|
343
|
+
const targetInfo = parseDiff2Side(targetRaw);
|
|
344
|
+
currentDiff = {
|
|
345
|
+
sourceFile: sourceInfo.depotFile,
|
|
346
|
+
sourceRevision: sourceInfo.revision,
|
|
347
|
+
sourceType: sourceInfo.type,
|
|
348
|
+
targetFile: targetInfo.depotFile,
|
|
349
|
+
targetRevision: targetInfo.revision,
|
|
350
|
+
targetType: targetInfo.type,
|
|
351
|
+
differenceType: differenceTypeRaw ? differenceTypeRaw.trim() : 'content',
|
|
352
|
+
};
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
if (summaryOnly) {
|
|
356
|
+
const pairMatch = trimmed.match(/^(.+?#\S+(?:\s+\(.+?\))?)\s+-\s+(.+?#\S+(?:\s+\(.+?\))?)(?:\s+(.*))?$/);
|
|
357
|
+
if (pairMatch) {
|
|
358
|
+
finalizeCurrentDiff();
|
|
359
|
+
const [, sourceRaw, targetRaw, differenceTypeRaw] = pairMatch;
|
|
360
|
+
const sourceInfo = parseDiff2Side(sourceRaw);
|
|
361
|
+
const targetInfo = parseDiff2Side(targetRaw);
|
|
362
|
+
differences.push({
|
|
363
|
+
sourceFile: sourceInfo.depotFile,
|
|
364
|
+
sourceRevision: sourceInfo.revision,
|
|
365
|
+
sourceType: sourceInfo.type,
|
|
366
|
+
targetFile: targetInfo.depotFile,
|
|
367
|
+
targetRevision: targetInfo.revision,
|
|
368
|
+
targetType: targetInfo.type,
|
|
369
|
+
differenceType: differenceTypeRaw ? differenceTypeRaw.trim() : 'different',
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
if (!summaryOnly && currentDiff) {
|
|
375
|
+
currentDiffLines.push(line);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
finalizeCurrentDiff();
|
|
379
|
+
return {
|
|
380
|
+
differences: differences,
|
|
381
|
+
totalDifferences: differences.length,
|
|
382
|
+
summaryOnly,
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Parse p4 describe -s output into structured changelist data
|
|
387
|
+
*/
|
|
388
|
+
function parseDescribeOutput(output) {
|
|
389
|
+
const result = {
|
|
390
|
+
files: [],
|
|
391
|
+
description: '',
|
|
392
|
+
rawText: '',
|
|
393
|
+
};
|
|
394
|
+
if (!output || typeof output !== 'string') {
|
|
395
|
+
return result;
|
|
396
|
+
}
|
|
397
|
+
const lines = getNormalizedLines(output);
|
|
398
|
+
result.rawText = lines
|
|
399
|
+
.filter((line) => line.trim())
|
|
400
|
+
.join('\n');
|
|
401
|
+
let inDescription = false;
|
|
402
|
+
let inFiles = false;
|
|
403
|
+
const descriptionLines = [];
|
|
404
|
+
const files = [];
|
|
405
|
+
for (const line of lines) {
|
|
406
|
+
const trimmed = line.trim();
|
|
407
|
+
if (!trimmed) {
|
|
408
|
+
if (inDescription && !inFiles && descriptionLines.length > 0 && descriptionLines[descriptionLines.length - 1] !== '') {
|
|
409
|
+
descriptionLines.push('');
|
|
410
|
+
}
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
const headerMatch = trimmed.match(/^Change\s+(\d+)\s+by\s+(.+?)@(\S+)\s+on\s+(.+?)(?:\s+\*(\w+)\*)?$/);
|
|
414
|
+
if (headerMatch) {
|
|
415
|
+
const [, change, user, client, date, status] = headerMatch;
|
|
416
|
+
result.change = parseInt(change, 10);
|
|
417
|
+
result.user = user;
|
|
418
|
+
result.client = client;
|
|
419
|
+
result.date = date.trim();
|
|
420
|
+
result.status = status ? status.toLowerCase() : 'submitted';
|
|
421
|
+
inDescription = true;
|
|
422
|
+
inFiles = false;
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
if (/^Affected files\s+\.\.\.$/i.test(trimmed)) {
|
|
426
|
+
inDescription = false;
|
|
427
|
+
inFiles = true;
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
if (/^Differences\s+\.\.\.$/i.test(trimmed)) {
|
|
431
|
+
inDescription = false;
|
|
432
|
+
inFiles = false;
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
if (inFiles && trimmed.startsWith('... ')) {
|
|
436
|
+
const fileMatch = trimmed.match(/^\.{3}\s+(.+?)#([^\s]+)\s+([^\s]+)(?:\s+\((.+?)\))?$/);
|
|
437
|
+
if (fileMatch) {
|
|
438
|
+
const [, depotFile, revisionRaw, action, type] = fileMatch;
|
|
439
|
+
const numericRevision = parseInt(revisionRaw, 10);
|
|
440
|
+
files.push({
|
|
441
|
+
depotFile,
|
|
442
|
+
revision: Number.isNaN(numericRevision) ? revisionRaw : numericRevision,
|
|
443
|
+
action,
|
|
444
|
+
type: type || undefined,
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
449
|
+
if (inDescription) {
|
|
450
|
+
descriptionLines.push(trimmed);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
while (descriptionLines.length > 0 && descriptionLines[descriptionLines.length - 1] === '') {
|
|
454
|
+
descriptionLines.pop();
|
|
455
|
+
}
|
|
456
|
+
result.description = descriptionLines.join('\n');
|
|
457
|
+
result.files = files;
|
|
458
|
+
result.fileCount = files.length;
|
|
459
|
+
return result;
|
|
460
|
+
}
|
|
295
461
|
/**
|
|
296
462
|
* Parse p4 sync output into sync records
|
|
297
463
|
*/
|
|
@@ -301,7 +467,7 @@ function parseSyncOutput(output) {
|
|
|
301
467
|
if (!output || typeof output !== 'string') {
|
|
302
468
|
return results;
|
|
303
469
|
}
|
|
304
|
-
const lines = output
|
|
470
|
+
const lines = getNormalizedLines(output, { removeEmpty: true });
|
|
305
471
|
for (const line of lines) {
|
|
306
472
|
// Format: "//depot/path#revision - updating /local/path"
|
|
307
473
|
// Format: "//depot/path#revision - added as /local/path"
|
|
@@ -328,7 +494,7 @@ function parseResolveOutput(output) {
|
|
|
328
494
|
if (!output || typeof output !== 'string') {
|
|
329
495
|
return results;
|
|
330
496
|
}
|
|
331
|
-
const lines = output
|
|
497
|
+
const lines = getNormalizedLines(output, { removeEmpty: true });
|
|
332
498
|
for (const line of lines) {
|
|
333
499
|
// Format: "C /path/to/file - merging //depot/path#123"
|
|
334
500
|
const match = line.match(/^([A-Z])\s+(.+?)\s+-\s+(.+)/);
|
|
@@ -353,7 +519,7 @@ function parseShelveOutput(output) {
|
|
|
353
519
|
if (!output || typeof output !== 'string') {
|
|
354
520
|
return results;
|
|
355
521
|
}
|
|
356
|
-
const lines = output
|
|
522
|
+
const lines = getNormalizedLines(output, { removeEmpty: true });
|
|
357
523
|
for (const line of lines) {
|
|
358
524
|
// Format: "change 12345 shelved"
|
|
359
525
|
const match = line.match(/^change\s+(\d+)\s+shelved/);
|
|
@@ -376,7 +542,7 @@ function parseUnshelveOutput(output) {
|
|
|
376
542
|
if (!output || typeof output !== 'string') {
|
|
377
543
|
return results;
|
|
378
544
|
}
|
|
379
|
-
const lines = output
|
|
545
|
+
const lines = getNormalizedLines(output, { removeEmpty: true });
|
|
380
546
|
for (const line of lines) {
|
|
381
547
|
// Format: "//depot/path#123 - unshelved"
|
|
382
548
|
const match = line.match(/^(.+?)#(\d+)\s+-\s+unshelved/);
|
|
@@ -400,7 +566,7 @@ function parseBlameOutput(output) {
|
|
|
400
566
|
if (!output || typeof output !== 'string') {
|
|
401
567
|
return results;
|
|
402
568
|
}
|
|
403
|
-
const lines = output
|
|
569
|
+
const lines = getNormalizedLines(output, { removeEmpty: true });
|
|
404
570
|
for (const line of lines) {
|
|
405
571
|
// Format: "12345: user 2023/01/01 12:34:56: content"
|
|
406
572
|
const match = line.match(/^(\d+):\s+(.+?)\s+(\d{4}\/\d{2}\/\d{2})\s+(\d{2}:\d{2}:\d{2}):\s*(.*)/);
|
|
@@ -426,7 +592,7 @@ function parseCopyOutput(output) {
|
|
|
426
592
|
if (!output || typeof output !== 'string') {
|
|
427
593
|
return results;
|
|
428
594
|
}
|
|
429
|
-
const lines = output
|
|
595
|
+
const lines = getNormalizedLines(output, { removeEmpty: true });
|
|
430
596
|
for (const line of lines) {
|
|
431
597
|
// Format: "//depot/src#123 - copied to //depot/dst#124"
|
|
432
598
|
const match = line.match(/^(.+?)#(\d+)\s+-\s+copied\s+to\s+(.+?)#(\d+)/);
|
|
@@ -452,7 +618,7 @@ function parseMoveOutput(output) {
|
|
|
452
618
|
if (!output || typeof output !== 'string') {
|
|
453
619
|
return results;
|
|
454
620
|
}
|
|
455
|
-
const lines = output
|
|
621
|
+
const lines = getNormalizedLines(output, { removeEmpty: true });
|
|
456
622
|
for (const line of lines) {
|
|
457
623
|
// Format: "//depot/old#123 - moved to //depot/new#124"
|
|
458
624
|
const match = line.match(/^(.+?)#(\d+)\s+-\s+moved\s+to\s+(.+?)#(\d+)/);
|
|
@@ -478,7 +644,7 @@ function parseGrepOutput(output) {
|
|
|
478
644
|
if (!output || typeof output !== 'string') {
|
|
479
645
|
return results;
|
|
480
646
|
}
|
|
481
|
-
const lines = output
|
|
647
|
+
const lines = getNormalizedLines(output, { removeEmpty: true });
|
|
482
648
|
for (const line of lines) {
|
|
483
649
|
// Format: "//depot/path:123:matched text here"
|
|
484
650
|
const match = line.match(/^(.+?):(\d+):(.*)/);
|
|
@@ -502,7 +668,7 @@ function parseFilesOutput(output) {
|
|
|
502
668
|
if (!output || typeof output !== 'string') {
|
|
503
669
|
return results;
|
|
504
670
|
}
|
|
505
|
-
const lines = output
|
|
671
|
+
const lines = getNormalizedLines(output, { removeEmpty: true });
|
|
506
672
|
for (const line of lines) {
|
|
507
673
|
// Format: "//depot/path#123 - action change 456 (type text)"
|
|
508
674
|
const match = line.match(/^(.+?)#(\d+)\s+-\s+(\w+)\s+change\s+(\d+)\s+\((.+?)\)/);
|
|
@@ -528,7 +694,7 @@ function parseDirsOutput(output) {
|
|
|
528
694
|
if (!output || typeof output !== 'string') {
|
|
529
695
|
return results;
|
|
530
696
|
}
|
|
531
|
-
const lines = output
|
|
697
|
+
const lines = getNormalizedLines(output, { removeEmpty: true });
|
|
532
698
|
for (const line of lines) {
|
|
533
699
|
// Directories are listed one per line
|
|
534
700
|
if (line.startsWith('//')) {
|
|
@@ -548,7 +714,7 @@ function parseUsersOutput(output) {
|
|
|
548
714
|
if (!output || typeof output !== 'string') {
|
|
549
715
|
return results;
|
|
550
716
|
}
|
|
551
|
-
const lines = output
|
|
717
|
+
const lines = getNormalizedLines(output, { removeEmpty: true });
|
|
552
718
|
for (const line of lines) {
|
|
553
719
|
// Format: "user <user@domain> (realname) accessed 2023/01/01 12:34:56"
|
|
554
720
|
const match = line.match(/^(\w+)\s+<([^>]+)>\s+\(([^)]+)\)\s+accessed\s+(\d{4}\/\d{2}\/\d{2})\s+(\d{2}:\d{2}:\d{2})/);
|
|
@@ -573,7 +739,7 @@ function parseUserOutput(output) {
|
|
|
573
739
|
if (!output || typeof output !== 'string') {
|
|
574
740
|
return result;
|
|
575
741
|
}
|
|
576
|
-
const lines = output
|
|
742
|
+
const lines = getNormalizedLines(output);
|
|
577
743
|
for (const line of lines) {
|
|
578
744
|
const colonIndex = line.indexOf(': ');
|
|
579
745
|
if (colonIndex > 0) {
|
|
@@ -593,7 +759,7 @@ function parseClientOutput(output) {
|
|
|
593
759
|
if (!output || typeof output !== 'string') {
|
|
594
760
|
return result;
|
|
595
761
|
}
|
|
596
|
-
const lines = output
|
|
762
|
+
const lines = getNormalizedLines(output);
|
|
597
763
|
for (const line of lines) {
|
|
598
764
|
const colonIndex = line.indexOf(': ');
|
|
599
765
|
if (colonIndex > 0) {
|
|
@@ -613,7 +779,7 @@ function parseJobsOutput(output) {
|
|
|
613
779
|
if (!output || typeof output !== 'string') {
|
|
614
780
|
return results;
|
|
615
781
|
}
|
|
616
|
-
const lines = output
|
|
782
|
+
const lines = getNormalizedLines(output, { removeEmpty: true });
|
|
617
783
|
for (const line of lines) {
|
|
618
784
|
// Format: "job000001 on 2023/01/01 by user *open* 'Job description'"
|
|
619
785
|
const match = line.match(/^(\w+)\s+on\s+(\d{4}\/\d{2}\/\d{2})\s+by\s+(\w+)\s+(\w+)\s+'(.*)'/);
|
|
@@ -639,7 +805,7 @@ function parseJobOutput(output) {
|
|
|
639
805
|
if (!output || typeof output !== 'string') {
|
|
640
806
|
return result;
|
|
641
807
|
}
|
|
642
|
-
const lines = output
|
|
808
|
+
const lines = getNormalizedLines(output);
|
|
643
809
|
for (const line of lines) {
|
|
644
810
|
const colonIndex = line.indexOf(': ');
|
|
645
811
|
if (colonIndex > 0) {
|
|
@@ -659,7 +825,7 @@ function parseFixesOutput(output) {
|
|
|
659
825
|
if (!output || typeof output !== 'string') {
|
|
660
826
|
return results;
|
|
661
827
|
}
|
|
662
|
-
const lines = output
|
|
828
|
+
const lines = getNormalizedLines(output, { removeEmpty: true });
|
|
663
829
|
for (const line of lines) {
|
|
664
830
|
// Format: "job000001 fixed by change 12345 on 2023/01/01 by user@client"
|
|
665
831
|
const match = line.match(/^(\w+)\s+fixed\s+by\s+change\s+(\d+)\s+on\s+(\d{4}\/\d{2}\/\d{2})\s+by\s+(.+)$/);
|
|
@@ -684,7 +850,7 @@ function parseLabelsOutput(output) {
|
|
|
684
850
|
if (!output || typeof output !== 'string') {
|
|
685
851
|
return results;
|
|
686
852
|
}
|
|
687
|
-
const lines = output
|
|
853
|
+
const lines = getNormalizedLines(output, { removeEmpty: true });
|
|
688
854
|
for (const line of lines) {
|
|
689
855
|
// Format: "Label labelname 2023/01/01 'Label description'"
|
|
690
856
|
const match = line.match(/^Label\s+(\w+)\s+(\d{4}\/\d{2}\/\d{2})\s+'(.*)'/);
|
|
@@ -708,7 +874,7 @@ function parseLabelOutput(output) {
|
|
|
708
874
|
if (!output || typeof output !== 'string') {
|
|
709
875
|
return result;
|
|
710
876
|
}
|
|
711
|
-
const lines = output
|
|
877
|
+
const lines = getNormalizedLines(output);
|
|
712
878
|
for (const line of lines) {
|
|
713
879
|
const colonIndex = line.indexOf(': ');
|
|
714
880
|
if (colonIndex > 0) {
|
|
@@ -732,7 +898,7 @@ function parseSizesOutput(output) {
|
|
|
732
898
|
};
|
|
733
899
|
}
|
|
734
900
|
const result = {};
|
|
735
|
-
const lines = output
|
|
901
|
+
const lines = getNormalizedLines(output, { removeEmpty: true });
|
|
736
902
|
for (const line of lines) {
|
|
737
903
|
if (line.includes('files,')) {
|
|
738
904
|
// Format: "12345 files, 67890123 bytes"
|
|
@@ -754,7 +920,7 @@ function parseHaveOutput(output) {
|
|
|
754
920
|
if (!output || typeof output !== 'string') {
|
|
755
921
|
return results;
|
|
756
922
|
}
|
|
757
|
-
const lines = output
|
|
923
|
+
const lines = getNormalizedLines(output, { removeEmpty: true });
|
|
758
924
|
for (const line of lines) {
|
|
759
925
|
// Format: "//depot/path#123 - /local/path"
|
|
760
926
|
const match = line.match(/^(.+?)#(\d+)\s+-\s+(.+)/);
|
|
@@ -778,7 +944,7 @@ function parseWhereOutput(output) {
|
|
|
778
944
|
if (!output || typeof output !== 'string') {
|
|
779
945
|
return results;
|
|
780
946
|
}
|
|
781
|
-
const lines = output
|
|
947
|
+
const lines = getNormalizedLines(output, { removeEmpty: true });
|
|
782
948
|
for (const line of lines) {
|
|
783
949
|
// Format: "//depot/path /local/path //depot/path"
|
|
784
950
|
const parts = line.split(/\s+/);
|
|
@@ -792,6 +958,105 @@ function parseWhereOutput(output) {
|
|
|
792
958
|
}
|
|
793
959
|
return results;
|
|
794
960
|
}
|
|
961
|
+
/**
|
|
962
|
+
* Parse p4 fstat output into records
|
|
963
|
+
*/
|
|
964
|
+
function parseFstatOutput(output) {
|
|
965
|
+
const records = parseZtagOutput(output);
|
|
966
|
+
return records.map((record) => ({ ...record }));
|
|
967
|
+
}
|
|
968
|
+
/**
|
|
969
|
+
* Parse p4 streams output into stream records
|
|
970
|
+
*/
|
|
971
|
+
function parseStreamsOutput(output) {
|
|
972
|
+
const results = [];
|
|
973
|
+
if (!output || typeof output !== 'string') {
|
|
974
|
+
return results;
|
|
975
|
+
}
|
|
976
|
+
const lines = getNormalizedLines(output, { removeEmpty: true });
|
|
977
|
+
for (const line of lines) {
|
|
978
|
+
// Format: "Stream //Depot/main 2025/01/01 owner 'description'"
|
|
979
|
+
const match = line.match(/^Stream\s+(\S+)\s+(\S+)\s+(\S+)\s+'(.*)'$/);
|
|
980
|
+
if (match) {
|
|
981
|
+
const [, stream, date, owner, description] = match;
|
|
982
|
+
results.push({
|
|
983
|
+
stream,
|
|
984
|
+
date,
|
|
985
|
+
owner,
|
|
986
|
+
description,
|
|
987
|
+
});
|
|
988
|
+
}
|
|
989
|
+
else if (line.startsWith('//')) {
|
|
990
|
+
results.push({ stream: line.trim() });
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
return results;
|
|
994
|
+
}
|
|
995
|
+
/**
|
|
996
|
+
* Parse p4 stream output into a stream spec key/value object
|
|
997
|
+
*/
|
|
998
|
+
function parseStreamOutput(output) {
|
|
999
|
+
const result = {};
|
|
1000
|
+
if (!output || typeof output !== 'string') {
|
|
1001
|
+
return result;
|
|
1002
|
+
}
|
|
1003
|
+
const lines = getNormalizedLines(output);
|
|
1004
|
+
for (const line of lines) {
|
|
1005
|
+
const colonIndex = line.indexOf(':');
|
|
1006
|
+
if (colonIndex > 0) {
|
|
1007
|
+
const key = line.substring(0, colonIndex).trim();
|
|
1008
|
+
const value = line.substring(colonIndex + 1).trim();
|
|
1009
|
+
if (key) {
|
|
1010
|
+
result[key] = value;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
return result;
|
|
1015
|
+
}
|
|
1016
|
+
/**
|
|
1017
|
+
* Parse p4 print output content
|
|
1018
|
+
*/
|
|
1019
|
+
function parsePrintOutput(output) {
|
|
1020
|
+
if (!output || typeof output !== 'string') {
|
|
1021
|
+
return { content: '' };
|
|
1022
|
+
}
|
|
1023
|
+
return {
|
|
1024
|
+
content: getNormalizedLines(output).join('\n').trim(),
|
|
1025
|
+
};
|
|
1026
|
+
}
|
|
1027
|
+
/**
|
|
1028
|
+
* Parse p4 integrate/merge output into operation records
|
|
1029
|
+
*/
|
|
1030
|
+
function parseIntegrateOutput(output) {
|
|
1031
|
+
const results = [];
|
|
1032
|
+
if (!output || typeof output !== 'string') {
|
|
1033
|
+
return results;
|
|
1034
|
+
}
|
|
1035
|
+
const lines = getNormalizedLines(output, { removeEmpty: true });
|
|
1036
|
+
for (const line of lines) {
|
|
1037
|
+
// Common formats:
|
|
1038
|
+
// //depot/target#5 - integrate from //depot/source#7
|
|
1039
|
+
// //depot/target#none - branch from //depot/source#1
|
|
1040
|
+
const match = line.match(/^(.+?)#([^\s]+)\s+-\s+(.+?)(?:\s+from\s+(.+?)#([^\s]+))?$/);
|
|
1041
|
+
if (match) {
|
|
1042
|
+
const [, targetFile, targetRevisionRaw, action, sourceFile, sourceRevisionRaw] = match;
|
|
1043
|
+
const targetRevisionNum = parseInt(targetRevisionRaw, 10);
|
|
1044
|
+
const sourceRevisionNum = sourceRevisionRaw ? parseInt(sourceRevisionRaw, 10) : NaN;
|
|
1045
|
+
results.push({
|
|
1046
|
+
targetFile,
|
|
1047
|
+
targetRevision: Number.isNaN(targetRevisionNum) ? targetRevisionRaw : targetRevisionNum,
|
|
1048
|
+
action: action.trim(),
|
|
1049
|
+
sourceFile: sourceFile || undefined,
|
|
1050
|
+
sourceRevision: sourceRevisionRaw
|
|
1051
|
+
? Number.isNaN(sourceRevisionNum)
|
|
1052
|
+
? sourceRevisionRaw
|
|
1053
|
+
: sourceRevisionNum
|
|
1054
|
+
: undefined,
|
|
1055
|
+
});
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
return results;
|
|
1059
|
+
}
|
|
795
1060
|
/**
|
|
796
1061
|
* Helper function to convert resolve status to action
|
|
797
1062
|
*/
|
|
@@ -804,6 +1069,53 @@ function getResolveAction(status) {
|
|
|
804
1069
|
default: return 'unknown';
|
|
805
1070
|
}
|
|
806
1071
|
}
|
|
1072
|
+
/**
|
|
1073
|
+
* Strip p4 -s script mode prefixes (info1:, error:, exit:, etc.) from output lines
|
|
1074
|
+
*/
|
|
1075
|
+
function stripScriptPrefix(line) {
|
|
1076
|
+
const match = line.match(/^(info\d*|text|warning|error|exit):\s?(.*)$/i);
|
|
1077
|
+
if (!match) {
|
|
1078
|
+
return line;
|
|
1079
|
+
}
|
|
1080
|
+
const [, prefix, value] = match;
|
|
1081
|
+
if (prefix.toLowerCase() === 'exit') {
|
|
1082
|
+
return '';
|
|
1083
|
+
}
|
|
1084
|
+
return value;
|
|
1085
|
+
}
|
|
1086
|
+
function getNormalizedLines(output, options = {}) {
|
|
1087
|
+
if (!output || typeof output !== 'string') {
|
|
1088
|
+
return [];
|
|
1089
|
+
}
|
|
1090
|
+
const { trim = false, removeEmpty = false } = options;
|
|
1091
|
+
const lines = output.split('\n');
|
|
1092
|
+
const normalized = [];
|
|
1093
|
+
for (const rawLine of lines) {
|
|
1094
|
+
const stripped = stripScriptPrefix(rawLine.replace(/\r$/, ''));
|
|
1095
|
+
const value = trim ? stripped.trim() : stripped;
|
|
1096
|
+
if (removeEmpty && value.trim().length === 0) {
|
|
1097
|
+
continue;
|
|
1098
|
+
}
|
|
1099
|
+
normalized.push(value);
|
|
1100
|
+
}
|
|
1101
|
+
return normalized;
|
|
1102
|
+
}
|
|
1103
|
+
/**
|
|
1104
|
+
* Parse a single side of a diff2 header line
|
|
1105
|
+
*/
|
|
1106
|
+
function parseDiff2Side(side) {
|
|
1107
|
+
const match = side.trim().match(/^(.+?)#([^\s]+)(?:\s+\((.+?)\))?$/);
|
|
1108
|
+
if (!match) {
|
|
1109
|
+
return { depotFile: side.trim(), revision: 'unknown' };
|
|
1110
|
+
}
|
|
1111
|
+
const [, depotFile, revisionRaw, type] = match;
|
|
1112
|
+
const numericRevision = parseInt(revisionRaw, 10);
|
|
1113
|
+
return {
|
|
1114
|
+
depotFile,
|
|
1115
|
+
revision: Number.isNaN(numericRevision) ? revisionRaw : numericRevision,
|
|
1116
|
+
type: type || undefined,
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
807
1119
|
/**
|
|
808
1120
|
* Parse individual values with type conversion
|
|
809
1121
|
*/
|