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(revert): introduce a new command "bit revert" #7649

Merged
merged 3 commits into from
Jul 13, 2023
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
72 changes: 72 additions & 0 deletions e2e/harmony/revert.e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import chai, { expect } from 'chai';
import Helper from '../../src/e2e-helper/e2e-helper';

chai.use(require('chai-fs'));

describe('bit revert command', function () {
this.timeout(0);
let helper: Helper;
before(() => {
helper = new Helper();
});
after(() => {
helper.scopeHelper.destroy();
});
describe('basic revert', () => {
let beforeRevert: string;
before(() => {
helper.scopeHelper.setNewLocalAndRemoteScopes();
helper.fixtures.populateComponents(1, false);
helper.command.tagAllWithoutBuild();
helper.fixtures.populateComponents(1, false, 'v2');
helper.command.tagAllWithoutBuild();
beforeRevert = helper.scopeHelper.cloneLocalScope();
helper.command.revert('comp1', '0.0.1', '-x');
});
it('should change the code to the specified version', () => {
const content = helper.fs.readFile('comp1/index.js');
expect(content).to.not.have.string('v2');
});
it('should keep the version in .bitmap intact', () => {
const bitmap = helper.bitMap.read();
expect(bitmap.comp1.version).to.equal('0.0.2');
});
describe('when the component is modified', () => {
before(() => {
helper.scopeHelper.getClonedLocalScope(beforeRevert);
helper.fixtures.populateComponents(1, false, 'v3');
helper.command.revert('comp1', '0.0.1', '-x');
});
it('should still change the code to the specified version', () => {
const content = helper.fs.readFile('comp1/index.js');
expect(content).to.not.have.string('v2');
expect(content).to.not.have.string('v3');
});
});
});
describe('revert from lane to main', () => {
before(() => {
helper.scopeHelper.setNewLocalAndRemoteScopes();
helper.fixtures.populateComponents(2);
helper.command.tagAllWithoutBuild();
helper.command.export();
helper.command.createLane('lane-a');
helper.fixtures.populateComponents(2, undefined, 'v2');
helper.command.snapAllComponentsWithoutBuild();
helper.command.export();

helper.command.revert('"**"', 'main');
});
it('should change the code according to main', () => {
const content1 = helper.fs.readFile('comp1/index.js');
const content2 = helper.fs.readFile('comp2/index.js');
expect(content1).to.not.have.string('v2');
expect(content2).to.not.have.string('v2');
});
it('should keep the versions intact', () => {
const bitmap = helper.bitMap.read();
expect(bitmap.comp1.version).to.not.equal('0.0.1');
expect(bitmap.comp2.version).to.not.equal('0.0.1');
});
});
});
14 changes: 10 additions & 4 deletions scopes/component/checkout/checkout-cmd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export class CheckoutCmd implements Command {
workspaceOnly = false,
verbose = false,
skipDependencyInstallation = false,
revert = false,
}: {
interactiveMerge?: boolean;
ours?: boolean;
Expand All @@ -77,6 +78,7 @@ export class CheckoutCmd implements Command {
workspaceOnly?: boolean;
verbose?: boolean;
skipDependencyInstallation?: boolean;
revert?: boolean;
}
) {
const checkoutProps: CheckoutProps = {
Expand All @@ -87,6 +89,7 @@ export class CheckoutCmd implements Command {
isLane: false,
skipNpmInstall: skipDependencyInstallation,
workspaceOnly,
revert,
};
const {
components,
Expand All @@ -102,6 +105,7 @@ export class CheckoutCmd implements Command {
const isHead = to === 'head';
const isReset = to === 'reset';
const isLatest = to === 'latest';
const isMain = to === 'main';
// components that failed for no legitimate reason. e.g. merge-conflict.
const realFailedComponents = (failedComponents || []).filter((f) => !f.unchangedLegitimately);
// components that weren't checked out for legitimate reasons, e.g. up-to-date.
Expand Down Expand Up @@ -141,12 +145,13 @@ once ready, snap/tag the components to persist the changes`;
return chalk.underline(title) + conflictSummaryReport(components) + chalk.yellow(suggestion);
};
const getSuccessfulOutput = () => {
const switchedOrReverted = revert ? 'reverted' : 'switched';
if (!components || !components.length) return '';
if (components.length === 1) {
const component = components[0];
const componentName = isReset ? component.id.toString() : component.id.toStringWithoutVersion();
if (isReset) return `successfully reset ${chalk.bold(componentName)}\n`;
const title = `successfully switched ${chalk.bold(componentName)} to version ${chalk.bold(
const title = `successfully ${switchedOrReverted} ${chalk.bold(componentName)} to version ${chalk.bold(
// @ts-ignore version is defined when !isReset
isHead || isLatest ? component.id.version : version
)}\n`;
Expand All @@ -160,11 +165,12 @@ once ready, snap/tag the components to persist the changes`;
const getVerOutput = () => {
if (isHead) return 'their head version';
if (isLatest) return 'their latest version';
if (isMain) return 'their main version';
// @ts-ignore version is defined when !isReset
return `version ${chalk.bold(version)}`;
};
const versionOutput = getVerOutput();
const title = `successfully switched the following components to ${versionOutput}\n\n`;
const title = `successfully ${switchedOrReverted} the following components to ${versionOutput}\n\n`;
const showVersion = isHead || isReset;
const componentsStr = applyVersionReport(components, true, showVersion);
return chalk.underline(title) + componentsStr;
Expand All @@ -182,8 +188,8 @@ once ready, snap/tag the components to persist the changes`;
const notCheckedOutLegitimately = notCheckedOutComponents.length;
const failedToCheckOut = realFailedComponents.length;
const newLines = '\n\n';
const title = chalk.bold.underline('Checkout Summary');
const checkedOutStr = `\nTotal CheckedOut: ${chalk.bold(checkedOut.toString())}`;
const title = chalk.bold.underline('Summary');
const checkedOutStr = `\nTotal Changed: ${chalk.bold(checkedOut.toString())}`;
const unchangedLegitimatelyStr = `\nTotal Unchanged: ${chalk.bold(notCheckedOutLegitimately.toString())}`;
const failedToCheckOutStr = `\nTotal Failed: ${chalk.bold(failedToCheckOut.toString())}`;
const newOnLaneNum = newFromLane?.length || 0;
Expand Down
28 changes: 20 additions & 8 deletions scopes/component/checkout/checkout.main.runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,20 @@ import {
deleteFilesIfNeeded,
ComponentStatusBase,
} from './checkout-version';
import { RevertCmd } from './revert-cmd';

export type CheckoutProps = {
version?: string; // if reset/head/latest is true, the version is undefined
ids?: ComponentID[];
head?: boolean;
latest?: boolean;
main?: boolean; // relevant for "revert" only
promptMergeOptions?: boolean;
mergeStrategy?: MergeStrategy | null;
verbose?: boolean;
skipNpmInstall?: boolean;
reset?: boolean; // remove local changes. if set, the version is undefined.
revert?: boolean;
all?: boolean; // checkout all ids
isLane?: boolean;
workspaceOnly?: boolean;
Expand All @@ -59,7 +62,7 @@ export type ComponentStatusBeforeMergeAttempt = ComponentStatusBase & {
};
};

type CheckoutTo = 'head' | 'reset' | string;
type CheckoutTo = 'head' | 'reset' | 'main' | string;

export class CheckoutMain {
constructor(
Expand Down Expand Up @@ -202,7 +205,8 @@ export class CheckoutMain {
componentPattern: string,
checkoutProps: CheckoutProps
): Promise<ApplyVersionResults> {
this.logger.setStatusLine(BEFORE_CHECKOUT);
const { revert } = checkoutProps;
this.logger.setStatusLine(revert ? 'reverting components...' : BEFORE_CHECKOUT);
if (!this.workspace) throw new OutsideWorkspaceError();
const consumer = this.workspace.consumer;
await this.importer.importCurrentObjects(); // important. among others, it fetches the remote lane object and its new components.
Expand Down Expand Up @@ -237,6 +241,7 @@ export class CheckoutMain {
if (to === HEAD) checkoutProps.head = true;
else if (to === LATEST) checkoutProps.latest = true;
else if (to === 'reset') checkoutProps.reset = true;
else if (to === 'main') checkoutProps.main = true;
else {
if (!BitId.isValidVersion(to)) throw new BitError(`the specified version "${to}" is not a valid version`);
checkoutProps.version = to;
Expand All @@ -262,6 +267,9 @@ export class CheckoutMain {
if (checkoutProps.workspaceOnly && !checkoutProps.head) {
throw new BitError(`--workspace-only flag can only be used with "head" (bit checkout head --workspace-only)`);
}
if (checkoutProps.revert) {
checkoutProps.skipUpdatingBitmap = true;
}
const idsOnWorkspace = componentPattern
? await this.workspace.idsByPattern(componentPattern)
: await this.workspace.listIds();
Expand Down Expand Up @@ -297,7 +305,7 @@ export class CheckoutMain {
checkoutProps: CheckoutProps
): Promise<ComponentStatusBeforeMergeAttempt> {
const consumer = this.workspace.consumer;
const { version, head: headVersion, reset, latest: latestVersion, versionPerId } = checkoutProps;
const { version, head: headVersion, reset, revert, main, latest: latestVersion, versionPerId } = checkoutProps;
const repo = consumer.scope.objects;
const componentModel = await consumer.scope.getModelComponentIfExist(component.id);
const componentStatus: ComponentStatusBeforeMergeAttempt = { id: component.id };
Expand All @@ -309,6 +317,9 @@ export class CheckoutMain {
if (!componentModel) {
return returnFailure(`component ${component.id.toString()} is new, no version to checkout`, true);
}
if (main && !componentModel.head) {
return returnFailure(`component ${component.id.toString()} is not available on main`);
}
const unmerged = repo.unmergedComponents.getEntry(component.name);
if (!reset && unmerged) {
return returnFailure(
Expand All @@ -317,8 +328,9 @@ export class CheckoutMain {
}
const getNewVersion = async (): Promise<string> => {
if (reset) return component.id.version as string;

if (headVersion) return componentModel.headIncludeRemote(repo);
// we verified previously that head exists in case of "main"
if (main) return componentModel.head?.toString() as string;
if (latestVersion) {
const latest = componentModel.latestVersionIfExist();
return latest || componentModel.headIncludeRemote(repo);
Expand All @@ -327,8 +339,8 @@ export class CheckoutMain {
return versionPerId.find((id) => id._legacy.isEqualWithoutVersion(component.id))?.version as string;
}

// @ts-ignore if !reset the version is defined
return version;
// if all above are false, the version is defined
return version as string;
};
const newVersion = await getNewVersion();
if (version && !headVersion) {
Expand Down Expand Up @@ -374,7 +386,7 @@ export class CheckoutMain {

const newId = component.id.changeVersion(newVersion);

if (reset || !isModified) {
if (reset || !isModified || revert) {
// if the component is not modified, no need to try merge the files, they will be written later on according to the
// checked out version. same thing when no version is specified, it'll be reset to the model-version later.
return { currentComponent: component, componentFromModel: componentVersion, id: newId };
Expand Down Expand Up @@ -439,7 +451,7 @@ export class CheckoutMain {
]) {
const logger = loggerMain.createLogger(CheckoutAspect.id);
const checkoutMain = new CheckoutMain(workspace, logger, compWriter, importer, remove);
cli.register(new CheckoutCmd(checkoutMain));
cli.register(new CheckoutCmd(checkoutMain), new RevertCmd(checkoutMain));
return checkoutMain;
}
}
Expand Down
45 changes: 45 additions & 0 deletions scopes/component/checkout/revert-cmd.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Command, CommandOptions } from '@teambit/cli';
import { COMPONENT_PATTERN_HELP } from '@teambit/legacy/dist/constants';
import { CheckoutMain } from './checkout.main.runtime';
import { CheckoutCmd } from './checkout-cmd';

export class RevertCmd implements Command {
name = 'revert <component-pattern> <to>';
arguments = [
{
name: 'component-pattern',
description: COMPONENT_PATTERN_HELP,
},
{
name: 'to',
description: "permitted values: [main, specific-version]. 'main' - head version on main.",
},
];
description = 'replace the current component files by the specified version, leave the version intact';
group = 'development';
alias = '';
options = [
['v', 'verbose', 'showing verbose output for inspection'],
['x', 'skip-dependency-installation', 'do not install packages of the imported components'],
] as CommandOptions;
loader = true;

constructor(private checkout: CheckoutMain) {}

async report(
[componentPattern, to]: [string, string],
{
verbose = false,
skipDependencyInstallation = false,
}: {
verbose?: boolean;
skipDependencyInstallation?: boolean;
}
) {
return new CheckoutCmd(this.checkout).report([to, componentPattern], {
verbose,
skipDependencyInstallation,
revert: true,
});
}
}
4 changes: 4 additions & 0 deletions src/e2e-helper/e2e-command-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,10 @@ export default class CommandHelper {
return JSON.parse(status);
}

revert(pattern: string, to: string, flags = '') {
return this.runCmd(`bit revert ${pattern} ${to} ${flags}`);
}

stash() {
return this.runCmd('bit stash');
}
Expand Down