Merge pull request #67 from jzhang026/feat/checout-to-from-selector

feat: Sparo checkout support --to/--from option for direct projects selection
This commit is contained in:
Cheng Liu 2024-05-09 15:46:41 -07:00 committed by GitHub
commit 486822b310
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 224 additions and 16 deletions

View file

@ -14,6 +14,8 @@ export interface ICheckoutCommandOptions {
B?: boolean; B?: boolean;
startPoint?: string; startPoint?: string;
addProfile?: string[]; addProfile?: string[];
to?: string[];
from?: string[];
} }
type ICheckoutTargetKind = 'branch' | 'tag' | 'commit' | 'filePath'; type ICheckoutTargetKind = 'branch' | 'tag' | 'commit' | 'filePath';
@ -43,6 +45,7 @@ export class CheckoutCommand implements ICommand<ICheckoutCommandOptions> {
* have been implemented, while other scenarios are yet to be implemented. * have been implemented, while other scenarios are yet to be implemented.
* 1. sparo checkout [-b|-B] <new-branch> [start-point] [--profile <profile...>] * 1. sparo checkout [-b|-B] <new-branch> [start-point] [--profile <profile...>]
* 2. sparo checkout [branch] [--profile <profile...>] * 2. sparo checkout [branch] [--profile <profile...>]
* 3. sparo checkout [branch] [--to <project-name...>] [--from <project-name...>]
* *
* TODO: implement more checkout functionalities * TODO: implement more checkout functionalities
*/ */
@ -67,7 +70,19 @@ export class CheckoutCommand implements ICommand<ICheckoutCommandOptions> {
.array('profile') .array('profile')
.default('profile', []) .default('profile', [])
.array('add-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 ( public handler = async (
@ -76,7 +91,9 @@ export class CheckoutCommand implements ICommand<ICheckoutCommandOptions> {
): Promise<void> => { ): Promise<void> => {
const { _gitService: gitService } = this; const { _gitService: gitService } = this;
terminalService.terminal.writeDebugLine(`got args in checkout command: ${JSON.stringify(args)}`); 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; let branch: string | undefined = args.branch;
@ -145,7 +162,8 @@ export class CheckoutCommand implements ICommand<ICheckoutCommandOptions> {
} }
// preprocess profile related args // preprocess profile related args
const { isNoProfile, profiles, addProfiles } = await this._sparoProfileService.preprocessProfileArgs({ const { isNoProfile, profiles, addProfiles, isProfileRestoreFromLocal } =
await this._sparoProfileService.preprocessProfileArgs({
addProfilesFromArg: args.addProfile ?? [], addProfilesFromArg: args.addProfile ?? [],
profilesFromArg: args.profile profilesFromArg: args.profile
}); });
@ -216,7 +234,10 @@ export class CheckoutCommand implements ICommand<ICheckoutCommandOptions> {
// Sync local sparse checkout state with given profiles. // Sync local sparse checkout state with given profiles.
await this._sparoProfileService.syncProfileState({ await this._sparoProfileService.syncProfileState({
profiles: isNoProfile ? undefined : profiles, profiles: isNoProfile ? undefined : profiles,
addProfiles addProfiles,
fromProjects,
toProjects,
isProfileRestoreFromLocal
}); });
} }
}; };

View file

@ -2,7 +2,7 @@ import { FileSystem, Async } from '@rushstack/node-core-library';
import path from 'path'; import path from 'path';
import { inject } from 'inversify'; import { inject } from 'inversify';
import { Service } from '../decorator'; import { Service } from '../decorator';
import { SparoProfile, ISelection } from '../logic/SparoProfile'; import { SparoProfile, ISelection, ISparoProfileJson } from '../logic/SparoProfile';
import { TerminalService } from './TerminalService'; import { TerminalService } from './TerminalService';
import { GitService } from './GitService'; import { GitService } from './GitService';
import { GitSparseCheckoutService } from './GitSparseCheckoutService'; import { GitSparseCheckoutService } from './GitSparseCheckoutService';
@ -18,6 +18,7 @@ export interface IResolveSparoProfileOptions {
} }
const defaultSparoProfileFolder: string = 'common/sparo-profiles'; const defaultSparoProfileFolder: string = 'common/sparo-profiles';
const INTERNAL_RUSH_SELECTOR_PSEUDO_PROFILE: string = '__INTERNAL_RUSH_SELECTOR_PSEUDO_PROFILE__';
@Service() @Service()
export class SparoProfileService { export class SparoProfileService {
@ -170,10 +171,12 @@ ${availableProfiles.join(',')}
addProfilesFromArg: string[]; addProfilesFromArg: string[];
}): Promise<{ }): Promise<{
isNoProfile: boolean; isNoProfile: boolean;
isProfileRestoreFromLocal: boolean;
profiles: Set<string>; profiles: Set<string>;
addProfiles: Set<string>; addProfiles: Set<string>;
}> { }> {
let isNoProfile: boolean = false; let isNoProfile: boolean = false;
let isProfileRestoreFromLocal: boolean = false;
/** /**
* --profile is defined as array type parameter, specifying --no-profile is resolved to false by yargs. * --profile is defined as array type parameter, specifying --no-profile is resolved to false by yargs.
* *
@ -210,28 +213,65 @@ ${availableProfiles.join(',')}
// 1. If profile specified from CLI parameter, preferential use it. // 1. If profile specified from CLI parameter, preferential use it.
// 2. If none profile specified, read from existing profile from local state as default. // 2. If none profile specified, read from existing profile from local state as default.
const localStateProfiles: ILocalStateProfiles | undefined = await this._localState.getProfiles(); const localStateProfiles: ILocalStateProfiles | undefined = await this._localState.getProfiles();
isProfileRestoreFromLocal = true;
if (localStateProfiles) { 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 { return {
isNoProfile, isNoProfile,
profiles, 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 * sync local sparse checkout state with specified profiles
*/ */
public async syncProfileState({ public async syncProfileState({
profiles, profiles,
addProfiles addProfiles,
fromProjects,
toProjects,
isProfileRestoreFromLocal
}: { }: {
profiles?: Set<string>; profiles?: Set<string>;
addProfiles?: Set<string>; addProfiles?: Set<string>;
fromProjects?: Set<string>;
toProjects?: Set<string>;
isProfileRestoreFromLocal?: boolean;
}): Promise<void> { }): 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(); this._localState.reset();
const allProfiles: string[] = Array.from([...(profiles ?? []), ...(addProfiles ?? [])]); const allProfiles: string[] = Array.from([...(profiles ?? []), ...(addProfiles ?? [])]);
if (allProfiles.length > 1) { if (allProfiles.length > 1) {
@ -239,11 +279,11 @@ ${availableProfiles.join(',')}
`Syncing checkout with these Sparo profiles:\n${allProfiles.join(', ')}` `Syncing checkout with these Sparo profiles:\n${allProfiles.join(', ')}`
); );
} else if (allProfiles.length === 1) { } else if (allProfiles.length === 1) {
this._terminalService.terminal.writeLine( this._terminalService.terminal.writeLine(`Syncing checkout with the Sparo profile: ${allProfiles[0]}`);
`Syncing checkout with the Sparo profile: ${allProfiles[0]}`
);
} else { } else {
this._terminalService.terminal.writeLine('Syncing checkout with the Sparo skeleton (no profile selection)'); this._terminalService.terminal.writeLine(
'Syncing checkout with the Sparo skeleton (no profile selection)'
);
} }
this._terminalService.terminal.writeLine(); this._terminalService.terminal.writeLine();
if (!profiles || profiles.size === 0) { if (!profiles || profiles.size === 0) {
@ -298,5 +338,32 @@ ${availableProfiles.join(',')}
checkoutAction: 'add' 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'
});
}
} }
} }

View file

@ -20,4 +20,19 @@ Options:
already exists, reset it to <start-point> [boolean] already exists, reset it to <start-point> [boolean]
--profile [array] [default: []] --profile [array] [default: []]
--add-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: []]
``` ```

View file

@ -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> [boolean]
-B Create a new branch and start it at <start-point>; if it -B Create a new branch and start it at <start-point>; if it
already exists, reset it to <start-point> [boolean] 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: []] --profile [array] [default: []]
--add-profile [array] [default: []] --add-profile [array] [default: []]

View file

@ -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__

View file

@ -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__

View file

@ -80,6 +80,20 @@ export async function runAsync(runScriptOptions: IRunScriptOptions): Promise<voi
args: ['checkout', '--profile', 'my-build-test'], args: ['checkout', '--profile', 'my-build-test'],
currentWorkingDirectory: repoFolder 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 // sparo list-profiles
{ {
kind: 'sparo-command', kind: 'sparo-command',

View file

@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "sparo",
"comment": "[sparo checkout] support cli --to/--from option for projects selection",
"type": "none"
}
],
"packageName": "sparo"
}