diff --git a/common/changes/@microsoft/rush/fix-git-dir-worktree-hook-repo-root_2026-06-08-19-02.json b/common/changes/@microsoft/rush/fix-git-dir-worktree-hook-repo-root_2026-06-08-19-02.json new file mode 100644 index 0000000000..736a936248 --- /dev/null +++ b/common/changes/@microsoft/rush/fix-git-dir-worktree-hook-repo-root_2026-06-08-19-02.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@microsoft/rush", + "comment": "No-op change to trigger changeset for rush publish for package-deps-hash changes.", + "type": "none" + } + ], + "packageName": "@microsoft/rush" +} \ No newline at end of file diff --git a/common/changes/@rushstack/package-deps-hash/fix-git-dir-worktree-hook_2026-06-03-00-00.json b/common/changes/@rushstack/package-deps-hash/fix-git-dir-worktree-hook_2026-06-03-00-00.json new file mode 100644 index 0000000000..76af342632 --- /dev/null +++ b/common/changes/@rushstack/package-deps-hash/fix-git-dir-worktree-hook_2026-06-03-00-00.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "Fix build cache failures when running inside a git linked worktree via a pre-commit hook, caused by GIT_DIR being set to the per-worktree metadata directory", + "type": "patch", + "packageName": "@rushstack/package-deps-hash" + } + ], + "packageName": "@rushstack/package-deps-hash", + "email": "istateside@users.noreply.github.com" +} diff --git a/libraries/package-deps-hash/src/getRepoState.ts b/libraries/package-deps-hash/src/getRepoState.ts index 7f01cde162..45cb07a8dd 100644 --- a/libraries/package-deps-hash/src/getRepoState.ts +++ b/libraries/package-deps-hash/src/getRepoState.ts @@ -33,9 +33,28 @@ const STANDARD_GIT_OPTIONS: readonly string[] = [ // `git hash-object` aborts the process. Such files are typically untracked artifacts left behind // by tooling (e.g. stray `nul` from a shell redirect). const WINDOWS_RESERVED_BASENAMES: ReadonlySet = new Set([ - 'CON', 'PRN', 'AUX', 'NUL', - 'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9', - 'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9' + 'CON', + 'PRN', + 'AUX', + 'NUL', + 'COM1', + 'COM2', + 'COM3', + 'COM4', + 'COM5', + 'COM6', + 'COM7', + 'COM8', + 'COM9', + 'LPT1', + 'LPT2', + 'LPT3', + 'LPT4', + 'LPT5', + 'LPT6', + 'LPT7', + 'LPT8', + 'LPT9' ]); /** @@ -254,6 +273,13 @@ export function parseGitStatus(output: string): Map { const repoRootCache: Map = new Map(); +// Strip GIT_DIR/GIT_WORK_TREE: git hooks in linked worktrees set GIT_DIR to the per-worktree metadata dir, causing rev-parse --show-toplevel to return CWD instead of the worktree root. +function getCleanGitEnvironment(): NodeJS.ProcessEnv { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { GIT_DIR, GIT_WORK_TREE, ...trimmedEnv } = process.env; + return trimmedEnv; +} + /** * Finds the root of the current Git repository * @@ -270,7 +296,8 @@ export function getRepoRoot(currentWorkingDirectory: string, gitPath?: string): gitPath || 'git', ['--no-optional-locks', 'rev-parse', '--show-toplevel'], { - currentWorkingDirectory + currentWorkingDirectory, + environment: getCleanGitEnvironment() } ); @@ -305,7 +332,8 @@ async function spawnGitAsync( ): Promise { const spawnOptions: IExecutableSpawnOptions = { currentWorkingDirectory, - stdio: ['pipe', 'pipe', 'pipe'] + stdio: ['pipe', 'pipe', 'pipe'], + environment: getCleanGitEnvironment() }; let stdout: string = ''; @@ -591,7 +619,8 @@ export function getRepoChanges( '--' ]), { - currentWorkingDirectory: rootDirectory + currentWorkingDirectory: rootDirectory, + environment: getCleanGitEnvironment() } ); diff --git a/libraries/package-deps-hash/src/test/getRepoDeps.test.ts b/libraries/package-deps-hash/src/test/getRepoDeps.test.ts index 6e01af1fd6..fd5746072d 100644 --- a/libraries/package-deps-hash/src/test/getRepoDeps.test.ts +++ b/libraries/package-deps-hash/src/test/getRepoDeps.test.ts @@ -45,6 +45,24 @@ describe(getRepoRoot.name, () => { const expectedRoot: string = path.resolve(__dirname, '../../../..').replace(/\\/g, '/'); expect(root).toEqual(expectedRoot); }); + + it(`ignores GIT_DIR set by git hooks in linked worktrees`, () => { + // GIT_DIR pointing to a non-existent path causes git rev-parse to fail unless stripped. + const originalGitDir: string | undefined = process.env.GIT_DIR; + try { + process.env.GIT_DIR = '/nonexistent-fake-gitdir-worktrees-for-testing'; + const testCwd: string = path.resolve(SOURCE_PATH, '..'); + const root: string = getRepoRoot(testCwd); + const expectedRoot: string = path.resolve(__dirname, '../../../..').replace(/\\/g, '/'); + expect(root).toEqual(expectedRoot); + } finally { + if (originalGitDir === undefined) { + delete process.env.GIT_DIR; + } else { + process.env.GIT_DIR = originalGitDir; + } + } + }); }); describe(parseGitLsTree.name, () => {