Add comprehensive worktree support across wrapper/corehooks/both#528
Add comprehensive worktree support across wrapper/corehooks/both#528svarlamov wants to merge 231 commits intofeat/corehooksfrom
Conversation
Bumps org.jetbrains.qodana from 2025.2.2 to 2025.3.1. --- updated-dependencies: - dependency-name: org.jetbrains.qodana dependency-version: 2025.3.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps [org.jetbrains.kotlin.jvm](https://github.com/JetBrains/kotlin) from 2.3.0 to 2.3.10. - [Release notes](https://github.com/JetBrains/kotlin/releases) - [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md) - [Commits](JetBrains/kotlin@v2.3.0...v2.3.10) --- updated-dependencies: - dependency-name: org.jetbrains.kotlin.jvm dependency-version: 2.3.10 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
- Create CHANGELOG.md following Keep a Changelog 1.1.0 format - Add update-changelog.yml workflow triggered on release publish - Uses OpenAI API to categorize commits into Added/Changed/Fixed sections - Falls back to keyword-based categorization if no API key configured - Automatically updates CHANGELOG.md and release notes - Skips prerelease/next channel releases Closes #510 Co-Authored-By: Sasha Varlamov <sasha@sashavarlamov.com>
Co-Authored-By: Sasha Varlamov <sasha@sashavarlamov.com>
Replaces custom OpenAI script with the open-source changelog-bot GitHub Action which handles AI changelog generation, CHANGELOG.md updates, and PR creation out of the box. Co-Authored-By: Sasha Varlamov <sasha@sashavarlamov.com>
- Add .github/release.yml for GitHub auto-generated release notes - Add placeholder in release body while AI changelog generates - Rewrite update-changelog.yml to directly commit CHANGELOG.md to main - Replace placeholder in release body with AI-generated changelog - Use changelog-bot CLI + Python for robust placeholder replacement Co-Authored-By: Sasha Varlamov <sasha@sashavarlamov.com>
Strip the \\?\ prefix that std::fs::canonicalize() adds on Windows from the binary path returned by get_current_binary_path(). This prefix was being embedded in hook command strings for Claude Code, Cursor, Gemini, Droid, Codex, and OpenCode installers. Adds clean_path() utility and regression tests. Fixes #519 Co-Authored-By: Sasha Varlamov <sasha@sashavarlamov.com>
Add llvm-tools-preview to the Rust toolchain extensions and cargo-llvm-cov to the dev shell packages. This enables running LLVM source-based code coverage instrumentation from within `nix develop`. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add four coverage-related tasks: - coverage: run tests with coverage and show summary - coverage:html: generate HTML report and open in browser - coverage:lcov: generate LCOV report for CI/IDE integration - coverage:check: enforce minimum line coverage threshold (default 50%) All tasks exclude test, bench, and example files from coverage metrics using --ignore-filename-regex. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add a GitHub Actions workflow that runs on PRs, pushes to main, and merge groups. The workflow: - Runs tests with LLVM source-based coverage instrumentation - Enforces a minimum 50% line coverage threshold via --fail-under-lines - Generates both LCOV and HTML coverage reports - Uploads reports as build artifacts (30-day retention) - Always generates reports even when threshold check fails The 50% threshold is based on measuring the current codebase at ~56% line coverage, providing a buffer while still catching significant regressions. This can be ratcheted up as test coverage improves. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
LLVM coverage instrumentation adds runtime overhead that causes timing-sensitive tests to fail. Skip tests matching "performance_regression" since their timing assertions are unreliable under instrumentation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add code coverage with cargo-llvm-cov
…ial-hooks vs code github copilot official hooks preview support
…ows-hook-path Fix Windows extended-length path prefix in hook commands
…upport/intellij/main/org.jetbrains.kotlin.jvm-2.3.10 chore(intellij): bump org.jetbrains.kotlin.jvm from 2.3.0 to 2.3.10 in /agent-support/intellij
Add OpenCode sqlite transcript parsing with legacy fallback
…upport/intellij/main/org.jetbrains.qodana-2025.3.1 chore(intellij): bump org.jetbrains.qodana from 2025.2.2 to 2025.3.1 in /agent-support/intellij
Bumps [org.jetbrains.kotlinx.kover](https://github.com/Kotlin/kotlinx-kover) from 0.9.5 to 0.9.6. - [Release notes](https://github.com/Kotlin/kotlinx-kover/releases) - [Changelog](https://github.com/Kotlin/kotlinx-kover/blob/main/CHANGELOG.md) - [Commits](Kotlin/kotlinx-kover@v0.9.5...v0.9.6) --- updated-dependencies: - dependency-name: org.jetbrains.kotlinx.kover dependency-version: 0.9.6 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
- Pass all github.event.release.* data via env: blocks instead of
inline ${{ }} interpolation in run: blocks (script injection fix)
- Export CURRENT_BODY and PLACEHOLDER before Python subprocess
- Conditionally hide placeholder for next/prerelease channel releases
Co-Authored-By: Sasha Varlamov <sasha@sashavarlamov.com>
Replace String::from_utf8() with String::from_utf8_lossy() at critical points where git command output is parsed, preventing crashes when repositories contain files with non-UTF-8 encodings (GBK, Latin-1, Shift-JIS, etc.). Changes: - src/git/repository.rs: diff_added_lines, diff_workdir_added_lines, and diff_workdir_added_lines_with_insertions now use lossy conversion - src/authorship/stats.rs: get_git_diff_stats uses lossy conversion - src/authorship/range_authorship.rs: get_git_diff_stats_for_range uses lossy conversion - src/commands/blame.rs: blame_hunks output parsing and working directory file reading use lossy conversion - tests/non_utf8_files.rs: 24 comprehensive tests covering GBK, Latin-1, Shift-JIS, binary files, mixed commits, stats, blame, checkpoint, and attribution flows Closes #426 Co-Authored-By: Sasha Varlamov <sasha@sashavarlamov.com>
…upport/intellij/main/org.jetbrains.kotlinx.kover-0.9.6 chore(intellij): bump org.jetbrains.kotlinx.kover from 0.9.5 to 0.9.6 in /agent-support/intellij
- Switch from python3 -c to heredoc to avoid shell quoting issues - Add workflow_dispatch trigger with dry_run option for pre-merge testing - Dry run generates changelog but skips commit and release update Co-Authored-By: Sasha Varlamov <sasha@sashavarlamov.com>
- Pass github.event_name, inputs.*, and github.event.release.tag_name via env: block in 'Set release tag' step - Use random delimiters (openssl rand -hex 8) for GITHUB_OUTPUT heredocs to prevent delimiter collision with release body content Co-Authored-By: Sasha Varlamov <sasha@sashavarlamov.com>
- Add committed output flag to commit step; downstream extract and update steps now check committed == 'true' before running - Escape dots in version strings for awk regex to prevent matching unintended versions Co-Authored-By: Sasha Varlamov <sasha@sashavarlamov.com>
There was a problem hiding this comment.
Devin Review found 1 new potential issue.
🐛 1 issue in files not directly in the diff
🐛 config_get_regexp returns lowercase keys but caller uses camelCase for rebase.autoStash lookup (src/commands/hooks/fetch_hooks.rs:289-291)
The config_get_regexp implementation (both old gix_config and new git config --get-regexp CLI) returns keys with lowercased section and variable names (git's canonical form). For example, rebase.autoStash becomes rebase.autostash in the returned HashMap. However, the caller in fetch_hooks.rs:290 does .get("rebase.autoStash") with camelCase, which is a case-sensitive HashMap lookup that will never match.
Root Cause and Impact
When git config --get-regexp "^(pull\.rebase|rebase\.autoStash)$" is executed, it outputs:
rebase.autostash true
The new code at src/git/repository.rs:1094-1097 correctly parses this into the HashMap with key rebase.autostash. The old gix_config implementation also lowercased via value_name.to_string().to_lowercase(), producing the same key.
But the caller at src/commands/hooks/fetch_hooks.rs:289-291:
config
.get("rebase.autoStash")
.map(|v| v.to_lowercase() == "true")
.unwrap_or(false)Since HashMap::get is case-sensitive, "rebase.autoStash" != "rebase.autostash", so the lookup always returns None and autostash detection from git config is silently ignored, defaulting to false. Users who set rebase.autoStash = true in their git config won't get autostash behavior detected by the hook system unless they also pass --autostash on the CLI.
View 17 additional findings in Devin Review.
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>
…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>
- Fix long_rev boundary output to show ^<39 hex> = 40 chars (matching git) - Update abbrev test to expect ^<N hex> = N+1 chars for boundary commits Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When `install-hooks` sets global `core.hooksPath` to the managed directory, only hooks listed in INSTALLED_HOOKS previously got shell scripts. Any user hook not in the list (e.g. commit-msg, prepare-commit-msg, pre-merge-commit) was silently lost because git would look for it in the managed directory and find nothing. This commit: 1. Adds commit-msg, prepare-commit-msg, and pre-merge-commit to INSTALLED_HOOKS so the most commonly used hooks get full managed scripts with chaining logic. 2. Adds sync_non_managed_core_hook_scripts() which scans the user's previous hooks directory and writes lightweight passthrough scripts for any remaining hooks not in INSTALLED_HOOKS. Stale passthrough scripts from prior installations are cleaned up automatically. This mirrors what the repo-local hooks mode already does via sync_non_managed_hook_symlinks in git_hook_handlers.rs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Gate worktree wrappers for test_rebase_squash_preserves_all_authorship and test_rebase_reword_commit_with_children with cfg(not(windows)), matching the original test functions. This fixes the Windows CI compilation errors. - In hooks-only mode, commit_with_env now falls back to a default AuthorshipLog when no authorship note exists, rather than returning an error. The wrapper pipeline that creates authorship notes does not run in hooks-only mode, so this was always failing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
git reset has no post-reset hook, so rewrite event tracking for reset operations requires the wrapper binary. Skip these two tests when GIT_AI_TEST_GIT_MODE=hooks. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
git cherry-pick does not fire the post-rewrite hook, so cherry-pick completion event tracking requires the wrapper binary. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The test relies on git reset --hard to clear stale AI metadata, which requires the wrapper binary since git has no post-reset hook. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
prepare-commit-msg, commit-msg, and pre-merge-commit are not handled by git-ai's core hook dispatcher. Having managed scripts for them unnecessarily spawns git-ai processes (which log "unknown core hook") and on Windows the extra process spawns can exceed the self-reference test's 10s timeout. These hooks will still get passthrough scripts via sync_non_managed_core_hook_scripts when a previous hooks dir exists. The passthrough template now includes pwd -P self-reference detection and path normalization to prevent infinite loops. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The self-reference hook test spawns multiple git-ai processes during a commit. On Windows CI runners under full test-suite load, process spawns are significantly slower and the 10s timeout is not sufficient. Use 60s on Windows to accommodate the overhead while still catching genuine infinite loops. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
GIT_CONFIG_GLOBAL points to test_home/.gitconfig, but the test_home directory was never created. Create it in all TestRepo constructors. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…eset detection docs - Buffer stdin for hooks that receive it (post-rewrite, reference-transaction, pre-push) so chained user hooks also receive the data instead of seeing EOF - In wrapper-only mode (no hooks state file), don't force core.hooksPath=/dev/null which was silently suppressing user's .git/hooks/ scripts - Document the inherent limitation of detect_reset_mode_from_worktree: during reference-transaction committed, the index hasn't been updated yet, so --hard resets are misidentified as --soft. No git hook fires after the worktree update for reset, making reliable detection impossible from hooks alone. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Normalize UNC extended-length paths (\\?\) and separator differences when comparing workdir paths on Windows - Normalize CRLF to LF when reading file contents in stash/reset test - Skip HOME env override test on Windows where HOME is not the standard home directory mechanism Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
a4ac993 to
c4f372a
Compare
…ailing newline - Changed GITAI_SKIP_CORE_HOOKS guard from `exit 0` to conditional block around git-ai invocation only, so user hook chaining still proceeds when the skip env is set. - Moved stdin capture before the skip check so chained hooks receive buffered stdin regardless of skip state. - Changed printf format from '%s' to '%s\n' to restore trailing newline stripped by $(cat) command substitution, fixing while-read loops in chained user hooks. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
tests/worktrees.rssuite covering repository discovery, storage isolation, config precedence, detached/locked/removed worktrees, and rewrite flows.worktree_test_wrappers!and extendsubdir_test_variants!with worktree +-Cworktree variants.initial_attributionsandstatstests.Repository::config_get_str/config_get_regexpnow delegate to git CLI for native worktree/includeIf precedence.Repositorynow tracks resolved common git dir (used by tests and future worktree-aware plumbing).statusnow forces--untracked-files=allwhen untracked files are included, preventing directory-collapsed paths likenested/from dropping checkpoint attribution.Validation
cargo test --no-runcargo test --test worktrees -- --test-threads=1GIT_AI_TEST_GIT_MODE=corehooks cargo test --test worktrees -- --test-threads=1GIT_AI_TEST_GIT_MODE=both cargo test --test worktrees -- --test-threads=1GIT_AI_TEST_GIT_MODE=corehooks cargo test --test checkpoint_size -- --test-threads=1GIT_AI_TEST_GIT_MODE=both cargo test --test checkpoint_size -- --test-threads=1GIT_AI_TEST_GIT_MODE=corehooks cargo test --test stats -- --test-threads=1GIT_AI_TEST_GIT_MODE=both cargo test --test stats -- --test-threads=1GIT_AI_TEST_GIT_MODE=corehooks cargo test --test initial_attributions -- --test-threads=1GIT_AI_TEST_GIT_MODE=both cargo test --test initial_attributions -- --test-threads=1GIT_AI_TEST_GIT_MODE=corehooks cargo test --test pull_rebase_ff -- --test-threads=1GIT_AI_TEST_GIT_MODE=both cargo test --test pull_rebase_ff -- --test-threads=1Notes
tests/search.rs/tests/continue_session.rsremain unchanged.