From beb43b94b5140253be681096fa500a3c9034b0e9 Mon Sep 17 00:00:00 2001 From: Kevin Fleischman Date: Wed, 3 Jun 2026 10:49:18 -0400 Subject: [PATCH 1/6] fix(package-deps-hash): strip GIT_DIR/GIT_WORK_TREE in git subprocess calls to fix build cache in linked worktrees When a git pre-commit hook runs in a linked worktree, git sets GIT_DIR to the per-worktree metadata directory (.git/worktrees/{name}) without setting GIT_WORK_TREE. With GIT_DIR set this way, `git rev-parse --show-toplevel` returns the CWD (e.g. the rushJsonFolder subdirectory) instead of the actual worktree root, causing all subsequent git calls to use the wrong root directory. This makes `git status -u` miss the top-level .gitignore, surfacing node_modules symlinks as untracked files, which then causes `git hash-object` to fail on symlink-to-directory entries and ultimately breaks the build cache. Fix: strip GIT_DIR and GIT_WORK_TREE from the environment in getRepoRoot, spawnGitAsync, and getRepoChanges so git auto-discovers the correct repo root from the working directory regardless of hook-injected env vars. --- .../package-deps-hash/src/getRepoState.ts | 42 ++++++++++++++++--- .../src/test/getRepoDeps.test.ts | 18 ++++++++ 2 files changed, 54 insertions(+), 6 deletions(-) diff --git a/libraries/package-deps-hash/src/getRepoState.ts b/libraries/package-deps-hash/src/getRepoState.ts index 7f01cde1620..21fe84e41b0 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,14 @@ 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 { + const env: NodeJS.ProcessEnv = { ...process.env }; + delete env.GIT_DIR; + delete env.GIT_WORK_TREE; + return env; +} + /** * Finds the root of the current Git repository * @@ -270,7 +297,8 @@ export function getRepoRoot(currentWorkingDirectory: string, gitPath?: string): gitPath || 'git', ['--no-optional-locks', 'rev-parse', '--show-toplevel'], { - currentWorkingDirectory + currentWorkingDirectory, + environment: getCleanGitEnvironment() } ); @@ -305,7 +333,8 @@ async function spawnGitAsync( ): Promise { const spawnOptions: IExecutableSpawnOptions = { currentWorkingDirectory, - stdio: ['pipe', 'pipe', 'pipe'] + stdio: ['pipe', 'pipe', 'pipe'], + environment: getCleanGitEnvironment() }; let stdout: string = ''; @@ -591,7 +620,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 6e01af1fd6a..fd5746072dc 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, () => { From ef318f0c380ea982ddc079198bef879e8158699f Mon Sep 17 00:00:00 2001 From: Kevin Fleischman Date: Wed, 3 Jun 2026 10:49:24 -0400 Subject: [PATCH 2/6] chore: add rush change file for package-deps-hash worktree fix --- .../fix-git-dir-worktree-hook_2026-06-03-00-00.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 common/changes/@rushstack/package-deps-hash/fix-git-dir-worktree-hook_2026-06-03-00-00.json 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 00000000000..584fb8ff317 --- /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": "kfleischman@squarespace.com" +} From 4cc0538cd711d57f2f5e5632479967ebe1c0c37c Mon Sep 17 00:00:00 2001 From: Kevin Fleischman Date: Mon, 8 Jun 2026 09:54:12 -0400 Subject: [PATCH 3/6] Update common/changes/@rushstack/package-deps-hash/fix-git-dir-worktree-hook_2026-06-03-00-00.json Co-authored-by: Ian Clanton-Thuon --- .../fix-git-dir-worktree-hook_2026-06-03-00-00.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 584fb8ff317..76af3426323 100644 --- 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 @@ -7,5 +7,5 @@ } ], "packageName": "@rushstack/package-deps-hash", - "email": "kfleischman@squarespace.com" + "email": "istateside@users.noreply.github.com" } From ee1ee19f4745504a21fe7606412e4f4a26d10578 Mon Sep 17 00:00:00 2001 From: Kevin Fleischman Date: Mon, 8 Jun 2026 09:54:31 -0400 Subject: [PATCH 4/6] Update libraries/package-deps-hash/src/getRepoState.ts Co-authored-by: Ian Clanton-Thuon --- libraries/package-deps-hash/src/getRepoState.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libraries/package-deps-hash/src/getRepoState.ts b/libraries/package-deps-hash/src/getRepoState.ts index 21fe84e41b0..4e14bb33927 100644 --- a/libraries/package-deps-hash/src/getRepoState.ts +++ b/libraries/package-deps-hash/src/getRepoState.ts @@ -278,7 +278,9 @@ function getCleanGitEnvironment(): NodeJS.ProcessEnv { const env: NodeJS.ProcessEnv = { ...process.env }; delete env.GIT_DIR; delete env.GIT_WORK_TREE; - return env; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { GIT_DIR, GIT_WORK_TREE, ...trimmedEnv } = process.env; + return trimmedEnv; } /** From 20d7b0618dd0771045c4771023c635078178160b Mon Sep 17 00:00:00 2001 From: Kevin Fleischman Date: Mon, 8 Jun 2026 14:59:59 -0400 Subject: [PATCH 5/6] Update libraries/package-deps-hash/src/getRepoState.ts Co-authored-by: Ian Clanton-Thuon --- libraries/package-deps-hash/src/getRepoState.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/libraries/package-deps-hash/src/getRepoState.ts b/libraries/package-deps-hash/src/getRepoState.ts index 4e14bb33927..45cb07a8dd7 100644 --- a/libraries/package-deps-hash/src/getRepoState.ts +++ b/libraries/package-deps-hash/src/getRepoState.ts @@ -275,9 +275,6 @@ 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 { - const env: NodeJS.ProcessEnv = { ...process.env }; - delete env.GIT_DIR; - delete env.GIT_WORK_TREE; // eslint-disable-next-line @typescript-eslint/no-unused-vars const { GIT_DIR, GIT_WORK_TREE, ...trimmedEnv } = process.env; return trimmedEnv; From 013803f0acf7be0e91771aca5d36535be5c9c314 Mon Sep 17 00:00:00 2001 From: Kevin Fleischman Date: Mon, 8 Jun 2026 15:03:01 -0400 Subject: [PATCH 6/6] Commit changefile for rush-lib to allow publish --- ...t-dir-worktree-hook-repo-root_2026-06-08-19-02.json | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 common/changes/@microsoft/rush/fix-git-dir-worktree-hook-repo-root_2026-06-08-19-02.json 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 00000000000..736a9362489 --- /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