Skip to content

Commit beb43b9

Browse files
committed
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.
1 parent fb68fb6 commit beb43b9

2 files changed

Lines changed: 54 additions & 6 deletions

File tree

libraries/package-deps-hash/src/getRepoState.ts

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,28 @@ const STANDARD_GIT_OPTIONS: readonly string[] = [
3333
// `git hash-object` aborts the process. Such files are typically untracked artifacts left behind
3434
// by tooling (e.g. stray `nul` from a shell redirect).
3535
const WINDOWS_RESERVED_BASENAMES: ReadonlySet<string> = new Set([
36-
'CON', 'PRN', 'AUX', 'NUL',
37-
'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9',
38-
'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9'
36+
'CON',
37+
'PRN',
38+
'AUX',
39+
'NUL',
40+
'COM1',
41+
'COM2',
42+
'COM3',
43+
'COM4',
44+
'COM5',
45+
'COM6',
46+
'COM7',
47+
'COM8',
48+
'COM9',
49+
'LPT1',
50+
'LPT2',
51+
'LPT3',
52+
'LPT4',
53+
'LPT5',
54+
'LPT6',
55+
'LPT7',
56+
'LPT8',
57+
'LPT9'
3958
]);
4059

4160
/**
@@ -254,6 +273,14 @@ export function parseGitStatus(output: string): Map<string, boolean> {
254273

255274
const repoRootCache: Map<string, string> = new Map();
256275

276+
// 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.
277+
function getCleanGitEnvironment(): NodeJS.ProcessEnv {
278+
const env: NodeJS.ProcessEnv = { ...process.env };
279+
delete env.GIT_DIR;
280+
delete env.GIT_WORK_TREE;
281+
return env;
282+
}
283+
257284
/**
258285
* Finds the root of the current Git repository
259286
*
@@ -270,7 +297,8 @@ export function getRepoRoot(currentWorkingDirectory: string, gitPath?: string):
270297
gitPath || 'git',
271298
['--no-optional-locks', 'rev-parse', '--show-toplevel'],
272299
{
273-
currentWorkingDirectory
300+
currentWorkingDirectory,
301+
environment: getCleanGitEnvironment()
274302
}
275303
);
276304

@@ -305,7 +333,8 @@ async function spawnGitAsync(
305333
): Promise<string> {
306334
const spawnOptions: IExecutableSpawnOptions = {
307335
currentWorkingDirectory,
308-
stdio: ['pipe', 'pipe', 'pipe']
336+
stdio: ['pipe', 'pipe', 'pipe'],
337+
environment: getCleanGitEnvironment()
309338
};
310339

311340
let stdout: string = '';
@@ -591,7 +620,8 @@ export function getRepoChanges(
591620
'--'
592621
]),
593622
{
594-
currentWorkingDirectory: rootDirectory
623+
currentWorkingDirectory: rootDirectory,
624+
environment: getCleanGitEnvironment()
595625
}
596626
);
597627

libraries/package-deps-hash/src/test/getRepoDeps.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,24 @@ describe(getRepoRoot.name, () => {
4545
const expectedRoot: string = path.resolve(__dirname, '../../../..').replace(/\\/g, '/');
4646
expect(root).toEqual(expectedRoot);
4747
});
48+
49+
it(`ignores GIT_DIR set by git hooks in linked worktrees`, () => {
50+
// GIT_DIR pointing to a non-existent path causes git rev-parse to fail unless stripped.
51+
const originalGitDir: string | undefined = process.env.GIT_DIR;
52+
try {
53+
process.env.GIT_DIR = '/nonexistent-fake-gitdir-worktrees-for-testing';
54+
const testCwd: string = path.resolve(SOURCE_PATH, '..');
55+
const root: string = getRepoRoot(testCwd);
56+
const expectedRoot: string = path.resolve(__dirname, '../../../..').replace(/\\/g, '/');
57+
expect(root).toEqual(expectedRoot);
58+
} finally {
59+
if (originalGitDir === undefined) {
60+
delete process.env.GIT_DIR;
61+
} else {
62+
process.env.GIT_DIR = originalGitDir;
63+
}
64+
}
65+
});
4866
});
4967

5068
describe(parseGitLsTree.name, () => {

0 commit comments

Comments
 (0)