sfdx-git-delta 7.4.0 → 7.4.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/README.md +22 -3
- package/lib/adapter/GitAdapter.d.ts +5 -3
- package/lib/adapter/GitAdapter.js +71 -32
- package/lib/adapter/GitAdapter.js.map +1 -1
- package/lib/adapter/pathMatching.d.ts +2 -1
- package/lib/adapter/pathMatching.js +35 -3
- package/lib/adapter/pathMatching.js.map +1 -1
- package/lib/adapter/treeReader.d.ts +1 -0
- package/lib/adapter/treeReader.js +10 -0
- package/lib/adapter/treeReader.js.map +1 -1
- package/lib/adapter/tsgitErrorMap.d.ts +5 -4
- package/lib/adapter/tsgitErrorMap.js +12 -5
- package/lib/adapter/tsgitErrorMap.js.map +1 -1
- package/lib/constant/metadataConstants.d.ts +12 -1
- package/lib/constant/metadataConstants.js +28 -3
- package/lib/constant/metadataConstants.js.map +1 -1
- package/lib/main.js +66 -63
- package/lib/main.js.map +1 -1
- package/lib/metadata/MetadataRepositoryImpl.d.ts +14 -2
- package/lib/metadata/MetadataRepositoryImpl.js +192 -50
- package/lib/metadata/MetadataRepositoryImpl.js.map +1 -1
- package/lib/post-processor/changesManifestProcessor.js +14 -1
- package/lib/post-processor/changesManifestProcessor.js.map +1 -1
- package/lib/post-processor/includeProcessor.js +8 -9
- package/lib/post-processor/includeProcessor.js.map +1 -1
- package/lib/service/botHandler.js +1 -1
- package/lib/service/botHandler.js.map +1 -1
- package/lib/service/inBundleHandler.js +10 -23
- package/lib/service/inBundleHandler.js.map +1 -1
- package/lib/service/inFolderHandler.js +1 -2
- package/lib/service/inFolderHandler.js.map +1 -1
- package/lib/service/objectTranslationHandler.d.ts +0 -1
- package/lib/service/objectTranslationHandler.js +11 -4
- package/lib/service/objectTranslationHandler.js.map +1 -1
- package/lib/types/runContext.d.ts +16 -5
- package/lib/types/runContext.js +17 -5
- package/lib/types/runContext.js.map +1 -1
- package/lib/utils/__mocks__/LoggingService.d.ts +2 -7
- package/lib/utils/__mocks__/LoggingService.js.map +1 -1
- package/lib/utils/changeSet.d.ts +4 -0
- package/lib/utils/changeSet.js +87 -37
- package/lib/utils/changeSet.js.map +1 -1
- package/lib/utils/configValidator.d.ts +11 -1
- package/lib/utils/configValidator.js +133 -80
- package/lib/utils/configValidator.js.map +1 -1
- package/lib/utils/errorUtils.d.ts +5 -0
- package/lib/utils/errorUtils.js +8 -0
- package/lib/utils/errorUtils.js.map +1 -1
- package/lib/utils/messageSanitizer.d.ts +1 -0
- package/lib/utils/messageSanitizer.js +46 -0
- package/lib/utils/messageSanitizer.js.map +1 -1
- package/lib/utils/metadataBoundaryResolver.d.ts +4 -0
- package/lib/utils/metadataBoundaryResolver.js +40 -29
- package/lib/utils/metadataBoundaryResolver.js.map +1 -1
- package/lib/utils/metadataDiff/deepEqualJson.js +51 -44
- package/lib/utils/metadataDiff/deepEqualJson.js.map +1 -1
- package/lib/utils/metadataDiff/xmlEventReader.js +60 -50
- package/lib/utils/metadataDiff/xmlEventReader.js.map +1 -1
- package/lib/utils/repoGitDiff.d.ts +20 -2
- package/lib/utils/repoGitDiff.js +129 -35
- package/lib/utils/repoGitDiff.js.map +1 -1
- package/lib/utils/treeIndexScope.js +25 -37
- package/lib/utils/treeIndexScope.js.map +1 -1
- package/lib/utils/treeReaderBuilder.d.ts +8 -0
- package/lib/utils/treeReaderBuilder.js +34 -0
- package/lib/utils/treeReaderBuilder.js.map +1 -0
- package/lib/utils/txmlAdapter.js +1 -1
- package/lib/utils/txmlAdapter.js.map +1 -1
- package/messages/delta.md +16 -5
- package/oclif.manifest.json +1 -1
- package/package.json +90 -49
|
@@ -1,4 +1,51 @@
|
|
|
1
1
|
'use strict';
|
|
2
|
+
// The push helpers return false on the first child pair already known to
|
|
3
|
+
// differ, so children are walked once rather than scanned then pushed.
|
|
4
|
+
const pushArrayChildPairs = (arrA, arrB, stackA, stackB) => {
|
|
5
|
+
const len = arrA.length;
|
|
6
|
+
if (len !== arrB.length)
|
|
7
|
+
return false;
|
|
8
|
+
// Stryker disable next-line EqualityOperator -- `i <= len` is equivalent: both arrays have length len, so the extra pass reads arrA[len] and arrB[len], both undefined, and `va === vb` continues out of the loop (the object loop's twin is killable: keysA[len] is undefined and Object.hasOwn(objB, undefined) refuses). `i >= len` shares the mutator name; on a non-empty array it skips the loop exactly like the killed ConditionalExpression false-flip on this condition
|
|
9
|
+
for (let i = 0; i < len; i++) {
|
|
10
|
+
const va = arrA[i];
|
|
11
|
+
const vb = arrB[i];
|
|
12
|
+
if (va === vb)
|
|
13
|
+
continue;
|
|
14
|
+
if (va === null ||
|
|
15
|
+
vb === null ||
|
|
16
|
+
typeof va !== 'object' ||
|
|
17
|
+
typeof vb !== 'object') {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
stackA.push(va);
|
|
21
|
+
stackB.push(vb);
|
|
22
|
+
}
|
|
23
|
+
return true;
|
|
24
|
+
};
|
|
25
|
+
const pushObjectChildPairs = (objA, objB, stackA, stackB) => {
|
|
26
|
+
const keysA = Object.keys(objA);
|
|
27
|
+
const len = keysA.length;
|
|
28
|
+
if (len !== Object.keys(objB).length)
|
|
29
|
+
return false;
|
|
30
|
+
for (let i = 0; i < len; i++) {
|
|
31
|
+
const key = keysA[i];
|
|
32
|
+
if (!Object.hasOwn(objB, key))
|
|
33
|
+
return false;
|
|
34
|
+
const va = objA[key];
|
|
35
|
+
const vb = objB[key];
|
|
36
|
+
if (va === vb)
|
|
37
|
+
continue;
|
|
38
|
+
if (va === null ||
|
|
39
|
+
vb === null ||
|
|
40
|
+
typeof va !== 'object' ||
|
|
41
|
+
typeof vb !== 'object') {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
stackA.push(va);
|
|
45
|
+
stackB.push(vb);
|
|
46
|
+
}
|
|
47
|
+
return true;
|
|
48
|
+
};
|
|
2
49
|
/**
|
|
3
50
|
* Iterative structural equality for JSON-like values (plain objects,
|
|
4
51
|
* arrays, primitives). Order-sensitive for arrays. Replaces the narrow
|
|
@@ -40,51 +87,11 @@ export const deepEqualJson = (a, b) => {
|
|
|
40
87
|
const aIsArr = Array.isArray(ai);
|
|
41
88
|
if (aIsArr !== Array.isArray(bi))
|
|
42
89
|
return false;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (len !== arrB.length)
|
|
48
|
-
return false;
|
|
49
|
-
for (let i = 0; i < len; i++) {
|
|
50
|
-
const va = arrA[i];
|
|
51
|
-
const vb = arrB[i];
|
|
52
|
-
if (va === vb)
|
|
53
|
-
continue;
|
|
54
|
-
if (va === null ||
|
|
55
|
-
vb === null ||
|
|
56
|
-
typeof va !== 'object' ||
|
|
57
|
-
typeof vb !== 'object') {
|
|
58
|
-
return false;
|
|
59
|
-
}
|
|
60
|
-
stackA.push(va);
|
|
61
|
-
stackB.push(vb);
|
|
62
|
-
}
|
|
63
|
-
continue;
|
|
64
|
-
}
|
|
65
|
-
const objA = ai;
|
|
66
|
-
const objB = bi;
|
|
67
|
-
const keysA = Object.keys(objA);
|
|
68
|
-
const len = keysA.length;
|
|
69
|
-
if (len !== Object.keys(objB).length)
|
|
90
|
+
const childrenMatch = aIsArr
|
|
91
|
+
? pushArrayChildPairs(ai, bi, stackA, stackB)
|
|
92
|
+
: pushObjectChildPairs(ai, bi, stackA, stackB);
|
|
93
|
+
if (!childrenMatch)
|
|
70
94
|
return false;
|
|
71
|
-
for (let i = 0; i < len; i++) {
|
|
72
|
-
const key = keysA[i];
|
|
73
|
-
if (!Object.hasOwn(objB, key))
|
|
74
|
-
return false;
|
|
75
|
-
const va = objA[key];
|
|
76
|
-
const vb = objB[key];
|
|
77
|
-
if (va === vb)
|
|
78
|
-
continue;
|
|
79
|
-
if (va === null ||
|
|
80
|
-
vb === null ||
|
|
81
|
-
typeof va !== 'object' ||
|
|
82
|
-
typeof vb !== 'object') {
|
|
83
|
-
return false;
|
|
84
|
-
}
|
|
85
|
-
stackA.push(va);
|
|
86
|
-
stackB.push(vb);
|
|
87
|
-
}
|
|
88
95
|
}
|
|
89
96
|
return true;
|
|
90
97
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deepEqualJson.js","sourceRoot":"","sources":["../../../src/utils/metadataDiff/deepEqualJson.ts"],"names":[],"mappings":"AAAA,YAAY,CAAA;AAEZ
|
|
1
|
+
{"version":3,"file":"deepEqualJson.js","sourceRoot":"","sources":["../../../src/utils/metadataDiff/deepEqualJson.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
|
-
import { parse as txmlParse } from 'txml';
|
|
2
|
+
import { parse as txmlParse } from 'txml/txml';
|
|
3
3
|
import { getErrorMessage } from '../errorUtils.js';
|
|
4
4
|
import { Logger, lazy } from '../LoggingService.js';
|
|
5
5
|
import { tNodeToXmlContent, } from '../txmlAdapter.js';
|
|
@@ -16,10 +16,8 @@ const parseDeclaration = (decl) => {
|
|
|
16
16
|
const tree = txmlParse(decl);
|
|
17
17
|
const declNode = tree.find(
|
|
18
18
|
// Stryker disable ConditionalExpression,LogicalOperator,StringLiteral -- equivalent: this guard discriminates the declaration node from txml's tree output; the SF metadata declaration is always the first/only TxmlNode with tagName XML_HEADER_ATTRIBUTE_KEY, so the AND-vs-OR and 'string' literal mutants find the same node
|
|
19
|
-
(n) => typeof n !== 'string' && n.tagName === XML_HEADER_ATTRIBUTE_KEY
|
|
19
|
+
(n) => typeof n !== 'string' && n.tagName === XML_HEADER_ATTRIBUTE_KEY);
|
|
20
20
|
// Stryker restore ConditionalExpression,LogicalOperator,StringLiteral
|
|
21
|
-
);
|
|
22
|
-
// Stryker disable next-line ConditionalExpression,ObjectLiteral -- equivalent: defensive fallback when txml returns no declaration node; SF metadata always provides a declaration so the fallback is unreachable, and the empty-object replacement is symmetric with the populated headerAttrs result downstream
|
|
23
21
|
if (!declNode)
|
|
24
22
|
return { [XML_HEADER_ATTRIBUTE_KEY]: {} };
|
|
25
23
|
const headerAttrs = {};
|
|
@@ -30,70 +28,64 @@ const parseDeclaration = (decl) => {
|
|
|
30
28
|
}
|
|
31
29
|
return { [XML_HEADER_ATTRIBUTE_KEY]: headerAttrs };
|
|
32
30
|
};
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
//
|
|
38
|
-
const parsePrologue = (xml) => {
|
|
39
|
-
let i = 0;
|
|
40
|
-
let xmlHeader;
|
|
41
|
-
const declMatch = XML_DECL_RE.exec(xml);
|
|
42
|
-
// Stryker disable next-line ConditionalExpression -- equivalent: the index === 0 anchor is symmetric with the regex's `^` anchor; for the only inputs reachable here both produce identical (declMatch.index === 0) outcomes
|
|
43
|
-
if (declMatch && declMatch.index === 0) {
|
|
44
|
-
xmlHeader = parseDeclaration(declMatch[0]);
|
|
45
|
-
i += declMatch[0].length;
|
|
46
|
-
}
|
|
47
|
-
// Stryker disable next-line BlockStatement,EqualityOperator -- equivalent: prologue scan loop; emptying the body returns null on missing root which is semantically equivalent to falling out the bottom and returning null via the unmatched ROOT_OPEN_RE; <= vs < changes the iteration boundary by one position which matters only when xml ends in whitespace right at the root tag boundary, a case not present in test fixtures
|
|
31
|
+
// Returns the offset of the first character that is not whitespace, a
|
|
32
|
+
// comment or a `<!...>` declaration, or null when a declaration never closes.
|
|
33
|
+
const skipPrologueMisc = (xml, start) => {
|
|
34
|
+
let i = start;
|
|
35
|
+
// Stryker disable next-line BlockStatement,EqualityOperator -- BlockStatement is not equivalent but unaffordable: an emptied body never advances `i`, so the mutant hangs until Stryker's timeout. EqualityOperator: `<=` is equivalent (one extra pass at i === xml.length matches nothing and breaks at the same offset); `>=` is killable but shares the mutator name, and the loop-skipping behaviour it produces is already killed through the ConditionalExpression false-flip
|
|
48
36
|
while (i < xml.length) {
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
const commentMatch = COMMENT_RE.exec(xml.slice(i));
|
|
55
|
-
if (commentMatch) {
|
|
56
|
-
i += commentMatch[0].length;
|
|
37
|
+
const rest = xml.slice(i);
|
|
38
|
+
const skippable = WS_RE.exec(rest) ?? COMMENT_RE.exec(rest);
|
|
39
|
+
if (skippable) {
|
|
40
|
+
i += skippable[0].length;
|
|
57
41
|
continue;
|
|
58
42
|
}
|
|
59
43
|
// `<!...>` declarations (DOCTYPE, ENTITY, etc.) before the root.
|
|
60
44
|
// Naive boundary at the next `>` — none of our metadata payloads use
|
|
61
45
|
// bracketed internal subsets, so a more elaborate scan would be
|
|
62
46
|
// unused work.
|
|
63
|
-
// Stryker disable MethodExpression,ArithmeticOperator,ConditionalExpression,EqualityOperator -- equivalent: this branch handles `<!DOCTYPE>` style prologue elements; SF metadata never includes DOCTYPE declarations, so the branch body is unreachable for the test corpus
|
|
64
47
|
if (xml.startsWith('<!', i) && !xml.startsWith('<!--', i)) {
|
|
65
48
|
const end = xml.indexOf('>', i + 2);
|
|
49
|
+
// Stryker disable next-line EqualityOperator -- `end <= 0` is equivalent: the search starts at i + 2, so indexOf returns -1 or an offset of at least 2, never 0. `end >= 0` shares the mutator name; whenever a `>` exists it returns null exactly like the killed ConditionalExpression true-flip on this condition
|
|
66
50
|
if (end < 0)
|
|
67
51
|
return null;
|
|
68
52
|
i = end + 1;
|
|
69
53
|
continue;
|
|
70
54
|
}
|
|
71
|
-
// Stryker restore MethodExpression,ArithmeticOperator,ConditionalExpression,EqualityOperator
|
|
72
55
|
break;
|
|
73
56
|
}
|
|
74
|
-
|
|
57
|
+
return i;
|
|
58
|
+
};
|
|
59
|
+
const matchRootOpenTag = (xml, start) => {
|
|
60
|
+
ROOT_OPEN_RE.lastIndex = start;
|
|
75
61
|
const rootMatch = ROOT_OPEN_RE.exec(xml);
|
|
76
62
|
// Stryker disable next-line ConditionalExpression -- equivalent: rootMatch validation; flipping to false continues with rootMatch undefined which throws on the next destructure — but the prologue function returns null in either case (the throw is caught by driveParse which surfaces it)
|
|
77
|
-
if (!rootMatch || rootMatch.index !==
|
|
63
|
+
if (!rootMatch || rootMatch.index !== start)
|
|
78
64
|
return null;
|
|
79
65
|
const [full, rootName, attrsRaw] = rootMatch;
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
66
|
+
return {
|
|
67
|
+
rootName: rootName,
|
|
68
|
+
attrsRaw: attrsRaw,
|
|
69
|
+
bodyStart: start + full.length,
|
|
70
|
+
// Stryker disable next-line MethodExpression -- equivalent: trimEnd().endsWith('/') checks if attrs end with self-closing slash; trimStart instead would not affect SF metadata test fixtures because the trailing slash detection still works on the original attrsRaw whose trailing whitespace doesn't include the slash
|
|
71
|
+
isSelfClosing: attrsRaw.trimEnd().endsWith('/'),
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
// Reuse txml to parse just the root open tag's attributes consistently
|
|
75
|
+
// with how it parses every other tag. Wrapping it as self-closing gives
|
|
76
|
+
// us a clean parse target that always returns one node.
|
|
77
|
+
const syntheticRootTag = ({ rootName, attrsRaw, isSelfClosing, }) => {
|
|
86
78
|
// Stryker disable Regex -- equivalent: the trailing slash strip lets us re-wrap as a self-closing tag; mutants on /\/\s*$/ produce a regex with broader/narrower whitespace matching but SF metadata's self-closing root tag has no trailing whitespace before the `/`, so the strip is a no-op either way
|
|
87
|
-
const
|
|
88
|
-
? `<${rootName}${attrsRaw.replace(/\/\s*$/, '')}/>`
|
|
89
|
-
: `<${rootName}${attrsRaw}/>`;
|
|
79
|
+
const attributes = isSelfClosing ? attrsRaw.replace(/\/\s*$/, '') : attrsRaw;
|
|
90
80
|
// Stryker restore Regex
|
|
91
|
-
|
|
81
|
+
return `<${rootName}${attributes}/>`;
|
|
82
|
+
};
|
|
83
|
+
const parseRootAttributes = (rootTag) => {
|
|
84
|
+
const syntheticTree = txmlParse(syntheticRootTag(rootTag));
|
|
92
85
|
const syntheticNode = syntheticTree.find(
|
|
93
86
|
// Stryker disable next-line ConditionalExpression,StringLiteral -- equivalent: txml's parse always returns one TxmlNode for our well-formed self-closing synthetic tag; the type-narrowing predicate is symmetric (true also picks the same node since there's only one)
|
|
94
87
|
(n) => typeof n !== 'string');
|
|
95
88
|
const rootAttributes = {};
|
|
96
|
-
// Stryker disable next-line ConditionalExpression -- equivalent: syntheticNode presence guard; txml always returns one node for our synthetic self-closing tag, so the false-flip is unreachable
|
|
97
89
|
if (syntheticNode) {
|
|
98
90
|
for (const key of Object.keys(syntheticNode.attributes)) {
|
|
99
91
|
const value = syntheticNode.attributes[key];
|
|
@@ -103,12 +95,31 @@ const parsePrologue = (xml) => {
|
|
|
103
95
|
value === null ? 'true' : value;
|
|
104
96
|
}
|
|
105
97
|
}
|
|
98
|
+
return rootAttributes;
|
|
99
|
+
};
|
|
100
|
+
// Walks the prologue (BOM, optional declaration, leading whitespace,
|
|
101
|
+
// comments, and `<!...>` declarations such as DOCTYPE) and stops at the
|
|
102
|
+
// root opening tag. Returns the root name, its attributes, the byte
|
|
103
|
+
// offset where the root's body starts so the streaming loop can pick up
|
|
104
|
+
// from there, and whether the root is self-closing.
|
|
105
|
+
const parsePrologue = (xml) => {
|
|
106
|
+
const declMatch = XML_DECL_RE.exec(xml);
|
|
107
|
+
// Stryker disable next-line ConditionalExpression -- equivalent: the index === 0 anchor is symmetric with the regex's `^` anchor; for the only inputs reachable here both produce identical (declMatch.index === 0) outcomes
|
|
108
|
+
const declaration = declMatch && declMatch.index === 0 ? declMatch[0] : '';
|
|
109
|
+
const xmlHeader = declaration ? parseDeclaration(declaration) : undefined;
|
|
110
|
+
const rootStart = skipPrologueMisc(xml, declaration.length);
|
|
111
|
+
// Stryker disable next-line ConditionalExpression -- the false-flip is equivalent: matchRootOpenTag receives null, and its `rootMatch.index !== start` guard can never match null, so it returns null as well. The true-flip is suppressed as a side effect; the always-return-null behaviour it produces is killed through the EqualityOperator mutant on the same condition
|
|
112
|
+
if (rootStart === null)
|
|
113
|
+
return null;
|
|
114
|
+
const rootTag = matchRootOpenTag(xml, rootStart);
|
|
115
|
+
if (rootTag === null)
|
|
116
|
+
return null;
|
|
106
117
|
return {
|
|
107
118
|
xmlHeader,
|
|
108
|
-
rootName: rootName,
|
|
109
|
-
rootAttributes,
|
|
110
|
-
bodyStart,
|
|
111
|
-
isSelfClosing,
|
|
119
|
+
rootName: rootTag.rootName,
|
|
120
|
+
rootAttributes: parseRootAttributes(rootTag),
|
|
121
|
+
bodyStart: rootTag.bodyStart,
|
|
122
|
+
isSelfClosing: rootTag.isSelfClosing,
|
|
112
123
|
};
|
|
113
124
|
};
|
|
114
125
|
// One-shot per-element parse via txml's parseNode primitive. We loop
|
|
@@ -173,20 +184,19 @@ const verifyTail = (xml, pos, rootName, isSelfClosing) => {
|
|
|
173
184
|
i++;
|
|
174
185
|
continue;
|
|
175
186
|
}
|
|
176
|
-
// Stryker disable ConditionalExpression,StringLiteral,EqualityOperator,ArithmeticOperator -- equivalent: trailing-comment branch; metadata XML doesn't have trailing comments after root close, so the branch body is unreachable for the tested fixtures
|
|
177
187
|
if (xml.startsWith('<!--', i)) {
|
|
178
188
|
const end = xml.indexOf('-->', i + 4);
|
|
189
|
+
// Stryker disable next-line EqualityOperator -- `end <= 0` is equivalent: the search starts at i + 4, so indexOf returns -1 or an offset of at least 4, never 0. `end >= 0` shares the mutator name; whenever the comment is terminated it throws exactly like the killed ConditionalExpression true-flip on this condition
|
|
179
190
|
if (end < 0)
|
|
180
191
|
throw new Error('unterminated comment after root close');
|
|
181
192
|
i = end + 3;
|
|
182
193
|
continue;
|
|
183
194
|
}
|
|
184
|
-
// Stryker
|
|
185
|
-
// Stryker disable MethodExpression,ArithmeticOperator,StringLiteral -- equivalent: this throw fires for unexpected content after root close; the slice with Math.min(i+30, xml.length) caps the message preview at 30 chars; mutants change the preview length/content which is observability only — tests assert that the throw fires
|
|
195
|
+
// Stryker disable MethodExpression,ArithmeticOperator -- equivalent: the slice with Math.min(i+30, xml.length) caps the message preview at 30 chars; these mutants change only that preview, which is observability only — tests pin the message prefix, not the preview
|
|
186
196
|
throw new Error(`unexpected content after root close: ${xml.slice(i, Math.min(i + 30, xml.length))}`);
|
|
187
|
-
// Stryker restore MethodExpression,ArithmeticOperator,StringLiteral
|
|
188
197
|
}
|
|
189
198
|
};
|
|
199
|
+
// Stryker restore MethodExpression,ArithmeticOperator
|
|
190
200
|
const driveParse = (source, onElement, options) => {
|
|
191
201
|
// Stryker disable next-line ConditionalExpression,StringLiteral -- equivalent: source string-vs-buffer dispatch; flipping to false (Buffer path) on a string source returns the same string (Buffer.from then toString roundtrips for ASCII), and tests pass both string and Buffer sources separately to verify each path
|
|
192
202
|
const payload = typeof source === 'string' ? source : source.toString('utf8');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"xmlEventReader.js","sourceRoot":"","sources":["../../../src/utils/metadataDiff/xmlEventReader.ts"],"names":[],"mappings":"AAAA,YAAY,CAAA;AACZ,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"xmlEventReader.js","sourceRoot":"","sources":["../../../src/utils/metadataDiff/xmlEventReader.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,31 +1,49 @@
|
|
|
1
1
|
import GitAdapter from '../adapter/GitAdapter.js';
|
|
2
2
|
import { MetadataRepository } from '../metadata/MetadataRepository.js';
|
|
3
3
|
import type { Config } from '../types/config.js';
|
|
4
|
+
import { type IgnoreHelper } from './ignoreHelper.js';
|
|
4
5
|
export type RenamePathPair = Readonly<{
|
|
5
6
|
fromPath: string;
|
|
6
7
|
toPath: string;
|
|
7
8
|
}>;
|
|
9
|
+
export type HeldAdditionProbeFailure = Readonly<{
|
|
10
|
+
candidateCount: number;
|
|
11
|
+
}>;
|
|
12
|
+
type DeferredDeletion = Readonly<{
|
|
13
|
+
line: string;
|
|
14
|
+
name: string;
|
|
15
|
+
}>;
|
|
8
16
|
export default class RepoGitDiff {
|
|
9
17
|
protected readonly config: Config;
|
|
10
18
|
protected readonly metadata: MetadataRepository;
|
|
11
19
|
protected readonly gitAdapter: GitAdapter;
|
|
12
20
|
private renamePairs;
|
|
13
21
|
private readonly diffScopeVerdict;
|
|
22
|
+
private probeFailure;
|
|
14
23
|
constructor(config: Config, metadata: MetadataRepository);
|
|
15
24
|
/**
|
|
16
25
|
* Streams the filtered, rename-expanded diff lines. Yields A/M lines as
|
|
17
26
|
* they arrive from git so handlers can start working immediately; D
|
|
18
27
|
* lines are buffered until upstream EOF because the deleted-renamed
|
|
19
|
-
* cancellation rule needs the full A-name set
|
|
20
|
-
*
|
|
28
|
+
* cancellation rule needs the full A-name set — including ignored
|
|
29
|
+
* additions, which are held and resolved against the `to` tree at EOF —
|
|
30
|
+
* before any D line can be classified.
|
|
21
31
|
*
|
|
22
32
|
* Rename pairs are captured along the way and exposed via
|
|
23
33
|
* getRenamePairs() once iteration completes.
|
|
24
34
|
*/
|
|
25
35
|
getLines(): AsyncGenerator<string>;
|
|
36
|
+
private _resetRunState;
|
|
37
|
+
private _routeLine;
|
|
38
|
+
private _routeAddition;
|
|
26
39
|
getRenamePairs(): readonly RenamePathPair[];
|
|
40
|
+
getHeldAdditionProbeFailure(): HeldAdditionProbeFailure | undefined;
|
|
27
41
|
getUnmatchedSourceScopes(): readonly string[];
|
|
28
42
|
protected _expandRename(line: string): Iterable<string>;
|
|
29
43
|
protected _extractComparisonName(line: string): string;
|
|
44
|
+
protected _vouchingHeldNames(held: ReadonlySet<string>, deferred: readonly DeferredDeletion[], registered: ReadonlySet<string>, ignoreHelper: IgnoreHelper): Promise<ReadonlySet<string>>;
|
|
45
|
+
protected _visibleNamesAtTo(candidates: ReadonlySet<string>, ignoreHelper: IgnoreHelper): Promise<ReadonlySet<string>>;
|
|
46
|
+
protected _listFilesAt(revision: string): Promise<readonly string[] | undefined>;
|
|
30
47
|
private diffSpec;
|
|
31
48
|
}
|
|
49
|
+
export {};
|
package/lib/utils/repoGitDiff.js
CHANGED
|
@@ -3,6 +3,7 @@ import GitAdapter from '../adapter/GitAdapter.js';
|
|
|
3
3
|
import { TAB } from '../constant/cliConstants.js';
|
|
4
4
|
import { ADDITION, DELETION, RENAMED } from '../constant/gitConstants.js';
|
|
5
5
|
import { buildIgnoreHelper } from './ignoreHelper.js';
|
|
6
|
+
import { Logger, lazy } from './LoggingService.js';
|
|
6
7
|
export default class RepoGitDiff {
|
|
7
8
|
config;
|
|
8
9
|
metadata;
|
|
@@ -17,6 +18,12 @@ export default class RepoGitDiff {
|
|
|
17
18
|
changesSeen: 0,
|
|
18
19
|
linesYielded: 0,
|
|
19
20
|
};
|
|
21
|
+
// Set when the `to` listing could not be read while held additions were
|
|
22
|
+
// still pending, so the run can tell the user its move detection was
|
|
23
|
+
// degraded instead of silently falling back to the uncancelled output.
|
|
24
|
+
// Reset per getLines() call, like the verdict above, and read after the
|
|
25
|
+
// diff is drained.
|
|
26
|
+
probeFailure;
|
|
20
27
|
constructor(config, metadata) {
|
|
21
28
|
this.config = config;
|
|
22
29
|
this.metadata = metadata;
|
|
@@ -26,59 +33,89 @@ export default class RepoGitDiff {
|
|
|
26
33
|
* Streams the filtered, rename-expanded diff lines. Yields A/M lines as
|
|
27
34
|
* they arrive from git so handlers can start working immediately; D
|
|
28
35
|
* lines are buffered until upstream EOF because the deleted-renamed
|
|
29
|
-
* cancellation rule needs the full A-name set
|
|
30
|
-
*
|
|
36
|
+
* cancellation rule needs the full A-name set — including ignored
|
|
37
|
+
* additions, which are held and resolved against the `to` tree at EOF —
|
|
38
|
+
* before any D line can be classified.
|
|
31
39
|
*
|
|
32
40
|
* Rename pairs are captured along the way and exposed via
|
|
33
41
|
* getRenamePairs() once iteration completes.
|
|
34
42
|
*/
|
|
35
43
|
async *getLines() {
|
|
36
|
-
this.
|
|
37
|
-
this.diffScopeVerdict.changesSeen = 0;
|
|
38
|
-
this.diffScopeVerdict.linesYielded = 0;
|
|
44
|
+
this._resetRunState();
|
|
39
45
|
const ignoreHelper = await buildIgnoreHelper(this.config);
|
|
40
|
-
const
|
|
41
|
-
|
|
46
|
+
const pending = {
|
|
47
|
+
additionNames: new Set(),
|
|
48
|
+
heldAdditionNames: new Set(),
|
|
49
|
+
deferredDeletions: [],
|
|
50
|
+
};
|
|
42
51
|
for await (const rawLine of this.gitAdapter.streamDiffLines({
|
|
43
52
|
spec: this.diffSpec(),
|
|
44
53
|
verdict: this.diffScopeVerdict,
|
|
45
54
|
scopes: this.config.source,
|
|
46
55
|
})) {
|
|
47
56
|
for (const expanded of this._expandRename(rawLine)) {
|
|
48
|
-
|
|
49
|
-
if (
|
|
50
|
-
|
|
51
|
-
// Stryker disable next-line ConditionalExpression -- equivalent: see v8 ignore — _expandRename emits paths that are routed through the metadata index by the producing test fixtures, so the false-flip (always continue) is unreachable when the test corpus is in use
|
|
52
|
-
/* v8 ignore next -- defensive: upstream RepoGitDiff already filters non-metadata paths via _expandRename, but kept as safety net */
|
|
53
|
-
if (!this.metadata.has(expanded))
|
|
54
|
-
continue;
|
|
55
|
-
if (!ignoreHelper.keep(expanded))
|
|
56
|
-
continue;
|
|
57
|
-
// Stryker disable ConditionalExpression -- equivalent: else-if for DELETION; flipping to true treats any non-ADDITION as a deferred deletion, but only A/M/D lines reach this branch (renames are decomposed by _expandRename), so M lines hit the else (yield directly)
|
|
58
|
-
if (expanded.startsWith(ADDITION)) {
|
|
59
|
-
additionNames.add(this._extractComparisonName(expanded));
|
|
60
|
-
yield expanded;
|
|
61
|
-
}
|
|
62
|
-
else if (expanded.startsWith(DELETION)) {
|
|
63
|
-
// Defer: the D line might cancel against an A line we haven't
|
|
64
|
-
// seen yet (rename-collapse case).
|
|
65
|
-
deferredDeletions.push(expanded);
|
|
66
|
-
}
|
|
67
|
-
else {
|
|
68
|
-
yield expanded;
|
|
69
|
-
}
|
|
70
|
-
// Stryker restore ConditionalExpression
|
|
57
|
+
const streamable = this._routeLine(expanded, ignoreHelper, pending);
|
|
58
|
+
if (streamable !== undefined)
|
|
59
|
+
yield streamable;
|
|
71
60
|
}
|
|
72
61
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
62
|
+
const vouching = await this._vouchingHeldNames(pending.heldAdditionNames, pending.deferredDeletions, pending.additionNames, ignoreHelper);
|
|
63
|
+
for (const { line, name } of pending.deferredDeletions) {
|
|
64
|
+
if (!pending.additionNames.has(name) && !vouching.has(name))
|
|
65
|
+
yield line;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
_resetRunState() {
|
|
69
|
+
this.renamePairs = [];
|
|
70
|
+
this.diffScopeVerdict.changesSeen = 0;
|
|
71
|
+
this.diffScopeVerdict.linesYielded = 0;
|
|
72
|
+
this.probeFailure = undefined;
|
|
73
|
+
}
|
|
74
|
+
// Returns the line when it streams right away, and records what the
|
|
75
|
+
// cancellation rule needs into `pending`: every addition name (held when
|
|
76
|
+
// ignored) and every kept deletion. Routing and recording share one pass
|
|
77
|
+
// because the generator must decide per line whether to yield it now.
|
|
78
|
+
_routeLine(expanded, ignoreHelper, pending) {
|
|
79
|
+
// Stryker disable next-line ConditionalExpression -- equivalent: on the false-flip an empty line reaches metadata.has, which rejects it, so the line is dropped (return undefined) either way
|
|
80
|
+
if (!expanded)
|
|
81
|
+
return undefined;
|
|
82
|
+
if (!this.metadata.has(expanded))
|
|
83
|
+
return undefined;
|
|
84
|
+
if (expanded.startsWith(ADDITION)) {
|
|
85
|
+
return this._routeAddition(expanded, ignoreHelper, pending);
|
|
86
|
+
}
|
|
87
|
+
if (!ignoreHelper.keep(expanded))
|
|
88
|
+
return undefined;
|
|
89
|
+
if (!expanded.startsWith(DELETION))
|
|
90
|
+
return expanded;
|
|
91
|
+
// Defer: the D line might cancel against an A line we haven't
|
|
92
|
+
// seen yet (rename-collapse case).
|
|
93
|
+
pending.deferredDeletions.push({
|
|
94
|
+
line: expanded,
|
|
95
|
+
name: this._extractComparisonName(expanded),
|
|
96
|
+
});
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
// An ignored addition is held rather than dropped: it may be the
|
|
100
|
+
// destination of a move into the ignore set, which can only be
|
|
101
|
+
// told from a stale copy once the whole diff has been seen.
|
|
102
|
+
_routeAddition(addition, ignoreHelper, pending) {
|
|
103
|
+
const kept = ignoreHelper.keep(addition);
|
|
104
|
+
const name = this._extractComparisonName(addition);
|
|
105
|
+
if (!kept) {
|
|
106
|
+
pending.heldAdditionNames.add(name);
|
|
107
|
+
return undefined;
|
|
77
108
|
}
|
|
109
|
+
pending.additionNames.add(name);
|
|
110
|
+
return addition;
|
|
78
111
|
}
|
|
79
112
|
getRenamePairs() {
|
|
80
113
|
return this.renamePairs;
|
|
81
114
|
}
|
|
115
|
+
// Meaningful only once the diff is drained, like getUnmatchedSourceScopes().
|
|
116
|
+
getHeldAdditionProbeFailure() {
|
|
117
|
+
return this.probeFailure;
|
|
118
|
+
}
|
|
82
119
|
getUnmatchedSourceScopes() {
|
|
83
120
|
return this.gitAdapter.getUnmatchedSourceScopes(this.diffScopeVerdict, this.config.source);
|
|
84
121
|
}
|
|
@@ -103,8 +140,65 @@ export default class RepoGitDiff {
|
|
|
103
140
|
yield `${DELETION}${TAB}${fromPath}`;
|
|
104
141
|
yield `${ADDITION}${TAB}${toPath}`;
|
|
105
142
|
}
|
|
143
|
+
// Locale-invariant on purpose: a locale-sensitive fold makes the
|
|
144
|
+
// cancellation key depend on the machine that ran the diff.
|
|
106
145
|
_extractComparisonName(line) {
|
|
107
|
-
return this.metadata.getFullyQualifiedName(line).
|
|
146
|
+
return this.metadata.getFullyQualifiedName(line).toLowerCase();
|
|
147
|
+
}
|
|
148
|
+
// A held name vouches for its component only when nothing of that
|
|
149
|
+
// component is visible at `to`: a component still alive under an
|
|
150
|
+
// unignored path keeps its deletions, because the ignored copy is then a
|
|
151
|
+
// stale duplicate rather than a move into the ignore set.
|
|
152
|
+
async _vouchingHeldNames(held, deferred, registered, ignoreHelper) {
|
|
153
|
+
// A name a kept addition already registered is cancelled whatever the
|
|
154
|
+
// tree says, so excluding it keeps the read to the names whose answer
|
|
155
|
+
// can still change an outcome.
|
|
156
|
+
const candidates = new Set(deferred
|
|
157
|
+
.map(({ name }) => name)
|
|
158
|
+
.filter(name => held.has(name) && !registered.has(name)));
|
|
159
|
+
if (candidates.size === 0)
|
|
160
|
+
return candidates;
|
|
161
|
+
const visible = await this._visibleNamesAtTo(candidates, ignoreHelper);
|
|
162
|
+
const vouching = new Set([...candidates].filter(name => !visible.has(name)));
|
|
163
|
+
if (vouching.size > 0) {
|
|
164
|
+
// A cancelled deletion appears in neither manifest, so without this
|
|
165
|
+
// line a debug run cannot explain why a destructive entry is missing.
|
|
166
|
+
Logger.debug(lazy `getLines: held addition(s) '${[...vouching].join("', '")}' survive only under ignored paths at '${this.config.to}', cancelling their deletions`);
|
|
167
|
+
}
|
|
168
|
+
return vouching;
|
|
169
|
+
}
|
|
170
|
+
// Visibility is decided by the global ignore, never the destructive one:
|
|
171
|
+
// the question is whether sgd would still treat a file of the component
|
|
172
|
+
// as live source, which is the A/M routing.
|
|
173
|
+
async _visibleNamesAtTo(candidates, ignoreHelper) {
|
|
174
|
+
const listing = await this._listFilesAt(this.config.to);
|
|
175
|
+
if (!listing) {
|
|
176
|
+
// Fail closed: a tree that cannot be read lets nothing vouch, so a
|
|
177
|
+
// read failure keeps the uncancelled output rather than silently
|
|
178
|
+
// suppressing a real deletion. Recorded so the run can warn — a
|
|
179
|
+
// degraded result the user never sees is the failure worth avoiding.
|
|
180
|
+
this.probeFailure = { candidateCount: candidates.size };
|
|
181
|
+
Logger.debug(lazy `_visibleNamesAtTo: '${this.config.to}' could not be listed, ${candidates.size} candidate component(s) cannot vouch`);
|
|
182
|
+
return candidates;
|
|
183
|
+
}
|
|
184
|
+
const visible = new Set();
|
|
185
|
+
for (const path of listing) {
|
|
186
|
+
const line = `${ADDITION}${TAB}${path}`;
|
|
187
|
+
if (!this.metadata.has(line))
|
|
188
|
+
continue;
|
|
189
|
+
const name = this._extractComparisonName(line);
|
|
190
|
+
if (candidates.has(name) && ignoreHelper.keep(line))
|
|
191
|
+
visible.add(name);
|
|
192
|
+
}
|
|
193
|
+
return visible;
|
|
194
|
+
}
|
|
195
|
+
// The one seam that touches git. `undefined` is buildTreeIndex's own
|
|
196
|
+
// failure signal and passes through untouched. Protected so a benchmark
|
|
197
|
+
// can drive the visibility pass over a synthetic listing without a
|
|
198
|
+
// repository, the way the cancellation-key bench reaches the key.
|
|
199
|
+
async _listFilesAt(revision) {
|
|
200
|
+
const index = await this.gitAdapter.buildTreeIndex(revision, this.config.source);
|
|
201
|
+
return index?.getFilesPath(this.config.source);
|
|
108
202
|
}
|
|
109
203
|
// Built fresh on every getLines() call (not cached at construction) so a
|
|
110
204
|
// later rewrite of config.from (ConfigValidator resolves SHAs after
|