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: Sparo checkout support --to/--from option for direct projects selection #67

Merged
merged 5 commits into from
May 9, 2024
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
35 changes: 28 additions & 7 deletions apps/sparo-lib/src/cli/commands/checkout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ export interface ICheckoutCommandOptions {
B?: boolean;
startPoint?: string;
addProfile?: string[];
to?: string[];
from?: string[];
}

type ICheckoutTargetKind = 'branch' | 'tag' | 'commit' | 'filePath';
Expand Down Expand Up @@ -43,6 +45,7 @@ export class CheckoutCommand implements ICommand<ICheckoutCommandOptions> {
* have been implemented, while other scenarios are yet to be implemented.
* 1. sparo checkout [-b|-B] <new-branch> [start-point] [--profile <profile...>]
* 2. sparo checkout [branch] [--profile <profile...>]
* 3. sparo checkout [branch] [--to <project-name...>] [--from <project-name...>]
*
* TODO: implement more checkout functionalities
*/
Expand All @@ -67,7 +70,19 @@ export class CheckoutCommand implements ICommand<ICheckoutCommandOptions> {
.array('profile')
.default('profile', [])
.array('add-profile')
.default('add-profile', []);
.default('add-profile', [])
.option('to', {
type: 'array',
default: [],
description:
'Checkout projects up to (and including) project <to..>, can be used together with option --profile/--add-profile to form a union selection of the two options. The projects selectors here will never replace what have been checked out by profiles'
})
.option('from', {
type: 'array',
default: [],
description:
'Checkout projects downstream from (and including itself and all its dependencies) project <from..>, can be used together with option --profile/--add-profile to form a union selection of the two options. The projects selectors here will never replace what have been checked out by profiles'
});
}

public handler = async (
Expand All @@ -76,7 +91,9 @@ export class CheckoutCommand implements ICommand<ICheckoutCommandOptions> {
): Promise<void> => {
const { _gitService: gitService } = this;
terminalService.terminal.writeDebugLine(`got args in checkout command: ${JSON.stringify(args)}`);
const { b, B, startPoint } = args;
const { b, B, startPoint, to, from } = args;
const toProjects: Set<string> = new Set(to);
const fromProjects: Set<string> = new Set(from);

let branch: string | undefined = args.branch;

Expand Down Expand Up @@ -138,10 +155,11 @@ export class CheckoutCommand implements ICommand<ICheckoutCommandOptions> {
}

// preprocess profile related args
const { isNoProfile, profiles, addProfiles } = await this._sparoProfileService.preprocessProfileArgs({
addProfilesFromArg: args.addProfile ?? [],
profilesFromArg: args.profile
});
const { isNoProfile, profiles, addProfiles, isProfileRestoreFromLocal } =
await this._sparoProfileService.preprocessProfileArgs({
addProfilesFromArg: args.addProfile ?? [],
profilesFromArg: args.profile
});

// Check wether profiles exist in local or operation branch
// Skip check in the following cases:
Expand Down Expand Up @@ -209,7 +227,10 @@ export class CheckoutCommand implements ICommand<ICheckoutCommandOptions> {
// Sync local sparse checkout state with given profiles.
await this._sparoProfileService.syncProfileState({
profiles: isNoProfile ? undefined : profiles,
addProfiles
addProfiles,
fromProjects,
toProjects,
isProfileRestoreFromLocal
});
}
};
Expand Down
83 changes: 75 additions & 8 deletions apps/sparo-lib/src/services/SparoProfileService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { FileSystem, Async } from '@rushstack/node-core-library';
import path from 'path';
import { inject } from 'inversify';
import { Service } from '../decorator';
import { SparoProfile, ISelection } from '../logic/SparoProfile';
import { SparoProfile, ISelection, ISparoProfileJson } from '../logic/SparoProfile';
import { TerminalService } from './TerminalService';
import { GitService } from './GitService';
import { GitSparseCheckoutService } from './GitSparseCheckoutService';
Expand All @@ -18,6 +18,7 @@ export interface IResolveSparoProfileOptions {
}

const defaultSparoProfileFolder: string = 'common/sparo-profiles';
const INTERNAL_RUSH_SELECTOR_PSEUDO_PROFILE: string = '__INTERNAL_RUSH_SELECTOR_PSEUDO_PROFILE__';

@Service()
export class SparoProfileService {
Expand Down Expand Up @@ -170,10 +171,12 @@ ${availableProfiles.join(',')}
addProfilesFromArg: string[];
}): Promise<{
isNoProfile: boolean;
isProfileRestoreFromLocal: boolean;
profiles: Set<string>;
addProfiles: Set<string>;
}> {
let isNoProfile: boolean = false;
let isProfileRestoreFromLocal: boolean = false;
/**
* --profile is defined as array type parameter, specifying --no-profile is resolved to false by yargs.
*
Expand Down Expand Up @@ -210,40 +213,77 @@ ${availableProfiles.join(',')}
// 1. If profile specified from CLI parameter, preferential use it.
// 2. If none profile specified, read from existing profile from local state as default.
const localStateProfiles: ILocalStateProfiles | undefined = await this._localState.getProfiles();

isProfileRestoreFromLocal = true;
if (localStateProfiles) {
Object.keys(localStateProfiles).forEach((p) => profiles.add(p));
Object.keys(localStateProfiles).forEach((p) => {
if (p === INTERNAL_RUSH_SELECTOR_PSEUDO_PROFILE) return;
profiles.add(p);
});
}
}
return {
isNoProfile,
profiles,
addProfiles
addProfiles,
isProfileRestoreFromLocal
};
}

private async _syncRushSelectors(): Promise<ISelection[]>;
private async _syncRushSelectors(selections: ISelection[]): Promise<void>;
private async _syncRushSelectors(selections?: ISelection[]): Promise<void | ISelection[]> {
if (typeof selections !== 'undefined') {
return this._localState.setProfiles(
{
[INTERNAL_RUSH_SELECTOR_PSEUDO_PROFILE]: {
selections
}
},
'add'
);
} else {
const localStateProfiles: ILocalStateProfiles | undefined = await this._localState.getProfiles();
if (localStateProfiles) {
const rushSelectorProfiles: ISparoProfileJson | undefined =
localStateProfiles[INTERNAL_RUSH_SELECTOR_PSEUDO_PROFILE];
return rushSelectorProfiles?.selections || [];
}
return [];
}
}

/**
* sync local sparse checkout state with specified profiles
*/
public async syncProfileState({
chengcyber marked this conversation as resolved.
Show resolved Hide resolved
profiles,
addProfiles
addProfiles,
fromProjects,
toProjects,
isProfileRestoreFromLocal
}: {
profiles?: Set<string>;
addProfiles?: Set<string>;
fromProjects?: Set<string>;
toProjects?: Set<string>;
isProfileRestoreFromLocal?: boolean;
}): Promise<void> {
// only if user didn't specify any profile during a sparo checkout, we need to
// retain any previously checked out projects based on Rush Selectors
// https://rushjs.io/pages/developer/selecting_subsets/
const rushSelectorState: ISelection[] = isProfileRestoreFromLocal ? await this._syncRushSelectors() : [];
this._localState.reset();
const allProfiles: string[] = Array.from([...(profiles ?? []), ...(addProfiles ?? [])]);
if (allProfiles.length > 1) {
this._terminalService.terminal.writeLine(
`Syncing checkout with these Sparo profiles:\n${allProfiles.join(', ')}`
);
} else if (allProfiles.length === 1) {
this._terminalService.terminal.writeLine(`Syncing checkout with the Sparo profile: ${allProfiles[0]}`);
} else {
this._terminalService.terminal.writeLine(
`Syncing checkout with the Sparo profile: ${allProfiles[0]}`
'Syncing checkout with the Sparo skeleton (no profile selection)'
);
} else {
this._terminalService.terminal.writeLine('Syncing checkout with the Sparo skeleton (no profile selection)');
}
this._terminalService.terminal.writeLine();
if (!profiles || profiles.size === 0) {
Expand Down Expand Up @@ -298,5 +338,32 @@ ${availableProfiles.join(',')}
checkoutAction: 'add'
});
}

// handle case of `sparo checkout --to project-A project-B --from project-C project-D
const toSelector: Set<string> = toProjects || new Set();
const fromSelector: Set<string> = fromProjects || new Set();
// If Rush Selector --to <projects> is specified, using `git sparse-checkout add` to add folders of the projects specified
const projectsSelections: ISelection[] = [...rushSelectorState];

for (const project of toSelector) {
projectsSelections.push({
selector: '--to',
argument: project
});
}
for (const project of fromSelector) {
projectsSelections.push({
selector: '--from',
argument: project
});
}

if (projectsSelections.length > 0) {
await this._syncRushSelectors(projectsSelections);
await this._gitSparseCheckoutService.checkoutAsync({
selections: projectsSelections,
checkoutAction: 'add'
});
}
}
}
15 changes: 15 additions & 0 deletions apps/website/docs/pages/commands/sparo_checkout.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,19 @@ Options:
already exists, reset it to <start-point> [boolean]
--profile [array] [default: []]
--add-profile [array] [default: []]
--to Checkout projects up to (and including) project <to..>, can
be used together with option --profile/--add-profile to
form a union selection of the two options. The projects
selectors here will never replace what have been checked
out by profiles [array] [default: []]
--from Checkout projects downstream from (and including itself and
all its dependencies) project <from..>, can be used
together with option --profile/--add-profile to form a
union selection of the two options. The projects selectors
here will never replace what have been checked out by
profiles [array] [default: []]
```




11 changes: 11 additions & 0 deletions build-tests/sparo-output-test/etc/checkout-help.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,16 @@ Options:
-b Create a new branch and start it at <start-point> [boolean]
-B Create a new branch and start it at <start-point>; if it
already exists, reset it to <start-point> [boolean]
--to Checkout projects up to (and including) project <to..>, can
be used together with option --profile/--add-profile to
form a union selection of the two options. The projects
selectors here will never replace what have been checked
out by profiles [array] [default: []]
--from Checkout projects downstream from (and including itself and
all its dependencies) project <from..>, can be used
together with option --profile/--add-profile to form a
union selection of the two options. The projects selectors
here will never replace what have been checked out by
profiles [array] [default: []]
--profile [array] [default: []]
--add-profile [array] [default: []]
35 changes: 35 additions & 0 deletions build-tests/sparo-real-repo-test/etc/checkout-from.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
Running "sparo checkout --from sparo":

[bold]Sparo accelerator for Git __VERSION__ -[normal][cyan] https://tiktok.github.io/sparo/[default]
Node.js version is __VERSION__ (LTS)
Git version is __VERSION__


[gray]--[[default] [bold]git checkout[normal] [gray]]-------------------------------------------------------------[default]
Your branch is up to date with 'origin/test-artifacts/sparo-real-repo-test'.
[gray]-------------------------------------------------------------------------------[default]

Syncing checkout with the Sparo profile: my-build-test

Checking out and updating core files...

[gray]--[[default] [bold]git sparse-checkout[normal] [gray]]------------------------------------------------------[default]
[gray]-------------------------------------------------------------------------------[default]

Checking out skeleton...

[gray]--[[default] [bold]git sparse-checkout[normal] [gray]]------------------------------------------------------[default]
[gray]-------------------------------------------------------------------------------[default]

Checking out __FOLDER_COUNT__ folders...

[gray]--[[default] [bold]git sparse-checkout[normal] [gray]]------------------------------------------------------[default]
[gray]-------------------------------------------------------------------------------[default]

Sparse checkout completed in __DURATION__
Checking out __FOLDER_COUNT__ folders...

[gray]--[[default] [bold]git sparse-checkout[normal] [gray]]------------------------------------------------------[default]
[gray]-------------------------------------------------------------------------------[default]

Sparse checkout completed in __DURATION__
35 changes: 35 additions & 0 deletions build-tests/sparo-real-repo-test/etc/checkout-to.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
Running "sparo checkout --to sparo-output-test":
chengcyber marked this conversation as resolved.
Show resolved Hide resolved

[bold]Sparo accelerator for Git __VERSION__ -[normal][cyan] https://tiktok.github.io/sparo/[default]
Node.js version is __VERSION__ (LTS)
Git version is __VERSION__


[gray]--[[default] [bold]git checkout[normal] [gray]]-------------------------------------------------------------[default]
Your branch is up to date with 'origin/test-artifacts/sparo-real-repo-test'.
[gray]-------------------------------------------------------------------------------[default]

Syncing checkout with the Sparo profile: my-build-test

Checking out and updating core files...

[gray]--[[default] [bold]git sparse-checkout[normal] [gray]]------------------------------------------------------[default]
[gray]-------------------------------------------------------------------------------[default]

Checking out skeleton...

[gray]--[[default] [bold]git sparse-checkout[normal] [gray]]------------------------------------------------------[default]
[gray]-------------------------------------------------------------------------------[default]

Checking out __FOLDER_COUNT__ folders...

[gray]--[[default] [bold]git sparse-checkout[normal] [gray]]------------------------------------------------------[default]
[gray]-------------------------------------------------------------------------------[default]

Sparse checkout completed in __DURATION__
Checking out __FOLDER_COUNT__ folders...

[gray]--[[default] [bold]git sparse-checkout[normal] [gray]]------------------------------------------------------[default]
[gray]-------------------------------------------------------------------------------[default]

Sparse checkout completed in __DURATION__
14 changes: 14 additions & 0 deletions build-tests/sparo-real-repo-test/src/start-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,20 @@ export async function runAsync(runScriptOptions: IRunScriptOptions): Promise<voi
args: ['checkout', '--profile', 'my-build-test'],
currentWorkingDirectory: repoFolder
},
// sparo checkout --to sparo-output-test
{
kind: 'sparo-command',
name: 'checkout-to',
args: ['checkout', '--to', 'sparo-output-test'],
currentWorkingDirectory: repoFolder
},
// sparo checkout --from build-test-utilities
{
kind: 'sparo-command',
name: 'checkout-from',
args: ['checkout', '--from', 'sparo'],
currentWorkingDirectory: repoFolder
},
// sparo list-profiles
{
kind: 'sparo-command',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "sparo",
"comment": "[sparo checkout] support cli --to/--from option for projects selection",
"type": "none"
}
],
"packageName": "sparo"
}
Loading