@teambit/checkout 1.0.106 → 1.0.108
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/checkout-cmd.ts +261 -0
- package/checkout-version.ts +240 -0
- package/checkout.aspect.ts +5 -0
- package/checkout.main.runtime.ts +514 -0
- package/dist/checkout-cmd.js +3 -3
- package/dist/checkout-cmd.js.map +1 -1
- package/dist/checkout-version.d.ts +4 -4
- package/dist/checkout-version.js +1 -1
- package/dist/checkout-version.js.map +1 -1
- package/dist/checkout.main.runtime.d.ts +3 -3
- package/dist/checkout.main.runtime.js +11 -15
- package/dist/checkout.main.runtime.js.map +1 -1
- package/index.ts +21 -0
- package/package.json +16 -25
- package/revert-cmd.ts +45 -0
- package/tsconfig.json +16 -21
- package/types/asset.d.ts +15 -3
- /package/dist/{preview-1703505948637.js → preview-1703647408454.js} +0 -0
package/checkout-cmd.ts
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { Command, CommandOptions } from '@teambit/cli';
|
|
3
|
+
import {
|
|
4
|
+
ApplyVersionResults,
|
|
5
|
+
applyVersionReport,
|
|
6
|
+
conflictSummaryReport,
|
|
7
|
+
installationErrorOutput,
|
|
8
|
+
compilationErrorOutput,
|
|
9
|
+
getRemovedOutput,
|
|
10
|
+
getAddedOutput,
|
|
11
|
+
} from '@teambit/merging';
|
|
12
|
+
import { COMPONENT_PATTERN_HELP, HEAD, LATEST } from '@teambit/legacy/dist/constants';
|
|
13
|
+
import { MergeStrategy } from '@teambit/legacy/dist/consumer/versions-ops/merge-version';
|
|
14
|
+
import { ComponentID } from '@teambit/component-id';
|
|
15
|
+
import { BitError } from '@teambit/bit-error';
|
|
16
|
+
import { CheckoutMain, CheckoutProps } from './checkout.main.runtime';
|
|
17
|
+
|
|
18
|
+
export class CheckoutCmd implements Command {
|
|
19
|
+
name = 'checkout <to> [component-pattern]';
|
|
20
|
+
arguments = [
|
|
21
|
+
{
|
|
22
|
+
name: 'to',
|
|
23
|
+
description:
|
|
24
|
+
"permitted values: [head, latest, reset, specific-version]. 'head' - last snap/tag. 'latest' - semver latest tag. 'reset' - removes local changes",
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: 'component-pattern',
|
|
28
|
+
description: COMPONENT_PATTERN_HELP,
|
|
29
|
+
},
|
|
30
|
+
];
|
|
31
|
+
description = 'switch between component versions or remove local changes';
|
|
32
|
+
helpUrl = 'reference/components/merging-changes#checkout-snaps-to-the-working-directory';
|
|
33
|
+
group = 'development';
|
|
34
|
+
extendedDescription = `
|
|
35
|
+
\`bit checkout <version> [component-pattern]\` => checkout the specified ids (or all components when --all is used) to the specified version
|
|
36
|
+
\`bit checkout head [component-pattern]\` => checkout to the last snap/tag (use --latest if you only want semver tags), omit [component-pattern] to checkout head for all
|
|
37
|
+
\`bit checkout latest [component-pattern]\` => checkout to the latest satisfying semver tag, omit [component-pattern] to checkout latest for all
|
|
38
|
+
\`bit checkout reset [component-pattern]\` => remove local modifications from the specified ids (or all components when --all is used)
|
|
39
|
+
when on a lane, "checkout head" only checks out components on this lane. to update main components, run "bit lane merge main"`;
|
|
40
|
+
alias = 'U';
|
|
41
|
+
options = [
|
|
42
|
+
[
|
|
43
|
+
'i',
|
|
44
|
+
'interactive-merge',
|
|
45
|
+
'when a component is modified and the merge process found conflicts, display options to resolve them',
|
|
46
|
+
],
|
|
47
|
+
['', 'ours', 'DEPRECATED. use --auto-merge-resolve. In the future, this flag will leave the current code intact'],
|
|
48
|
+
[
|
|
49
|
+
'',
|
|
50
|
+
'theirs',
|
|
51
|
+
'DEPRECATED. use --auto-merge-resolve. In the future, this flag will override the current code with the incoming code',
|
|
52
|
+
],
|
|
53
|
+
['', 'manual', 'DEPRECATED. use --auto-merge-resolve'],
|
|
54
|
+
[
|
|
55
|
+
'',
|
|
56
|
+
'auto-merge-resolve <merge-strategy>',
|
|
57
|
+
'in case of merge conflict, resolve according to the provided strategy: [ours, theirs, manual]',
|
|
58
|
+
],
|
|
59
|
+
['r', 'reset', 'revert changes that were not snapped/tagged'],
|
|
60
|
+
['a', 'all', 'all components'],
|
|
61
|
+
[
|
|
62
|
+
'e',
|
|
63
|
+
'workspace-only',
|
|
64
|
+
"only relevant for 'bit checkout head' when on a lane. don't import components from the remote lane that are not already in the workspace",
|
|
65
|
+
],
|
|
66
|
+
['v', 'verbose', 'showing verbose output for inspection'],
|
|
67
|
+
['x', 'skip-dependency-installation', 'do not auto-install dependencies of the imported components'],
|
|
68
|
+
] as CommandOptions;
|
|
69
|
+
loader = true;
|
|
70
|
+
|
|
71
|
+
constructor(private checkout: CheckoutMain) {}
|
|
72
|
+
|
|
73
|
+
async report(
|
|
74
|
+
[to, componentPattern]: [string, string],
|
|
75
|
+
{
|
|
76
|
+
interactiveMerge = false,
|
|
77
|
+
ours = false,
|
|
78
|
+
theirs = false,
|
|
79
|
+
manual = false,
|
|
80
|
+
autoMergeResolve,
|
|
81
|
+
all = false,
|
|
82
|
+
workspaceOnly = false,
|
|
83
|
+
verbose = false,
|
|
84
|
+
skipDependencyInstallation = false,
|
|
85
|
+
revert = false,
|
|
86
|
+
}: {
|
|
87
|
+
interactiveMerge?: boolean;
|
|
88
|
+
ours?: boolean;
|
|
89
|
+
theirs?: boolean;
|
|
90
|
+
manual?: boolean;
|
|
91
|
+
autoMergeResolve?: MergeStrategy;
|
|
92
|
+
all?: boolean;
|
|
93
|
+
workspaceOnly?: boolean;
|
|
94
|
+
verbose?: boolean;
|
|
95
|
+
skipDependencyInstallation?: boolean;
|
|
96
|
+
revert?: boolean;
|
|
97
|
+
}
|
|
98
|
+
) {
|
|
99
|
+
if (ours || theirs || manual) {
|
|
100
|
+
throw new BitError(
|
|
101
|
+
'the "--ours", "--theirs" and "--manual" flags are deprecated. use "--auto-merge-resolve" instead.'
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
if (
|
|
105
|
+
autoMergeResolve &&
|
|
106
|
+
autoMergeResolve !== 'ours' &&
|
|
107
|
+
autoMergeResolve !== 'theirs' &&
|
|
108
|
+
autoMergeResolve !== 'manual'
|
|
109
|
+
) {
|
|
110
|
+
throw new BitError('--auto-merge-resolve must be one of the following: [ours, theirs, manual]');
|
|
111
|
+
}
|
|
112
|
+
if (workspaceOnly && to !== HEAD) {
|
|
113
|
+
throw new BitError('--workspace-only is only relevant when running "bit checkout head" on a lane');
|
|
114
|
+
}
|
|
115
|
+
const checkoutProps: CheckoutProps = {
|
|
116
|
+
promptMergeOptions: interactiveMerge,
|
|
117
|
+
mergeStrategy: autoMergeResolve,
|
|
118
|
+
all,
|
|
119
|
+
verbose,
|
|
120
|
+
isLane: false,
|
|
121
|
+
skipNpmInstall: skipDependencyInstallation,
|
|
122
|
+
workspaceOnly,
|
|
123
|
+
revert,
|
|
124
|
+
};
|
|
125
|
+
if (to === HEAD) checkoutProps.head = true;
|
|
126
|
+
else if (to === LATEST) checkoutProps.latest = true;
|
|
127
|
+
else if (to === 'reset') checkoutProps.reset = true;
|
|
128
|
+
else if (to === 'main') checkoutProps.main = true;
|
|
129
|
+
else {
|
|
130
|
+
if (!ComponentID.isValidVersion(to)) throw new BitError(`the specified version "${to}" is not a valid version`);
|
|
131
|
+
checkoutProps.version = to;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const checkoutResults = await this.checkout.checkoutByCLIValues(componentPattern || '', checkoutProps);
|
|
135
|
+
return checkoutOutput(checkoutResults, checkoutProps);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function checkoutOutput(checkoutResults: ApplyVersionResults, checkoutProps: CheckoutProps) {
|
|
140
|
+
const {
|
|
141
|
+
components,
|
|
142
|
+
version,
|
|
143
|
+
failedComponents,
|
|
144
|
+
removedComponents,
|
|
145
|
+
addedComponents,
|
|
146
|
+
leftUnresolvedConflicts,
|
|
147
|
+
newFromLane,
|
|
148
|
+
newFromLaneAdded,
|
|
149
|
+
installationError,
|
|
150
|
+
compilationError,
|
|
151
|
+
}: ApplyVersionResults = checkoutResults;
|
|
152
|
+
|
|
153
|
+
const { head, reset, latest, main, revert, verbose, all } = checkoutProps;
|
|
154
|
+
|
|
155
|
+
// components that failed for no legitimate reason. e.g. merge-conflict.
|
|
156
|
+
const realFailedComponents = (failedComponents || []).filter((f) => !f.unchangedLegitimately);
|
|
157
|
+
// components that weren't checked out for legitimate reasons, e.g. up-to-date.
|
|
158
|
+
const notCheckedOutComponents = (failedComponents || []).filter((f) => f.unchangedLegitimately);
|
|
159
|
+
|
|
160
|
+
const getFailureOutput = () => {
|
|
161
|
+
if (!realFailedComponents.length) return '';
|
|
162
|
+
const title = 'the checkout has been failed on the following component(s)';
|
|
163
|
+
const body = realFailedComponents
|
|
164
|
+
.map(
|
|
165
|
+
(failedComponent) =>
|
|
166
|
+
`${chalk.bold(failedComponent.id.toString())} - ${chalk.red(failedComponent.unchangedMessage)}`
|
|
167
|
+
)
|
|
168
|
+
.join('\n');
|
|
169
|
+
return `${chalk.underline(title)}\n${body}\n\n`;
|
|
170
|
+
};
|
|
171
|
+
const getNotCheckedOutOutput = () => {
|
|
172
|
+
if (!notCheckedOutComponents.length) return '';
|
|
173
|
+
if (!verbose && all) {
|
|
174
|
+
return chalk.green(
|
|
175
|
+
`checkout was not needed for ${chalk.bold(
|
|
176
|
+
notCheckedOutComponents.length.toString()
|
|
177
|
+
)} components (use --verbose to get more details)\n`
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
const title = 'checkout was not required for the following component(s)';
|
|
181
|
+
const body = notCheckedOutComponents
|
|
182
|
+
.map((failedComponent) => `${failedComponent.id.toString()} - ${failedComponent.unchangedMessage}`)
|
|
183
|
+
.join('\n');
|
|
184
|
+
return `${chalk.underline(title)}\n${body}\n\n`;
|
|
185
|
+
};
|
|
186
|
+
const getConflictSummary = () => {
|
|
187
|
+
if (!components || !components.length || !leftUnresolvedConflicts) return '';
|
|
188
|
+
const title = `\n\nfiles with conflicts summary\n`;
|
|
189
|
+
const suggestion = `\n\nfix the conflicts above manually and then run "bit install".
|
|
190
|
+
once ready, snap/tag the components to persist the changes`;
|
|
191
|
+
return chalk.underline(title) + conflictSummaryReport(components) + chalk.yellow(suggestion);
|
|
192
|
+
};
|
|
193
|
+
const getSuccessfulOutput = () => {
|
|
194
|
+
const switchedOrReverted = revert ? 'reverted' : 'switched';
|
|
195
|
+
if (!components || !components.length) return '';
|
|
196
|
+
if (components.length === 1) {
|
|
197
|
+
const component = components[0];
|
|
198
|
+
const componentName = reset ? component.id.toString() : component.id.toStringWithoutVersion();
|
|
199
|
+
if (reset) return `successfully reset ${chalk.bold(componentName)}\n`;
|
|
200
|
+
const title = `successfully ${switchedOrReverted} ${chalk.bold(componentName)} to version ${chalk.bold(
|
|
201
|
+
// @ts-ignore version is defined when !reset
|
|
202
|
+
head || latest ? component.id.version : version
|
|
203
|
+
)}\n`;
|
|
204
|
+
return chalk.bold(title) + applyVersionReport(components, false);
|
|
205
|
+
}
|
|
206
|
+
if (reset) {
|
|
207
|
+
const title = 'successfully reset the following components\n\n';
|
|
208
|
+
const body = components.map((component) => chalk.bold(component.id.toString())).join('\n');
|
|
209
|
+
return chalk.underline(title) + body;
|
|
210
|
+
}
|
|
211
|
+
const getVerOutput = () => {
|
|
212
|
+
if (head) return 'their head version';
|
|
213
|
+
if (latest) return 'their latest version';
|
|
214
|
+
if (main) return 'their main version';
|
|
215
|
+
// @ts-ignore version is defined when !reset
|
|
216
|
+
return `version ${chalk.bold(version)}`;
|
|
217
|
+
};
|
|
218
|
+
const versionOutput = getVerOutput();
|
|
219
|
+
const title = `successfully ${switchedOrReverted} ${components.length} components to ${versionOutput}\n`;
|
|
220
|
+
const showVersion = head || reset;
|
|
221
|
+
return chalk.bold(title) + applyVersionReport(components, true, showVersion);
|
|
222
|
+
};
|
|
223
|
+
const getNewOnLaneOutput = () => {
|
|
224
|
+
if (!newFromLane?.length) return '';
|
|
225
|
+
const title = newFromLaneAdded
|
|
226
|
+
? `successfully added the following components from the lane`
|
|
227
|
+
: `the following components exist on the lane but were not added to the workspace. omit --workspace-only flag to add them`;
|
|
228
|
+
const body = newFromLane.join('\n');
|
|
229
|
+
return `\n\n${chalk.underline(title)}\n${body}`;
|
|
230
|
+
};
|
|
231
|
+
const getSummary = () => {
|
|
232
|
+
const checkedOut = components?.length || 0;
|
|
233
|
+
const notCheckedOutLegitimately = notCheckedOutComponents.length;
|
|
234
|
+
const failedToCheckOut = realFailedComponents.length;
|
|
235
|
+
const newLines = '\n\n';
|
|
236
|
+
const title = chalk.bold.underline('Summary');
|
|
237
|
+
const checkedOutStr = `\nTotal Changed: ${chalk.bold(checkedOut.toString())}`;
|
|
238
|
+
const unchangedLegitimatelyStr = `\nTotal Unchanged: ${chalk.bold(notCheckedOutLegitimately.toString())}`;
|
|
239
|
+
const failedToCheckOutStr = `\nTotal Failed: ${chalk.bold(failedToCheckOut.toString())}`;
|
|
240
|
+
const newOnLaneNum = newFromLane?.length || 0;
|
|
241
|
+
const newOnLaneAddedStr = newFromLaneAdded ? ' (added)' : ' (not added)';
|
|
242
|
+
const newOnLaneStr = newOnLaneNum
|
|
243
|
+
? `\nNew on lane${newOnLaneAddedStr}: ${chalk.bold(newOnLaneNum.toString())}`
|
|
244
|
+
: '';
|
|
245
|
+
|
|
246
|
+
return newLines + title + checkedOutStr + unchangedLegitimatelyStr + failedToCheckOutStr + newOnLaneStr;
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
return (
|
|
250
|
+
getFailureOutput() +
|
|
251
|
+
getNotCheckedOutOutput() +
|
|
252
|
+
getSuccessfulOutput() +
|
|
253
|
+
getRemovedOutput(removedComponents) +
|
|
254
|
+
getAddedOutput(addedComponents) +
|
|
255
|
+
getNewOnLaneOutput() +
|
|
256
|
+
getConflictSummary() +
|
|
257
|
+
getSummary() +
|
|
258
|
+
installationErrorOutput(installationError) +
|
|
259
|
+
compilationErrorOutput(compilationError)
|
|
260
|
+
);
|
|
261
|
+
}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
import { Consumer } from '@teambit/legacy/dist/consumer';
|
|
3
|
+
import { ComponentID } from '@teambit/component-id';
|
|
4
|
+
import GeneralError from '@teambit/legacy/dist/error/general-error';
|
|
5
|
+
import Version from '@teambit/legacy/dist/scope/models/version';
|
|
6
|
+
import { SourceFile } from '@teambit/legacy/dist/consumer/component/sources';
|
|
7
|
+
import { pathNormalizeToLinux, PathOsBased } from '@teambit/legacy/dist/utils/path';
|
|
8
|
+
import DataToPersist from '@teambit/legacy/dist/consumer/component/sources/data-to-persist';
|
|
9
|
+
import RemovePath from '@teambit/legacy/dist/consumer/component/sources/remove-path';
|
|
10
|
+
import {
|
|
11
|
+
ApplyVersionResult,
|
|
12
|
+
FilesStatus,
|
|
13
|
+
FileStatus,
|
|
14
|
+
MergeOptions,
|
|
15
|
+
MergeStrategy,
|
|
16
|
+
} from '@teambit/legacy/dist/consumer/versions-ops/merge-version';
|
|
17
|
+
import { MergeResultsThreeWay } from '@teambit/legacy/dist/consumer/versions-ops/merge-version/three-way-merge';
|
|
18
|
+
import ConsumerComponent from '@teambit/legacy/dist/consumer/component';
|
|
19
|
+
import { BitError } from '@teambit/bit-error';
|
|
20
|
+
import chalk from 'chalk';
|
|
21
|
+
|
|
22
|
+
export type CheckoutProps = {
|
|
23
|
+
version?: string; // if reset is true, the version is undefined
|
|
24
|
+
ids?: ComponentID[];
|
|
25
|
+
latestVersion?: boolean;
|
|
26
|
+
promptMergeOptions?: boolean;
|
|
27
|
+
mergeStrategy?: MergeStrategy | null;
|
|
28
|
+
verbose?: boolean;
|
|
29
|
+
skipNpmInstall?: boolean;
|
|
30
|
+
ignorePackageJson?: boolean;
|
|
31
|
+
writeConfig?: boolean;
|
|
32
|
+
reset?: boolean; // remove local changes. if set, the version is undefined.
|
|
33
|
+
all?: boolean; // checkout all ids
|
|
34
|
+
ignoreDist?: boolean;
|
|
35
|
+
isLane?: boolean;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type ComponentStatusBase = {
|
|
39
|
+
currentComponent?: ConsumerComponent;
|
|
40
|
+
componentFromModel?: Version;
|
|
41
|
+
id: ComponentID;
|
|
42
|
+
shouldBeRemoved?: boolean; // in case the component is soft-removed, it should be removed from the workspace
|
|
43
|
+
unchangedMessage?: string; // this gets populated either upon skip or failure.
|
|
44
|
+
unchangedLegitimately?: boolean; // true for skipped legitimately (e.g. already up to date). false for failure.
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export type ComponentStatus = ComponentStatusBase & {
|
|
48
|
+
mergeResults?: MergeResultsThreeWay | null | undefined;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export type ApplyVersionWithComps = {
|
|
52
|
+
applyVersionResult: ApplyVersionResult;
|
|
53
|
+
component?: ConsumerComponent;
|
|
54
|
+
// in case the component needs to be written to the filesystem, this is the component to write.
|
|
55
|
+
legacyCompToWrite?: ConsumerComponent;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 1) when the files are modified with conflicts and the strategy is "ours", leave the FS as is
|
|
60
|
+
* and update only bitmap id version. (not the componentMap object).
|
|
61
|
+
*
|
|
62
|
+
* 2) when the files are modified with conflicts and the strategy is "theirs", write the component
|
|
63
|
+
* according to id.version.
|
|
64
|
+
*
|
|
65
|
+
* 3) when files are modified with no conflict or files are modified with conflicts and the
|
|
66
|
+
* strategy is manual, load the component according to id.version and update component.files.
|
|
67
|
+
* applyModifiedVersion() docs explains what files are updated/added.
|
|
68
|
+
*
|
|
69
|
+
* 4) when --reset flag is used, write the component according to the bitmap version
|
|
70
|
+
*
|
|
71
|
+
* Side note:
|
|
72
|
+
* Deleted file => if files are in used version but not in the modified one, no need to delete it. (similar to git).
|
|
73
|
+
* Added file => if files are not in used version but in the modified one, they'll be under mergeResults.addFiles
|
|
74
|
+
*/
|
|
75
|
+
export async function applyVersion(
|
|
76
|
+
consumer: Consumer,
|
|
77
|
+
id: ComponentID,
|
|
78
|
+
componentFromFS: ConsumerComponent | null | undefined, // it can be null only when isLanes is true
|
|
79
|
+
mergeResults: MergeResultsThreeWay | null | undefined,
|
|
80
|
+
checkoutProps: CheckoutProps
|
|
81
|
+
): Promise<ApplyVersionWithComps> {
|
|
82
|
+
if (!checkoutProps.isLane && !componentFromFS)
|
|
83
|
+
throw new Error(`applyVersion expect to get componentFromFS for ${id.toString()}`);
|
|
84
|
+
const { mergeStrategy } = checkoutProps;
|
|
85
|
+
let filesStatus = {};
|
|
86
|
+
if (mergeResults && mergeResults.hasConflicts && mergeStrategy === MergeOptions.ours) {
|
|
87
|
+
// even when isLane is true, the mergeResults is possible only when the component is on the filesystem
|
|
88
|
+
// otherwise it's impossible to have conflicts
|
|
89
|
+
if (!componentFromFS) throw new Error(`applyVersion expect to get componentFromFS for ${id.toString()}`);
|
|
90
|
+
componentFromFS.files.forEach((file) => {
|
|
91
|
+
filesStatus[pathNormalizeToLinux(file.relative)] = FileStatus.unchanged;
|
|
92
|
+
});
|
|
93
|
+
consumer.bitMap.updateComponentId(id);
|
|
94
|
+
return { applyVersionResult: { id, filesStatus } };
|
|
95
|
+
}
|
|
96
|
+
const component = await consumer.loadComponentFromModelImportIfNeeded(id);
|
|
97
|
+
const componentMap = componentFromFS && componentFromFS.componentMap;
|
|
98
|
+
if (componentFromFS && !componentMap) throw new GeneralError('applyVersion: componentMap was not found');
|
|
99
|
+
|
|
100
|
+
const files = component.files;
|
|
101
|
+
updateFileStatus(files, filesStatus, componentFromFS || undefined);
|
|
102
|
+
|
|
103
|
+
await removeFilesIfNeeded(filesStatus, componentFromFS || undefined);
|
|
104
|
+
|
|
105
|
+
if (mergeResults) {
|
|
106
|
+
// update files according to the merge results
|
|
107
|
+
const { filesStatus: modifiedStatus, modifiedFiles } = applyModifiedVersion(files, mergeResults, mergeStrategy);
|
|
108
|
+
filesStatus = { ...filesStatus, ...modifiedStatus };
|
|
109
|
+
component.files = modifiedFiles;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
applyVersionResult: { id, filesStatus },
|
|
114
|
+
component,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function updateFileStatus(files: SourceFile[], filesStatus: FilesStatus, componentFromFS?: ConsumerComponent) {
|
|
119
|
+
files.forEach((file) => {
|
|
120
|
+
const fileFromFs = componentFromFS?.files.find((f) => f.relative === file.relative);
|
|
121
|
+
const areFilesEqual = fileFromFs && Buffer.compare(fileFromFs.contents, file.contents) === 0;
|
|
122
|
+
// @ts-ignore
|
|
123
|
+
filesStatus[pathNormalizeToLinux(file.relative)] = areFilesEqual ? FileStatus.unchanged : FileStatus.updated;
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* when files exist on the filesystem but not on the checked out versions, they need to be deleted.
|
|
129
|
+
* without this function, these files would be left on the filesystem. (we don't delete the comp-dir before writing).
|
|
130
|
+
* this needs to be done *before* the component is written to the filesystem, otherwise, it won't work when a file
|
|
131
|
+
* has a case change. e.g. from uppercase to lowercase. (see merge-lane.e2e 'renaming files from uppercase to lowercase').
|
|
132
|
+
*/
|
|
133
|
+
export async function removeFilesIfNeeded(filesStatus: FilesStatus, componentFromFS?: ConsumerComponent) {
|
|
134
|
+
if (!componentFromFS) return;
|
|
135
|
+
const filePathsFromFS = componentFromFS.files || [];
|
|
136
|
+
const dataToPersist = new DataToPersist();
|
|
137
|
+
filePathsFromFS.forEach((file) => {
|
|
138
|
+
const filename = pathNormalizeToLinux(file.relative);
|
|
139
|
+
if (!filesStatus[filename]) {
|
|
140
|
+
// @ts-ignore todo: typescript has a good point here. it should be the string "removed", not chalk.green(removed).
|
|
141
|
+
filesStatus[filename] = FileStatus.removed;
|
|
142
|
+
dataToPersist.removePath(new RemovePath(file.path));
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
await dataToPersist.persistAllToFS();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* relevant only when
|
|
150
|
+
* 1) there is no conflict => add files from mergeResults: addFiles, overrideFiles and modifiedFiles.output.
|
|
151
|
+
* 2) there is conflict and mergeStrategy is manual => add files from mergeResults: addFiles, overrideFiles and modifiedFiles.conflict.
|
|
152
|
+
*
|
|
153
|
+
* this function only updates the files content, it doesn't write the files
|
|
154
|
+
*/
|
|
155
|
+
export function applyModifiedVersion(
|
|
156
|
+
componentFiles: SourceFile[],
|
|
157
|
+
mergeResults: MergeResultsThreeWay,
|
|
158
|
+
mergeStrategy: MergeStrategy | null | undefined
|
|
159
|
+
): { filesStatus: Record<string, any>; modifiedFiles: SourceFile[] } {
|
|
160
|
+
let modifiedFiles = componentFiles.map((file) => file.clone());
|
|
161
|
+
const filesStatus = {};
|
|
162
|
+
if (mergeResults.hasConflicts && mergeStrategy !== MergeOptions.manual) {
|
|
163
|
+
return { filesStatus, modifiedFiles };
|
|
164
|
+
}
|
|
165
|
+
mergeResults.modifiedFiles.forEach((file) => {
|
|
166
|
+
const filePath: PathOsBased = path.normalize(file.filePath);
|
|
167
|
+
const foundFile = modifiedFiles.find((componentFile) => componentFile.relative === filePath);
|
|
168
|
+
if (!foundFile) throw new GeneralError(`file ${filePath} not found`);
|
|
169
|
+
if (file.conflict) {
|
|
170
|
+
foundFile.contents = Buffer.from(file.conflict);
|
|
171
|
+
filesStatus[file.filePath] = FileStatus.manual;
|
|
172
|
+
} else if (typeof file.output === 'string') {
|
|
173
|
+
foundFile.contents = Buffer.from(file.output);
|
|
174
|
+
filesStatus[file.filePath] = FileStatus.merged;
|
|
175
|
+
} else if (file.isBinaryConflict) {
|
|
176
|
+
// leave the file as is and notify the user later about it.
|
|
177
|
+
foundFile.contents = file.fsFile.contents;
|
|
178
|
+
filesStatus[file.filePath] = FileStatus.binaryConflict;
|
|
179
|
+
} else {
|
|
180
|
+
throw new GeneralError(`file ${filePath} does not have output nor conflict`);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
mergeResults.addFiles.forEach((file) => {
|
|
185
|
+
const filePath: PathOsBased = path.normalize(file.filePath);
|
|
186
|
+
if (modifiedFiles.find((m) => m.relative === filePath)) return;
|
|
187
|
+
modifiedFiles.push(file.fsFile);
|
|
188
|
+
filesStatus[file.filePath] = FileStatus.added;
|
|
189
|
+
});
|
|
190
|
+
mergeResults.deletedConflictFiles.forEach((file) => {
|
|
191
|
+
if (!file.fsFile) return;
|
|
192
|
+
const filePath: PathOsBased = path.normalize(file.filePath);
|
|
193
|
+
if (modifiedFiles.find((m) => m.relative === filePath)) return;
|
|
194
|
+
modifiedFiles.push(file.fsFile);
|
|
195
|
+
filesStatus[file.filePath] = FileStatus.added;
|
|
196
|
+
});
|
|
197
|
+
mergeResults.removeFiles.forEach((file) => {
|
|
198
|
+
const filePath: PathOsBased = path.normalize(file.filePath);
|
|
199
|
+
filesStatus[file.filePath] = FileStatus.removed;
|
|
200
|
+
modifiedFiles = modifiedFiles.filter((f) => f.relative !== filePath);
|
|
201
|
+
});
|
|
202
|
+
mergeResults.remainDeletedFiles.forEach((file) => {
|
|
203
|
+
const filePath: PathOsBased = path.normalize(file.filePath);
|
|
204
|
+
modifiedFiles = modifiedFiles.filter((f) => f.relative !== filePath);
|
|
205
|
+
filesStatus[file.filePath] = FileStatus.remainDeleted;
|
|
206
|
+
});
|
|
207
|
+
mergeResults.deletedConflictFiles.forEach((file) => {
|
|
208
|
+
filesStatus[file.filePath] = FileStatus.deletedConflict;
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
mergeResults.overrideFiles.forEach((file) => {
|
|
212
|
+
const filePath: PathOsBased = path.normalize(file.filePath);
|
|
213
|
+
const foundFile = modifiedFiles.find((componentFile) => componentFile.relative === filePath);
|
|
214
|
+
if (!foundFile) throw new GeneralError(`file ${filePath} not found`);
|
|
215
|
+
foundFile.contents = file.fsFile.contents;
|
|
216
|
+
filesStatus[file.filePath] = FileStatus.overridden;
|
|
217
|
+
});
|
|
218
|
+
mergeResults.updatedFiles.forEach((file) => {
|
|
219
|
+
const filePath: PathOsBased = path.normalize(file.filePath);
|
|
220
|
+
const foundFile = modifiedFiles.find((componentFile) => componentFile.relative === filePath);
|
|
221
|
+
if (!foundFile) throw new GeneralError(`file ${filePath} not found`);
|
|
222
|
+
foundFile.contents = file.content;
|
|
223
|
+
filesStatus[file.filePath] = FileStatus.updated;
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
return { filesStatus, modifiedFiles };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function throwForFailures(allComponentsStatus: ComponentStatusBase[]) {
|
|
230
|
+
const failedComponents = allComponentsStatus.filter((c) => c.unchangedMessage && !c.unchangedLegitimately);
|
|
231
|
+
if (failedComponents.length) {
|
|
232
|
+
const failureMsgs = failedComponents
|
|
233
|
+
.map(
|
|
234
|
+
(failedComponent) =>
|
|
235
|
+
`${chalk.bold(failedComponent.id.toString())} - ${chalk.red(failedComponent.unchangedMessage as string)}`
|
|
236
|
+
)
|
|
237
|
+
.join('\n');
|
|
238
|
+
throw new BitError(`unable to proceed due to the following failures:\n${failureMsgs}`);
|
|
239
|
+
}
|
|
240
|
+
}
|