From 55b1c41f4a8c8ab070ebc1ec82a65f94047ebf45 Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 2 Oct 2024 19:11:24 +0000 Subject: [PATCH 01/14] Expose analysis of file changes using ProjectChangeAnalyzer.getChangedFilesAsync --- common/reviews/api/rush-lib.api.md | 23 ++++++- libraries/rush-lib/src/index.ts | 2 + .../src/logic/ProjectChangeAnalyzer.ts | 65 ++++++++++++++----- 3 files changed, 70 insertions(+), 20 deletions(-) diff --git a/common/reviews/api/rush-lib.api.md b/common/reviews/api/rush-lib.api.md index 31cb1da9f8a..6d47a0bbaf7 100644 --- a/common/reviews/api/rush-lib.api.md +++ b/common/reviews/api/rush-lib.api.md @@ -14,6 +14,7 @@ import type { CollatedWriter } from '@rushstack/stream-collator'; import type { CommandLineParameter } from '@rushstack/ts-command-line'; import { CommandLineParameterKind } from '@rushstack/ts-command-line'; import { HookMap } from 'tapable'; +import { IFileDiffStatus } from '@rushstack/package-deps-hash'; import { IPackageJson } from '@rushstack/node-core-library'; import { IPrefixMatch } from '@rushstack/lookup-by-path'; import { ITerminal } from '@rushstack/terminal'; @@ -503,17 +504,29 @@ export interface IGenerateCacheEntryIdOptions { } // @beta (undocumented) -export interface IGetChangedProjectsOptions { +export interface IGetChangedFilesOptions extends IGetMergeCommitOptions { + // (undocumented) + mergeCommit?: string; +} + +// @beta (undocumented) +export interface IGetChangedProjectsOptions extends IGetChangedFilesOptions { + // (undocumented) + changedFiles?: Map; enableFiltering: boolean; includeExternalDependencies: boolean; + // (undocumented) + variant?: string; +} + +// @beta (undocumented) +export interface IGetMergeCommitOptions { // (undocumented) shouldFetch?: boolean; // (undocumented) targetBranchName: string; // (undocumented) terminal: ITerminal; - // (undocumented) - variant?: string; } // @beta @@ -1115,7 +1128,11 @@ export class ProjectChangeAnalyzer { _ensureInitializedAsync(terminal: ITerminal): Promise<_IRawRepoState | undefined>; // (undocumented) _filterProjectDataAsync(project: RushConfigurationProject, unfilteredProjectData: Map, rootDir: string, terminal: ITerminal): Promise>; + // (undocumented) + getChangedFilesAsync(options: IGetChangedFilesOptions): Promise>; getChangedProjectsAsync(options: IGetChangedProjectsOptions): Promise>; + // (undocumented) + getMergeCommitAsync(options: IGetMergeCommitOptions): Promise; // @internal _tryGetProjectDependenciesAsync(project: RushConfigurationProject, terminal: ITerminal): Promise | undefined>; // @internal diff --git a/libraries/rush-lib/src/index.ts b/libraries/rush-lib/src/index.ts index f7d2e9a2278..820409f3a6b 100644 --- a/libraries/rush-lib/src/index.ts +++ b/libraries/rush-lib/src/index.ts @@ -123,6 +123,8 @@ export { export { ProjectChangeAnalyzer, + type IGetMergeCommitOptions, + type IGetChangedFilesOptions, type IGetChangedProjectsOptions, type IRawRepoState as _IRawRepoState } from './logic/ProjectChangeAnalyzer'; diff --git a/libraries/rush-lib/src/logic/ProjectChangeAnalyzer.ts b/libraries/rush-lib/src/logic/ProjectChangeAnalyzer.ts index 9b068940425..cecd798a518 100644 --- a/libraries/rush-lib/src/logic/ProjectChangeAnalyzer.ts +++ b/libraries/rush-lib/src/logic/ProjectChangeAnalyzer.ts @@ -27,11 +27,25 @@ import { UNINITIALIZED } from '../utilities/Utilities'; /** * @beta */ -export interface IGetChangedProjectsOptions { - targetBranchName: string; +export interface IGetMergeCommitOptions { terminal: ITerminal; + targetBranchName: string; shouldFetch?: boolean; +} + +/** + * @beta + */ +export interface IGetChangedFilesOptions extends IGetMergeCommitOptions { + mergeCommit?: string; +} + +/** + * @beta + */ +export interface IGetChangedProjectsOptions extends IGetChangedFilesOptions { variant?: string; + changedFiles?: Map; /** * If set to `true`, consider a project's external dependency installation layout as defined in the @@ -208,6 +222,27 @@ export class ProjectChangeAnalyzer { return filteredProjectData; } + public async getMergeCommitAsync(options: IGetMergeCommitOptions): Promise { + const { terminal, targetBranchName, shouldFetch } = options; + + // if the given targetBranchName is a commit, we assume it is the merge base + const isTargetBranchACommit: boolean = await this._git.determineIfRefIsACommitAsync(targetBranchName); + return isTargetBranchACommit + ? targetBranchName + : await this._git.getMergeBaseAsync(targetBranchName, terminal, shouldFetch); + } + + public async getChangedFilesAsync(options: IGetChangedFilesOptions): Promise> { + const { terminal, targetBranchName, mergeCommit, shouldFetch } = options; + const { _rushConfiguration: rushConfiguration } = this; + + const gitPath: string = this._git.getGitPathOrThrow(); + const repoRoot: string = getRepoRoot(rushConfiguration.rushJsonFolder); + const resolvedMergeCommit: string = + mergeCommit ?? (await this.getMergeCommitAsync({ terminal, targetBranchName, shouldFetch })); + return getRepoChanges(repoRoot, resolvedMergeCommit, gitPath); + } + /** * Gets a list of projects that have changed in the current state of the repo * when compared to the specified branch, optionally taking the shrinkwrap and settings in @@ -216,23 +251,19 @@ export class ProjectChangeAnalyzer { public async getChangedProjectsAsync( options: IGetChangedProjectsOptions ): Promise> { + const { terminal, includeExternalDependencies, enableFiltering, variant } = options; + let { changedFiles, mergeCommit } = options; const { _rushConfiguration: rushConfiguration } = this; - - const { targetBranchName, terminal, includeExternalDependencies, enableFiltering, shouldFetch, variant } = - options; - - const gitPath: string = this._git.getGitPathOrThrow(); const repoRoot: string = getRepoRoot(rushConfiguration.rushJsonFolder); + const changedProjects: Set = new Set(); - // if the given targetBranchName is a commit, we assume it is the merge base - const IsTargetBranchACommit: boolean = await this._git.determineIfRefIsACommitAsync(targetBranchName); - const mergeCommit: string = IsTargetBranchACommit - ? targetBranchName - : await this._git.getMergeBaseAsync(targetBranchName, terminal, shouldFetch); - - const repoChanges: Map = getRepoChanges(repoRoot, mergeCommit, gitPath); + if (!mergeCommit) { + mergeCommit = await this.getMergeCommitAsync(options); + } - const changedProjects: Set = new Set(); + if (!changedFiles) { + changedFiles = await this.getChangedFilesAsync({ ...options, mergeCommit }); + } if (includeExternalDependencies) { // Even though changing the installed version of a nested dependency merits a change file, @@ -246,7 +277,7 @@ export class ProjectChangeAnalyzer { const relativeShrinkwrapFilePath: string = Path.convertToSlashes( path.relative(repoRoot, fullShrinkwrapPath) ); - const shrinkwrapStatus: IFileDiffStatus | undefined = repoChanges.get(relativeShrinkwrapFilePath); + const shrinkwrapStatus: IFileDiffStatus | undefined = changedFiles.get(relativeShrinkwrapFilePath); if (shrinkwrapStatus) { if (shrinkwrapStatus.status !== 'M') { @@ -293,7 +324,7 @@ export class ProjectChangeAnalyzer { const lookup: LookupByPath = rushConfiguration.getProjectLookupForRoot(repoRoot); - for (const [file, diffStatus] of repoChanges) { + for (const [file, diffStatus] of changedFiles) { const project: RushConfigurationProject | undefined = lookup.findChildPath(file); if (project) { if (changedProjects.has(project)) { From 5c9dc6aa10d9abe82e22c924a3b55777e0edb42d Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 2 Oct 2024 19:13:34 +0000 Subject: [PATCH 02/14] Rush change --- ...nade-ExposeFileChangeAnalysis_2024-10-02-19-12.json | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 common/changes/@microsoft/rush/user-danade-ExposeFileChangeAnalysis_2024-10-02-19-12.json diff --git a/common/changes/@microsoft/rush/user-danade-ExposeFileChangeAnalysis_2024-10-02-19-12.json b/common/changes/@microsoft/rush/user-danade-ExposeFileChangeAnalysis_2024-10-02-19-12.json new file mode 100644 index 00000000000..37bc24b3f33 --- /dev/null +++ b/common/changes/@microsoft/rush/user-danade-ExposeFileChangeAnalysis_2024-10-02-19-12.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@microsoft/rush", + "comment": "Expose changed file analysis in ProjectChangeAnalyzer", + "type": "patch" + } + ], + "packageName": "@microsoft/rush" +} \ No newline at end of file From 5cd167fdc936587bced4992ce5245d5c7e631063 Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 2 Oct 2024 20:05:04 +0000 Subject: [PATCH 03/14] Add a hook to allow modifying file change analysis --- .../src/logic/ProjectChangeAnalyzer.ts | 151 ++++++++---------- 1 file changed, 67 insertions(+), 84 deletions(-) diff --git a/libraries/rush-lib/src/logic/ProjectChangeAnalyzer.ts b/libraries/rush-lib/src/logic/ProjectChangeAnalyzer.ts index cecd798a518..04a4bab6daa 100644 --- a/libraries/rush-lib/src/logic/ProjectChangeAnalyzer.ts +++ b/libraries/rush-lib/src/logic/ProjectChangeAnalyzer.ts @@ -27,25 +27,11 @@ import { UNINITIALIZED } from '../utilities/Utilities'; /** * @beta */ -export interface IGetMergeCommitOptions { - terminal: ITerminal; +export interface IGetChangedProjectsOptions { targetBranchName: string; + terminal: ITerminal; shouldFetch?: boolean; -} - -/** - * @beta - */ -export interface IGetChangedFilesOptions extends IGetMergeCommitOptions { - mergeCommit?: string; -} - -/** - * @beta - */ -export interface IGetChangedProjectsOptions extends IGetChangedFilesOptions { variant?: string; - changedFiles?: Map; /** * If set to `true`, consider a project's external dependency installation layout as defined in the @@ -60,6 +46,11 @@ export interface IGetChangedProjectsOptions extends IGetChangedFilesOptions { enableFiltering: boolean; } +export interface IGetChangesByProjectOptions { + lookup: LookupByPath; + changedFiles: Map; +} + interface IGitState { gitPath: string; hashes: Map; @@ -222,27 +213,6 @@ export class ProjectChangeAnalyzer { return filteredProjectData; } - public async getMergeCommitAsync(options: IGetMergeCommitOptions): Promise { - const { terminal, targetBranchName, shouldFetch } = options; - - // if the given targetBranchName is a commit, we assume it is the merge base - const isTargetBranchACommit: boolean = await this._git.determineIfRefIsACommitAsync(targetBranchName); - return isTargetBranchACommit - ? targetBranchName - : await this._git.getMergeBaseAsync(targetBranchName, terminal, shouldFetch); - } - - public async getChangedFilesAsync(options: IGetChangedFilesOptions): Promise> { - const { terminal, targetBranchName, mergeCommit, shouldFetch } = options; - const { _rushConfiguration: rushConfiguration } = this; - - const gitPath: string = this._git.getGitPathOrThrow(); - const repoRoot: string = getRepoRoot(rushConfiguration.rushJsonFolder); - const resolvedMergeCommit: string = - mergeCommit ?? (await this.getMergeCommitAsync({ terminal, targetBranchName, shouldFetch })); - return getRepoChanges(repoRoot, resolvedMergeCommit, gitPath); - } - /** * Gets a list of projects that have changed in the current state of the repo * when compared to the specified branch, optionally taking the shrinkwrap and settings in @@ -251,20 +221,56 @@ export class ProjectChangeAnalyzer { public async getChangedProjectsAsync( options: IGetChangedProjectsOptions ): Promise> { - const { terminal, includeExternalDependencies, enableFiltering, variant } = options; - let { changedFiles, mergeCommit } = options; const { _rushConfiguration: rushConfiguration } = this; + + const { targetBranchName, terminal, includeExternalDependencies, enableFiltering, shouldFetch, variant } = + options; + + const gitPath: string = this._git.getGitPathOrThrow(); const repoRoot: string = getRepoRoot(rushConfiguration.rushJsonFolder); - const changedProjects: Set = new Set(); - if (!mergeCommit) { - mergeCommit = await this.getMergeCommitAsync(options); - } + // if the given targetBranchName is a commit, we assume it is the merge base + const isTargetBranchACommit: boolean = await this._git.determineIfRefIsACommitAsync(targetBranchName); + const mergeCommit: string = isTargetBranchACommit + ? targetBranchName + : await this._git.getMergeBaseAsync(targetBranchName, terminal, shouldFetch); + + const changedFiles: Map = getRepoChanges(repoRoot, mergeCommit, gitPath); + const lookup: LookupByPath = + rushConfiguration.getProjectLookupForRoot(repoRoot); + const changesByProject: Map< + RushConfigurationProject, + Map + > = this.getChangesByProject({ changedFiles, lookup }); + + const changedProjects: Set = new Set(); + if (enableFiltering) { + // Reading rush-project.json may be problematic if, e.g. rush install has not yet occurred and rigs are in use + await Async.forEachAsync( + changesByProject, + async ([project, projectChanges]) => { + const filteredChanges: Map = await this._filterProjectDataAsync( + project, + projectChanges, + repoRoot, + terminal + ); - if (!changedFiles) { - changedFiles = await this.getChangedFilesAsync({ ...options, mergeCommit }); + if (filteredChanges.size > 0) { + changedProjects.add(project); + } + }, + { concurrency: 10 } + ); + } else { + for (const [project, projectChanges] of changesByProject) { + if (projectChanges.size > 0) { + changedProjects.add(project); + } + } } + // External dependency changes are not allowed to be filtered, so add these after filtering if (includeExternalDependencies) { // Even though changing the installed version of a nested dependency merits a change file, // ignore lockfile changes for `rush change` for the moment @@ -320,52 +326,29 @@ export class ProjectChangeAnalyzer { } } + return changedProjects; + } + + protected getChangesByProject( + options: IGetChangesByProjectOptions + ): Map> { + const { lookup, changedFiles } = options; const changesByProject: Map> = new Map(); - const lookup: LookupByPath = - rushConfiguration.getProjectLookupForRoot(repoRoot); for (const [file, diffStatus] of changedFiles) { const project: RushConfigurationProject | undefined = lookup.findChildPath(file); - if (project) { - if (changedProjects.has(project)) { - // Lockfile changes cannot be ignored via rush-project.json - continue; - } - - if (enableFiltering) { - let projectChanges: Map | undefined = changesByProject.get(project); - if (!projectChanges) { - projectChanges = new Map(); - changesByProject.set(project, projectChanges); - } - projectChanges.set(file, diffStatus); - } else { - changedProjects.add(project); - } + if (!project) { + continue; } + let projectChanges: Map | undefined = changesByProject.get(project); + if (!projectChanges) { + projectChanges = new Map(); + changesByProject.set(project, projectChanges); + } + projectChanges.set(file, diffStatus); } - if (enableFiltering) { - // Reading rush-project.json may be problematic if, e.g. rush install has not yet occurred and rigs are in use - await Async.forEachAsync( - changesByProject, - async ([project, projectChanges]) => { - const filteredChanges: Map = await this._filterProjectDataAsync( - project, - projectChanges, - repoRoot, - terminal - ); - - if (filteredChanges.size > 0) { - changedProjects.add(project); - } - }, - { concurrency: 10 } - ); - } - - return changedProjects; + return changesByProject; } private async _getDataAsync(terminal: ITerminal): Promise { From 8d6983fa4f612acc55536006f22df79f630f1cd8 Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 2 Oct 2024 20:06:38 +0000 Subject: [PATCH 04/14] Update change --- .../user-danade-ExposeFileChangeAnalysis_2024-10-02-19-12.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/changes/@microsoft/rush/user-danade-ExposeFileChangeAnalysis_2024-10-02-19-12.json b/common/changes/@microsoft/rush/user-danade-ExposeFileChangeAnalysis_2024-10-02-19-12.json index 37bc24b3f33..d4a1166b634 100644 --- a/common/changes/@microsoft/rush/user-danade-ExposeFileChangeAnalysis_2024-10-02-19-12.json +++ b/common/changes/@microsoft/rush/user-danade-ExposeFileChangeAnalysis_2024-10-02-19-12.json @@ -2,7 +2,7 @@ "changes": [ { "packageName": "@microsoft/rush", - "comment": "Expose changed file analysis in ProjectChangeAnalyzer", + "comment": "Expose `getChangesByProject` to allow classes that extend ProjectChangeAnalyzer to override file change analysis", "type": "patch" } ], From 7908859887f81c2841537dba88aaaf0a477e8cf7 Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 2 Oct 2024 20:09:02 +0000 Subject: [PATCH 05/14] Revert index changes --- libraries/rush-lib/src/index.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/libraries/rush-lib/src/index.ts b/libraries/rush-lib/src/index.ts index 820409f3a6b..f7d2e9a2278 100644 --- a/libraries/rush-lib/src/index.ts +++ b/libraries/rush-lib/src/index.ts @@ -123,8 +123,6 @@ export { export { ProjectChangeAnalyzer, - type IGetMergeCommitOptions, - type IGetChangedFilesOptions, type IGetChangedProjectsOptions, type IRawRepoState as _IRawRepoState } from './logic/ProjectChangeAnalyzer'; From 7b6010ae2c48e5038c2ea3cf3d427960748dcb7d Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 2 Oct 2024 20:13:19 +0000 Subject: [PATCH 06/14] Update API --- common/reviews/api/rush-lib.api.md | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/common/reviews/api/rush-lib.api.md b/common/reviews/api/rush-lib.api.md index 6d47a0bbaf7..6d436e8a306 100644 --- a/common/reviews/api/rush-lib.api.md +++ b/common/reviews/api/rush-lib.api.md @@ -504,29 +504,17 @@ export interface IGenerateCacheEntryIdOptions { } // @beta (undocumented) -export interface IGetChangedFilesOptions extends IGetMergeCommitOptions { - // (undocumented) - mergeCommit?: string; -} - -// @beta (undocumented) -export interface IGetChangedProjectsOptions extends IGetChangedFilesOptions { - // (undocumented) - changedFiles?: Map; +export interface IGetChangedProjectsOptions { enableFiltering: boolean; includeExternalDependencies: boolean; - // (undocumented) - variant?: string; -} - -// @beta (undocumented) -export interface IGetMergeCommitOptions { // (undocumented) shouldFetch?: boolean; // (undocumented) targetBranchName: string; // (undocumented) terminal: ITerminal; + // (undocumented) + variant?: string; } // @beta @@ -1128,11 +1116,11 @@ export class ProjectChangeAnalyzer { _ensureInitializedAsync(terminal: ITerminal): Promise<_IRawRepoState | undefined>; // (undocumented) _filterProjectDataAsync(project: RushConfigurationProject, unfilteredProjectData: Map, rootDir: string, terminal: ITerminal): Promise>; - // (undocumented) - getChangedFilesAsync(options: IGetChangedFilesOptions): Promise>; getChangedProjectsAsync(options: IGetChangedProjectsOptions): Promise>; + // Warning: (ae-forgotten-export) The symbol "IGetChangesByProjectOptions" needs to be exported by the entry point index.d.ts + // // (undocumented) - getMergeCommitAsync(options: IGetMergeCommitOptions): Promise; + protected getChangesByProject(options: IGetChangesByProjectOptions): Map>; // @internal _tryGetProjectDependenciesAsync(project: RushConfigurationProject, terminal: ITerminal): Promise | undefined>; // @internal From ae2e7d4e0b21f110045df5e20eb7b2a14ed0e466 Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 2 Oct 2024 20:22:57 +0000 Subject: [PATCH 07/14] Update API --- common/reviews/api/rush-lib.api.md | 10 ++++++++-- libraries/rush-lib/src/index.ts | 1 + libraries/rush-lib/src/logic/ProjectChangeAnalyzer.ts | 3 +++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/common/reviews/api/rush-lib.api.md b/common/reviews/api/rush-lib.api.md index 6d436e8a306..7757b8e2b7c 100644 --- a/common/reviews/api/rush-lib.api.md +++ b/common/reviews/api/rush-lib.api.md @@ -517,6 +517,14 @@ export interface IGetChangedProjectsOptions { variant?: string; } +// @beta (undocumented) +export interface IGetChangesByProjectOptions { + // (undocumented) + changedFiles: Map; + // (undocumented) + lookup: LookupByPath; +} + // @beta export interface IGlobalCommand extends IRushCommand { } @@ -1117,8 +1125,6 @@ export class ProjectChangeAnalyzer { // (undocumented) _filterProjectDataAsync(project: RushConfigurationProject, unfilteredProjectData: Map, rootDir: string, terminal: ITerminal): Promise>; getChangedProjectsAsync(options: IGetChangedProjectsOptions): Promise>; - // Warning: (ae-forgotten-export) The symbol "IGetChangesByProjectOptions" needs to be exported by the entry point index.d.ts - // // (undocumented) protected getChangesByProject(options: IGetChangesByProjectOptions): Map>; // @internal diff --git a/libraries/rush-lib/src/index.ts b/libraries/rush-lib/src/index.ts index f7d2e9a2278..8fcaac9adcc 100644 --- a/libraries/rush-lib/src/index.ts +++ b/libraries/rush-lib/src/index.ts @@ -123,6 +123,7 @@ export { export { ProjectChangeAnalyzer, + type IGetChangesByProjectOptions, type IGetChangedProjectsOptions, type IRawRepoState as _IRawRepoState } from './logic/ProjectChangeAnalyzer'; diff --git a/libraries/rush-lib/src/logic/ProjectChangeAnalyzer.ts b/libraries/rush-lib/src/logic/ProjectChangeAnalyzer.ts index 04a4bab6daa..1c76682e53f 100644 --- a/libraries/rush-lib/src/logic/ProjectChangeAnalyzer.ts +++ b/libraries/rush-lib/src/logic/ProjectChangeAnalyzer.ts @@ -46,6 +46,9 @@ export interface IGetChangedProjectsOptions { enableFiltering: boolean; } +/** + * @beta + */ export interface IGetChangesByProjectOptions { lookup: LookupByPath; changedFiles: Map; From d12b0a699123dd287d0f35648eb39252dc9d7615 Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 2 Oct 2024 20:46:48 +0000 Subject: [PATCH 08/14] Fix issue with type export --- .../build-tests-subspace/pnpm-lock.yaml | 25 ++++++++++--------- .../build-tests-subspace/repo-state.json | 4 +-- .../config/subspaces/default/pnpm-lock.yaml | 3 +++ libraries/rush-sdk/package.json | 1 + 4 files changed, 19 insertions(+), 14 deletions(-) diff --git a/common/config/subspaces/build-tests-subspace/pnpm-lock.yaml b/common/config/subspaces/build-tests-subspace/pnpm-lock.yaml index ae65f678df3..bed159417b9 100644 --- a/common/config/subspaces/build-tests-subspace/pnpm-lock.yaml +++ b/common/config/subspaces/build-tests-subspace/pnpm-lock.yaml @@ -126,10 +126,10 @@ importers: version: file:../../../apps/heft(@types/node@18.17.15) '@rushstack/heft-lint-plugin': specifier: file:../../heft-plugins/heft-lint-plugin - version: file:../../../heft-plugins/heft-lint-plugin(@rushstack/heft@0.67.2)(@types/node@18.17.15) + version: file:../../../heft-plugins/heft-lint-plugin(@rushstack/heft@0.68.0)(@types/node@18.17.15) '@rushstack/heft-typescript-plugin': specifier: file:../../heft-plugins/heft-typescript-plugin - version: file:../../../heft-plugins/heft-typescript-plugin(@rushstack/heft@0.67.2)(@types/node@18.17.15) + version: file:../../../heft-plugins/heft-typescript-plugin(@rushstack/heft@0.68.0)(@types/node@18.17.15) eslint: specifier: ~8.57.0 version: 8.57.0 @@ -6240,7 +6240,7 @@ packages: - typescript dev: true - file:../../../heft-plugins/heft-api-extractor-plugin(@rushstack/heft@0.67.2)(@types/node@18.17.15): + file:../../../heft-plugins/heft-api-extractor-plugin(@rushstack/heft@0.68.0)(@types/node@18.17.15): resolution: {directory: ../../../heft-plugins/heft-api-extractor-plugin, type: directory} id: file:../../../heft-plugins/heft-api-extractor-plugin name: '@rushstack/heft-api-extractor-plugin' @@ -6255,7 +6255,7 @@ packages: - '@types/node' dev: true - file:../../../heft-plugins/heft-jest-plugin(@rushstack/heft@0.67.2)(@types/node@18.17.15)(jest-environment-node@29.5.0): + file:../../../heft-plugins/heft-jest-plugin(@rushstack/heft@0.68.0)(@types/node@18.17.15)(jest-environment-node@29.5.0): resolution: {directory: ../../../heft-plugins/heft-jest-plugin, type: directory} id: file:../../../heft-plugins/heft-jest-plugin name: '@rushstack/heft-jest-plugin' @@ -6289,7 +6289,7 @@ packages: - ts-node dev: true - file:../../../heft-plugins/heft-lint-plugin(@rushstack/heft@0.67.2)(@types/node@18.17.15): + file:../../../heft-plugins/heft-lint-plugin(@rushstack/heft@0.68.0)(@types/node@18.17.15): resolution: {directory: ../../../heft-plugins/heft-lint-plugin, type: directory} id: file:../../../heft-plugins/heft-lint-plugin name: '@rushstack/heft-lint-plugin' @@ -6303,7 +6303,7 @@ packages: - '@types/node' dev: true - file:../../../heft-plugins/heft-typescript-plugin(@rushstack/heft@0.67.2)(@types/node@18.17.15): + file:../../../heft-plugins/heft-typescript-plugin(@rushstack/heft@0.68.0)(@types/node@18.17.15): resolution: {directory: ../../../heft-plugins/heft-typescript-plugin, type: directory} id: file:../../../heft-plugins/heft-typescript-plugin name: '@rushstack/heft-typescript-plugin' @@ -6479,6 +6479,7 @@ packages: dependencies: '@rushstack/lookup-by-path': file:../../../libraries/lookup-by-path(@types/node@18.17.15) '@rushstack/node-core-library': file:../../../libraries/node-core-library(@types/node@18.17.15) + '@rushstack/package-deps-hash': file:../../../libraries/package-deps-hash(@types/node@18.17.15) '@rushstack/terminal': file:../../../libraries/terminal(@types/node@18.17.15) '@types/node-fetch': 2.6.2 tapable: 2.2.1 @@ -6527,7 +6528,7 @@ packages: transitivePeerDependencies: - '@types/node' - file:../../../rigs/heft-node-rig(@rushstack/heft@0.67.2)(@types/node@18.17.15): + file:../../../rigs/heft-node-rig(@rushstack/heft@0.68.0)(@types/node@18.17.15): resolution: {directory: ../../../rigs/heft-node-rig, type: directory} id: file:../../../rigs/heft-node-rig name: '@rushstack/heft-node-rig' @@ -6537,10 +6538,10 @@ packages: '@microsoft/api-extractor': file:../../../apps/api-extractor(@types/node@18.17.15) '@rushstack/eslint-config': file:../../../eslint/eslint-config(eslint@8.57.0)(typescript@5.4.5) '@rushstack/heft': file:../../../apps/heft(@types/node@18.17.15) - '@rushstack/heft-api-extractor-plugin': file:../../../heft-plugins/heft-api-extractor-plugin(@rushstack/heft@0.67.2)(@types/node@18.17.15) - '@rushstack/heft-jest-plugin': file:../../../heft-plugins/heft-jest-plugin(@rushstack/heft@0.67.2)(@types/node@18.17.15)(jest-environment-node@29.5.0) - '@rushstack/heft-lint-plugin': file:../../../heft-plugins/heft-lint-plugin(@rushstack/heft@0.67.2)(@types/node@18.17.15) - '@rushstack/heft-typescript-plugin': file:../../../heft-plugins/heft-typescript-plugin(@rushstack/heft@0.67.2)(@types/node@18.17.15) + '@rushstack/heft-api-extractor-plugin': file:../../../heft-plugins/heft-api-extractor-plugin(@rushstack/heft@0.68.0)(@types/node@18.17.15) + '@rushstack/heft-jest-plugin': file:../../../heft-plugins/heft-jest-plugin(@rushstack/heft@0.68.0)(@types/node@18.17.15)(jest-environment-node@29.5.0) + '@rushstack/heft-lint-plugin': file:../../../heft-plugins/heft-lint-plugin(@rushstack/heft@0.68.0)(@types/node@18.17.15) + '@rushstack/heft-typescript-plugin': file:../../../heft-plugins/heft-typescript-plugin(@rushstack/heft@0.68.0)(@types/node@18.17.15) '@types/heft-jest': 1.0.1 eslint: 8.57.0 jest-environment-node: 29.5.0 @@ -6560,7 +6561,7 @@ packages: dependencies: '@microsoft/api-extractor': file:../../../apps/api-extractor(@types/node@18.17.15) '@rushstack/heft': file:../../../apps/heft(@types/node@18.17.15) - '@rushstack/heft-node-rig': file:../../../rigs/heft-node-rig(@rushstack/heft@0.67.2)(@types/node@18.17.15) + '@rushstack/heft-node-rig': file:../../../rigs/heft-node-rig(@rushstack/heft@0.68.0)(@types/node@18.17.15) '@types/heft-jest': 1.0.1 '@types/node': 18.17.15 eslint: 8.57.0 diff --git a/common/config/subspaces/build-tests-subspace/repo-state.json b/common/config/subspaces/build-tests-subspace/repo-state.json index 6e1ea1003d3..7056d69ed4b 100644 --- a/common/config/subspaces/build-tests-subspace/repo-state.json +++ b/common/config/subspaces/build-tests-subspace/repo-state.json @@ -1,6 +1,6 @@ // DO NOT MODIFY THIS FILE MANUALLY BUT DO COMMIT IT. It is generated and used by Rush. { - "pnpmShrinkwrapHash": "5bcae02a351bdf06f03416e25805514c5933db4d", + "pnpmShrinkwrapHash": "44393232fa3a5d6a3021af39938c60d86220f566", "preferredVersionsHash": "ce857ea0536b894ec8f346aaea08cfd85a5af648", - "packageJsonInjectedDependenciesHash": "145bb9f959c5c63291e466f8101ba34db6c75c2b" + "packageJsonInjectedDependenciesHash": "b48146fe462eee7690b40b864d3418e3a156d988" } diff --git a/common/config/subspaces/default/pnpm-lock.yaml b/common/config/subspaces/default/pnpm-lock.yaml index 7fa1f5d5236..96add31b6c6 100644 --- a/common/config/subspaces/default/pnpm-lock.yaml +++ b/common/config/subspaces/default/pnpm-lock.yaml @@ -3517,6 +3517,9 @@ importers: '@rushstack/node-core-library': specifier: workspace:* version: link:../node-core-library + '@rushstack/package-deps-hash': + specifier: workspace:* + version: link:../package-deps-hash '@rushstack/terminal': specifier: workspace:* version: link:../terminal diff --git a/libraries/rush-sdk/package.json b/libraries/rush-sdk/package.json index 685ae0cdf4e..30180cb6801 100644 --- a/libraries/rush-sdk/package.json +++ b/libraries/rush-sdk/package.json @@ -40,6 +40,7 @@ "dependencies": { "@rushstack/lookup-by-path": "workspace:*", "@rushstack/node-core-library": "workspace:*", + "@rushstack/package-deps-hash": "workspace:*", "@rushstack/terminal": "workspace:*", "@types/node-fetch": "2.6.2", "tapable": "2.2.1" From bff849e8240b2bad9faef2342a0fcce52bff8e7c Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 2 Oct 2024 21:46:25 +0000 Subject: [PATCH 09/14] Rush update --- .../build-tests-subspace/pnpm-lock.yaml | 24 +++++++++---------- .../build-tests-subspace/repo-state.json | 4 ++-- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/common/config/subspaces/build-tests-subspace/pnpm-lock.yaml b/common/config/subspaces/build-tests-subspace/pnpm-lock.yaml index bed159417b9..d96170e3a18 100644 --- a/common/config/subspaces/build-tests-subspace/pnpm-lock.yaml +++ b/common/config/subspaces/build-tests-subspace/pnpm-lock.yaml @@ -126,10 +126,10 @@ importers: version: file:../../../apps/heft(@types/node@18.17.15) '@rushstack/heft-lint-plugin': specifier: file:../../heft-plugins/heft-lint-plugin - version: file:../../../heft-plugins/heft-lint-plugin(@rushstack/heft@0.68.0)(@types/node@18.17.15) + version: file:../../../heft-plugins/heft-lint-plugin(@rushstack/heft@0.68.2)(@types/node@18.17.15) '@rushstack/heft-typescript-plugin': specifier: file:../../heft-plugins/heft-typescript-plugin - version: file:../../../heft-plugins/heft-typescript-plugin(@rushstack/heft@0.68.0)(@types/node@18.17.15) + version: file:../../../heft-plugins/heft-typescript-plugin(@rushstack/heft@0.68.2)(@types/node@18.17.15) eslint: specifier: ~8.57.0 version: 8.57.0 @@ -6240,7 +6240,7 @@ packages: - typescript dev: true - file:../../../heft-plugins/heft-api-extractor-plugin(@rushstack/heft@0.68.0)(@types/node@18.17.15): + file:../../../heft-plugins/heft-api-extractor-plugin(@rushstack/heft@0.68.2)(@types/node@18.17.15): resolution: {directory: ../../../heft-plugins/heft-api-extractor-plugin, type: directory} id: file:../../../heft-plugins/heft-api-extractor-plugin name: '@rushstack/heft-api-extractor-plugin' @@ -6255,7 +6255,7 @@ packages: - '@types/node' dev: true - file:../../../heft-plugins/heft-jest-plugin(@rushstack/heft@0.68.0)(@types/node@18.17.15)(jest-environment-node@29.5.0): + file:../../../heft-plugins/heft-jest-plugin(@rushstack/heft@0.68.2)(@types/node@18.17.15)(jest-environment-node@29.5.0): resolution: {directory: ../../../heft-plugins/heft-jest-plugin, type: directory} id: file:../../../heft-plugins/heft-jest-plugin name: '@rushstack/heft-jest-plugin' @@ -6289,7 +6289,7 @@ packages: - ts-node dev: true - file:../../../heft-plugins/heft-lint-plugin(@rushstack/heft@0.68.0)(@types/node@18.17.15): + file:../../../heft-plugins/heft-lint-plugin(@rushstack/heft@0.68.2)(@types/node@18.17.15): resolution: {directory: ../../../heft-plugins/heft-lint-plugin, type: directory} id: file:../../../heft-plugins/heft-lint-plugin name: '@rushstack/heft-lint-plugin' @@ -6303,7 +6303,7 @@ packages: - '@types/node' dev: true - file:../../../heft-plugins/heft-typescript-plugin(@rushstack/heft@0.68.0)(@types/node@18.17.15): + file:../../../heft-plugins/heft-typescript-plugin(@rushstack/heft@0.68.2)(@types/node@18.17.15): resolution: {directory: ../../../heft-plugins/heft-typescript-plugin, type: directory} id: file:../../../heft-plugins/heft-typescript-plugin name: '@rushstack/heft-typescript-plugin' @@ -6528,7 +6528,7 @@ packages: transitivePeerDependencies: - '@types/node' - file:../../../rigs/heft-node-rig(@rushstack/heft@0.68.0)(@types/node@18.17.15): + file:../../../rigs/heft-node-rig(@rushstack/heft@0.68.2)(@types/node@18.17.15): resolution: {directory: ../../../rigs/heft-node-rig, type: directory} id: file:../../../rigs/heft-node-rig name: '@rushstack/heft-node-rig' @@ -6538,10 +6538,10 @@ packages: '@microsoft/api-extractor': file:../../../apps/api-extractor(@types/node@18.17.15) '@rushstack/eslint-config': file:../../../eslint/eslint-config(eslint@8.57.0)(typescript@5.4.5) '@rushstack/heft': file:../../../apps/heft(@types/node@18.17.15) - '@rushstack/heft-api-extractor-plugin': file:../../../heft-plugins/heft-api-extractor-plugin(@rushstack/heft@0.68.0)(@types/node@18.17.15) - '@rushstack/heft-jest-plugin': file:../../../heft-plugins/heft-jest-plugin(@rushstack/heft@0.68.0)(@types/node@18.17.15)(jest-environment-node@29.5.0) - '@rushstack/heft-lint-plugin': file:../../../heft-plugins/heft-lint-plugin(@rushstack/heft@0.68.0)(@types/node@18.17.15) - '@rushstack/heft-typescript-plugin': file:../../../heft-plugins/heft-typescript-plugin(@rushstack/heft@0.68.0)(@types/node@18.17.15) + '@rushstack/heft-api-extractor-plugin': file:../../../heft-plugins/heft-api-extractor-plugin(@rushstack/heft@0.68.2)(@types/node@18.17.15) + '@rushstack/heft-jest-plugin': file:../../../heft-plugins/heft-jest-plugin(@rushstack/heft@0.68.2)(@types/node@18.17.15)(jest-environment-node@29.5.0) + '@rushstack/heft-lint-plugin': file:../../../heft-plugins/heft-lint-plugin(@rushstack/heft@0.68.2)(@types/node@18.17.15) + '@rushstack/heft-typescript-plugin': file:../../../heft-plugins/heft-typescript-plugin(@rushstack/heft@0.68.2)(@types/node@18.17.15) '@types/heft-jest': 1.0.1 eslint: 8.57.0 jest-environment-node: 29.5.0 @@ -6561,7 +6561,7 @@ packages: dependencies: '@microsoft/api-extractor': file:../../../apps/api-extractor(@types/node@18.17.15) '@rushstack/heft': file:../../../apps/heft(@types/node@18.17.15) - '@rushstack/heft-node-rig': file:../../../rigs/heft-node-rig(@rushstack/heft@0.68.0)(@types/node@18.17.15) + '@rushstack/heft-node-rig': file:../../../rigs/heft-node-rig(@rushstack/heft@0.68.2)(@types/node@18.17.15) '@types/heft-jest': 1.0.1 '@types/node': 18.17.15 eslint: 8.57.0 diff --git a/common/config/subspaces/build-tests-subspace/repo-state.json b/common/config/subspaces/build-tests-subspace/repo-state.json index 7a3f317fa1a..ef88d270727 100644 --- a/common/config/subspaces/build-tests-subspace/repo-state.json +++ b/common/config/subspaces/build-tests-subspace/repo-state.json @@ -1,6 +1,6 @@ // DO NOT MODIFY THIS FILE MANUALLY BUT DO COMMIT IT. It is generated and used by Rush. { - "pnpmShrinkwrapHash": "0e569d956f72f98565ea4207cba2d7b359e5cdaa", + "pnpmShrinkwrapHash": "5b75a8ef91af53a8caf52319e5eb0042c4d06852", "preferredVersionsHash": "ce857ea0536b894ec8f346aaea08cfd85a5af648", - "packageJsonInjectedDependenciesHash": "15081ac6b4174f98e6a82a839055fbda1a33680d" + "packageJsonInjectedDependenciesHash": "8927ca4e0147b9436659f98a2ff8ca347107d52f" } From 949da36ee40fd29ff4eb0feaa65b7c43f7c21b1d Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 2 Oct 2024 22:01:39 +0000 Subject: [PATCH 10/14] Make generic --- .../src/logic/ProjectChangeAnalyzer.ts | 50 +++++++++++-------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/libraries/rush-lib/src/logic/ProjectChangeAnalyzer.ts b/libraries/rush-lib/src/logic/ProjectChangeAnalyzer.ts index 1c76682e53f..3ab72d09a96 100644 --- a/libraries/rush-lib/src/logic/ProjectChangeAnalyzer.ts +++ b/libraries/rush-lib/src/logic/ProjectChangeAnalyzer.ts @@ -49,9 +49,31 @@ export interface IGetChangedProjectsOptions { /** * @beta */ -export interface IGetChangesByProjectOptions { - lookup: LookupByPath; - changedFiles: Map; +export interface IClusterFileInfoOptions { + lookup: LookupByPath; + infoByPath: ReadonlyMap; +} + +function clusterFileInfo( + options: IClusterFileInfoOptions +): Map> { + const { lookup, infoByPath } = options; + const clusteredFileInfo: Map> = new Map(); + + for (const [file, diffStatus] of infoByPath) { + const group: TGroup | undefined = lookup.findChildPath(file); + if (!group) { + continue; + } + let groupInfo: Map | undefined = clusteredFileInfo.get(group); + if (!groupInfo) { + groupInfo = new Map(); + clusteredFileInfo.set(group, groupInfo); + } + groupInfo.set(file, diffStatus); + } + + return clusteredFileInfo; } interface IGitState { @@ -244,7 +266,7 @@ export class ProjectChangeAnalyzer { const changesByProject: Map< RushConfigurationProject, Map - > = this.getChangesByProject({ changedFiles, lookup }); + > = this.getChangesByProject({ infoByPath: changedFiles, lookup }); const changedProjects: Set = new Set(); if (enableFiltering) { @@ -333,25 +355,9 @@ export class ProjectChangeAnalyzer { } protected getChangesByProject( - options: IGetChangesByProjectOptions + options: IClusterFileInfoOptions ): Map> { - const { lookup, changedFiles } = options; - const changesByProject: Map> = new Map(); - - for (const [file, diffStatus] of changedFiles) { - const project: RushConfigurationProject | undefined = lookup.findChildPath(file); - if (!project) { - continue; - } - let projectChanges: Map | undefined = changesByProject.get(project); - if (!projectChanges) { - projectChanges = new Map(); - changesByProject.set(project, projectChanges); - } - projectChanges.set(file, diffStatus); - } - - return changesByProject; + return clusterFileInfo(options); } private async _getDataAsync(terminal: ITerminal): Promise { From 64ea7331bb0efa3aa9fe6c2b08f8bdb965cb8b29 Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 2 Oct 2024 22:06:51 +0000 Subject: [PATCH 11/14] PR feedback --- common/reviews/api/rush-lib.api.md | 18 +++++++++--------- libraries/rush-lib/src/index.ts | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/common/reviews/api/rush-lib.api.md b/common/reviews/api/rush-lib.api.md index 7757b8e2b7c..22270147a47 100644 --- a/common/reviews/api/rush-lib.api.md +++ b/common/reviews/api/rush-lib.api.md @@ -352,6 +352,14 @@ export interface ICloudBuildCacheProvider { updateCachedCredentialInteractiveAsync(terminal: ITerminal): Promise; } +// @beta (undocumented) +export interface IClusterFileInfoOptions { + // (undocumented) + infoByPath: ReadonlyMap; + // (undocumented) + lookup: LookupByPath; +} + // @beta (undocumented) export interface ICobuildCompletedState { cacheId: string; @@ -517,14 +525,6 @@ export interface IGetChangedProjectsOptions { variant?: string; } -// @beta (undocumented) -export interface IGetChangesByProjectOptions { - // (undocumented) - changedFiles: Map; - // (undocumented) - lookup: LookupByPath; -} - // @beta export interface IGlobalCommand extends IRushCommand { } @@ -1126,7 +1126,7 @@ export class ProjectChangeAnalyzer { _filterProjectDataAsync(project: RushConfigurationProject, unfilteredProjectData: Map, rootDir: string, terminal: ITerminal): Promise>; getChangedProjectsAsync(options: IGetChangedProjectsOptions): Promise>; // (undocumented) - protected getChangesByProject(options: IGetChangesByProjectOptions): Map>; + protected getChangesByProject(options: IClusterFileInfoOptions): Map>; // @internal _tryGetProjectDependenciesAsync(project: RushConfigurationProject, terminal: ITerminal): Promise | undefined>; // @internal diff --git a/libraries/rush-lib/src/index.ts b/libraries/rush-lib/src/index.ts index 8fcaac9adcc..2058e7549c3 100644 --- a/libraries/rush-lib/src/index.ts +++ b/libraries/rush-lib/src/index.ts @@ -123,7 +123,7 @@ export { export { ProjectChangeAnalyzer, - type IGetChangesByProjectOptions, + type IClusterFileInfoOptions, type IGetChangedProjectsOptions, type IRawRepoState as _IRawRepoState } from './logic/ProjectChangeAnalyzer'; From 102b2c84b89b4a4c46be6e77d45eb6c6563f5804 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 3 Oct 2024 00:09:11 +0000 Subject: [PATCH 12/14] Move helper function into @rushstack/lookup-by-path --- common/reviews/api/lookup-by-path.api.md | 1 + common/reviews/api/rush-lib.api.md | 10 +--- libraries/lookup-by-path/src/LookupByPath.ts | 27 +++++++++ .../src/{ => test}/LookupByPath.test.ts | 60 ++++++++++++++++++- libraries/rush-lib/src/index.ts | 1 - .../src/logic/ProjectChangeAnalyzer.ts | 37 ++---------- 6 files changed, 92 insertions(+), 44 deletions(-) rename libraries/lookup-by-path/src/{ => test}/LookupByPath.test.ts (77%) diff --git a/common/reviews/api/lookup-by-path.api.md b/common/reviews/api/lookup-by-path.api.md index b2c6c1c308f..1d779056878 100644 --- a/common/reviews/api/lookup-by-path.api.md +++ b/common/reviews/api/lookup-by-path.api.md @@ -18,6 +18,7 @@ export class LookupByPath { findChildPath(childPath: string): TItem | undefined; findChildPathFromSegments(childPathSegments: Iterable): TItem | undefined; findLongestPrefixMatch(query: string): IPrefixMatch | undefined; + groupByChild(infoByPath: Map): Map>; static iteratePathSegments(serializedPath: string, delimiter?: string): Iterable; setItem(serializedPath: string, value: TItem): this; setItemFromSegments(pathSegments: Iterable, value: TItem): this; diff --git a/common/reviews/api/rush-lib.api.md b/common/reviews/api/rush-lib.api.md index 22270147a47..005423e2727 100644 --- a/common/reviews/api/rush-lib.api.md +++ b/common/reviews/api/rush-lib.api.md @@ -352,14 +352,6 @@ export interface ICloudBuildCacheProvider { updateCachedCredentialInteractiveAsync(terminal: ITerminal): Promise; } -// @beta (undocumented) -export interface IClusterFileInfoOptions { - // (undocumented) - infoByPath: ReadonlyMap; - // (undocumented) - lookup: LookupByPath; -} - // @beta (undocumented) export interface ICobuildCompletedState { cacheId: string; @@ -1126,7 +1118,7 @@ export class ProjectChangeAnalyzer { _filterProjectDataAsync(project: RushConfigurationProject, unfilteredProjectData: Map, rootDir: string, terminal: ITerminal): Promise>; getChangedProjectsAsync(options: IGetChangedProjectsOptions): Promise>; // (undocumented) - protected getChangesByProject(options: IClusterFileInfoOptions): Map>; + protected getChangesByProject(lookup: LookupByPath, changedFiles: Map): Map>; // @internal _tryGetProjectDependenciesAsync(project: RushConfigurationProject, terminal: ITerminal): Promise | undefined>; // @internal diff --git a/libraries/lookup-by-path/src/LookupByPath.ts b/libraries/lookup-by-path/src/LookupByPath.ts index d9fa65fb81d..d42a8abedf3 100644 --- a/libraries/lookup-by-path/src/LookupByPath.ts +++ b/libraries/lookup-by-path/src/LookupByPath.ts @@ -246,6 +246,33 @@ export class LookupByPath { return best; } + /** + * Groups the provided map of info by the nearest entry in the trie that contains the path. If the path + * is not found in the trie, the info is ignored. + * + * @returns The grouped info, grouped by the nearest entry in the trie that contains the path + * + * @param infoByPath - The info to be grouped, keyed by path + */ + public groupByChild(infoByPath: Map): Map> { + const groupedInfo: Map> = new Map(); + + for (const [path, info] of infoByPath) { + const group: TItem | undefined = this.findChildPath(path); + if (!group) { + continue; + } + let groupInfo: Map | undefined = groupedInfo.get(group); + if (!groupInfo) { + groupInfo = new Map(); + groupedInfo.set(group, groupInfo); + } + groupInfo.set(path, info); + } + + return groupedInfo; + } + /** * Iterates through progressively longer prefixes of a given string and returns as soon * as the number of candidate items that match the prefix are 1 or 0. diff --git a/libraries/lookup-by-path/src/LookupByPath.test.ts b/libraries/lookup-by-path/src/test/LookupByPath.test.ts similarity index 77% rename from libraries/lookup-by-path/src/LookupByPath.test.ts rename to libraries/lookup-by-path/src/test/LookupByPath.test.ts index 1086f23c151..81fe5612d60 100644 --- a/libraries/lookup-by-path/src/LookupByPath.test.ts +++ b/libraries/lookup-by-path/src/test/LookupByPath.test.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. -import { LookupByPath } from './LookupByPath'; +import { LookupByPath } from '../LookupByPath'; describe(LookupByPath.iteratePathSegments.name, () => { it('returns empty for an empty string', () => { @@ -143,3 +143,61 @@ describe(LookupByPath.prototype.findLongestPrefixMatch.name, () => { expect(tree.findLongestPrefixMatch('foo/foo')).toEqual({ value: 1, index: 3 }); }); }); + +describe(LookupByPath.prototype.groupByChild.name, () => { + const lookup: LookupByPath = new LookupByPath([ + ['foo', 'foo'], + ['foo/bar', 'bar'], + ['foo/bar/baz', 'baz'] + ]); + + it('returns empty map for empty input', () => { + expect(lookup.groupByChild(new Map())).toEqual(new Map()); + }); + + it('groups items by the closest group that contains the file path', () => { + const infoByPath: Map = new Map([ + ['foo', 'foo'], + ['foo/bar', 'bar'], + ['foo/bar/baz', 'baz'], + ['foo/bar/baz/qux', 'qux'], + ['foo/bar/baz/qux/quux', 'quux'] + ]); + + const expected: Map> = new Map([ + ['foo', new Map([['foo', 'foo']])], + ['bar', new Map([['foo/bar', 'bar']])], + [ + 'baz', + new Map([ + ['foo/bar/baz', 'baz'], + ['foo/bar/baz/qux', 'qux'], + ['foo/bar/baz/qux/quux', 'quux'] + ]) + ] + ]); + + expect(lookup.groupByChild(infoByPath)).toEqual(expected); + }); + + it('ignores items that do not exist in the lookup', () => { + const infoByPath: Map = new Map([ + ['foo', 'foo'], + ['foo/qux', 'qux'], + ['bar', 'bar'], + ['baz', 'baz'] + ]); + + const expected: Map> = new Map([ + [ + 'foo', + new Map([ + ['foo', 'foo'], + ['foo/qux', 'qux'] + ]) + ] + ]); + + expect(lookup.groupByChild(infoByPath)).toEqual(expected); + }); +}); diff --git a/libraries/rush-lib/src/index.ts b/libraries/rush-lib/src/index.ts index 2058e7549c3..f7d2e9a2278 100644 --- a/libraries/rush-lib/src/index.ts +++ b/libraries/rush-lib/src/index.ts @@ -123,7 +123,6 @@ export { export { ProjectChangeAnalyzer, - type IClusterFileInfoOptions, type IGetChangedProjectsOptions, type IRawRepoState as _IRawRepoState } from './logic/ProjectChangeAnalyzer'; diff --git a/libraries/rush-lib/src/logic/ProjectChangeAnalyzer.ts b/libraries/rush-lib/src/logic/ProjectChangeAnalyzer.ts index 3ab72d09a96..acda3b3ccd8 100644 --- a/libraries/rush-lib/src/logic/ProjectChangeAnalyzer.ts +++ b/libraries/rush-lib/src/logic/ProjectChangeAnalyzer.ts @@ -46,36 +46,6 @@ export interface IGetChangedProjectsOptions { enableFiltering: boolean; } -/** - * @beta - */ -export interface IClusterFileInfoOptions { - lookup: LookupByPath; - infoByPath: ReadonlyMap; -} - -function clusterFileInfo( - options: IClusterFileInfoOptions -): Map> { - const { lookup, infoByPath } = options; - const clusteredFileInfo: Map> = new Map(); - - for (const [file, diffStatus] of infoByPath) { - const group: TGroup | undefined = lookup.findChildPath(file); - if (!group) { - continue; - } - let groupInfo: Map | undefined = clusteredFileInfo.get(group); - if (!groupInfo) { - groupInfo = new Map(); - clusteredFileInfo.set(group, groupInfo); - } - groupInfo.set(file, diffStatus); - } - - return clusteredFileInfo; -} - interface IGitState { gitPath: string; hashes: Map; @@ -266,7 +236,7 @@ export class ProjectChangeAnalyzer { const changesByProject: Map< RushConfigurationProject, Map - > = this.getChangesByProject({ infoByPath: changedFiles, lookup }); + > = this.getChangesByProject(lookup, changedFiles); const changedProjects: Set = new Set(); if (enableFiltering) { @@ -355,9 +325,10 @@ export class ProjectChangeAnalyzer { } protected getChangesByProject( - options: IClusterFileInfoOptions + lookup: LookupByPath, + changedFiles: Map ): Map> { - return clusterFileInfo(options); + return lookup.groupByChild(changedFiles); } private async _getDataAsync(terminal: ITerminal): Promise { From 2c159da9043e474a0769cb03a5f07de930e9bed9 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 3 Oct 2024 00:12:03 +0000 Subject: [PATCH 13/14] Rush change --- ...nade-ExposeFileChangeAnalysis_2024-10-03-00-11.json | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 common/changes/@rushstack/lookup-by-path/user-danade-ExposeFileChangeAnalysis_2024-10-03-00-11.json diff --git a/common/changes/@rushstack/lookup-by-path/user-danade-ExposeFileChangeAnalysis_2024-10-03-00-11.json b/common/changes/@rushstack/lookup-by-path/user-danade-ExposeFileChangeAnalysis_2024-10-03-00-11.json new file mode 100644 index 00000000000..cf453a09dcc --- /dev/null +++ b/common/changes/@rushstack/lookup-by-path/user-danade-ExposeFileChangeAnalysis_2024-10-03-00-11.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@rushstack/lookup-by-path", + "comment": "Allow for a map of file paths to arbitrary info to be grouped by the nearest entry in the LookupByPath trie", + "type": "minor" + } + ], + "packageName": "@rushstack/lookup-by-path" +} \ No newline at end of file From 7abd80c4d47ed45fe16b6716b833645b657573a9 Mon Sep 17 00:00:00 2001 From: Daniel Date: Thu, 3 Oct 2024 00:27:14 +0000 Subject: [PATCH 14/14] Fix an issue with grouping when the entries are falsy --- libraries/lookup-by-path/src/LookupByPath.ts | 18 +++++------ .../src/test/LookupByPath.test.ts | 31 +++++++++++++++++++ 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/libraries/lookup-by-path/src/LookupByPath.ts b/libraries/lookup-by-path/src/LookupByPath.ts index d42a8abedf3..1f89630af01 100644 --- a/libraries/lookup-by-path/src/LookupByPath.ts +++ b/libraries/lookup-by-path/src/LookupByPath.ts @@ -255,22 +255,22 @@ export class LookupByPath { * @param infoByPath - The info to be grouped, keyed by path */ public groupByChild(infoByPath: Map): Map> { - const groupedInfo: Map> = new Map(); + const groupedInfoByChild: Map> = new Map(); for (const [path, info] of infoByPath) { - const group: TItem | undefined = this.findChildPath(path); - if (!group) { + const child: TItem | undefined = this.findChildPath(path); + if (child === undefined) { continue; } - let groupInfo: Map | undefined = groupedInfo.get(group); - if (!groupInfo) { - groupInfo = new Map(); - groupedInfo.set(group, groupInfo); + let groupedInfo: Map | undefined = groupedInfoByChild.get(child); + if (!groupedInfo) { + groupedInfo = new Map(); + groupedInfoByChild.set(child, groupedInfo); } - groupInfo.set(path, info); + groupedInfo.set(path, info); } - return groupedInfo; + return groupedInfoByChild; } /** diff --git a/libraries/lookup-by-path/src/test/LookupByPath.test.ts b/libraries/lookup-by-path/src/test/LookupByPath.test.ts index 81fe5612d60..3d3aa341080 100644 --- a/libraries/lookup-by-path/src/test/LookupByPath.test.ts +++ b/libraries/lookup-by-path/src/test/LookupByPath.test.ts @@ -200,4 +200,35 @@ describe(LookupByPath.prototype.groupByChild.name, () => { expect(lookup.groupByChild(infoByPath)).toEqual(expected); }); + + it('ignores items that do not exist in the lookup when the lookup children are possibly falsy', () => { + const falsyLookup: LookupByPath = new LookupByPath([ + ['foo', 'foo'], + ['foo/bar', 'bar'], + ['foo/bar/baz', ''] + ]); + + const infoByPath: Map = new Map([ + ['foo', 'foo'], + ['foo/bar', 'bar'], + ['foo/bar/baz', 'baz'], + ['foo/bar/baz/qux', 'qux'], + ['foo/bar/baz/qux/quux', 'quux'] + ]); + + const expected: Map> = new Map([ + ['foo', new Map([['foo', 'foo']])], + ['bar', new Map([['foo/bar', 'bar']])], + [ + '', + new Map([ + ['foo/bar/baz', 'baz'], + ['foo/bar/baz/qux', 'qux'], + ['foo/bar/baz/qux/quux', 'quux'] + ]) + ] + ]); + + expect(falsyLookup.groupByChild(infoByPath)).toEqual(expected); + }); });