Skip to content

Commit

Permalink
enable tagging components that their head is a snap rather than a tag (
Browse files Browse the repository at this point in the history
…#4778)

Also, add a new section "snapped components" in `bit status` to display these components.
  • Loading branch information
davidfirst authored Aug 20, 2021
1 parent 3503b59 commit 6bec27c
Show file tree
Hide file tree
Showing 9 changed files with 90 additions and 9 deletions.
23 changes: 20 additions & 3 deletions e2e/commands/snap.e2e.2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import chai, { expect } from 'chai';
import fs from 'fs-extra';
import path from 'path';
import { AUTO_SNAPPED_MSG } from '../../src/cli/commands/public-cmds/snap-cmd';
import { statusWorkspaceIsCleanMsg } from '../../src/cli/commands/public-cmds/status-cmd';
import { HASH_SIZE } from '../../src/constants';
import ComponentsPendingMerge from '../../src/consumer/component-ops/exceptions/components-pending-merge';
import Helper from '../../src/e2e-helper/e2e-helper';
Expand Down Expand Up @@ -89,6 +88,25 @@ describe('bit snap command', function () {
expect(barFoo.dependencies).to.have.lengthOf(1);
expect(barFoo.dependencies[0].id.version).to.be.a('string').and.have.lengthOf(HASH_SIZE);
});
it('bit status should show them in the "snapped" section', () => {
const status = helper.command.statusJson();
expect(status.snappedComponents).to.have.lengthOf(3);
});
describe('tagging the components', () => {
let scopeBeforeTag: string;
before(() => {
scopeBeforeTag = helper.scopeHelper.cloneLocalScope();
});
it('--all flag should include the snapped components', () => {
const output = helper.command.tagAllWithoutBuild();
expect(output).to.include('3 component(s) tagged');
});
it('--snapped flag should include the snapped components', () => {
helper.scopeHelper.getClonedLocalScope(scopeBeforeTag);
const output = helper.command.tagWithoutBuild(undefined, '--snapped');
expect(output).to.include('3 component(s) tagged');
});
});
});
describe('untag a snap', () => {
let firstSnap: string;
Expand Down Expand Up @@ -206,8 +224,7 @@ describe('bit snap command', function () {
helper.bitMap.expectToHaveIdHarmony('bar/foo', secondSnap, helper.scopes.remote);
});
it('bit status should be clean', () => {
const status = helper.command.status();
expect(status).to.have.string(statusWorkspaceIsCleanMsg);
helper.command.expectStatusToBeClean(['snappedComponents']);
});
});
});
Expand Down
7 changes: 6 additions & 1 deletion scopes/workspace/workspace/tag-cmd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ ${WILDCARD_HELP('tag')}`;
['m', 'message <message>', 'log message describing the user changes'],
['a', 'all [version]', 'tag all new and modified components'],
['s', 'scope [version]', 'tag all components of the current scope'],
['', 'snapped [version]', 'tag components that their head is a snap (not a tag)'],
['', 'ver <version>', 'tag specified components with the given version'],
['p', 'patch', 'increment the patch version number'],
['', 'minor', 'increment the minor version number'],
Expand Down Expand Up @@ -61,6 +62,7 @@ ${WILDCARD_HELP('tag')}`;
message = '',
ver,
all = false,
snapped = false,
patch,
minor,
major,
Expand All @@ -82,6 +84,7 @@ ${WILDCARD_HELP('tag')}`;
incrementBy = 1,
}: {
all?: boolean | string;
snapped?: boolean | string;
ver?: string;
patch?: boolean;
minor?: boolean;
Expand All @@ -99,10 +102,11 @@ ${WILDCARD_HELP('tag')}`;
function getVersion(): string | undefined {
if (scope && isString(scope)) return scope;
if (all && isString(all)) return all;
if (snapped && isString(snapped)) return snapped;
return ver;
}

if (!id.length && !all && !scope && !persist) {
if (!id.length && !all && !snapped && !scope && !persist) {
throw new GeneralError('missing [id]. to tag all components, please use --all flag');
}
if (id.length && all) {
Expand Down Expand Up @@ -147,6 +151,7 @@ ${WILDCARD_HELP('tag')}`;
const params = {
ids: id,
all: Boolean(all),
snapped: Boolean(snapped),
message,
exactVersion: getVersion(),
releaseType,
Expand Down
3 changes: 3 additions & 0 deletions src/api/consumer/lib/status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export type StatusResult = {
componentsWithIndividualFiles: Component[];
componentsWithTrackDirs: Component[];
softTaggedComponents: BitId[];
snappedComponents: BitId[];
};

export default async function status(): Promise<StatusResult> {
Expand Down Expand Up @@ -55,6 +56,7 @@ export default async function status(): Promise<StatusResult> {
});
const componentsDuringMergeState = componentsList.listDuringMergeStateComponents();
const softTaggedComponents = componentsList.listSoftTaggedComponents();
const snappedComponents = (await componentsList.listSnappedComponentsOnMain()).map((c) => c.toBitId());
Analytics.setExtraData('new_components', newComponents.length);
Analytics.setExtraData('staged_components', stagedComponents.length);
Analytics.setExtraData('num_components_with_missing_dependencies', componentsWithIssues.length);
Expand All @@ -77,5 +79,6 @@ export default async function status(): Promise<StatusResult> {
componentsWithIndividualFiles: await componentsList.listComponentsWithIndividualFiles(),
componentsWithTrackDirs: await componentsList.listComponentsWithTrackDir(),
softTaggedComponents,
snappedComponents,
};
}
19 changes: 16 additions & 3 deletions src/api/consumer/lib/tag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,15 @@ type TagParams = {
ignoreNewestVersion: boolean;
ids: string[];
all: boolean;
snapped: boolean;
scope?: string | boolean;
includeImported: boolean;
incrementBy: number;
} & BasicTagParams;

export async function tagAction(tagParams: TagParams): Promise<TagResults | null> {
const { ids, all, exactVersion, releaseType, force, ignoreIssues, scope, includeImported, persist } = tagParams;
const { ids, all, exactVersion, releaseType, force, ignoreIssues, scope, includeImported, persist, snapped } =
tagParams;
const idsHasWildcard = hasWildcard(ids);
const isAll = Boolean(all || scope || idsHasWildcard);
const validExactVersion = validateVersion(exactVersion);
Expand All @@ -72,7 +74,8 @@ export async function tagAction(tagParams: TagParams): Promise<TagResults | null
includeImported,
persist,
force,
ids
ids,
snapped
);
if (R.isEmpty(bitIds)) return null;

Expand Down Expand Up @@ -108,7 +111,8 @@ async function getComponentsToTag(
includeImported: boolean,
persist: boolean,
force: boolean,
ids: string[]
ids: string[],
snapped: boolean
): Promise<{ bitIds: BitId[]; warnings: string[] }> {
const warnings: string[] = [];
const componentsList = new ComponentsList(consumer);
Expand All @@ -121,6 +125,9 @@ async function getComponentsToTag(
? await componentsList.listTagPendingOfAllScope(includeImported)
: await componentsList.listTagPendingComponents();

const snappedComponents = await componentsList.listSnappedComponentsOnMain();
const snappedComponentsIds = snappedComponents.map((c) => c.toBitId());

if (ids.length) {
const bitIds = await Promise.all(
ids.map(async (id) => {
Expand All @@ -142,6 +149,12 @@ async function getComponentsToTag(
return { bitIds: compact(bitIds.flat()), warnings };
}

if (snapped) {
return { bitIds: snappedComponentsIds, warnings };
}

tagPendingComponents.push(...snappedComponentsIds);

if (isAllScope && exactVersion) {
const tagPendingComponentsLatest = await consumer.scope.latestVersions(tagPendingComponents, false);
tagPendingComponentsLatest.forEach((componentId) => {
Expand Down
10 changes: 10 additions & 0 deletions src/cli/commands/public-cmds/status-cmd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export default class Status implements LegacyCommand {
componentsWithIndividualFiles,
componentsWithTrackDirs,
softTaggedComponents,
snappedComponents,
}: StatusResult): string {
if (this.json) {
return JSON.stringify(
Expand All @@ -78,6 +79,7 @@ export default class Status implements LegacyCommand {
componentsWithIndividualFiles: componentsWithIndividualFiles.map((c) => c.id.toString()),
componentsWithTrackDirs: componentsWithTrackDirs.map((c) => c.id.toString()),
softTaggedComponents: softTaggedComponents.map((s) => s.toString()),
snappedComponents: snappedComponents.map((s) => s.toString()),
},
null,
2
Expand Down Expand Up @@ -217,6 +219,13 @@ or use "bit merge [component-id] --abort" to cancel the merge operation)\n`;
stagedComponents.length ? chalk.underline.white('staged components') + stagedDesc : ''
).join('\n');

const snappedDesc = '\n(use "bit tag --all [version]" or "bit tag --snapped [version]" to lock a version)\n';
const snappedComponentsOutput = immutableUnshift(
// @ts-ignore AUTO-ADDED-AFTER-MIGRATION-PLEASE-FIX!
snappedComponents.map((c) => format(c, true)),
snappedComponents.length ? chalk.underline.white('snapped components') + snappedDesc : ''
).join('\n');

const troubleshootingStr = showTroubleshootingLink ? `\n${TROUBLESHOOTING_MESSAGE}` : '';

return (
Expand All @@ -227,6 +236,7 @@ or use "bit merge [component-id] --abort" to cancel the merge operation)\n`;
compWithConflictsStr,
newComponentsOutput,
modifiedComponentOutput,
snappedComponentsOutput,
stagedComponentsOutput,
autoTagPendingOutput,
invalidComponentOutput,
Expand Down
19 changes: 19 additions & 0 deletions src/consumer/component/components-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,25 @@ export default class ComponentsList {
return fileSystemComponents.filter((f) => f.latestVersion);
}

/**
* list components that their head is a snap, not a tag.
* this is relevant only when the lane is the default (main), otherwise, the head is always a snap.
* components that are during-merge are filtered out, we don't want them during tag and don't want
* to show them in the "snapped" section in bit-status.
*/
async listSnappedComponentsOnMain() {
if (!this.scope.lanes.isOnDefaultLane()) {
return [];
}
const componentsFromModel = await this.getModelComponents();
const authoredAndImportedIds = this.bitMap.getAuthoredAndImportedBitIds();
const compsDuringMerge = this.listDuringMergeStateComponents();
return componentsFromModel
.filter((c) => authoredAndImportedIds.hasWithoutVersion(c.toBitId()))
.filter((c) => !compsDuringMerge.hasWithoutVersion(c.toBitId()))
.filter((c) => c.isHeadSnap());
}

async listMergePendingComponents(): Promise<DivergedComponent[]> {
if (!this._mergePendingComponents) {
const componentsFromFs = await this.getAuthoredAndImportedFromFS();
Expand Down
5 changes: 3 additions & 2 deletions src/e2e-helper/e2e-command-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ export default class CommandHelper {
expect(result).to.not.have.string(NOTHING_TO_TAG_MSG);
return result;
}
tagWithoutBuild(id: string, options = '') {
tagWithoutBuild(id = '', options = '') {
const result = this.runCmd(`bit tag ${id} ${options}`, undefined, undefined, BUILD_ON_CI);
expect(result).to.not.have.string(NOTHING_TO_TAG_MSG);
return result;
Expand Down Expand Up @@ -409,9 +409,10 @@ export default class CommandHelper {
return JSON.parse(status);
}

expectStatusToBeClean() {
expectStatusToBeClean(exclude: string[] = []) {
const statusJson = this.statusJson();
Object.keys(statusJson).forEach((key) => {
if (exclude.includes(key)) return;
expect(statusJson[key], `status.${key} should be empty`).to.have.lengthOf(0);
});
}
Expand Down
5 changes: 5 additions & 0 deletions src/scope/lanes/lanes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ export default class Lanes {
return laneName;
}

isOnDefaultLane(): boolean {
const currentLane = this.getCurrentLaneName();
return currentLane === DEFAULT_LANE;
}

getCurrentLaneId(): LocalLaneId {
return LocalLaneId.from(this.getCurrentLaneName() || DEFAULT_LANE);
}
Expand Down
8 changes: 8 additions & 0 deletions src/scope/models/model-component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,14 @@ export default class Component extends BitObject {
return Boolean(this.versions[version] || this.orphanedVersions[version]);
}

/**
* whether the head is a snap (not a tag)
*/
isHeadSnap() {
const tagsHashes = this.versionArray.map((ref) => ref.toString());
return this.head && !tagsHashes.includes(this.head.toString());
}

/**
* add a new remote if it is not there already
*/
Expand Down

0 comments on commit 6bec27c

Please sign in to comment.