Skip to content

Commit f4209dd

Browse files
Cli merge with abort rebase (#1094)
* changeset * feat: workflow merge compatibility * feat: add history to workflow property * feat: error when there are incompatible workflows * feat: ignore incompatibility when force passed * tests: fix workflow check * tests: incompatibility merging via project class * tests: fix error wording * chore: updates * conflict * changeset --------- Co-authored-by: Farhan Yahaya <yahyafarhan48@gmail.com>
1 parent ee6086f commit f4209dd

6 files changed

Lines changed: 71 additions & 5 deletions

File tree

.changeset/fluffy-eggs-wink.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@openfn/project': patch
3+
---
4+
5+
On merge, warn when projects may have diverged

.changeset/weak-gifts-work.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@openfn/cli': patch
3+
---
4+
5+
Warn when merging projects which may have diverged

packages/cli/src/merge/command.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import yargs from 'yargs';
22
import { Opts } from '../options';
3-
import { ensure, build } from '../util/command-builders';
3+
import { ensure, build, override } from '../util/command-builders';
44
import * as o from '../options';
55

66
export type MergeOptions = Required<
@@ -12,6 +12,7 @@ export type MergeOptions = Required<
1212
| 'removeUnmapped'
1313
| 'workflowMappings'
1414
| 'log'
15+
| 'force'
1516
>
1617
>;
1718

@@ -21,6 +22,9 @@ const options = [
2122
o.removeUnmapped,
2223
o.workflowMappings,
2324
o.log,
25+
override(o.force, {
26+
description: 'Force a merge even when workflows are incompatible',
27+
}),
2428
];
2529

2630
const mergeCommand: yargs.CommandModule = {

packages/cli/src/merge/handler.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ const mergeHandler = async (options: MergeOptions, logger: Logger) => {
5555
const final = Project.merge(sourceProject, targetProject, {
5656
removeUnmapped: options.removeUnmapped,
5757
workflowMappings: options.workflowMappings,
58+
force: options.force,
5859
});
5960
const yaml = final.serialize('state', { format: 'yaml' });
6061
await fs.writeFile(finalPath, yaml);

packages/project/src/merge/merge-project.ts

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
1-
import { Workflow } from '@openfn/lexicon';
21
import { defaultsDeep, isEmpty } from 'lodash-es';
32

43
import { Project } from '../Project';
54
import { mergeWorkflows } from './merge-node';
65
import mapUuids from './map-uuids';
76
import baseMerge from '../util/base-merge';
87
import getDuplicates from '../util/get-duplicates';
8+
import Workflow from '../Workflow';
99

1010
export type MergeProjectOptions = Partial<{
1111
workflowMappings: Record<string, string>; // <source, target>
1212
removeUnmapped: boolean;
13-
14-
force: boolean; // TODO not implemented yet
13+
force: boolean;
1514
}>;
1615

1716
/**
@@ -56,6 +55,27 @@ export function merge(
5655
return !!options?.workflowMappings[w.id];
5756
});
5857

58+
// mergeability
59+
const potentialConflicts: Record<string, string> = {};
60+
for (const sourceWorkflow of sourceWorkflows) {
61+
const targetId =
62+
options.workflowMappings?.[sourceWorkflow.id] ?? sourceWorkflow.id;
63+
const targetWorkflow = target.getWorkflow(targetId);
64+
if (targetWorkflow && !sourceWorkflow.canMergeInto(targetWorkflow)) {
65+
potentialConflicts[sourceWorkflow.name] = targetWorkflow?.name;
66+
}
67+
}
68+
69+
if (Object.keys(potentialConflicts).length && !options?.force) {
70+
throw new Error(
71+
`The below workflows can't be merged directly without losing data\n${Object.entries(
72+
potentialConflicts
73+
)
74+
.map(([from, to]) => `${from}${to}`)
75+
.join('\n')}\nPass --force to force the merge anyway`
76+
);
77+
}
78+
5979
for (const sourceWorkflow of sourceWorkflows) {
6080
const targetId =
6181
options.workflowMappings?.[sourceWorkflow.id] ?? sourceWorkflow.id;

packages/project/test/project.test.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import test from 'ava';
33
import type { Provisioner } from '@openfn/lexicon/lightning';
44
import { Project } from '../src/Project';
5-
import generateWorkflow from '../src/gen/generator';
5+
import generateWorkflow, { generateProject } from '../src/gen/generator';
66

77
// TODO move to fixtures and re-use?
88
// Or use util function instead?
@@ -154,3 +154,34 @@ test('should return UUIDs for everything', (t) => {
154154
},
155155
});
156156
});
157+
158+
test('incompatible-merge: should throw error when merge is incompatible', (t) => {
159+
const source = generateWorkflow('trigger-x');
160+
source.pushHistory(source.getVersionHash());
161+
const target = generateWorkflow('trigger-y');
162+
target.pushHistory(target.getVersionHash());
163+
164+
t.false(source.canMergeInto(target));
165+
166+
const sourceProject = new Project({ workflows: [source] });
167+
const targetProject = new Project({ workflows: [target] });
168+
t.throws(() => Project.merge(sourceProject, targetProject), {
169+
message: `The below workflows can't be merged directly without losing data\nWorkflow → Workflow\nPass --force to force the merge anyway`,
170+
});
171+
});
172+
173+
test('incompatible-merge-force: should ignore incompatiblity and merge when forced', (t) => {
174+
// same as the above test with force
175+
const source = generateWorkflow('trigger-x');
176+
source.pushHistory(source.getVersionHash());
177+
const target = generateWorkflow('trigger-y');
178+
target.pushHistory(target.getVersionHash());
179+
180+
t.false(source.canMergeInto(target));
181+
182+
const sourceProject = new Project({ workflows: [source] });
183+
const targetProject = new Project({ workflows: [target] });
184+
t.notThrows(() =>
185+
Project.merge(sourceProject, targetProject, { force: true })
186+
);
187+
});

0 commit comments

Comments
 (0)