Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #508 +/- ##
==========================================
+ Coverage 82.10% 83.01% +0.91%
==========================================
Files 27 29 +2
Lines 2900 3556 +656
Branches 581 705 +124
==========================================
+ Hits 2381 2952 +571
- Misses 333 388 +55
- Partials 186 216 +30 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
This pull request adds comprehensive git worktree support to vcspull, enabling users to manage multiple checkouts of the same repository at different branches, tags, or commits. The feature addresses issue #507, which requested the ability to rapidly clone repositories at different refs for LLM context harvesting and parallel work.
Changes:
- Adds new
WorktreeConfigDicttype for worktree configuration with support for tags, branches, commits, detachment, and locking options - Introduces comprehensive worktree sync logic including planning, creation, updating, pruning, and dirty state detection
- Adds new
vcspull worktreeCLI command withlist,sync, andprunesubcommands - Integrates
--include-worktreesflag across existing commands (sync, list, status, search, discover) - Implements worktree detection to exclude them from discovery by default
- Adds 30 tests covering config parsing, path resolution, sync planning, execution, and pruning
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
src/vcspull/types.py |
Defines WorktreeConfigDict with fields for dir, tag/branch/commit refs, detach, lock, and lock_reason |
src/vcspull/exc.py |
Adds worktree-specific exception classes (WorktreeError, WorktreeExistsError, WorktreeRefNotFoundError, WorktreeConfigError, WorktreeDirtyError) |
src/vcspull/_internal/worktree_sync.py |
Core worktree synchronization logic including planning, creation, update, pruning, and validation functions |
src/vcspull/config.py |
Adds _validate_worktrees_config function to parse and validate worktree configuration from YAML/JSON |
src/vcspull/cli/worktree.py |
New CLI command with list/sync/prune subcommands, output formatting, and color-coded status indicators |
src/vcspull/cli/__init__.py |
Integrates worktree command into main CLI parser and routing |
src/vcspull/cli/sync.py |
Adds --include-worktrees flag and integrates worktree sync after main repo sync |
src/vcspull/cli/list.py |
Adds --include-worktrees flag to display worktrees in flat and tree views |
src/vcspull/cli/status.py |
Adds --include-worktrees flag stub for future status integration |
src/vcspull/cli/search.py |
Adds --include-worktrees flag stub for future search integration |
src/vcspull/cli/discover.py |
Adds is_git_worktree() helper and --include-worktrees flag to control worktree discovery |
tests/test_worktree.py |
Comprehensive test suite with 30 tests covering config parsing, validation, sync, prune, and CLI integration |
tests/test_log.py |
Updates logger name list to include vcspull.cli.worktree |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Code reviewNo issues found. Checked for bugs and CLAUDE.md compliance. 🤖 Generated with Claude Code - If this code review was useful, please react with 👍. Otherwise, react with 👎. |
2c32978 to
764a716
Compare
why: Enable LLMs and developers to access repositories at multiple versions simultaneously for context harvesting and parallel work (Issue #507). what: - Add WorktreeConfigDict type and extend ConfigDict in types.py - Add worktree exceptions (WorktreeError, WorktreeExistsError, etc.) in exc.py - Create worktree_sync.py module with sync/prune/planning logic - Add worktree config validation in extract_repos() - Create vcspull worktree CLI subcommand (list, sync, prune actions) - Add --include-worktrees flag to sync, list, status, search, discover - Add is_git_worktree() helper to filter worktrees from discovery - Add 30 comprehensive tests for worktree functionality
why: The flags were added but never implemented - silently ignored. what: - Remove --include-worktrees from search subparser - Remove --include-worktrees from status subparser
…ent comparison why: Path mismatch between resolved configured paths and unresolved git paths could cause valid worktrees to be incorrectly identified as orphaned. what: - Resolve worktree paths in list_existing_worktrees() before returning
why: CLAUDE.md requires all functions have working doctests. what: - Add doctest examples to all 14 functions - Use doctest_namespace fixtures where git operations needed
why: Improve test coverage from 75.57% to 82%. what: - Add CLI tests for worktree list, sync, prune commands - Add edge case tests for _is_worktree_dirty, _ref_exists, _get_worktree_head - Add tests for validate_worktree_config edge cases - Add test for plan_worktree_sync with invalid config - Add test for sync_worktree branch UPDATE action
why: Fill 5 specific coverage gaps identified by analysis to improve worktree module coverage from 81%→88% and CLI from 78%→83%. what: - Add test_sync_worktree_executes_update for UPDATE execution path - Add test_sync_all_worktrees_counts_mixed for action counting - Add test_cli_worktree_sync_no_repos for empty repos path - Add test_cli_worktree_prune_no_repos for empty repos path - Add test_cli_worktree_prune_no_orphans for no-orphans path
… CLI why: Improve test coverage for worktree modules from 88%/83% toward 95%. what: - Add CreateWorktreeOptionsFixture for lock/detach options (lines 606-619) - Add CLIFilteringFixture for pattern/workspace filtering (lines 133-153) - Add test_worktree_exists_path_no_git for edge case (line 346) - Add test_prune_worktree_failure for error handling (lines 824-826) - Add test_cli_sync_skips_empty_worktrees (lines 201, 288) - Add exception handling tests for _ref_exists, _is_worktree_dirty, _get_worktree_head
why: Improve test coverage from 94% toward 98%. what: - Add WorktreeActionOutputFixture for color branch coverage (lines 227-231) - Add test_sync_worktree_unchanged_execution (lines 549-552) - Add test_sync_worktree_oserror_exception (lines 557-559) - Add test_handle_list_with_empty_worktrees_direct (line 201) - Add test_handle_sync_with_empty_worktrees_direct (line 288) - Add test_cli_prune_no_existing_worktrees (line 356) - Add test_get_worktree_head_oserror (lines 305-306)
why: Submodules also have .git files with "gitdir:" but point to .git/modules/ what: - Add check for "/worktrees/" in gitdir path to distinguish from submodules - Worktrees point to .git/worktrees/, submodules point to .git/modules/
why: --dry-run flag was ignored for worktree operations what: - Pass dry_run parameter to sync_all_worktrees() instead of hardcoded False
why: Recursive and non-recursive discovery had inconsistent .git detection what: - Change (item / ".git").exists() to (item / ".git").is_dir() - Both branches now consistently match only regular repos with .git directories
…onal doctests why: CLAUDE.md requires doctests to actually execute and test behavior what: - Remove try/except pass workarounds from _create_worktree and _update_worktree - Replace Examples sections with Notes sections referencing integration tests
why: NumPy docstring style requires all parameters to be documented what: - Add include_worktrees parameter to list_repos docstring
why: CLAUDE.md requires proper docstrings for all functions what: - Add Parameters sections to _add_common_args, _handle_list, _emit_worktree_entry, _handle_sync, _handle_prune - Add Notes sections referencing integration tests
why: CLAUDE.md requires all functions to have working doctests what: - Add Examples section with valid config doctests - Add error case doctests for invalid configurations
why: CLAUDE.md requires namespace imports for stdlib modules what: - Change from dataclasses import to import dataclasses - Update @DataClass to @dataclasses.dataclass - Update field() to dataclasses.field()
why: Git worktrees have .git as a file, not a directory what: - Add .is_file() check for non-recursive discovery - Add .git file check for recursive discovery via os.walk - Enables --include-worktrees to actually discover worktrees
…tream tracking why: git pull --ff-only fails with "no tracking information" for local-only branches. The worktree is already on the correct branch after checkout, so pull is unnecessary when no upstream exists. what: - Check branch.<name>.remote before attempting git pull - Skip pull with debug log when no upstream tracking ref exists - Update test_sync_worktree_executes_update to expect UPDATE (not ERROR)
why: Worktree operations were emitted as inline messages but not tallied in the summary dict, so worktree failures didn't affect the exit code and the summary didn't reflect the full scope of work done. what: - Tally worktree CREATE/UPDATE/ERROR counts into summary dict - Count worktree errors as failures for exit code - Update _emit_summary to display worktree counts when non-zero - Add test_sync_worktree_summary_counts regression test
why: assert statements are stripped when Python runs with -O (optimize), which would cause an AttributeError when trying to unpack ref_info. what: - Replace assert ref_info is not None with if/continue guard - Add log.warning for the unexpected case where validation passes but _get_ref_type_and_value returns None
…ir paths why: _resolve_worktree_path didn't call expanduser(), so dir: "~/worktrees/proj" was treated as a relative path and resolved against workspace_root, creating a literal "~" directory instead of expanding to $HOME. what: - Call pathlib.Path.expanduser() before the absolute/relative check - Add test_resolve_worktree_path_tilde regression test
why: WorktreeExistsError, WorktreeRefNotFoundError, and WorktreeDirtyError are never imported, raised, or caught anywhere in the codebase. The worktree sync code uses WorktreeSyncResult entries with error strings instead. what: - Remove WorktreeExistsError class - Remove WorktreeRefNotFoundError class - Remove WorktreeDirtyError class - Keep WorktreeError (base) and WorktreeConfigError (actively used)
…yped error values why: The worktree sync system needs typed exceptions that carry structured attributes (ref, ref_type, repo_path, path) so audit trail checks can attach the original exception object rather than just a string message. what: - Add WorktreeRefNotFoundError with ref, ref_type, repo_path attributes - Add WorktreeDirtyError with path attribute - Add parametrized exception construction tests for all ref types - Add exception hierarchy test verifying inheritance chain
…reePlanEntry why: When debugging worktree sync decisions, the existing action/error fields show what happened but not the chain of checks that led there. A step-by-step audit trail with typed exceptions enables programmatic inspection of why a particular action was chosen. what: - Add WorktreeCheck dataclass with name, passed, detail, exception fields - Add checks field to WorktreePlanEntry (list[WorktreeCheck]) - Record validate_config, ref_exists, worktree_exists, is_dirty checks in plan_worktree_sync with typed exceptions on failure - Add parametrized audit trail tests for all 5 scenarios - Add standalone tests for exception attribute access via check trail
…rty check fails why: Returning False on failure is fail-open — it allows destructive operations (update/overwrite) to proceed when the dirty state is actually unknown. Fail-safe is the correct default for data-protection checks. what: - Return True instead of False in exception handler - Add returncode != 0 check before trusting stdout - Log warnings on both failure paths for observability - Update docstring and doctest to reflect fail-safe behavior - Update test to assert True; add test for non-zero returncode path
…/ namespace
why: Bare `rev-parse --verify ref` resolves any ref type (tags, notes, commits),
so a tag named "v2" would falsely pass branch validation. This could cause
incorrect worktree actions (update instead of unchanged).
what:
- Use refs/heads/{ref} for local branch check instead of bare ref
- Use refs/remotes/origin/{ref} for remote branch check instead of origin/{ref}
- Add test verifying tags are rejected by branch validation
…ancestry why: str.startswith() incorrectly matches path prefixes like /repo/.git-other against /repo/.git. This could cause _worktree_exists to return True for worktrees belonging to a different (similarly named) git directory. what: - Replace str(path).startswith() with Path.relative_to() for proper ancestry check - Add test for prefix collision case (.git-other vs .git)
why: Worktree errors incremented summary["failed"] but the exit_on_error guard was only checked after regular sync failures and unmatched patterns, not after worktree sync failures. This meant --exit-on-error would not stop processing when worktree operations failed. what: - Add exit_on_error check after worktree error tally (line 950) - Add test verifying SystemExit is raised on worktree failure with --exit-on-error
…une scan why: Filtering to repos_with_worktrees before prune meant repos where all worktree entries were removed from config would never have orphaned worktrees detected. This defeats the purpose of prune for cleanup scenarios. what: - Pass found_repos instead of repos_with_worktrees to _handle_prune - Add test for prune with empty worktree config detecting orphans - Update test_cli_worktree_prune_no_repos expected output
why: Multiple overlapping patterns (e.g. 'flask*' and '*lask') would cause the same repo to be processed multiple times for list/sync/prune operations, leading to duplicate output and redundant work. what: - Add seen_paths deduplication after filter_repos loop (matches cli/sync.py) - Add test verifying overlapping patterns produce single repo header
…lure why: Lock failure after successful worktree creation marked the entire operation as ERROR via CalledProcessError propagation. The worktree is still valid and usable without a lock — locking is a non-critical annotation. what: - Wrap lock subprocess.run in try/except CalledProcessError - Log warning on lock failure instead of propagating exception - Add test verifying creation succeeds despite lock failure
why: plan_worktree_sync should always return at least one entry for a single config input, but an internal bug could cause an empty list. An IndexError with no context is harder to debug than an explicit ERROR entry. what: - Add guard before entries[0] access in sync_worktree - Return ERROR entry with descriptive message when plan is empty - Add test mocking empty plan return to verify ERROR instead of crash
why: The CHANGES file needs a comprehensive entry for the worktree feature before release. what: - Add entry for `vcspull worktree` command (list, sync, prune) - Document configuration format, subcommands, key features - Document `vcspull sync --include-worktrees` integration - Add bug fix entries for discover worktree detection and config reader - Add internal/development section for exception types and test suite
why: Provide a standalone YAML example showing all worktree config options for documentation and user reference. what: - Add examples/worktrees.yaml with tag, branch, commit worktrees - Include lock and lock_reason fields - Add inline comments explaining required and optional fields
why: The worktree feature has no user-facing documentation in docs/. Every other command has a CLI guide page; this brings worktree to parity. what: - Add docs/cli/worktree.md with (cli-worktree) cross-reference label - Document all three subcommands: list, sync, prune - Cover configuration schema, safety rules, JSON/NDJSON output - Include --include-worktrees integration with vcspull sync - Add use cases: LLM workflows, parallel development, versioned references
why: API reference pages exist for all other CLI modules; add one for worktree. what: - Add docs/api/cli/worktree.md with automodule directive
why: The worktree_sync module has no API reference page in docs/. what: - Add docs/api/internals/worktree_sync.md with automodule directive - Include internal API stability warning matching config_reader pattern
why: Wire the new worktree CLI guide into the docs navigation. what: - Add worktree to toctree under General commands, after status - Add :ref:\`cli-worktree\` to subparser_name replacement text
why: Wire the new worktree API reference into the docs navigation. what: - Add worktree to toctree after status
why: Wire the new worktree_sync API reference into the docs navigation. what: - Add worktree_sync to toctree between config_reader and private_path
why: Document the worktree schema alongside existing configuration docs. what: - Add Worktree configuration section before Caveats - Include literalinclude of examples/worktrees.yaml - List required and optional fields - Cross-reference cli-worktree for full command docs
why: Surface the worktree feature in the quickstart guide for discoverability. what: - Add brief worktree mention after the sync section - Cross-reference cli-worktree for details
why: Match the import/ directory structure where the overview page lives at worktree/index.md and links to per-subcommand pages via toctree. what: - Delete docs/cli/worktree.md - Create docs/cli/worktree/index.md with overview, config, safety, use cases - Add :nosubcommands: and :nodescription: to argparse directive - Add hidden toctree linking to list, sync, prune subcommand pages - Add cross-reference links to cli-worktree-list, cli-worktree-sync, cli-worktree-prune
why: Each subcommand gets its own page with scoped argparse reference, matching the import/ pattern (e.g. import/github.md, import/gitlab.md). what: - Add docs/cli/worktree/list.md with cli-worktree-list label - Add docs/cli/worktree/sync.md with cli-worktree-sync label - Add docs/cli/worktree/prune.md with cli-worktree-prune label - Each page has its own argparse block scoped to its subcommand path
why: The worktree docs moved from a flat file to a directory structure. what: - Change toctree entry from worktree to worktree/index
Summary
Resolves #507
Adds declarative git worktree management to vcspull — configure worktrees per-repo in YAML, then list, sync, and prune them from the CLI.
Configuration format
Each repository can declare a
worktreeslist with entries targeting a tag, branch, or commit:Fields per entry:
dirtagbranchcommitlocklock_reasonlock: true)detachCLI subcommands
vcspull worktree listPreview the plan for all configured worktrees. Each entry shows one of:
+~✓⚠✗vcspull worktree syncCreate or update worktrees to match configuration. Supports
--dry-runto preview without changes.vcspull worktree pruneRemove worktrees that exist on disk but are not in configuration. Supports
--dry-run.Common flags
All worktree subcommands support:
-f/--file— config file path-w/--workspace— filter by workspace root--json/--ndjson— machine-readable output--color {auto,always,never}— color controlOutput modes
+,~,✓,⚠,✗)--json): array of plan entry objects--ndjson): one JSON object per lineJSON schema per entry:
{ "worktree_path": "~/repos/myproject-v1.0", "ref_type": "tag", "ref_value": "v1.0.0", "action": "create", "exists": false, "is_dirty": false, "detail": "will create worktree at tag v1.0.0", "error": null }Safety behaviors
lock: truepasses--locktogit worktree add;lock_reasonlocks with a reason after creationgit status --porcelainitself fails, the worktree is treated as dirtydir, and non-string valuesIntegration with
vcspull sync--include-worktreessyncs repos then their worktrees in one pass:$ vcspull sync --all --include-worktreesWorktree results are included in the sync summary and affect the exit code when
--exit-on-erroris set.Bug fixes included
vcspull discovernow correctly distinguishes git worktrees from submodulesChanges
src/vcspull/types.pyWorktreeConfigDict, extendConfigDictsrc/vcspull/exc.pyWorktreeErrorhierarchy (WorktreeConfigError,WorktreeRefNotFoundError,WorktreeDirtyError)src/vcspull/_internal/worktree_sync.pysrc/vcspull/_internal/config_reader.pysrc/vcspull/config.pyworktreesconfig sectionsrc/vcspull/cli/worktree.pyworktreesubcommand (list, sync, prune)src/vcspull/cli/sync.py--include-worktreesflag and worktree sync integrationsrc/vcspull/cli/discover.pyis_git_worktree()helper,--include-worktreesflagsrc/vcspull/cli/__init__.pytests/test_worktree.pyTest plan
uv run pytest)uv run mypy)vcspull sync --include-worktreestested