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;
startPoint?: string;
addProfile?: string[];
to?: string[];
from?: string[];
}
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.
* 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
*/
@ -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 (
@ -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;
@ -145,7 +162,8 @@ export class CheckoutCommand implements ICommand<ICheckoutCommandOptions> {
}
// preprocess profile related args
const { isNoProfile, profiles, addProfiles } = await this._sparoProfileService.preprocessProfileArgs({
const { isNoProfile, profiles, addProfiles, isProfileRestoreFromLocal } =
await this._sparoProfileService.preprocessProfileArgs({
addProfilesFromArg: args.addProfile ?? [],
profilesFromArg: args.profile
});
@ -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
});
}
};

View file

@ -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';
@ -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 {
@ -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.
*
@ -210,28 +213,65 @@ ${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) {
@ -239,11 +279,11 @@ ${availableProfiles.join(',')}
`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]}`
);
this._terminalService.terminal.writeLine(`Syncing checkout with the Sparo profile: ${allProfiles[0]}`);
} 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();
if (!profiles || profiles.size === 0) {
@ -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'
});
}
}
}

View file

@ -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: []]
```

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>; 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: []]

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'],
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',

View file

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