Skip to content
Open
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
39 changes: 39 additions & 0 deletions e2e/harmony/lanes/merge-lanes-from-scope.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -588,4 +588,43 @@ describe('merge lanes from scope', function () {
expect(obj.dependencies).to.have.lengthOf(0);
});
});
describe('merge from scope lane to main when it is not up to date with deps changes', () => {
let bareMerge;
before(() => {
helper.scopeHelper.setNewLocalAndRemoteScopes();
helper.fs.outputFile('comp/index.js', `require('@${helper.scopes.remote}/dep1');\nrequire('@${helper.scopes.remote}/dep2');`);
helper.fs.outputFile('dep1/index.js', 'console.log("hello");');
helper.fs.outputFile('dep2/index.js', 'console.log("hello");');
helper.command.addComponent('comp');
helper.command.addComponent('dep1');
helper.command.addComponent('dep2');
helper.command.tagAllWithoutBuild();
helper.command.export();

helper.command.createLane();
helper.fs.outputFile('dep1/index.js', 'console.log("hello-from-lane");');
helper.command.snapComponentWithoutBuild('dep1');
helper.command.export();

helper.command.switchLocalLane('main', '-x');
helper.fs.outputFile('dep2/index.js', 'console.log("hello-from-main");');
helper.command.tagAllWithoutBuild();
helper.command.export();

bareMerge = helper.scopeHelper.getNewBareScope('-bare-merge');
helper.scopeHelper.addRemoteScope(helper.scopes.remotePath, bareMerge.scopePath);
});
it('should throw without --allow-outdated-deps flag', () => {
const mergeFunc = () => helper.command.mergeLaneFromScope(bareMerge.scopePath, `${helper.scopes.remote}/dev`);
expect(mergeFunc).to.throw('unable to merge, the following components are not up-to-date');
});
it('should succeed with --allow-outdated-deps flag', () => {
const mergeFunc = () => helper.command.mergeLaneFromScope(
bareMerge.scopePath,
`${helper.scopes.remote}/dev`,
'--allow-outdated-deps --no-squash'
);
expect(mergeFunc).to.not.throw();
});
});
});
9 changes: 5 additions & 4 deletions scopes/lanes/lanes/lanes.main.runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -887,15 +887,16 @@ please create a new lane instead, which will include all components of this lane
changed.push(ChangeType.SOURCE_CODE);
}

if (compare.fields.length > 0) {
changed.push(ChangeType.ASPECTS);
}

const depsFields = ['dependencies', 'devDependencies', 'extensionDependencies'];
if (compare.fields.some((field) => depsFields.includes(field.fieldName))) {
changed.push(ChangeType.DEPENDENCY);
}

const compareFieldsWithoutDeps = compare.fields.filter((field) => !depsFields.includes(field.fieldName));
if (compareFieldsWithoutDeps.length > 0) {
changed.push(ChangeType.ASPECTS);
}

return changed;
};

Expand Down
4 changes: 4 additions & 0 deletions scopes/lanes/merge-lanes/merge-lane-from-scope.cmd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type Flags = {
title?: string;
titleBase64?: string;
reMerge?: boolean;
allowOutdatedDeps?: boolean;
};

/**
Expand Down Expand Up @@ -50,6 +51,7 @@ the lane must be up-to-date with the other lane, otherwise, conflicts might occu
['', 'no-squash', 'relevant for merging lanes into main, which by default squash.'],
['', 'include-deps', 'relevant for "--pattern". merge also dependencies of the given components'],
['', 're-merge', 'helpful when last merge failed during export. do not skip components that seemed to be merged'],
['', 'allow-outdated-deps', 'if a component is out of date but has only dependencies changes, allow the merge'],
['j', 'json', 'output as json format'],
] as CommandOptions;
loader = true;
Expand All @@ -69,6 +71,7 @@ the lane must be up-to-date with the other lane, otherwise, conflicts might occu
title,
titleBase64,
reMerge,
allowOutdatedDeps,
}: Flags
): Promise<string> {
if (includeDeps && !pattern) {
Expand All @@ -91,6 +94,7 @@ the lane must be up-to-date with the other lane, otherwise, conflicts might occu
includeDeps,
snapMessage: titleBase64Decoded || title,
reMerge,
throwIfNotUpToDateForCodeChanges: allowOutdatedDeps
}
);

Expand Down
26 changes: 22 additions & 4 deletions scopes/lanes/merge-lanes/merge-lanes.main.runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { MergeAbortLaneCmd, MergeAbortOpts } from './merge-abort.cmd';
import { LastMerged } from './last-merged';
import { MergeMoveLaneCmd } from './merge-move.cmd';
import { DETACH_HEAD, isFeatureEnabled } from '@teambit/harmony.modules.feature-toggle';
import { ChangeType } from '@teambit/lanes.entities.lane-diff';

export type MergeLaneOptions = {
mergeStrategy: MergeStrategy;
Expand All @@ -59,6 +60,7 @@ export type MergeLaneOptions = {
excludeNonLaneComps?: boolean;
shouldIncludeUpdateDependents?: boolean;
throwIfNotUpToDate?: boolean; // relevant when merging from a scope
throwIfNotUpToDateForCodeChanges?: boolean; // relevant when merging from a scope
fetchCurrent?: boolean; // needed when merging from a bare-scope (because it's empty)
detachHead?: boolean;
};
Expand Down Expand Up @@ -129,6 +131,7 @@ export class MergeLanesMain {
excludeNonLaneComps,
shouldIncludeUpdateDependents,
throwIfNotUpToDate,
throwIfNotUpToDateForCodeChanges,
fetchCurrent,
detachHead,
} = options;
Expand Down Expand Up @@ -201,7 +204,9 @@ export class MergeLanesMain {
};
const idsToMerge = await getBitIds();

if (throwIfNotUpToDate) await this.throwIfNotUpToDate(otherLaneId, currentLaneId);
if (throwIfNotUpToDate || throwIfNotUpToDateForCodeChanges) {
await this.throwIfNotUpToDate(otherLaneId, currentLaneId, throwIfNotUpToDateForCodeChanges);
}

this.logger.debug(`merging the following ids: ${idsToMerge.toString()}`);

Expand Down Expand Up @@ -498,13 +503,26 @@ export class MergeLanesMain {
mergeSnapError,
};
}
private async throwIfNotUpToDate(fromLaneId: LaneId, toLaneId: LaneId) {
const status = await this.lanes.diffStatus(fromLaneId, toLaneId, { skipChanges: true });
private async throwIfNotUpToDate(fromLaneId: LaneId, toLaneId: LaneId, throwForCodeChangesOnly?: boolean) {
// if "throwForCodeChangesOnly" is true, we have to check what type of change is it. if it is a dependency change,
// it's fine. if it's a code change, throw.
const status = await this.lanes.diffStatus(fromLaneId, toLaneId, { skipChanges: !throwForCodeChangesOnly });
const compsNotUpToDate = status.componentsStatus.filter((s) => !s.upToDate);
if (compsNotUpToDate.length) {
if (!compsNotUpToDate.length) {
return;
}
if (!throwForCodeChangesOnly) {
throw new Error(`unable to merge, the following components are not up-to-date:
${compsNotUpToDate.map((s) => s.componentId.toString()).join('\n')}`);
}
const codeChanges = compsNotUpToDate.filter((s) => {
return s.changes?.includes(ChangeType.SOURCE_CODE) || s.changes?.includes(ChangeType.ASPECTS);
});

if (codeChanges.length) {
throw new Error(`unable to merge, the following components are not up-to-date and have code/aspects changes:
${codeChanges.map((s) => s.componentId.toString()).join('\n')}`);
}
}

static slots = [];
Expand Down