From 0e4da2cb386d6b8406eb99dbaf80591e9761b568 Mon Sep 17 00:00:00 2001 From: Jeremy Knows Date: Tue, 12 May 2026 10:06:02 -0400 Subject: [PATCH] test(frontmatter-install-hook): isolate hooksPath assertion from developer global config MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "installHook writes ... and sets core.hooksPath" test asserted `git config --get core.hooksPath` returns `.githooks`, which falls back to the global scope when local is unset. Developers who set `core.hooksPath` globally (common with dotfiles managers pointing at ~/.config/git/hooks) saw a deterministic FAIL because installHook intentionally respects an existing global value and skips writing the local one — exactly the documented contract. Fix: read via `git config --local --get core.hooksPath` (scope-locked) and branch the assertion on whether a global is already set. Both clean-CI (local should be '.githooks') and developer-with-global (local should be empty; installHook correctly didn't clobber) now pass deterministically. No API change. installHook behavior is unchanged. Verified locally with the affected test passing under `GIT_CONFIG_GLOBAL=~/.gitconfig` carrying `core.hooksPath=...`. --- test/frontmatter-install-hook.test.ts | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/test/frontmatter-install-hook.test.ts b/test/frontmatter-install-hook.test.ts index f714b7cdf..4f11177a0 100644 --- a/test/frontmatter-install-hook.test.ts +++ b/test/frontmatter-install-hook.test.ts @@ -31,9 +31,26 @@ describe('frontmatter install-hook (B13)', () => { const content = readFileSync(hookPath, 'utf8'); expect(content).toContain('gbrain frontmatter'); expect(content).toContain('git diff --cached'); - // Configured hooksPath - const hooksPath = execFileSync('git', ['-C', tmp, 'config', '--get', 'core.hooksPath'], { encoding: 'utf8' }).trim(); - expect(hooksPath).toBe('.githooks'); + // installHook's contract is "set core.hooksPath unless it's already set + // elsewhere". Test BOTH branches deterministically by reading the local + // scope only: clean CI → local should be `.githooks`; developer with a + // global core.hooksPath (e.g. dotfiles → ~/.config/git/hooks) → local + // should be empty because installHook correctly skipped clobbering. + // Reading via `--get` without `--local` falls back to global scope when + // local is unset, which made this test environmentally fragile. + let globalHooksPath = ''; + try { + globalHooksPath = execFileSync('git', ['config', '--global', '--get', 'core.hooksPath'], { encoding: 'utf8' }).trim(); + } catch { /* unset is the expected clean-env case */ } + let localHooksPath = ''; + try { + localHooksPath = execFileSync('git', ['-C', tmp, 'config', '--local', '--get', 'core.hooksPath'], { encoding: 'utf8' }).trim(); + } catch { /* unset is fine when global was present */ } + if (globalHooksPath) { + expect(localHooksPath).toBe(''); + } else { + expect(localHooksPath).toBe('.githooks'); + } }); test('installHook refuses to clobber existing hook without --force', () => {