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

fix missing head history when on a lane #6549

Merged
merged 14 commits into from
Oct 18, 2022
28 changes: 28 additions & 0 deletions e2e/harmony/lanes/bit-import-on-lanes.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,5 +185,33 @@ describe('bit lane command', function () {
expect(() => helper.command.import()).to.not.throw();
});
});
describe('when the components on the lane have history other than head on main', () => {
before(() => {
helper.scopeHelper.setNewLocalAndRemoteScopes();
helper.bitJsonc.setupDefault();
helper.fixtures.populateComponents(1);
helper.command.tagAllWithoutBuild(); // 0.0.1
helper.command.export();
helper.command.createLane();
helper.command.snapAllComponentsWithoutBuild('--unmodified');
helper.command.export();
helper.command.switchLocalLane('main', '--skip-dependency-installation');
helper.command.tagAllWithoutBuild('--unmodified'); // 0.0.2
helper.command.tagAllWithoutBuild('--unmodified'); // 0.0.3
helper.command.export();
helper.command.switchLocalLane('dev', '--skip-dependency-installation');
helper.fs.deletePath('.bit');
helper.scopeHelper.addRemoteScope();
helper.command.import();
});
// previously, it wasn't importing 0.0.2, because it's not part of the lane history and it's not the head.
it('should bring all history of main', () => {
const comp = helper.command.catComponent(`${helper.scopes.remote}/comp1`);
const v2Hash = comp.versions['0.0.2'];
const v3Hash = comp.versions['0.0.3'];
expect(() => helper.command.catObject(v2Hash)).to.not.throw();
expect(() => helper.command.catObject(v3Hash)).to.not.throw();
});
});
});
});
2 changes: 1 addition & 1 deletion scopes/lanes/lanes/lanes.main.runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,7 @@ export class LanesMain {
reset: false,
all: false,
};
return new LaneSwitcher(this.workspace, this.logger, switchProps, checkoutProps).switch();
return new LaneSwitcher(this.workspace, this.logger, switchProps, checkoutProps, this).switch();
}

/**
Expand Down
17 changes: 4 additions & 13 deletions scopes/lanes/lanes/switch-lanes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import mapSeries from 'p-map-series';
import { Consumer } from '@teambit/legacy/dist/consumer';
import GeneralError from '@teambit/legacy/dist/error/general-error';
import { LaneId, DEFAULT_LANE } from '@teambit/lane-id';
import ScopeComponentsImporter from '@teambit/legacy/dist/scope/component-ops/scope-components-importer';
import { BitId } from '@teambit/legacy-bit-id';
import { ComponentWithDependencies } from '@teambit/legacy/dist/scope';
import { Version, Lane } from '@teambit/legacy/dist/scope/models';
Expand All @@ -26,6 +25,7 @@ import threeWayMerge, {
import { Workspace } from '@teambit/workspace';
import { Logger } from '@teambit/logger';
import { BitError } from '@teambit/bit-error';
import { LanesMain } from './lanes.main.runtime';
import { createLane } from './create-lane';

export type SwitchProps = {
Expand All @@ -45,7 +45,8 @@ export class LaneSwitcher {
private workspace: Workspace,
private logger: Logger,
private switchProps: SwitchProps,
private checkoutProps: CheckoutProps
private checkoutProps: CheckoutProps,
private Lanes: LanesMain
) {
this.consumer = this.workspace.consumer;
}
Expand Down Expand Up @@ -125,17 +126,7 @@ export class LaneSwitcher {
if (this.consumer.getCurrentLaneId().isEqual(remoteLaneId)) {
throw new BitError(`already checked out to "${remoteLaneId.toString()}"`);
}
// fetch the remote to update all heads
const scopeComponentImporter = ScopeComponentsImporter.getInstance(this.consumer.scope);
const remoteLaneObjects = await scopeComponentImporter.importFromLanes([remoteLaneId]);
if (remoteLaneObjects.length === 0) {
throw new BitError(`error: the lane ${this.switchProps.laneName} doesn't exist.`);
}
if (remoteLaneObjects.length > 1) {
const allLanes = remoteLaneObjects.map((l) => l.id()).join(', ');
throw new BitError(`switching to multiple lanes is not supported. got: ${allLanes}`);
}
const remoteLane = remoteLaneObjects[0];
const remoteLane = await this.Lanes.fetchLaneWithItsComponents(remoteLaneId);
this.switchProps.laneName = remoteLaneId.name;
this.switchProps.ids = remoteLane.components.map((l) => l.id.changeVersion(l.head.toString()));
this.switchProps.localTrackedLane = this.consumer.scope.lanes.getAliasByLaneId(remoteLaneId) || undefined;
Expand Down
9 changes: 7 additions & 2 deletions scopes/scope/importer/import-components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,10 @@ export default class ImportComponents {
const bitIds: BitIds = await this.getBitIds();
const beforeImportVersions = await this._getCurrentVersions(bitIds);
await this._throwForPotentialIssues(bitIds);
const componentsWithDependencies = await this._importObjectsDisregardLocalCache(bitIds, this.laneObjects);
const componentsWithDependencies = await this._importComponentsObjects(bitIds, {
lane: this.laneObjects?.[0],
cache: false,
});
if (this.laneObjects && this.options.objectsOnly) {
// merge the lane objects
const mergeAllLanesResults = await pMapSeries(this.laneObjects, (laneObject) =>
Expand Down Expand Up @@ -255,19 +258,21 @@ export default class ImportComponents {
allHistory = false,
lane,
ignoreMissingHead = false,
cache = true,
}: {
fromOriginalScope?: boolean;
allHistory?: boolean;
lane?: Lane;
ignoreMissingHead?: boolean;
cache?: boolean;
}
): Promise<ComponentWithDependencies[]> {
const scopeComponentsImporter = ScopeComponentsImporter.getInstance(this.scope);
await scopeComponentsImporter.importManyDeltaWithoutDeps(ids, allHistory, lane, ignoreMissingHead);
loader.start(`import ${ids.length} components with their dependencies (if missing)`);
const versionDependenciesArr: VersionDependencies[] = fromOriginalScope
? await scopeComponentsImporter.importManyFromOriginalScopes(ids)
: await scopeComponentsImporter.importMany({ ids, ignoreMissingHead, lanes: lane ? [lane] : undefined });
: await scopeComponentsImporter.importMany({ ids, ignoreMissingHead, lanes: lane ? [lane] : undefined, cache });
const componentWithDependencies = await multipleVersionDependenciesToConsumer(
versionDependenciesArr,
this.scope.objects
Expand Down
62 changes: 0 additions & 62 deletions src/scope/component-ops/scope-components-importer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,30 +121,10 @@ export default class ScopeComponentsImporter {
// we don't care about the VersionDeps returned here as it may belong to the dependencies
await this.getExternalMany(uniqExternals, remotes, throwForDependencyNotFound, lanes, throwForSeederNotFound);
const versionDeps = await this.bitIdsToVersionDeps(idsToImport, throwForSeederNotFound);
await this.temporarilyFetchHeadsIfNeeded(ids, versionDeps, lanes);
logger.debug('importMany, completed!');
return versionDeps;
}

/**
* a temporarily fetch of the heads until the all remotes have a version >= 0.0.881
* in this version 0.0.881, the remote-fetch already send the head in the first fetch.
*
* this is only needed when users ask importMany for ids with specific version.
* otherwise, the fetch sends the head even with the old version.
*/
private async temporarilyFetchHeadsIfNeeded(ids: BitIds, versionDeps: VersionDependencies[], lanes?: Lane[]) {
if (ids.every((id) => !id.hasVersion())) return; // importMany already got the head.
const idsWithHead = versionDeps.map((v) => {
const head = v.component.component.head;
if (!head) return null;
if (head.isEqual(v.version.hash())) return null;
return v.component.id.changeVersion(head.toString());
});
const bitIds = BitIds.fromArray(compact(idsWithHead));
await this.importWithoutDeps(bitIds, true, lanes);
}

/**
* as opposed to importMany, which imports from dependents only.
* needed mostly for cases when importMany doesn't work due to corrupted cache or the cache
Expand Down Expand Up @@ -205,40 +185,6 @@ export default class ScopeComponentsImporter {
return [...compact(componentVersionArr), ...externalDeps];
}

// // probably not needed. leaving it here temporarily as it was slightly changed before removal.
// async importManyWithAllVersions(
// ids: BitIds,
// cache = true,
// lanes: Lane[] = []
// ): Promise<VersionDependencies[]> {
// logger.debug(`scope.getManyWithAllVersions, Ids: ${ids.join(', ')}`);
// Analytics.addBreadCrumb('getManyWithAllVersions', `scope.getManyWithAllVersions, Ids: ${Analytics.hashData(ids)}`);
// const idsWithoutNils = removeNils(ids);
// if (R.isEmpty(idsWithoutNils)) return Promise.resolve([]);
// const versionDependenciesArr: VersionDependencies[] = await this.importMany({
// ids: idsWithoutNils,
// cache,
// lanes,
// });
// const allIdsWithAllVersions = new BitIds();
// versionDependenciesArr.forEach((versionDependencies) => {
// const versions = versionDependencies.component.component.listVersions();
// const versionId = versionDependencies.component.id;
// const idsWithAllVersions = versions.map((version) => {
// if (version === versionDependencies.component.version) return null; // imported already
// return versionId.changeVersion(version);
// });
// allIdsWithAllVersions.push(...removeNils(idsWithAllVersions));
// const head = versionDependencies.component.component.getHead();
// if (head) {
// allIdsWithAllVersions.push(versionId.changeVersion(head.toString()));
// }
// });
// await this.importWithoutDeps(allIdsWithAllVersions, undefined, lanes);

// return versionDependenciesArr;
// }

/**
* delta between the local head and the remote head. mainly to improve performance
* not applicable and won't work for legacy. for legacy, refer to importManyWithAllVersions
Expand Down Expand Up @@ -296,14 +242,6 @@ export default class ScopeComponentsImporter {
).fetchFromRemoteAndWrite();
}

async importFromLanes(remoteLaneIds: LaneId[]): Promise<Lane[]> {
const lanes = await this.importLanes(remoteLaneIds);
const ids = lanes.map((lane) => lane.toBitIds());
const bitIds = BitIds.uniqFromArray(R.flatten(ids));
await this.importMany({ ids: bitIds, cache: false, lanes });
return lanes;
}

async importLanes(remoteLaneIds: LaneId[]): Promise<Lane[]> {
const remotes = await getScopeRemotes(this.scope);
const objectsStreamPerRemote = await remotes.fetch(groupByScopeName(remoteLaneIds), this.scope, { type: 'lane' });
Expand Down
4 changes: 1 addition & 3 deletions src/scope/objects/objects-readable-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,11 +122,9 @@ export class ObjectsReadableGenerator {
stopAt: collectParentsUntil ? [collectParentsUntil] : undefined,
});
const missingParentsHashes = allParentsHashes.filter((h) => !h.isEqual(version.hash()));
if (component.head && !component.head.isEqual(version.hash())) {
missingParentsHashes.push(component.head); // always add the head
}
const parentVersions = await pMapSeries(missingParentsHashes, (parentHash) => parentHash.load(this.repo));
allVersions.push(...(parentVersions as Version[]));
// note: don't bring the head. otherwise, component-delta of the head won't bring all history of this comp.
}
await pMapSeries(allVersions, async (ver) => {
const versionObjects = await collectVersionObjects(ver);
Expand Down