Skip to content

Commit

Permalink
Merge pull request #67 from jzhang026/feat/checout-to-from-selector
Browse files Browse the repository at this point in the history
feat: Sparo checkout support --to/--from option for direct projects selection
  • Loading branch information
chengcyber authored May 9, 2024
2 parents bdd846b + 580e47b commit 486822b
Show file tree
Hide file tree
Showing 8 changed files with 223 additions and 15 deletions.
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 @@ -145,10 +162,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 @@ -216,7 +234,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({
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":

[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"
}

0 comments on commit 486822b

Please sign in to comment.