Skip to content

fix: make installer idempotent and respect core.hooksPath#405

Open
voidborne-d wants to merge 1 commit intosafishamsi:v4from
voidborne-d:fix/idempotent-install-hookspath
Open

fix: make installer idempotent and respect core.hooksPath#405
voidborne-d wants to merge 1 commit intosafishamsi:v4from
voidborne-d:fix/idempotent-install-hookspath

Conversation

@voidborne-d
Copy link
Copy Markdown

Fixes #401

Problem

graphify install can damage a repository that already vendors its Graphify integration:

  1. It writes duplicate files (.claude/settings.json, .codex/hooks.json, .cursor/rules/graphify.mdc) that are already tracked in git, causing git pull to fail with "would be overwritten by merge"
  2. graphify hook install always writes to .git/hooks/, ignoring core.hooksPath — this bypasses Husky's hook directory and can disable existing pre-commit/pre-push hooks
  3. No guard against core.bare=true on a working tree

Solution

1. Already-integrated repo detection (__main__.py)

  • New _is_already_integrated() function checks git ls-files for known integration markers (.claude/settings.json, .codex/hooks.json, .cursor/rules/graphify.mdc, etc.)
  • install() now no-ops with clear guidance when tracked integration files are detected
  • --force flag added to override the guard when the user explicitly wants to reinstall

2. Respect core.hooksPath (hooks.py)

  • New _resolve_hooks_dir() reads core.hooksPath from git config
  • Supports absolute and relative paths; falls back to .git/hooks when the configured directory doesn't exist
  • install(), uninstall(), and status() all use the resolved hooks directory
  • This means Husky's .husky/_ or .husky directory is respected — graphify hooks are written there instead of .git/hooks/

3. core.bare=true guard (hooks.py)

  • hook install now checks core.bare and refuses to proceed if it's true, with a helpful fix suggestion

Tests

18 new regression tests in tests/test_idempotent_install.py:

  • Already-integrated detection: empty repo, tracked files, untracked files (no false positive), non-git dir, multiple markers
  • --force flag: install skips in integrated repo, force overrides guard
  • core.hooksPath: default resolution, absolute path, relative path, missing dir fallback, hooks written to correct dir, uninstall/status respect hooksPath
  • core.bare guard: refuses when true, allows when false, allows when unset

All 18 new + 51 existing tests pass (test_hooks.py + test_install.py + test_idempotent_install.py).

…#401)

Three problems fixed:

1. **Already-integrated repos**:  now detects repos that
   already track integration files (.claude/settings.json, .codex/hooks.json,
   etc.) via `git ls-files` and no-ops with guidance instead of writing
   duplicate untracked files that block the next `git pull`. Pass `--force`
   to override.

2. **core.hooksPath (Husky compatibility)**: `graphify hook install/uninstall/
   status` now reads `core.hooksPath` from git config and writes hooks there
   instead of always targeting `.git/hooks/`. This prevents the installer from
   bypassing Husky's hook directory and from accidentally unsetting
   `core.hooksPath` by writing duplicate hooks to the wrong location.

3. **core.bare=true guard**: `graphify hook install` now refuses to operate
   when `core.bare=true` is set on a working tree, preventing invalid state.

Adds 18 regression tests covering:
- Tracked vs untracked integration file detection
- --force flag bypass
- core.hooksPath resolution (absolute, relative, missing dir fallback)
- Hooks written to correct directory with Husky-style config
- core.bare=true rejection and false/unset acceptance

Closes safishamsi#401
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Installer should no-op in already-integrated repos and preserve Git hooks config

1 participant