Repository navigation
Keep test binaries out of the repository whose git hook runs them - #144
Open
joelachance wants to merge 1 commit into
Open
joelachance wants to merge 1 commit into
joelachance wants to merge 1 commit into
Conversation
Git exports GIT_DIR to every hook it runs from a linked worktree, and every checkout under .claude/worktrees is one. A child git honours GIT_DIR over its working directory, so a test that runs `git -C <t.TempDir()> config user.name "Test User"` inside a hook writes the enclosing repository's shared .git/config instead of its temp repo. That is how gx's own clone came to carry Test User <test@example.com>: on 2026-08-15 the CLI suite ran as the prepare-commit-msg hook in a worktree (a test had pinned the hook to the cli.test binary, since fixed) and wrote core.bare = true and the identity. The bare flag was reverted by hand that night; the identity stayed until 2026-10-06. Pinning the hook to a test binary was the trigger, and it is gone. The exposure was not: running the suite from any hook, or from `rebase --exec`, still did it. Run inside a pre-commit hook in a decoy repository's linked worktree, four packages landed 167 test commits in the decoy, rewrote its main branch, removed the worktree's own branch, installed gx's lifecycle hooks, and set core.bare, user.name, a fake origin and gx.enabled = false in its config. gxtest.DetachFromEnclosingGit unsets what git uses to pin a command to one repository (the `git rev-parse --local-env-vars` list), plus the commit identity and date that git hands commit hooks. Every package whose test binary can run git now calls it first in TestMain. That is the process, not the test helpers, because the code under test spawns git too: the hooks in the decoy came from cli.NewRoot's auto-init in cmd/gx, whose tests never mention git. Three tests hold it: - TestGitHelpersIgnoreAnEnclosingRepository re-runs the gxtest binary with an enclosing worktree's GIT_DIR, GIT_INDEX_FILE and identity inherited at process start, and fails if anything its fixtures do lands there. Without the call it fails with core.bare flipped to true. - TestEveryPackageThatRunsGitDetachesFromTheEnclosingGit follows the module's import graph and fails for any package whose test binary links git-running code without the call. - TestEnclosingGitEnvCoversGitsOwnList pins the list to the installed git. gx review's static tools already build go test's environment from an allowlist with no GIT_* in it; TestStaticToolChildEnvDropsTheEnclosingGitRepository pins that, since gx review itself can run from a hook. After the change, the full suite run inside the decoy's hook passes all 37 packages and leaves the decoy's config, hooks and refs untouched. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> gx: https://gx.run/r/8tBYMh29iabqB-WdB0MyJg
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Notes for the reviewer:
Test User <test@example.com>identity in~/git/gx/.git/config: on 2026-08-15 the CLI suite ran as the prepare-commit-msg hook in a linked worktree. That was the cli.test binary, pinned into the hooks by a test, fixed in 7d3a040. Git exports an absoluteGIT_DIRto every hook in a linked worktree, so the suite's temp-dirgit config/git init --barecalls wrote the real common config, settingcore.bare = trueand the identity. The bare flag was reverted by hand that night; the identity stayed.main, rungo test ./internal/{cli,codereview,vcs,hooks}/from apre-commithook in a scratch repo's linked worktree. 167 test commits land in the scratch repo, itsmainbranch is rewritten, gx's hooks are installed into it, and its config gainsbare = true,user.name, a fake origin andgx.enabled = false. With this branch, the full suite run from that hook passes all 37 packages and leaves the scratch repo untouched.TestMain, not per helper, because production code under test spawns git too.cmd/gx's tests never run git themselves, yet they installed the hooks throughcli.NewRoot's auto-init.TestMains. The rest isinternal/gxtest/gitenv.goandgitenv_test.go.gx review's static tools already dropGIT_*, and that is now pinned by a test.background.StartDetachedGxstill passes the hook'sGIT_DIRto the detached workers, but they don't run git today.🤖 Generated with Claude Code