@jayree/sfdx-plugin-manifest 2.9.0 → 3.0.0
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/CHANGELOG.md +21 -0
- package/README.md +91 -6
- package/lib/commands/jayree/manifest/git/diff.d.ts +23 -20
- package/lib/commands/jayree/manifest/git/diff.js +87 -584
- package/lib/commands/jayree/manifest/git/diff.js.map +1 -1
- package/lib/commands/jayree/manifest/legacy/git/diff.d.ts +37 -0
- package/lib/commands/jayree/manifest/legacy/git/diff.js +640 -0
- package/lib/commands/jayree/manifest/legacy/git/diff.js.map +1 -0
- package/oclif.manifest.json +22 -14
- package/package.json +22 -14
- package/schemas/jayree-manifest-git-diff.json +24 -5
- package/schemas/jayree-manifest-legacy-git-diff.json +19 -0
- package/lib/commands/jayree/manifest/beta/git/diff.d.ts +0 -38
- package/lib/commands/jayree/manifest/beta/git/diff.js +0 -141
- package/lib/commands/jayree/manifest/beta/git/diff.js.map +0 -1
- package/schemas/jayree-manifest-beta-git-diff.json +0 -38
@@ -0,0 +1,640 @@
|
|
1
|
+
/*
|
2
|
+
* Copyright (c) 2022, jayree
|
3
|
+
* All rights reserved.
|
4
|
+
* Licensed under the BSD 3-Clause license.
|
5
|
+
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
6
|
+
*/
|
7
|
+
import { join, basename, sep, posix, dirname, relative, resolve } from 'node:path';
|
8
|
+
import { fileURLToPath } from 'node:url';
|
9
|
+
import { format } from 'node:util';
|
10
|
+
import { SfCommand, Flags, arrayWithDeprecation } from '@salesforce/sf-plugins-core';
|
11
|
+
import { Messages, SfError, SfProject } from '@salesforce/core';
|
12
|
+
import { Args } from '@oclif/core';
|
13
|
+
import fs from 'fs-extra';
|
14
|
+
import { Logger, Listr } from 'listr2';
|
15
|
+
import { env } from '@salesforce/kit';
|
16
|
+
import { getString } from '@salesforce/ts-types';
|
17
|
+
import equal from 'fast-deep-equal';
|
18
|
+
import { ComponentSet, RegistryAccess, registry, VirtualTreeContainer, MetadataResolver, DestructiveChangesType, } from '@salesforce/source-deploy-retrieve';
|
19
|
+
import { parseMetadataXml } from '@salesforce/source-deploy-retrieve/lib/src/utils/index.js';
|
20
|
+
import Debug from 'debug';
|
21
|
+
import git from 'isomorphic-git';
|
22
|
+
// eslint-disable-next-line no-underscore-dangle
|
23
|
+
const __filename = fileURLToPath(import.meta.url);
|
24
|
+
// eslint-disable-next-line no-underscore-dangle
|
25
|
+
const __dirname = dirname(__filename);
|
26
|
+
Messages.importMessagesDirectory(__dirname);
|
27
|
+
const messages = Messages.loadMessages('@jayree/sfdx-plugin-manifest', 'gitdiff');
|
28
|
+
const logger = new Logger({ useIcons: false });
|
29
|
+
const debug = Debug('jayree:manifest:git:diff');
|
30
|
+
const registryAccess = new RegistryAccess();
|
31
|
+
export default class GitDiff extends SfCommand {
|
32
|
+
async run() {
|
33
|
+
const { flags, args } = await this.parse(GitDiff);
|
34
|
+
const sourcepath = flags['source-dir'];
|
35
|
+
this.destructiveChangesOnly = flags['destructive-changes-only'];
|
36
|
+
this.outputDir = flags['output-dir'];
|
37
|
+
this.projectRoot = this.project.getPath();
|
38
|
+
this.sourceApiVersion = (await this.getSourceApiVersion());
|
39
|
+
this.destructiveChanges = join(this.outputDir, 'destructiveChanges.xml');
|
40
|
+
this.manifest = join(this.outputDir, 'package.xml');
|
41
|
+
debug({
|
42
|
+
outputDir: this.outputDir,
|
43
|
+
projectRoot: this.projectRoot,
|
44
|
+
});
|
45
|
+
const isContentTypeJSON = env.getString('SFDX_CONTENT_TYPE', '').toUpperCase() === 'JSON';
|
46
|
+
this.isOutputEnabled = !(this.argv.find((arg) => arg === '--json') ?? isContentTypeJSON);
|
47
|
+
const gitArgs = await getGitArgsFromArgv(args.ref1, args.ref2, this.argv, this.projectRoot);
|
48
|
+
debug({ gitArgs });
|
49
|
+
const tasks = new Listr([
|
50
|
+
{
|
51
|
+
title: `Execute 'git --no-pager diff --name-status --no-renames ${gitArgs.refString}'`,
|
52
|
+
task: async (ctx, task) => {
|
53
|
+
const { gitlines, warnings } = await getGitDiff(gitArgs.ref1, gitArgs.ref2, this.projectRoot);
|
54
|
+
this.gitLines = gitlines;
|
55
|
+
this.outputWarnings = warnings;
|
56
|
+
task.output = `Changed files: ${this.gitLines.length}`;
|
57
|
+
},
|
58
|
+
options: { persistentOutput: true },
|
59
|
+
},
|
60
|
+
{
|
61
|
+
// title: 'Warning output',
|
62
|
+
skip: () => !this.outputWarnings?.length,
|
63
|
+
task: (ctx, task) => {
|
64
|
+
debug({ warnings: this.outputWarnings });
|
65
|
+
const moreWarnings = this.outputWarnings.splice(5);
|
66
|
+
for (const message of this.outputWarnings) {
|
67
|
+
task.output = `Warning: unstaged file ${message}`;
|
68
|
+
}
|
69
|
+
task.output = moreWarnings.length ? `... ${moreWarnings.length} more warnings` : '';
|
70
|
+
},
|
71
|
+
options: { persistentOutput: true, bottomBar: 6 },
|
72
|
+
},
|
73
|
+
{
|
74
|
+
title: 'Create virtual tree container',
|
75
|
+
skip: () => !this.gitLines.length,
|
76
|
+
task: (ctx, task) => task.newListr([
|
77
|
+
{
|
78
|
+
title: `ref1: ${gitArgs.ref1}`,
|
79
|
+
task: async () => {
|
80
|
+
this.ref1VirtualTreeContainer = await createVirtualTreeContainer(gitArgs.ref1, this.projectRoot, this.gitLines.filter((l) => l.status === 'M').map((l) => l.path));
|
81
|
+
},
|
82
|
+
},
|
83
|
+
{
|
84
|
+
title: gitArgs.ref2 !== '' ? `ref2: ${gitArgs.ref2}` : 'ref2: (staging area)',
|
85
|
+
task: async () => {
|
86
|
+
this.ref2VirtualTreeContainer = await createVirtualTreeContainer(gitArgs.ref2, this.projectRoot, this.gitLines.filter((l) => l.status === 'M').map((l) => l.path));
|
87
|
+
},
|
88
|
+
},
|
89
|
+
], { concurrent: true }),
|
90
|
+
},
|
91
|
+
{
|
92
|
+
title: 'Analyze git diff results',
|
93
|
+
skip: () => !this.gitLines.length,
|
94
|
+
task: async (ctx, task) => {
|
95
|
+
if (sourcepath) {
|
96
|
+
this.fsPaths = sourcepath.map((filepath) => {
|
97
|
+
filepath = resolve(filepath);
|
98
|
+
if (!this.ref1VirtualTreeContainer.exists(filepath) &&
|
99
|
+
!this.ref2VirtualTreeContainer.exists(filepath)) {
|
100
|
+
throw new SfError(`The sourcepath "${filepath}" is not a valid source file path.`);
|
101
|
+
}
|
102
|
+
return filepath;
|
103
|
+
});
|
104
|
+
debug(`fsPaths: ${this.fsPaths.join(', ')}`);
|
105
|
+
}
|
106
|
+
const { manifest, output } = await getGitResults(this.gitLines, this.ref1VirtualTreeContainer, this.ref2VirtualTreeContainer, this.destructiveChangesOnly, this.fsPaths);
|
107
|
+
task.output = `Added: ${output.counts.added}, Deleted: ${output.counts.deleted}, Modified: ${output.counts.modified}, Unchanged: ${output.counts.unchanged}, Ignored: ${output.counts.ignored}${output.counts.error ? `, Errors: ${output.counts.error}` : ''}`;
|
108
|
+
this.outputErrors = output.errors;
|
109
|
+
debug({ manifest });
|
110
|
+
this.componentSet = fixComponentSetChilds(manifest);
|
111
|
+
this.componentSet.sourceApiVersion = this.sourceApiVersion;
|
112
|
+
},
|
113
|
+
options: { persistentOutput: true },
|
114
|
+
},
|
115
|
+
{
|
116
|
+
// title: 'Error output',
|
117
|
+
skip: () => !this.outputErrors?.length,
|
118
|
+
task: (ctx, task) => {
|
119
|
+
debug({ errors: this.outputErrors });
|
120
|
+
const moreErrors = this.outputErrors.splice(5);
|
121
|
+
for (const message of this.outputErrors) {
|
122
|
+
task.output = `Error: ${message}`;
|
123
|
+
}
|
124
|
+
task.output = moreErrors.length ? `... ${moreErrors.length} more errors` : '';
|
125
|
+
},
|
126
|
+
options: { persistentOutput: true, bottomBar: 6 },
|
127
|
+
},
|
128
|
+
{
|
129
|
+
title: 'Generate manifests',
|
130
|
+
skip: () => !this.componentSet?.size,
|
131
|
+
task: (ctx, task) => task.newListr([
|
132
|
+
{
|
133
|
+
title: this.manifest,
|
134
|
+
skip: () => !this.isOutputEnabled,
|
135
|
+
task: async () => {
|
136
|
+
await fs.ensureDir(dirname(this.manifest));
|
137
|
+
await fs.writeFile(this.manifest, await this.componentSet.getPackageXml());
|
138
|
+
},
|
139
|
+
options: { persistentOutput: true },
|
140
|
+
},
|
141
|
+
{
|
142
|
+
title: this.destructiveChanges,
|
143
|
+
skip: () => !this.componentSet.getTypesOfDestructiveChanges().length || !this.isOutputEnabled,
|
144
|
+
task: async () => {
|
145
|
+
await fs.ensureDir(dirname(this.destructiveChanges));
|
146
|
+
await fs.writeFile(this.destructiveChanges, await this.componentSet.getPackageXml(undefined, DestructiveChangesType.POST));
|
147
|
+
},
|
148
|
+
options: { persistentOutput: true },
|
149
|
+
},
|
150
|
+
], { concurrent: true }),
|
151
|
+
},
|
152
|
+
], {
|
153
|
+
rendererOptions: { showTimer: true, collapse: false, lazy: true, collapseErrors: false },
|
154
|
+
rendererSilent: !this.isOutputEnabled,
|
155
|
+
rendererFallback: debug.enabled,
|
156
|
+
});
|
157
|
+
try {
|
158
|
+
await tasks.run();
|
159
|
+
return {
|
160
|
+
destructiveChanges: await this.componentSet?.getObject(DestructiveChangesType.POST),
|
161
|
+
manifest: await this.componentSet?.getObject(),
|
162
|
+
};
|
163
|
+
}
|
164
|
+
catch (e) {
|
165
|
+
if (debug.enabled && this.isOutputEnabled) {
|
166
|
+
logger.fail(e.message);
|
167
|
+
}
|
168
|
+
throw e;
|
169
|
+
}
|
170
|
+
}
|
171
|
+
async getSourceApiVersion() {
|
172
|
+
const projectConfig = await this.project.resolveProjectConfig();
|
173
|
+
return getString(projectConfig, 'sourceApiVersion') ?? undefined;
|
174
|
+
}
|
175
|
+
}
|
176
|
+
GitDiff.summary = messages.getMessage('summary');
|
177
|
+
GitDiff.description = messages.getMessage('description');
|
178
|
+
GitDiff.examples = messages.getMessages('examples');
|
179
|
+
GitDiff.args = {
|
180
|
+
ref1: Args.string({
|
181
|
+
required: true,
|
182
|
+
description: messages.getMessage('args.ref1.description'),
|
183
|
+
}),
|
184
|
+
ref2: Args.string({
|
185
|
+
description: messages.getMessage('args.ref2.description'),
|
186
|
+
}),
|
187
|
+
};
|
188
|
+
GitDiff.requiresProject = true;
|
189
|
+
GitDiff.flags = {
|
190
|
+
'source-dir': arrayWithDeprecation({
|
191
|
+
char: 'd',
|
192
|
+
summary: messages.getMessage('flags.source-dir.summary'),
|
193
|
+
description: messages.getMessage('flags.source-dir.description'),
|
194
|
+
deprecateAliases: true,
|
195
|
+
aliases: ['sourcepath', 'p'],
|
196
|
+
}),
|
197
|
+
'output-dir': Flags.directory({
|
198
|
+
summary: messages.getMessage('flags.output-dir.summary'),
|
199
|
+
description: messages.getMessage('flags.output-dir.description'),
|
200
|
+
default: '',
|
201
|
+
deprecateAliases: true,
|
202
|
+
aliases: ['outputdir', 'o'],
|
203
|
+
}),
|
204
|
+
'destructive-changes-only': Flags.boolean({
|
205
|
+
summary: messages.getMessage('flags.destructive-changes-only.summary'),
|
206
|
+
description: messages.getMessage('flags.destructive-changes-only.description'),
|
207
|
+
deprecateAliases: true,
|
208
|
+
aliases: ['destructivechangesonly'],
|
209
|
+
}),
|
210
|
+
};
|
211
|
+
async function resolveRef(refOrig, dir) {
|
212
|
+
if (refOrig === '') {
|
213
|
+
return '';
|
214
|
+
}
|
215
|
+
const getCommitLog = async (ref) => {
|
216
|
+
try {
|
217
|
+
const [log] = await git.log({
|
218
|
+
fs,
|
219
|
+
dir,
|
220
|
+
ref,
|
221
|
+
depth: 1,
|
222
|
+
});
|
223
|
+
return { oid: log.oid, parents: log.commit.parent };
|
224
|
+
}
|
225
|
+
catch (error) {
|
226
|
+
throw new Error(`ambiguous argument '${ref}': unknown revision or path not in the working tree.
|
227
|
+
See more help with --help`);
|
228
|
+
}
|
229
|
+
};
|
230
|
+
if (!['~', '^'].some((el) => refOrig.includes(el))) {
|
231
|
+
return (await getCommitLog(refOrig)).oid;
|
232
|
+
}
|
233
|
+
const firstIndex = [refOrig.indexOf('^'), refOrig.indexOf('~')]
|
234
|
+
.filter((a) => a >= 0)
|
235
|
+
.reduce((a, b) => Math.min(a, b));
|
236
|
+
let path = refOrig.substring(firstIndex);
|
237
|
+
let ref = refOrig.substring(0, firstIndex);
|
238
|
+
while (path.length && ref !== undefined) {
|
239
|
+
if (path.startsWith('^')) {
|
240
|
+
path = path.substring(1);
|
241
|
+
let next = Number(path.substring(0, 1));
|
242
|
+
path = next ? path.substring(1) : path;
|
243
|
+
next = next ? next : 1;
|
244
|
+
// eslint-disable-next-line no-await-in-loop
|
245
|
+
ref = (await getCommitLog(ref)).parents[next - 1];
|
246
|
+
}
|
247
|
+
else if (path.startsWith('~')) {
|
248
|
+
path = path.substring(1);
|
249
|
+
let next = Number(path.substring(0, 1));
|
250
|
+
path = next ? path.substring(1) : path;
|
251
|
+
next = next ? next : 1;
|
252
|
+
for (let index = 0; index <= next - 1; index++) {
|
253
|
+
// eslint-disable-next-line no-await-in-loop
|
254
|
+
ref = (await getCommitLog(ref)).parents[0];
|
255
|
+
}
|
256
|
+
}
|
257
|
+
else {
|
258
|
+
ref = undefined;
|
259
|
+
}
|
260
|
+
}
|
261
|
+
if (ref === undefined) {
|
262
|
+
throw new Error(`ambiguous argument '${refOrig}': unknown revision or path not in the working tree.`);
|
263
|
+
}
|
264
|
+
return ref;
|
265
|
+
}
|
266
|
+
async function getGitArgsFromArgv(ref1, ref2, argv, dir) {
|
267
|
+
const newArgv = [];
|
268
|
+
while (argv.length) {
|
269
|
+
let [e] = argv.splice(0, 1);
|
270
|
+
if (e.includes('=')) {
|
271
|
+
// skip parameter=value
|
272
|
+
}
|
273
|
+
else if (e.includes('-')) {
|
274
|
+
// remove value
|
275
|
+
if (argv[0] && !argv[0].includes('-') && ![ref1, ref2].includes(argv[0])) {
|
276
|
+
[e] = argv.splice(0, 1);
|
277
|
+
}
|
278
|
+
}
|
279
|
+
else {
|
280
|
+
newArgv.push(e);
|
281
|
+
}
|
282
|
+
}
|
283
|
+
argv = newArgv;
|
284
|
+
let refString = ref1;
|
285
|
+
const a = argv.join('.').split('.');
|
286
|
+
if ((a.length === 3 || a.length === 4) && typeof ref2 === 'undefined') {
|
287
|
+
ref1 = a[0];
|
288
|
+
ref2 = a[a.length - 1];
|
289
|
+
}
|
290
|
+
else if (a.length === 2 && typeof ref2 !== 'undefined') {
|
291
|
+
refString = `${ref1}..${ref2}`;
|
292
|
+
}
|
293
|
+
else if (a.length === 1) {
|
294
|
+
ref2 = '';
|
295
|
+
}
|
296
|
+
else {
|
297
|
+
throw new Error(`Ambiguous ${format('argument%s', argv.length === 1 ? '' : 's')}: ${argv.join(', ')}
|
298
|
+
See more help with --help`);
|
299
|
+
}
|
300
|
+
ref1 = await resolveRef(ref1, dir);
|
301
|
+
ref2 = await resolveRef(ref2, dir);
|
302
|
+
if (a.length === 4) {
|
303
|
+
ref1 = (await git.findMergeBase({
|
304
|
+
fs,
|
305
|
+
dir,
|
306
|
+
oids: [ref2, ref1],
|
307
|
+
}))[0];
|
308
|
+
}
|
309
|
+
return { ref1, ref2, refString };
|
310
|
+
}
|
311
|
+
function ensureOSPath(path) {
|
312
|
+
return path.split(posix.sep).join(sep);
|
313
|
+
}
|
314
|
+
function ensureGitRelPath(dir, path) {
|
315
|
+
return relative(dir, path).split(sep).join(posix.sep);
|
316
|
+
}
|
317
|
+
async function createVirtualTreeContainer(ref, dir, modifiedFiles) {
|
318
|
+
const paths = (await git.listFiles({ fs, dir, ref })).map((p) => join(dir, ensureOSPath(p)));
|
319
|
+
const oid = ref ? await git.resolveRef({ fs, dir, ref }) : '';
|
320
|
+
const virtualDirectoryByFullPath = new Map();
|
321
|
+
for await (const filename of paths) {
|
322
|
+
let dirPath = dirname(filename);
|
323
|
+
virtualDirectoryByFullPath.set(dirPath, {
|
324
|
+
dirPath,
|
325
|
+
children: Array.from(new Set(virtualDirectoryByFullPath.get(dirPath)?.children ?? []).add({
|
326
|
+
name: basename(filename),
|
327
|
+
data: parseMetadataXml(filename) && modifiedFiles.includes(filename)
|
328
|
+
? oid
|
329
|
+
? Buffer.from((await git.readBlob({ fs, dir, oid, filepath: ensureGitRelPath(dir, filename) })).blob)
|
330
|
+
: await fs.readFile(ensureOSPath(filename))
|
331
|
+
: Buffer.from(''),
|
332
|
+
})),
|
333
|
+
});
|
334
|
+
const splits = filename.split(sep);
|
335
|
+
for (let i = 1; i < splits.length - 1; i++) {
|
336
|
+
dirPath = splits.slice(0, i + 1).join(sep);
|
337
|
+
virtualDirectoryByFullPath.set(dirPath, {
|
338
|
+
dirPath,
|
339
|
+
children: Array.from(new Set(virtualDirectoryByFullPath.get(dirPath)?.children ?? []).add(splits[i + 1])),
|
340
|
+
});
|
341
|
+
}
|
342
|
+
}
|
343
|
+
return new VirtualTreeContainer(Array.from(virtualDirectoryByFullPath.values()));
|
344
|
+
}
|
345
|
+
async function analyzeFile(path, ref1VirtualTreeContainer, ref2VirtualTreeContainer) {
|
346
|
+
if (!parseMetadataXml(path)) {
|
347
|
+
return { path, status: 0 };
|
348
|
+
}
|
349
|
+
const ref2resolver = new MetadataResolver(registryAccess, ref2VirtualTreeContainer);
|
350
|
+
const [ref2Component] = ref2resolver.getComponentsFromPath(path); // git path only conaints files
|
351
|
+
const ref1resolver = new MetadataResolver(registryAccess, ref1VirtualTreeContainer);
|
352
|
+
const [ref1Component] = ref1resolver.getComponentsFromPath(path); // git path only conaints files
|
353
|
+
if (ref1resolver.forceIgnoredPaths.has(path) || ref2resolver.forceIgnoredPaths.has(path)) {
|
354
|
+
return { path, status: -2 };
|
355
|
+
}
|
356
|
+
if (equal(await ref1Component.parseXml(), await ref2Component.parseXml())) {
|
357
|
+
return { path, status: -1 };
|
358
|
+
}
|
359
|
+
if (ref1Component.type.strictDirectoryName === true || !ref1Component.type.children) {
|
360
|
+
return { path, status: 0 };
|
361
|
+
}
|
362
|
+
const ref2ChildUniqueIdArray = ref2Component
|
363
|
+
.getChildren()
|
364
|
+
.map((childComponent) => getUniqueIdentifier(childComponent));
|
365
|
+
const ref1ChildUniqueIdArray = ref1Component
|
366
|
+
.getChildren()
|
367
|
+
.map((childComponent) => getUniqueIdentifier(childComponent));
|
368
|
+
const childComponentsNotInRef2 = ref1Component
|
369
|
+
.getChildren()
|
370
|
+
.filter((childComponent) => !ref2ChildUniqueIdArray.includes(getUniqueIdentifier(childComponent))); // deleted
|
371
|
+
const childComponentsNotInRef1 = ref2Component
|
372
|
+
.getChildren()
|
373
|
+
.filter((childComponent) => !ref1ChildUniqueIdArray.includes(getUniqueIdentifier(childComponent))); // added
|
374
|
+
const childComponentsInRef1AndRef2 = ref1Component
|
375
|
+
.getChildren()
|
376
|
+
.filter((childComponent) => ref2ChildUniqueIdArray.includes(getUniqueIdentifier(childComponent))); // modified?
|
377
|
+
debug({ childComponentsNotInRef2, childComponentsNotInRef1, childComponentsInRef1AndRef2 });
|
378
|
+
for await (const childComponentRef1 of childComponentsInRef1AndRef2) {
|
379
|
+
const [childComponentRef2] = ref2Component
|
380
|
+
.getChildren()
|
381
|
+
.filter((childComponent) => getUniqueIdentifier(childComponentRef1) === getUniqueIdentifier(childComponent));
|
382
|
+
if (!equal(await childComponentRef1.parseXml(), await childComponentRef2.parseXml())) {
|
383
|
+
childComponentsNotInRef1.push(childComponentRef2); // modified! -> add to added
|
384
|
+
}
|
385
|
+
}
|
386
|
+
debug({ childComponentsNotInRef1 });
|
387
|
+
return {
|
388
|
+
path,
|
389
|
+
status: 1 + childComponentsNotInRef2.length + childComponentsNotInRef1.length,
|
390
|
+
toManifest: childComponentsNotInRef1,
|
391
|
+
toDestructiveChanges: childComponentsNotInRef2,
|
392
|
+
};
|
393
|
+
}
|
394
|
+
function getUniqueIdentifier(component) {
|
395
|
+
return `${component.type.name}#${getString(component, component.type.uniqueIdElement)}`;
|
396
|
+
}
|
397
|
+
async function getFileStateChanges(commitHash1, commitHash2, dir) {
|
398
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
399
|
+
return git.walk({
|
400
|
+
fs,
|
401
|
+
dir,
|
402
|
+
trees: [git.TREE({ ref: commitHash1 }), git.TREE({ ref: commitHash2 })],
|
403
|
+
async map(filepath, [A, B]) {
|
404
|
+
if (filepath === '.' || (await A?.type()) === 'tree' || (await B?.type()) === 'tree') {
|
405
|
+
return;
|
406
|
+
}
|
407
|
+
const Aoid = await A?.oid();
|
408
|
+
const Boid = await B?.oid();
|
409
|
+
let type = 'EQ';
|
410
|
+
if (Aoid !== Boid) {
|
411
|
+
type = 'M';
|
412
|
+
}
|
413
|
+
if (Aoid === undefined) {
|
414
|
+
type = 'A';
|
415
|
+
}
|
416
|
+
if (Boid === undefined) {
|
417
|
+
type = 'D';
|
418
|
+
}
|
419
|
+
if (type !== 'EQ') {
|
420
|
+
return {
|
421
|
+
path: join(dir, ensureOSPath(filepath)),
|
422
|
+
status: type,
|
423
|
+
};
|
424
|
+
}
|
425
|
+
},
|
426
|
+
});
|
427
|
+
}
|
428
|
+
async function getStatusMatrix(dir, ref) {
|
429
|
+
const getStatus = (row) => {
|
430
|
+
if ([
|
431
|
+
[0, 2, 2],
|
432
|
+
[0, 2, 3], // added, staged, with unstaged changes
|
433
|
+
].some((a) => a.every((val, index) => val === row[index]))) {
|
434
|
+
return 'A';
|
435
|
+
}
|
436
|
+
if ([
|
437
|
+
[1, 0, 0],
|
438
|
+
[1, 0, 1],
|
439
|
+
[1, 1, 0],
|
440
|
+
[1, 2, 0],
|
441
|
+
[1, 0, 3], // modified, staged, with unstaged deletion
|
442
|
+
].some((a) => a.every((val, index) => val === row[index]))) {
|
443
|
+
return 'D';
|
444
|
+
}
|
445
|
+
if ([
|
446
|
+
[1, 2, 1],
|
447
|
+
[1, 2, 2],
|
448
|
+
[1, 2, 3], // modified, staged, with unstaged changes
|
449
|
+
].some((a) => a.every((val, index) => val === row[index]))) {
|
450
|
+
return 'M';
|
451
|
+
}
|
452
|
+
return undefined;
|
453
|
+
};
|
454
|
+
const statusMatrix = await git.statusMatrix({ fs, dir, ref });
|
455
|
+
const warnings = statusMatrix
|
456
|
+
.filter((row) => [
|
457
|
+
[0, 2, 0],
|
458
|
+
[0, 0, 3],
|
459
|
+
[0, 2, 3],
|
460
|
+
[1, 2, 1],
|
461
|
+
[1, 0, 3],
|
462
|
+
[1, 1, 3],
|
463
|
+
[1, 2, 3],
|
464
|
+
[1, 1, 0],
|
465
|
+
[1, 2, 0],
|
466
|
+
[1, 0, 1], // deleted, unstaged
|
467
|
+
].some((a) => a.every((val, index) => val === row.slice(1)[index])))
|
468
|
+
.map((row) => join(dir, ensureOSPath(row[0])));
|
469
|
+
const gitlines = statusMatrix
|
470
|
+
.filter((row) => ![
|
471
|
+
[0, 0, 0],
|
472
|
+
[1, 1, 1],
|
473
|
+
[0, 0, 3],
|
474
|
+
[0, 2, 0],
|
475
|
+
[1, 1, 3], // modified, staged, with unstaged original file
|
476
|
+
].some((a) => a.every((val, index) => val === row.slice(1)[index])))
|
477
|
+
.map((row) => ({
|
478
|
+
path: join(dir, ensureOSPath(row[0])),
|
479
|
+
status: getStatus(row.slice(1)),
|
480
|
+
}));
|
481
|
+
return { warnings, lines: gitlines };
|
482
|
+
}
|
483
|
+
async function getGitDiff(ref1, ref2, dir) {
|
484
|
+
let gitlines;
|
485
|
+
let warnings = [];
|
486
|
+
const proj = await SfProject.resolve();
|
487
|
+
const resolveSourcePaths = proj.getUniquePackageDirectories().map((pDir) => pDir.fullPath);
|
488
|
+
if (ref2) {
|
489
|
+
gitlines = (await getFileStateChanges(ref1, ref2, dir)).filter((l) => resolveSourcePaths.some((f) => l.path.startsWith(f)));
|
490
|
+
}
|
491
|
+
else {
|
492
|
+
const { warnings: warn, lines } = await getStatusMatrix(dir, ref1);
|
493
|
+
warnings = warn.filter((l) => resolveSourcePaths.some((f) => l.startsWith(f)));
|
494
|
+
gitlines = lines.filter((l) => resolveSourcePaths.some((f) => l.path.startsWith(f)));
|
495
|
+
}
|
496
|
+
gitlines = gitlines.filter((line) => {
|
497
|
+
if (line.status === 'D') {
|
498
|
+
for (const sourcePath of resolveSourcePaths) {
|
499
|
+
const defaultFolder = join(sourcePath, 'main', 'default');
|
500
|
+
const filePath = line.path.replace(line.path.startsWith(defaultFolder) ? defaultFolder : sourcePath, '');
|
501
|
+
const target = gitlines.find((t) => t.path.endsWith(filePath) && t.status === 'A');
|
502
|
+
if (target) {
|
503
|
+
debug(`rename: ${line.path} -> ${target.path}`);
|
504
|
+
return false;
|
505
|
+
}
|
506
|
+
}
|
507
|
+
}
|
508
|
+
return true;
|
509
|
+
});
|
510
|
+
debug({ gitlines, warnings });
|
511
|
+
return { gitlines, warnings };
|
512
|
+
}
|
513
|
+
// eslint-disable-next-line complexity
|
514
|
+
async function getGitResults(gitLines, ref1VirtualTreeContainer, ref2VirtualTreeContainer, destructiveChangesOnly, fsPaths) {
|
515
|
+
const results = {
|
516
|
+
manifest: new ComponentSet(undefined, registryAccess),
|
517
|
+
output: {
|
518
|
+
unchanged: [],
|
519
|
+
ignored: { ref1: [], ref2: [] },
|
520
|
+
counts: { added: 0, deleted: 0, modified: 0, unchanged: 0, ignored: 0, error: 0 },
|
521
|
+
errors: [],
|
522
|
+
},
|
523
|
+
};
|
524
|
+
const ref1Resolver = new MetadataResolver(registryAccess, ref1VirtualTreeContainer);
|
525
|
+
const ref2Resolver = new MetadataResolver(registryAccess, ref2VirtualTreeContainer);
|
526
|
+
const getComponentsFromPath = (resolver, path) => {
|
527
|
+
let result = [];
|
528
|
+
try {
|
529
|
+
result = resolver.getComponentsFromPath(path);
|
530
|
+
}
|
531
|
+
catch (error) {
|
532
|
+
results.output.counts.error++;
|
533
|
+
results.output.errors.push(error.message);
|
534
|
+
}
|
535
|
+
return result;
|
536
|
+
};
|
537
|
+
const analyzedFilesPromises = [];
|
538
|
+
for (const [, { status, path }] of gitLines.entries()) {
|
539
|
+
if (!fsPaths || fsPaths.some((fsPath) => resolve(path).startsWith(fsPath))) {
|
540
|
+
if (status === 'D') {
|
541
|
+
for (const c of getComponentsFromPath(ref1Resolver, path)) {
|
542
|
+
if (c.xml === path || gitLines.find((x) => x.path === c.xml)) {
|
543
|
+
results.manifest.add(c, DestructiveChangesType.POST);
|
544
|
+
results.output.counts.deleted++;
|
545
|
+
}
|
546
|
+
else {
|
547
|
+
try {
|
548
|
+
if (c.xml) {
|
549
|
+
// in case a binary source file of a bundle was deleted, check if the bundle ist still valid and update instead of delete
|
550
|
+
ref2Resolver.getComponentsFromPath(c.xml);
|
551
|
+
}
|
552
|
+
if (!destructiveChangesOnly) {
|
553
|
+
results.manifest.add(c);
|
554
|
+
results.output.counts.added++;
|
555
|
+
}
|
556
|
+
}
|
557
|
+
catch (error) {
|
558
|
+
results.output.counts.error++;
|
559
|
+
results.output.errors.push(error.message);
|
560
|
+
}
|
561
|
+
}
|
562
|
+
}
|
563
|
+
}
|
564
|
+
else if (status === 'A') {
|
565
|
+
if (!destructiveChangesOnly) {
|
566
|
+
for (const c of getComponentsFromPath(ref2Resolver, path)) {
|
567
|
+
results.manifest.add(c);
|
568
|
+
results.output.counts.added++;
|
569
|
+
}
|
570
|
+
}
|
571
|
+
}
|
572
|
+
else {
|
573
|
+
analyzedFilesPromises.push(analyzeFile(path, ref1VirtualTreeContainer, ref2VirtualTreeContainer));
|
574
|
+
}
|
575
|
+
}
|
576
|
+
else {
|
577
|
+
debug(`${path} not included in sourcepath`);
|
578
|
+
}
|
579
|
+
}
|
580
|
+
for await (const check of analyzedFilesPromises) {
|
581
|
+
if (check.status === 0) {
|
582
|
+
if (!destructiveChangesOnly) {
|
583
|
+
for (const c of getComponentsFromPath(ref2Resolver, check.path)) {
|
584
|
+
results.manifest.add(c);
|
585
|
+
results.output.counts.modified++;
|
586
|
+
}
|
587
|
+
}
|
588
|
+
}
|
589
|
+
else if (check.status === -1) {
|
590
|
+
results.output.unchanged.push(check.path);
|
591
|
+
results.output.counts.unchanged++;
|
592
|
+
}
|
593
|
+
else if (check.status === -2) {
|
594
|
+
results.output.counts.ignored++;
|
595
|
+
results.output.ignored.ref2.push(check.path);
|
596
|
+
}
|
597
|
+
else {
|
598
|
+
if ((check.toDestructiveChanges && check.toDestructiveChanges.length > 0) ||
|
599
|
+
(check.toManifest && check.toManifest.length > 0 && !destructiveChangesOnly)) {
|
600
|
+
results.output.counts.modified++;
|
601
|
+
}
|
602
|
+
if (check.toDestructiveChanges) {
|
603
|
+
for (const c of check.toDestructiveChanges) {
|
604
|
+
results.manifest.add(c, DestructiveChangesType.POST);
|
605
|
+
}
|
606
|
+
}
|
607
|
+
if (!destructiveChangesOnly && check.toManifest) {
|
608
|
+
for (const c of check.toManifest) {
|
609
|
+
results.manifest.add(c);
|
610
|
+
}
|
611
|
+
}
|
612
|
+
}
|
613
|
+
}
|
614
|
+
results.output.ignored = {
|
615
|
+
ref1: Array.from(ref1Resolver.forceIgnoredPaths),
|
616
|
+
ref2: results.output.ignored.ref2.concat(Array.from(ref2Resolver.forceIgnoredPaths)),
|
617
|
+
};
|
618
|
+
results.output.counts.ignored =
|
619
|
+
results.output.counts.ignored + ref1Resolver.forceIgnoredPaths.size + ref2Resolver.forceIgnoredPaths.size;
|
620
|
+
return results;
|
621
|
+
}
|
622
|
+
function fixComponentSetChilds(cs) {
|
623
|
+
let sourceComponents = cs.getSourceComponents();
|
624
|
+
// SDR library is more strict and avoids fixes like this
|
625
|
+
const childsTobeReplacedByParent = [
|
626
|
+
...Object.keys(registry.types.workflow.children?.types ?? {}),
|
627
|
+
...Object.keys(registry.types.sharingrules.children?.types ?? {}),
|
628
|
+
...Object.keys(registry.types.customobjecttranslation.children?.types ?? {}),
|
629
|
+
...Object.keys(registry.types.bot.children?.types ?? {}),
|
630
|
+
];
|
631
|
+
sourceComponents = sourceComponents.map((component) => {
|
632
|
+
if (!component.isMarkedForDelete() && childsTobeReplacedByParent.includes(component.type.id) && component.parent) {
|
633
|
+
debug(`replace: ${component.type.name}:${component.fullName} -> ${component.parent.type.name}:${component.parent.fullName}`);
|
634
|
+
return component.parent;
|
635
|
+
}
|
636
|
+
return component;
|
637
|
+
});
|
638
|
+
return new ComponentSet(sourceComponents, registryAccess);
|
639
|
+
}
|
640
|
+
//# sourceMappingURL=diff.js.map
|