feat(workspace filtering): add support for filtering packages since last commit under git worktree (#10542)

support managing repo in git worktree for filtering for packages changed since last commit
This commit is contained in:
Karl Kaiser
2026-02-10 17:05:45 -08:00
committed by GitHub
parent a49b243573
commit 1fd7370639
4 changed files with 88 additions and 1 deletions

View File

@@ -0,0 +1,5 @@
---
"@pnpm/filter-workspace-packages": minor
---
Drop `directory` as required filetype for `findUp` to allow git-based filtering to work inside git worktrees, which store `.git` as a file rather than directory.

View File

@@ -287,6 +287,7 @@
"testcase",
"TLSV",
"todomvc",
"toplevel",
"tsgo",
"tsparticles",
"typecheck",
@@ -307,6 +308,8 @@
"webcontainer",
"winst",
"workleap",
"worktree",
"worktrees",
"wrappy",
"xmarw",
"yazl",

View File

@@ -18,7 +18,13 @@ export async function getChangedPackages (
commit: string,
opts: { workspaceDir: string, testPattern?: string[], changedFilesIgnorePattern?: string[] }
): Promise<[ProjectRootDir[], ProjectRootDir[]]> {
const repoRoot = path.resolve(await findUp('.git', { cwd: opts.workspaceDir, type: 'directory' }) ?? opts.workspaceDir, '..')
// .git is a directory in regular repos, but a file in worktrees
const gitPath = await findUp('.git', { cwd: opts.workspaceDir, type: 'directory' }) ??
await findUp('.git', { cwd: opts.workspaceDir, type: 'file' })
const repoRoot = path.resolve(gitPath ?? opts.workspaceDir, '..')
const changedDirs = (await getChangedDirsSinceCommit(commit, opts.workspaceDir, opts.testPattern ?? [], opts.changedFilesIgnorePattern ?? []))
.map(changedDir => ({ ...changedDir, dir: path.join(repoRoot, changedDir.dir) }))
const pkgChangeTypes = new Map<ProjectRootDir, ChangeType | undefined>()

View File

@@ -474,6 +474,79 @@ test('select changed packages', async () => {
}
})
test('select changed packages when operating under a git worktree', async () => {
if (isCI && isWindows()) {
return
}
const mainRepoDir = temporaryDirectory()
await execa('git', ['init', '--initial-branch=main'], { cwd: mainRepoDir })
await execa('git', ['config', 'user.email', 'x@y.z'], { cwd: mainRepoDir })
await execa('git', ['config', 'user.name', 'xyz'], { cwd: mainRepoDir })
await execa('git', ['commit', '--allow-empty', '--allow-empty-message', '-m', '', '--no-gpg-sign'], { cwd: mainRepoDir })
const mainPkgADir = path.join(mainRepoDir, 'package-a')
const mainPkgBDir = path.join(mainRepoDir, 'package-b')
const mainPkgCDir = path.join(mainRepoDir, 'package-c')
await mkdir(mainPkgADir)
await mkdir(mainPkgBDir)
await mkdir(mainPkgCDir)
await touch(path.join(mainPkgADir, 'file.js'))
await touch(path.join(mainPkgBDir, 'file.js'))
await touch(path.join(mainPkgCDir, 'file.js'))
await execa('git', ['add', '.'], { cwd: mainRepoDir })
await execa('git', ['commit', '--allow-empty-message', '-m', '', '--no-gpg-sign'], { cwd: mainRepoDir })
const worktreeParent = temporaryDirectory()
const worktreeDir = path.join(worktreeParent, 'worktree')
await execa('git', ['worktree', 'add', '-b', 'worktree-branch', worktreeDir, 'main'], { cwd: mainRepoDir })
const worktreePkgADir = path.join(worktreeDir, 'package-a') as ProjectRootDir
const worktreePkgBDir = path.join(worktreeDir, 'package-b') as ProjectRootDir
const worktreePkgCDir = path.join(worktreeDir, 'package-c') as ProjectRootDir
await touch(path.join(worktreePkgADir, 'new-file.js'))
await execa('git', ['add', '.'], { cwd: worktreeDir })
await execa('git', ['commit', '--allow-empty-message', '-m', '', '--no-gpg-sign'], { cwd: worktreeDir })
const pkgsGraph: PackageGraph<Package> = {
[worktreeDir as ProjectRootDir]: {
dependencies: [],
package: {
rootDir: worktreeDir as ProjectRootDir,
manifest: { name: 'root', version: '0.0.0' },
},
},
[worktreePkgADir]: {
dependencies: [],
package: {
rootDir: worktreePkgADir,
manifest: { name: 'package-a', version: '0.0.0' },
},
},
[worktreePkgBDir]: {
dependencies: [],
package: {
rootDir: worktreePkgBDir,
manifest: { name: 'package-b', version: '0.0.0' },
},
},
[worktreePkgCDir]: {
dependencies: [],
package: {
rootDir: worktreePkgCDir,
manifest: { name: 'package-c', version: '0.0.0' },
},
},
}
const { selectedProjectsGraph } = await filterWorkspacePackages(pkgsGraph, [{
diff: 'HEAD~1',
}], { workspaceDir: worktreeDir })
expect(Object.keys(selectedProjectsGraph)).toStrictEqual([worktreePkgADir])
})
test('selection should fail when diffing to a branch that does not exist', async () => {
let err!: PnpmError
try {