Improve hook handler quality: fix bugs, simplify, add tests#540
Merged
svarlamov merged 5 commits intocodex/global-hooks-dual-modefrom Feb 17, 2026
Merged
Conversation
Bug fixes: - Cache forward hooks dir lookup in handle_git_hook_invocation to avoid redundant state file reads (was reading twice per hook invocation) - Add is_valid_git_oid_or_abbrev for sequencer done file parsing which may contain abbreviated commit hashes (>= 7 chars) Simplifications: - Extract parse_whitespace_fields to consolidate parse_hook_stdin and parse_reference_transaction_stdin which shared identical parsing logic - Remove 19-line redundant no-op hook match arm in run_managed_hook that duplicated hook_has_no_managed_behavior; wildcard already handles this Tests (18 new unit tests): - is_valid_git_oid: SHA-1, SHA-256, rejects short/invalid - is_valid_git_oid_or_abbrev: abbreviated hex acceptance - parse_reference_transaction_stdin: 3-field extraction, incomplete lines - parse_hook_stdin: single-field skip, extra field handling - parse_whitespace_fields: empty input edge cases - is_path_inside_component: nested segment detection - is_path_inside_any_git_ai_dir: .git/ai subtree detection - hook_has_no_managed_behavior: correct classification - cherry_pick_batch_state: serialization roundtrip - repo_hook_state: serialization roundtrip - forward_mode_none: serialization format - ensure_hook_symlink: idempotency - rebase_hook_mask: double-enable is noop - managed/rebase hook name subset invariants Co-Authored-By: Sasha Varlamov <sasha@sashavarlamov.com>
Contributor
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
|
No AI authorship found for these commits. Please install git-ai to start tracking AI generated code in your commits. |
|
No AI authorship found for these commits. Please install git-ai to start tracking AI generated code in your commits. |
|
|
…anaged_behavior, share git_dir lookup, remove dead code, add tests Co-Authored-By: Sasha Varlamov <sasha@sashavarlamov.com>
…, fix silent hook breakage in hooks-only mode Co-Authored-By: Sasha Varlamov <sasha@sashavarlamov.com>
… original exists in forward dir - Extract sync_non_managed_hook_symlinks to scan forward target dir and only install symlinks for non-managed hooks whose original script exists there - Symlinks point to git-ai binary (not original script) so forwarding preserves $0/dirname for Husky-style hooks that resolve relative paths - Stale symlinks cleaned up when original hook is removed from forward dir - Self-healing via ensure_repo_hooks_installed re-scans and keeps in sync - Extract remove_hook_entry helper to deduplicate cleanup logic - 4 new tests: provisioned-only-when-original-exists, resync cleanup, no-forward-target skips non-managed, forward-target with empty dir Co-Authored-By: Sasha Varlamov <sasha@sashavarlamov.com>
Co-Authored-By: Sasha Varlamov <sasha@sashavarlamov.com>
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.
Fix bugs, simplify parsing, add unit tests for hook handlers
Summary
Follow-up quality improvements to the hook handlers introduced in PR #530. All changes in
src/commands/git_hook_handlers.rs:Bug fixes:
core.hooksPathfor a repo, onlyMANAGED_GIT_HOOK_NAMESwere installed as symlinks. Non-managed core hooks (e.g.commit-msg,pre-merge-commit) had no symlinks in the managed hooks directory, so pre-existing repo-level or global hooks for those events silently stopped executing. Now selectively installs symlinks (to the git-ai binary) for non-managed hooks only when the corresponding script actually exists in the forward target directory. This avoids unnecessary hook interception while preserving$0/dirnamefor Husky-style hooks that resolve relative paths. Stale symlinks are cleaned up when the original hook is removed.handle_git_hook_invocationcalledshould_forward_repo_state_first(None)twice per hook invocation (once to check existence, once insideexecute_forwarded_hook). Now cached intocached_forward_dirand threaded through.latest_cherry_pick_source_from_sequencerusedis_valid_git_oid(requires exactly 40 or 64 hex chars) to validate entries in the sequencer done file, which can contain abbreviated SHAs. Addedis_valid_git_oid_or_abbrev(≥7 hex chars) and use it there instead.maybe_enable_rebase_hook_maskcalledSystemTime::now()twice — once forsession_idand once forcreated_at_unix_ms— producing potentially inconsistent timestamps. Now computes once and reuses.handle_rebase_post_rewrite_from_stdin:.last().unwrap_or_else(|| original_commits[0].clone())had a dead-code panic fallback (vec is guaranteed non-empty by earlieris_empty()guard). Simplified to.last().unwrap().Simplifications:
sync_non_managed_hook_symlinksto scan the forward target directory and selectively install/clean up non-managed hook symlinks. Extractedremove_hook_entryhelper to deduplicate cleanup logic.parse_whitespace_fields(stdin, min_fields)to consolidate the duplicatedsplit_whitespace+ filter logic betweenparse_hook_stdinandparse_reference_transaction_stdin.run_managed_hook("commit-msg" | "pre-merge-commit" | ...) that was already covered by the_ => 0wildcard.is_null_oidhelper to replace 5 scatteredchars().all(|c| c == '0')patterns.hook_has_no_managed_behaviorto derive fromMANAGED_GIT_HOOK_NAMESconstant instead of manually maintaining a 20-entry match list. Self-maintaining and can't drift.git_dirlookup inhook_requires_managed_repo_lookupreference-transaction arm (was callinggit_dir_from_context()twice via separate helper functions).is_cherry_pick_in_progress_from_contextfunction (inlined into single call site).Tests (31 total, +26 new):
Unit tests for
is_valid_git_oid,is_valid_git_oid_or_abbrev,is_null_oid,parse_hook_stdin,parse_reference_transaction_stdin,parse_whitespace_fields,is_path_inside_component,is_path_inside_any_git_ai_dir,hook_has_no_managed_behavior(3 tests),CherryPickBatchStateserde roundtrip,RepoHookStateserde roundtrip,ForwardMode::Noneserialization,ensure_hook_symlinkidempotency, rebase hook mask double-enable safety, hook name subset invariants, and non-managed hook forwarding behavior (4 tests: provisioned-only-when-original-exists, resync cleanup, no-forward-target skips non-managed, forward-target with empty dir).Review & Testing Checklist for Human
.husky/commit-msg). Test with Husky/lefthook: (1) verify hooks execute correctly, (2) verify$0anddirname "$0"resolve to the original hook path (not.git/ai/hooks), (3) add a new hook to the forward dir and rungit-ai git-hooks ensureto verify it gets picked up, (4) remove a hook from the forward dir and verify the symlink is cleaned up on nextensure.core.hooksPathchanges, not when individual hook files are added/removed in the forward dir. If a user adds a new hook to their hooks dir, it won't be picked up until the nextgit-ai git-hooks ensureor a self-heal trigger. Verify this is acceptable behavior or if we need to add file-watching.handle_git_hook_invocation→execute_forwarded_hook(forwards to original) →run_managed_hook(no-op for non-managed). Confirm forwarding happens before managed logic and that non-managed hooks execute correctly.is_valid_git_oid_or_abbrevthreshold: Verify that 7 hex chars is sufficient for git's abbreviated OID format. Git's defaultcore.abbrevis "auto" which can go as low as 4 in small repos. Check if sequencer done files ever contain <7 char abbreviations.cargo test --lib). Run full test suite includingtests/hook_modes.rsandtests/stash_attribution.rsto verify no regressions. Run the Heavy Matrix and 10x Nasty Rebase benchmarks from PR Add dual-mode git-ai operation via global hooks and wrapper #530 to confirm no performance regression.Notes
cargo checkandcargo clippypass with no warnings