Skip to content

Add comprehensive worktree support across wrapper/corehooks/both#528

Open
svarlamov wants to merge 231 commits intofeat/corehooksfrom
codex/worktree-support-corehooks
Open

Add comprehensive worktree support across wrapper/corehooks/both#528
svarlamov wants to merge 231 commits intofeat/corehooksfrom
codex/worktree-support-corehooks

Conversation

@svarlamov
Copy link
Member

@svarlamov svarlamov commented Feb 14, 2026

Summary

  • Recreate and modernize the worktree test harness from PR Worktrees in-depth support, tests #419 while preserving the current corehooks/wrapper mode matrix harness.
  • Add a dedicated tests/worktrees.rs suite covering repository discovery, storage isolation, config precedence, detached/locked/removed worktrees, and rewrite flows.
  • Add worktree wrapper variants across integration tests via worktree_test_wrappers! and extend subdir_test_variants! with worktree + -C worktree variants.
  • Add worktree snapshot coverage for initial_attributions and stats tests.
  • Implement minimal production fixes needed for robust worktree behavior:
    • Repository::config_get_str / config_get_regexp now delegate to git CLI for native worktree/includeIf precedence.
    • Repository now tracks resolved common git dir (used by tests and future worktree-aware plumbing).
    • status now forces --untracked-files=all when untracked files are included, preventing directory-collapsed paths like nested/ from dropping checkpoint attribution.

Validation

  • cargo test --no-run
  • cargo test --test worktrees -- --test-threads=1
  • GIT_AI_TEST_GIT_MODE=corehooks cargo test --test worktrees -- --test-threads=1
  • GIT_AI_TEST_GIT_MODE=both cargo test --test worktrees -- --test-threads=1
  • GIT_AI_TEST_GIT_MODE=corehooks cargo test --test checkpoint_size -- --test-threads=1
  • GIT_AI_TEST_GIT_MODE=both cargo test --test checkpoint_size -- --test-threads=1
  • GIT_AI_TEST_GIT_MODE=corehooks cargo test --test stats -- --test-threads=1
  • GIT_AI_TEST_GIT_MODE=both cargo test --test stats -- --test-threads=1
  • GIT_AI_TEST_GIT_MODE=corehooks cargo test --test initial_attributions -- --test-threads=1
  • GIT_AI_TEST_GIT_MODE=both cargo test --test initial_attributions -- --test-threads=1
  • GIT_AI_TEST_GIT_MODE=corehooks cargo test --test pull_rebase_ff -- --test-threads=1
  • GIT_AI_TEST_GIT_MODE=both cargo test --test pull_rebase_ff -- --test-threads=1

Notes

  • Existing unrelated warnings in tests/search.rs / tests/continue_session.rs remain unchanged.
  • Per request, corehooks e2e instability relative to the corehooks baseline PR can be handled in CI triage.

Open with Devin

dependabot bot and others added 25 commits February 10, 2026 13:47
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>
@git-ai-cloud-dev
Copy link

git-ai-cloud-dev bot commented Feb 14, 2026

Stats powered by Git AI

🧠 you    █████████░░░░░░░░░░░  45%
🤖 ai     ░░░░░░░░░███████████  55%
More stats
  • 0.1 lines generated for every 1 accepted
  • 2 minutes waiting for AI
  • Top model: claude::claude-sonnet-4-5-20250929 (21897 accepted lines, 0 generated lines)

AI code tracked with git-ai

Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 9 additional findings.

Open in Devin Review

- 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>
@git-ai-cloud
Copy link

git-ai-cloud bot commented Feb 14, 2026

Stats powered by Git AI

🧠 you    █████████░░░░░░░░░░░  45%
🤖 ai     ░░░░░░░░░███████████  55%
More stats
  • 0.1 lines generated for every 1 accepted
  • 2 minutes waiting for AI
  • Top model: claude::claude-sonnet-4-5-20250929 (21897 accepted lines, 0 generated lines)

AI code tracked with git-ai

Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Open in Devin Review

devin-ai-integration bot and others added 28 commits February 19, 2026 01:20
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>
@jwiegley jwiegley force-pushed the codex/worktree-support-corehooks branch from a4ac993 to c4f372a Compare February 19, 2026 09:22
…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>
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.

5 participants

Comments