Skip to content

fix: Merge failures occurring when using different strategies (overwrite with compare, merge base, merge compare) #1917

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .talismanrc
Original file line number Diff line number Diff line change
Expand Up @@ -115,5 +115,5 @@ fileignoreconfig:
- filename: pnpm-lock.yaml
checksum: fc379207a835de8d851caa256837e2a50e0278c43e0251372f2a5292bee41fac
- filename: package-lock.json
checksum: da059d11bf1083833509cd963761d0d0916da7cf90943100735bf0ddc22e498f
checksum: 0cb373716595912a75e9fd356e7a90a67eb1d2de712c454701231e2d5dd3caf7
version: ""
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/contentstack-branches/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ $ npm install -g @contentstack/cli-cm-branches
$ csdx COMMAND
running command...
$ csdx (--version)
@contentstack/cli-cm-branches/1.4.2 darwin-arm64 node-v23.11.0
@contentstack/cli-cm-branches/1.4.2 darwin-arm64 node-v22.14.0
$ csdx --help [COMMAND]
USAGE
$ csdx COMMAND
Expand Down
120 changes: 94 additions & 26 deletions packages/contentstack-branches/src/branch/merge-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,15 +108,13 @@ export default class MergeHandler {
deleted: [],
};
const selectedItems = await selectCustomPreferences(module, this.branchCompareData[module]);
if (!selectedItems.length) {
cliux.print(chalk.red('No items were selected'));
process.exit(1);
if (selectedItems?.length) {
forEach(selectedItems, (item) => {
this.mergeSettings.mergeContent[module][item.status].push(item.value);
this.mergeSettings.itemMergeStrategies.push(item.value);
});
this.mergeSettings.strategy = 'ignore';
}
forEach(selectedItems, (item) => {
this.mergeSettings.mergeContent[module][item.status].push(item.value);
this.mergeSettings.itemMergeStrategies.push(item.value);
});
this.mergeSettings.strategy = 'ignore';
}
} else if (this.strategy === 'merge_prefer_base') {
if (this.strategySubOption === 'new') {
Expand All @@ -137,12 +135,25 @@ export default class MergeHandler {
} else if (this.strategy === 'overwrite_with_compare') {
this.mergeSettings.strategy = 'overwrite_with_compare';
}
if (this.checkEmptySelection()) {
cliux.print(chalk.red('No items selected'));
} else {
await this.displayMergeSummary();
}

const { allEmpty, moduleStatus } = this.checkEmptySelection();
const strategyName = this.mergeSettings.strategy;

if (allEmpty) {
cliux.print(chalk.red(`No items selected according to the '${strategyName}' strategy.`));
process.exit(1);
}

for (const [type, { exists, empty }] of Object.entries(moduleStatus)) {
if (exists && empty) {
const readable = type === 'contentType' ? 'Content Types' : 'Global fields';
cliux.print('\n')
cliux.print(chalk.yellow(`Note: No ${readable} selected according to the '${strategyName}' strategy.`));
}
}

this.displayMergeSummary();

if (!this.executeOption) {
const executionResponse = await selectMergeExecution();
if (executionResponse === 'previous') {
Expand All @@ -160,17 +171,71 @@ export default class MergeHandler {
}
}

checkEmptySelection() {
for (let module in this.branchCompareData) {
if (this.mergeSettings.mergeContent[module]?.modified?.length
|| this.mergeSettings.mergeContent[module]?.added?.length
|| this.mergeSettings.mergeContent[module]?.deleted?.length) {
return false;
/**
* Checks whether the selection of modules in the compare branch data is empty.
*
* This method evaluates the branch compare data and determines if there are any changes
* (added, modified, or deleted) in the modules based on the merge strategy defined in the
* merge settings. It categorizes the status of each module as either existing and empty or
* not empty.
*
* @returns An object containing:
* - `allEmpty`: A boolean indicating whether all modules are either non-existent or empty.
* - `moduleStatus`: A record mapping module types (`contentType` and `globalField`) to their
* respective statuses, which include:
* - `exists`: A boolean indicating whether the module exists in the branch comparison data.
* - `empty`: A boolean indicating whether the module has no changes (added, modified, or deleted).
*/
checkEmptySelection(): {
allEmpty: boolean;
moduleStatus: Record<string, { exists: boolean; empty: boolean }>;
} {
const strategy = this.mergeSettings.strategy;

const useMergeContent = new Set(['custom_preferences', 'ignore']);
const modifiedOnlyStrategies = new Set(['merge_modified_only_prefer_base', 'merge_modified_only_prefer_compare']);
const addedOnlyStrategies = new Set(['merge_new_only']);

const moduleStatus: Record<string, { exists: boolean; empty: boolean }> = {
contentType: { exists: false, empty: true },
globalField: { exists: false, empty: true },
};

for (const module in this.branchCompareData) {
const content = useMergeContent.has(strategy)
? this.mergeSettings.mergeContent[module]
: this.branchCompareData[module];

if (!content) continue;

const isGlobalField = module === 'global_fields';
const type = isGlobalField ? 'globalField' : 'contentType';
moduleStatus[type].exists = true;

let hasChanges = false;
if (modifiedOnlyStrategies.has(strategy)) {
hasChanges = Array.isArray(content.modified) && content.modified.length > 0;
} else if (addedOnlyStrategies.has(strategy)) {
hasChanges = Array.isArray(content.added) && content.added.length > 0;
} else {
hasChanges =
(Array.isArray(content.modified) && content.modified.length > 0) ||
(Array.isArray(content.added) && content.added.length > 0) ||
(Array.isArray(content.deleted) && content.deleted.length > 0);
}

if (hasChanges) {
moduleStatus[type].empty = false;
}
}
return true;

const allEmpty = Object.values(moduleStatus).every(
(status) => !status.exists || status.empty
);

return { allEmpty, moduleStatus };
}

displayMergeSummary() {
if (this.mergeSettings.strategy !== 'ignore') {
for (let module in this.branchCompareData) {
Expand Down Expand Up @@ -269,10 +334,10 @@ export default class MergeHandler {
};

const mergePreferencesMap = {
'existing_new': 'merge_existing_new',
'new': 'merge_new',
'existing': 'merge_existing',
'ask_preference': 'custom',
existing_new: 'merge_existing_new',
new: 'merge_new',
existing: 'merge_existing',
ask_preference: 'custom',
};
const selectedMergePreference = mergePreferencesMap[mergePreference];

Expand Down Expand Up @@ -301,7 +366,10 @@ export default class MergeHandler {

if (scriptFolderPath !== undefined) {
cliux.success(`\nSuccess! We have generated entry migration files in the folder ${scriptFolderPath}`);
cliux.print('\nWARNING!!! Migration is not intended to be run more than once. Migrated(entries/assets) will be duplicated if run more than once', { color: 'yellow' });
cliux.print(
'\nWARNING!!! Migration is not intended to be run more than once. Migrated(entries/assets) will be duplicated if run more than once',
{ color: 'yellow' },
);

let migrationCommand: string;
if (os.platform() === 'win32') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,24 +79,23 @@ async function branchCompareSDK(payload: BranchDiffPayload, skip?: number, limit
const module = payload.module || 'all';

switch (module) {
case 'content_types' || 'content_type':
case 'content_types':
case 'content_type':
return await branchQuery
.contentTypes(queryParams)
.then((data) => data)
.catch((err) => handleErrorMsg(err, payload.spinner));
break;
case 'global_fields' || 'global_field':
case 'global_fields':
case 'global_field':
return await branchQuery
.globalFields(queryParams)
.then((data) => data)
.catch((err) => handleErrorMsg(err, payload.spinner));
break;
case 'all':
return await branchQuery
.all(queryParams)
.then((data) => data)
.catch((err) => handleErrorMsg(err, payload.spinner));
break;
default:
handleErrorMsg({ errorMessage: 'Invalid module!' }, payload.spinner);
}
Expand Down
2 changes: 1 addition & 1 deletion packages/contentstack-migration/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ $ npm install -g @contentstack/cli-migration
$ csdx COMMAND
running command...
$ csdx (--version)
@contentstack/cli-migration/1.7.0 darwin-arm64 node-v23.11.0
@contentstack/cli-migration/1.7.2 darwin-arm64 node-v22.14.0
$ csdx --help [COMMAND]
USAGE
$ csdx COMMAND
Expand Down
2 changes: 1 addition & 1 deletion packages/contentstack-migration/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@contentstack/cli-migration",
"version": "1.7.1",
"version": "1.7.2",
"author": "@contentstack",
"bugs": "https://github.com/contentstack/cli/issues",
"dependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/contentstack-migration/src/utils/modules.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ function executeShellCommand(pkg, directory = '') {
try {
const result = spawnSync(`npm`, ['i', pkg], { stdio: 'inherit', cwd: directory, shell: false });
if (result?.error) throw result.error;
console.log(`Command executed successfully: ${command}`);
console.log(`Command executed successfully`);
} catch (error) {
console.error(`Command execution failed. Error: ${error?.message}`);
}
Expand Down
5 changes: 3 additions & 2 deletions packages/contentstack/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ $ npm install -g @contentstack/cli
$ csdx COMMAND
running command...
$ csdx (--version|-v)
@contentstack/cli/1.40.0 darwin-arm64 node-v23.11.0
@contentstack/cli/1.40.4 darwin-arm64 node-v22.14.0
$ csdx --help [COMMAND]
USAGE
$ csdx COMMAND
Expand Down Expand Up @@ -3776,7 +3776,8 @@ USAGE
$ csdx launch:functions [-p <value>] [-d <value>]

FLAGS
-d, --data-dir=<value> [default: /Users/sunil.lakshman/Documents/cli/packages/contentstack] Current working directory
-d, --data-dir=<value> [default: /Users/aman.kumar/Documents/cli-repos/cli/packages/contentstack] Current working
directory
-p, --port=<value> [default: 3000] Port number

DESCRIPTION
Expand Down
4 changes: 2 additions & 2 deletions packages/contentstack/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"@contentstack/cli-audit": "~1.12.1",
"@contentstack/cli-auth": "~1.4.0",
"@contentstack/cli-cm-bootstrap": "~1.14.0",
"@contentstack/cli-cm-branches": "~1.4.1",
"@contentstack/cli-cm-branches": "~1.4.2",
"@contentstack/cli-cm-bulk-publish": "~1.8.0",
"@contentstack/cli-cm-clone": "~1.14.0",
"@contentstack/cli-cm-export": "~1.16.1",
Expand All @@ -37,7 +37,7 @@
"@contentstack/cli-command": "~1.5.0",
"@contentstack/cli-config": "~1.12.0",
"@contentstack/cli-launch": "^1.8.0",
"@contentstack/cli-migration": "~1.7.1",
"@contentstack/cli-migration": "~1.7.2",
"@contentstack/cli-utilities": "~1.11.0",
"@contentstack/cli-variants": "~1.2.1",
"@contentstack/management": "~1.20.3",
Expand Down
2 changes: 1 addition & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading