Skip to content

Commit f2b7307

Browse files
authored
feat(stash), introduce a new flag "--include-new" for new components (#9273)
By default, only modified components are stashed. With this flag, new components are stashed as well and then get removed from the workspace. Upon `bit stash load`, they are re-created.
1 parent 10ac015 commit f2b7307

File tree

7 files changed

+141
-20
lines changed

7 files changed

+141
-20
lines changed

e2e/harmony/stash.e2e.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,4 +106,31 @@ describe('bit stash command', function () {
106106
expect(index).to.not.have.string('hello');
107107
});
108108
});
109+
describe('stash new components along with modified', () => {
110+
before(() => {
111+
helper.scopeHelper.reInitLocalScope();
112+
helper.fixtures.populateComponents(2);
113+
helper.command.tagWithoutBuild('comp2');
114+
helper.fixtures.populateComponents(2, undefined, 'version2');
115+
helper.command.stash('--include-new');
116+
});
117+
it('should stash both of them', () => {
118+
const stashList = helper.command.stashList();
119+
expect(stashList).to.have.string('2 components');
120+
});
121+
it('should remove the new component from .bitmap', () => {
122+
const bitMap = helper.bitMap.read();
123+
expect(bitMap).to.have.property('comp2');
124+
expect(bitMap).to.not.have.property('comp1');
125+
});
126+
describe('stash load them', () => {
127+
before(() => {
128+
helper.command.stashLoad();
129+
});
130+
it('should re-create the new component', () => {
131+
const bitMap = helper.bitMap.read();
132+
expect(bitMap).to.have.property('comp1');
133+
});
134+
});
135+
});
109136
});

scopes/component/checkout/checkout.main.runtime.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { CheckoutCmd } from './checkout-cmd';
2424
import { CheckoutAspect } from './checkout.aspect';
2525
import { applyVersion, ComponentStatus, ComponentStatusBase, throwForFailures } from './checkout-version';
2626
import { RevertCmd } from './revert-cmd';
27+
import { ComponentMap } from '@teambit/legacy.bit-map';
2728

2829
export type CheckoutProps = {
2930
version?: string; // if reset/head/latest is true, the version is undefined
@@ -47,6 +48,7 @@ export type CheckoutProps = {
4748
versionPerId?: ComponentID[]; // if given, the ComponentID.version is the version to checkout to.
4849
skipUpdatingBitmap?: boolean; // needed for stash
4950
loadStash?: boolean;
51+
stashedBitmapEntries?: Array<Partial<ComponentMap>>;
5052
restoreMissingComponents?: boolean; // in case .bitmap has a component and it's missing from the workspace, restore it (from model)
5153
allowAddingComponentsFromScope?: boolean; // in case the id doesn't exist in .bitmap, add it from the scope (relevant for switch)
5254
includeLocallyDeleted?: boolean; // include components that were deleted locally. currently enabled for "bit checkout reset" only.
@@ -74,6 +76,7 @@ export class CheckoutMain {
7476
const { version, ids, promptMergeOptions } = checkoutProps;
7577
await this.syncNewComponents(checkoutProps);
7678
const addedComponents = await this.restoreMissingComponents(checkoutProps);
79+
const newComponents = await this.addNewComponents(checkoutProps);
7780
const bitIds = ComponentIdList.fromArray(ids?.map((id) => id) || []);
7881
// don't use Promise.all, it loads the components and this operation must be in sequence.
7982
const allComponentStatusBeforeMerge = await mapSeries(bitIds, (id) =>
@@ -189,6 +192,7 @@ export class CheckoutMain {
189192
components: appliedVersionComponents,
190193
removedComponents: componentIdsToRemove,
191194
addedComponents,
195+
newComponents,
192196
version,
193197
failedComponents,
194198
leftUnresolvedConflicts,
@@ -231,6 +235,44 @@ export class CheckoutMain {
231235
return missing;
232236
}
233237

238+
async addNewComponents(checkoutProps: CheckoutProps): Promise<undefined | ComponentID[]> {
239+
const stashedBitmapEntries = checkoutProps.stashedBitmapEntries;
240+
if (!stashedBitmapEntries) return;
241+
const newBitmapEntries = stashedBitmapEntries.filter((entry) => entry.defaultScope);
242+
if (!newBitmapEntries.length) return;
243+
const newComps = await mapSeries(newBitmapEntries, async (bitmapEntry) => {
244+
const id = bitmapEntry.id!;
245+
const existingId = this.workspace.bitMap.getBitmapEntryIfExist(id, { ignoreVersion: true });
246+
if (existingId) return;
247+
const modelComponent = ModelComponent.fromBitId(id);
248+
const repo = this.workspace.scope.legacyScope.objects;
249+
const consumerComp = await modelComponent.toConsumerComponent(id.version, id.scope, repo);
250+
const newCompId = ComponentID.fromObject({ name: id.fullName }, bitmapEntry.defaultScope!);
251+
await this.componentWriter.writeMany({
252+
components: [consumerComp],
253+
skipDependencyInstallation: true,
254+
writeToPath: bitmapEntry.rootDir,
255+
skipUpdatingBitMap: true,
256+
});
257+
258+
this.workspace.consumer.bitMap.addComponent({
259+
componentId: newCompId,
260+
files: consumerComp.files.map((f) => ({
261+
name: f.basename,
262+
relativePath: f.relative,
263+
test: f.test,
264+
})),
265+
mainFile: bitmapEntry.mainFile!,
266+
config: bitmapEntry.config,
267+
defaultScope: bitmapEntry.defaultScope,
268+
});
269+
await this.workspace.triggerOnComponentAdd(newCompId, { compile: true });
270+
return newCompId;
271+
});
272+
await this.workspace.bitMap.write();
273+
return compact(newComps);
274+
}
275+
234276
async checkoutByCLIValues(componentPattern: string, checkoutProps: CheckoutProps): Promise<ApplyVersionResults> {
235277
const { revert, head } = checkoutProps;
236278
this.logger.setStatusLine(revert ? 'reverting components...' : BEFORE_CHECKOUT);

scopes/component/merging/merging.main.runtime.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ export type ApplyVersionResults = {
9696
failedComponents?: FailedComponents[];
9797
removedComponents?: ComponentID[];
9898
addedComponents?: ComponentID[]; // relevant when restoreMissingComponents is true (e.g. bit lane merge-abort)
99+
newComponents?: ComponentID[]; // relevant for "bit stash load". (stashedBitmapEntries is populated)
99100
resolvedComponents?: ConsumerComponent[]; // relevant for bit merge --resolve
100101
abortedComponents?: ApplyVersionResult[]; // relevant for bit merge --abort
101102
mergeSnapResults?: MergeSnapResults;

scopes/component/stash/stash-data.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { ComponentID, ComponentIdObj } from '@teambit/component-id';
22

3-
export type StashCompData = { id: ComponentID; hash: string };
4-
export type StashCompDataWithIdObj = { id: ComponentIdObj; hash: string };
3+
type StashCompBase = { hash: string; isNew: boolean; bitmapEntry: Record<string, any> };
4+
export type StashCompData = { id: ComponentID } & StashCompBase;
5+
export type StashCompDataWithIdObj = { id: ComponentIdObj } & StashCompBase;
56
export type StashMetadata = { message?: string };
67
export type StashDataObj = {
78
metadata: StashMetadata;
@@ -17,9 +18,11 @@ export class StashData {
1718
toObject(): StashDataObj {
1819
return {
1920
metadata: this.metadata,
20-
stashCompsData: this.stashCompsData.map(({ id, hash }) => ({
21+
stashCompsData: this.stashCompsData.map(({ id, hash, bitmapEntry, isNew }) => ({
2122
id: id.changeVersion(undefined).toObject(),
2223
hash,
24+
bitmapEntry,
25+
isNew,
2326
})),
2427
};
2528
}
@@ -31,6 +34,8 @@ export class StashData {
3134
return {
3235
id,
3336
hash: compData.hash,
37+
bitmapEntry: compData.bitmapEntry,
38+
isNew: compData.isNew,
3439
};
3540
})
3641
);

scopes/component/stash/stash.cmd.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ export class StashSaveCmd implements Command {
1414
group = 'development';
1515
options = [
1616
['p', 'pattern', COMPONENT_PATTERN_HELP],
17+
[
18+
'',
19+
'include-new',
20+
'EXPERIMENTAL. by default, only modified components are stashed. use this flag to include new components',
21+
],
1722
['m', 'message <string>', 'message to be attached to the stashed components'],
1823
] as CommandOptions;
1924
loader = true;
@@ -25,12 +30,14 @@ export class StashSaveCmd implements Command {
2530
{
2631
pattern,
2732
message,
33+
includeNew,
2834
}: {
2935
pattern?: string;
3036
message?: string;
37+
includeNew?: boolean;
3138
}
3239
) {
33-
const compIds = await this.stash.save({ pattern, message });
40+
const compIds = await this.stash.save({ pattern, message, includeNew });
3441
return chalk.green(`stashed ${compIds.length} components`);
3542
}
3643
}

scopes/component/stash/stash.main.runtime.ts

Lines changed: 49 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import { StashAspect } from './stash.aspect';
1111
import { StashCmd, StashListCmd, StashLoadCmd, StashSaveCmd } from './stash.cmd';
1212
import { StashData } from './stash-data';
1313
import { StashFiles } from './stash-files';
14+
import { getBasicLog } from '@teambit/harmony.modules.get-basic-log';
15+
import { RemoveAspect, RemoveMain } from '@teambit/remove';
1416

1517
type ListResult = {
1618
id: string;
@@ -23,33 +25,45 @@ export class StashMain {
2325
constructor(
2426
private workspace: Workspace,
2527
private checkout: CheckoutMain,
26-
private snapping: SnappingMain
28+
private snapping: SnappingMain,
29+
private remove: RemoveMain
2730
) {
2831
this.stashFiles = new StashFiles(workspace);
2932
}
3033

31-
async save(options: { message?: string; pattern?: string }): Promise<ComponentID[]> {
34+
async save(options: { message?: string; pattern?: string; includeNew?: boolean }): Promise<ComponentID[]> {
3235
const compIds = options?.pattern ? await this.workspace.idsByPattern(options?.pattern) : this.workspace.listIds();
3336
const comps = await this.workspace.getMany(compIds);
37+
const newComps: Component[] = [];
3438
const modifiedComps = compact(
3539
await Promise.all(
3640
comps.map(async (comp) => {
37-
if (!comp.head) return undefined; // it's new
41+
if (!comp.head) {
42+
// it's a new component
43+
if (options.includeNew) newComps.push(comp);
44+
return undefined;
45+
}
3846
const isModified = await this.workspace.isModified(comp);
3947
if (isModified) return comp;
4048
return undefined;
4149
})
4250
)
4351
);
44-
if (!modifiedComps.length) return [];
52+
const allComps = [...modifiedComps, ...newComps];
53+
if (!allComps.length) return [];
4554

4655
// per comp: create Version object, save it in the local scope and return the hash. don't save anything in the .bitmap
47-
const consumeComponents = modifiedComps.map((comp) => comp.state._consumer);
56+
const consumeComponents = allComps.map((comp) => comp.state._consumer);
4857
await this.snapping._addFlattenedDependenciesToComponents(consumeComponents);
4958
const hashPerId = await Promise.all(
50-
modifiedComps.map(async (comp) => {
59+
allComps.map(async (comp) => {
5160
const versionObj = await this.addComponentDataToRepo(comp);
52-
return { id: comp.id, hash: versionObj.hash().toString() };
61+
return {
62+
id: comp.id,
63+
hash: versionObj.hash().toString(),
64+
bitmapEntry: this.workspace.bitMap.getBitmapEntry(comp.id).toPlainObject(),
65+
isNew: !comp.id.hasVersion(),
66+
};
5367
})
5468
);
5569
await this.workspace.scope.legacyScope.objects.persist();
@@ -63,8 +77,13 @@ export class StashMain {
6377
skipNpmInstall: true,
6478
reset: true,
6579
});
80+
// remove new components from the workspace
81+
const newCompIds = newComps.map((c) => c.id);
82+
if (newComps.length) {
83+
await this.remove.removeLocallyByIds(newCompIds);
84+
}
6685

67-
return modifiedCompIds;
86+
return [...modifiedCompIds, ...newCompIds];
6887
}
6988

7089
async list(): Promise<ListResult[]> {
@@ -87,8 +106,14 @@ export class StashMain {
87106
throw new BitError('no stashed components found');
88107
}
89108
const stashData = await this.stashFiles.getStashData(stashFile);
90-
const compIds = stashData.stashCompsData.map((c) => c.id);
91-
const versionPerId = stashData.stashCompsData.map((c) => c.id.changeVersion(c.hash.toString()));
109+
const stashModifiedCompsData = stashData.stashCompsData.filter((c) => !c.isNew);
110+
const stashNewCompsData = stashData.stashCompsData.filter((c) => c.isNew);
111+
const compIds = stashModifiedCompsData.map((c) => c.id);
112+
const versionPerId = stashModifiedCompsData.map((c) => c.id.changeVersion(c.hash.toString()));
113+
const stashedBitmapEntries = stashNewCompsData.map((s) => ({
114+
...s.bitmapEntry,
115+
id: s.id.changeVersion(s.hash.toString()),
116+
}));
92117

93118
await this.checkout.checkout({
94119
...checkoutProps,
@@ -98,17 +123,21 @@ export class StashMain {
98123
skipUpdatingBitmap: true,
99124
promptMergeOptions: true,
100125
loadStash: true,
126+
stashedBitmapEntries,
101127
});
102128

103129
await this.stashFiles.deleteStashFile(stashFile);
104130

105-
return compIds;
131+
return [...compIds, ...stashNewCompsData.map((c) => c.id)];
106132
}
107133

108134
private async addComponentDataToRepo(component: Component) {
109135
const previousVersion = component.getSnapHash();
110136
const consumerComponent = component.state._consumer.clone() as ConsumerComponent;
111137
consumerComponent.setNewVersion();
138+
if (!consumerComponent.log) {
139+
consumerComponent.log = await getBasicLog();
140+
}
112141
const { version, files } =
113142
await this.workspace.scope.legacyScope.sources.consumerComponentToVersion(consumerComponent);
114143
if (previousVersion) {
@@ -126,10 +155,16 @@ export class StashMain {
126155
}
127156

128157
static slots = [];
129-
static dependencies = [CLIAspect, WorkspaceAspect, CheckoutAspect, SnappingAspect];
158+
static dependencies = [CLIAspect, WorkspaceAspect, CheckoutAspect, SnappingAspect, RemoveAspect];
130159
static runtime = MainRuntime;
131-
static async provider([cli, workspace, checkout, snapping]: [CLIMain, Workspace, CheckoutMain, SnappingMain]) {
132-
const stashMain = new StashMain(workspace, checkout, snapping);
160+
static async provider([cli, workspace, checkout, snapping, remove]: [
161+
CLIMain,
162+
Workspace,
163+
CheckoutMain,
164+
SnappingMain,
165+
RemoveMain,
166+
]) {
167+
const stashMain = new StashMain(workspace, checkout, snapping, remove);
133168
const stashCmd = new StashCmd(stashMain);
134169
stashCmd.commands = [new StashSaveCmd(stashMain), new StashLoadCmd(stashMain), new StashListCmd(stashMain)];
135170
cli.register(stashCmd);

src/e2e-helper/e2e-command-helper.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -637,8 +637,12 @@ export default class CommandHelper {
637637
return this.runCmd(`bit revert ${pattern} ${to} ${flags}`);
638638
}
639639

640-
stash() {
641-
return this.runCmd('bit stash save');
640+
stash(flags = '') {
641+
return this.runCmd(`bit stash save ${flags}`);
642+
}
643+
644+
stashList(flags = '') {
645+
return this.runCmd(`bit stash list ${flags}`);
642646
}
643647

644648
stashLoad(flags = '') {

0 commit comments

Comments
 (0)