diff --git a/e2e/harmony/recover.e2e.ts b/e2e/harmony/recover.e2e.ts index 6cc28bfb7a0e..48655d65e322 100644 --- a/e2e/harmony/recover.e2e.ts +++ b/e2e/harmony/recover.e2e.ts @@ -235,4 +235,53 @@ describe('bit recover command', function () { expect(removeData.config.removed).to.be.true; }); }); + describe('remove in one lane, recover in other lane, then merged to the first lane', () => { + before(() => { + helper.scopeHelper.setNewLocalAndRemoteScopes(); + helper.command.createLane('lane-a'); + helper.fixtures.populateComponents(2); + helper.command.snapAllComponentsWithoutBuild(); + helper.command.export(); + + helper.command.createLane('lane-b'); + helper.command.snapAllComponentsWithoutBuild('--unmodified'); + helper.command.export(); + + helper.command.switchLocalLane('lane-a', '-x'); + helper.command.removeLaneComp('comp1'); + helper.command.snapAllComponentsWithoutBuild(); + helper.command.export(); + + helper.command.switchLocalLane('lane-b', '-x'); + helper.command.mergeLane('lane-a', '-x'); + helper.command.recover(`${helper.scopes.remote}/comp1`); + helper.command.snapAllComponentsWithoutBuild(); + helper.command.export(); + helper.command.switchLocalLane('lane-a', '-x'); + helper.command.mergeLane('lane-b'); + }); + it('should bring back the previously removed component', () => { + const list = helper.command.listParsed(); + expect(list).to.have.lengthOf(2); + helper.bitMap.expectToHaveId('comp1'); + }); + }); + + describe('remove in one lane, recover in an empty lane', () => { + before(() => { + helper.scopeHelper.setNewLocalAndRemoteScopes(); + helper.command.createLane('lane-a'); + helper.fixtures.populateComponents(2); + helper.command.snapAllComponentsWithoutBuild(); + helper.command.export(); + helper.command.removeLaneComp('comp1'); + helper.command.snapAllComponentsWithoutBuild(); + helper.command.export(); + helper.command.createLane('lane-b'); + }); + // currently it threw version "0.0.0" of component onpp7beq-remote/comp1 was not found + it('should show a descriptive error', () => { + expect(() => helper.command.recover(`${helper.scopes.remote}/comp1`)).to.throw('unable to find the component'); + }); + }); }); diff --git a/scopes/component/merging/merging.main.runtime.ts b/scopes/component/merging/merging.main.runtime.ts index 3be39a0fb050..5cb42e7dd12d 100644 --- a/scopes/component/merging/merging.main.runtime.ts +++ b/scopes/component/merging/merging.main.runtime.ts @@ -634,7 +634,17 @@ export class MergingMain { }; const currentComponent = await getCurrentComponent(); if (currentComponent.isRemoved()) { - return returnUnmerged(`component has been removed`, true); + // we made sure before that the component is not removed on the "other". so we have a few options: + // 1. other is ahead. in this case, other recovered the component. so we can continue with the merge. + // it is possible that it is diverged, in which case, still continue with the merge, and later on, the + // merge-config will show a config conflict of the remove aspect. + // 2. other is not ahead. in this case, just ignore this component, no point to merge it, we want it removed. + // 3. there are errors when calculating the divergeData, e.g. no snap in common. in such cases, we assume + // there are issues with this component, and is better not to merge it. + const divergeData = await getDivergeData({ repo, modelComponent, targetHead: otherLaneHead, throws: false }); + if (divergeData.err || !divergeData.isTargetAhead()) { + return returnUnmerged(`component has been removed`, true); + } } const isModified = async () => { const componentModificationStatus = await consumer.getComponentStatusById(currentComponent.id); diff --git a/scopes/component/remove/remove.main.runtime.ts b/scopes/component/remove/remove.main.runtime.ts index b0bbd2a2e956..7d910fb3296d 100644 --- a/scopes/component/remove/remove.main.runtime.ts +++ b/scopes/component/remove/remove.main.runtime.ts @@ -12,6 +12,7 @@ import { ComponentID } from '@teambit/component-id'; import { BitError } from '@teambit/bit-error'; import deleteComponentsFiles from '@teambit/legacy/dist/consumer/component-ops/delete-component-files'; import ComponentAspect, { Component, ComponentMain } from '@teambit/component'; +import { VersionNotFound } from '@teambit/legacy/dist/scope/exceptions'; import { removeComponentsFromNodeModules } from '@teambit/legacy/dist/consumer/component/package-json-utils'; import { RemoveCmd } from './remove-cmd'; import { removeComponents } from './remove-components'; @@ -163,11 +164,24 @@ export class RemoveMain { return true; } const compId = await this.workspace.scope.resolveComponentId(compIdStr); - const compFromScope = await this.workspace.scope.get(compId); + const currentLane = await this.workspace.getCurrentLaneObject(); + const idOnLane = currentLane?.getComponent(compId._legacy); + const compIdWithPossibleVer = idOnLane ? compId.changeVersion(idOnLane.head.toString()) : compId; + let compFromScope: Component | undefined; + try { + compFromScope = await this.workspace.scope.get(compIdWithPossibleVer); + } catch (err: any) { + if (err instanceof VersionNotFound && err.version === '0.0.0') { + throw new BitError( + `unable to find the component ${compIdWithPossibleVer.toString()} in the current lane or main` + ); + } + throw err; + } if (compFromScope && this.isRemoved(compFromScope)) { // case #2 and #3 - await importComp(compId._legacy.toString()); - await setAsRemovedFalse(compId); + await importComp(compIdWithPossibleVer._legacy.toString()); + await setAsRemovedFalse(compIdWithPossibleVer); return true; } // case #5 diff --git a/src/scope/exceptions/version-not-found.ts b/src/scope/exceptions/version-not-found.ts index 7ef819a64ccb..5399525dc5b3 100644 --- a/src/scope/exceptions/version-not-found.ts +++ b/src/scope/exceptions/version-not-found.ts @@ -6,7 +6,7 @@ import chalk from 'chalk'; * @see VersionNotFoundOnFS for cases when the Version object is missing from the filesystem. */ export default class VersionNotFound extends BitError { - constructor(version: string, componentId: string) { + constructor(readonly version: string, componentId: string) { super(`error: version "${chalk.bold(version)}" of component ${chalk.bold(componentId)} was not found.`); } }