Skip to content
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

feat(switch), introduce a new flag --head to checkout to main/lane head once switched #8851

Merged
merged 4 commits into from
May 3, 2024
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
65 changes: 65 additions & 0 deletions e2e/harmony/lanes/switch-lanes.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -319,4 +319,69 @@ describe('bit lane command', function () {
});
}
);
describe('switch to main when main has updates on the remote', () => {
let beforeSwitch: string;
before(() => {
helper.scopeHelper.setNewLocalAndRemoteScopes();
helper.fixtures.populateComponents(1);
helper.command.tagAllWithoutBuild();
helper.command.export();
helper.command.createLane();
helper.command.snapAllComponentsWithoutBuild('--unmodified');
helper.command.export();
const beforeUpdatingMain = helper.scopeHelper.cloneLocalScope();
helper.command.switchLocalLane('main', '-x');
helper.command.tagAllWithoutBuild('--unmodified');
helper.command.export();
helper.scopeHelper.getClonedLocalScope(beforeUpdatingMain);
beforeSwitch = helper.scopeHelper.cloneLocalScope();
});
it('when --head is used, it should switch to the head of main ', () => {
helper.command.switchLocalLane('main', '-x --head');
const bitmap = helper.bitMap.read();
expect(bitmap.comp1.version).to.equal('0.0.2');
});
it('when --head was not used, it should switch to where the main was before', () => {
helper.scopeHelper.getClonedLocalScope(beforeSwitch);
helper.command.switchLocalLane('main', '-x');
const bitmap = helper.bitMap.read();
expect(bitmap.comp1.version).to.equal('0.0.1');
});
});
describe('switch to a lane when it has updates on the remote', () => {
let beforeSwitch: string;
let firstSnap: string;
let headSnap: string;
before(() => {
helper.scopeHelper.setNewLocalAndRemoteScopes();
helper.fixtures.populateComponents(1);
helper.command.createLane();
helper.command.snapAllComponentsWithoutBuild();
firstSnap = helper.command.getHeadOfLane('dev', 'comp1');
helper.command.export();
helper.command.switchLocalLane('main', '-x');
helper.command.mergeLane('dev', '-x');
helper.command.export();
const beforeUpdatingLane = helper.scopeHelper.cloneLocalScope();
helper.command.switchLocalLane('dev', '-x');
helper.command.snapAllComponentsWithoutBuild('--unmodified');
headSnap = helper.command.getHeadOfLane('dev', 'comp1');
helper.command.export();
helper.scopeHelper.getClonedLocalScope(beforeUpdatingLane);
beforeSwitch = helper.scopeHelper.cloneLocalScope();
});
it('when --head is used, it should switch to the head of the lane ', () => {
helper.command.switchLocalLane('dev', '-x --head');
const bitmap = helper.bitMap.read();
expect(bitmap.comp1.version).to.equal(headSnap);
expect(bitmap.comp1.version).to.not.equal(firstSnap);
});
it('when --head was not used, it should switch to where the lane was before', () => {
helper.scopeHelper.getClonedLocalScope(beforeSwitch);
helper.command.switchLocalLane('dev', '-x');
const bitmap = helper.bitMap.read();
expect(bitmap.comp1.version).to.not.equal(headSnap);
expect(bitmap.comp1.version).to.equal(firstSnap);
});
});
});
4 changes: 3 additions & 1 deletion scopes/lanes/lanes/lanes.main.runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ export type CreateLaneOptions = {
};

export type SwitchLaneOptions = {
head?: boolean;
alias?: string;
merge?: MergeStrategy;
workspaceOnly?: boolean;
Expand Down Expand Up @@ -562,7 +563,7 @@ please create a new lane instead, which will include all components of this lane
*/
async switchLanes(
laneName: string,
{ alias, merge, pattern, workspaceOnly, skipDependencyInstallation = false }: SwitchLaneOptions
{ alias, merge, pattern, workspaceOnly, skipDependencyInstallation = false, head }: SwitchLaneOptions
) {
if (!this.workspace) {
throw new BitError(`unable to switch lanes outside of Bit workspace`);
Expand All @@ -584,6 +585,7 @@ please create a new lane instead, which will include all components of this lane
existingOnWorkspaceOnly: workspaceOnly,
pattern,
alias,
head,
};
const checkoutProps = {
mergeStrategy,
Expand Down
44 changes: 29 additions & 15 deletions scopes/lanes/lanes/switch-lanes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export type SwitchProps = {
ids?: ComponentID[];
laneBitIds?: ComponentID[]; // only needed for the deprecated onLanesOnly prop. once this prop is removed, this prop can be removed as well.
pattern?: string;
head?: boolean;
existingOnWorkspaceOnly?: boolean;
remoteLane?: Lane;
localTrackedLane?: string;
Expand Down Expand Up @@ -68,31 +69,44 @@ export class LaneSwitcher {
const laneId = await this.consumer.scope.lanes.parseLaneIdFromString(this.switchProps.laneName);

const localLane = await this.consumer.scope.loadLane(laneId);
const mainIds = await this.consumer.getIdsOfDefaultLane();
const getMainIds = async () => {
const mainIds = await this.consumer.getIdsOfDefaultLane();
if (this.switchProps.head) {
await this.workspace.scope.legacyScope.scopeImporter.importWithoutDeps(mainIds, { cache: false });
return this.consumer.getIdsOfDefaultLane();
}
return mainIds;
};
const mainIds = await getMainIds();
if (laneId.isDefault()) {
await this.populatePropsAccordingToDefaultLane();
this.switchProps.ids = mainIds;
} else {
const laneIds = localLane
? this.populatePropsAccordingToLocalLane(localLane)
: await this.populatePropsAccordingToRemoteLane(laneId);
const laneIds =
localLane && !this.switchProps.head
? this.populatePropsAccordingToLocalLane(localLane)
: await this.populatePropsAccordingToRemoteLane(laneId);
const idsOnLaneOnly = laneIds.filter((id) => !mainIds.find((i) => i.isEqualWithoutVersion(id)));
const idsOnMainOnly = mainIds.filter((id) => !laneIds.find((i) => i.isEqualWithoutVersion(id)));
this.switchProps.ids = [...idsOnMainOnly, ...laneIds];
this.switchProps.laneBitIds = idsOnLaneOnly;
}
await this.populateIdsAccordingToPattern();
}

if (this.switchProps.pattern) {
if (this.consumer.bitMap.getAllBitIdsFromAllLanes().length) {
// if the workspace is not empty, it's possible that it has components from lane-x, and is now switching
// partially to lane-y, while lane-y has the same components as lane-x. in which case, the user ends up with
// an invalid state of components from lane-x and lane-y together.
throw new BitError('error: use --pattern only when the workspace is empty');
}
const allIds = await this.workspace.resolveMultipleComponentIds(this.switchProps.ids || []);
const patternIds = await this.workspace.filterIdsFromPoolIdsByPattern(this.switchProps.pattern, allIds);
this.switchProps.ids = patternIds.map((id) => id);
private async populateIdsAccordingToPattern() {
if (!this.switchProps.pattern) {
return;
}
if (this.consumer.bitMap.getAllBitIdsFromAllLanes().length) {
// if the workspace is not empty, it's possible that it has components from lane-x, and is now switching
// partially to lane-y, while lane-y has the same components as lane-x. in which case, the user ends up with
// an invalid state of components from lane-x and lane-y together.
throw new BitError('error: use --pattern only when the workspace is empty');
}
const allIds = this.switchProps.ids || [];
const patternIds = await this.workspace.filterIdsFromPoolIdsByPattern(this.switchProps.pattern, allIds);
this.switchProps.ids = patternIds.map((id) => id);
}

private async populatePropsAccordingToRemoteLane(remoteLaneId: LaneId): Promise<ComponentID[]> {
Expand Down Expand Up @@ -126,7 +140,7 @@ export class LaneSwitcher {
? this.laneIdToSwitchTo.name
: this.laneIdToSwitchTo.toString();
throw new BitError(`already checked out to "${laneIdStr}".
to be up to date with the remote lane, please run "bit checkout head""`);
to be up to date with the remote lane, please run "bit checkout head"`);
}
}

Expand Down
14 changes: 9 additions & 5 deletions scopes/lanes/lanes/switch.cmd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,7 @@ export class SwitchCmd implements Command {
},
];
options = [
[
'n',
'alias <string>',
"relevant when the specified lane is a remote lane. create a local alias for the lane (doesnt affect the lane's name on the remote",
],
['h', 'head', 'switch to the head of the lane/main (fetches the latest changes from the remote)'],
[
'',
'auto-merge-resolve <merge-strategy>',
Expand All @@ -38,6 +34,11 @@ export class SwitchCmd implements Command {
`switch only the lane components matching the specified component-pattern. only works when the workspace is empty\n
${COMPONENT_PATTERN_HELP}`,
],
[
'n',
'alias <string>',
"relevant when the specified lane is a remote lane. create a local alias for the lane (doesnt affect the lane's name on the remote",
],
['j', 'json', 'return the output as JSON'],
] as CommandOptions;
loader = true;
Expand All @@ -47,6 +48,7 @@ ${COMPONENT_PATTERN_HELP}`,
async report(
[lane]: [string],
{
head,
alias,
autoMergeResolve,
getAll = false,
Expand All @@ -55,6 +57,7 @@ ${COMPONENT_PATTERN_HELP}`,
pattern,
json = false,
}: {
head?: boolean;
alias?: string;
autoMergeResolve?: MergeStrategy;
getAll?: boolean;
Expand All @@ -66,6 +69,7 @@ ${COMPONENT_PATTERN_HELP}`,
}
) {
const { components, failedComponents, installationError, compilationError } = await this.lanes.switchLanes(lane, {
head,
alias,
merge: autoMergeResolve,
workspaceOnly,
Expand Down