erk 0.4.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- erk/__init__.py +12 -0
- erk/__main__.py +6 -0
- erk/agent_docs/__init__.py +5 -0
- erk/agent_docs/models.py +123 -0
- erk/agent_docs/operations.py +666 -0
- erk/artifacts/__init__.py +5 -0
- erk/artifacts/artifact_health.py +623 -0
- erk/artifacts/detection.py +16 -0
- erk/artifacts/discovery.py +343 -0
- erk/artifacts/models.py +63 -0
- erk/artifacts/staleness.py +56 -0
- erk/artifacts/state.py +100 -0
- erk/artifacts/sync.py +624 -0
- erk/cli/__init__.py +0 -0
- erk/cli/activation.py +132 -0
- erk/cli/alias.py +53 -0
- erk/cli/cli.py +221 -0
- erk/cli/commands/__init__.py +0 -0
- erk/cli/commands/admin.py +153 -0
- erk/cli/commands/artifact/__init__.py +1 -0
- erk/cli/commands/artifact/check.py +260 -0
- erk/cli/commands/artifact/group.py +31 -0
- erk/cli/commands/artifact/list_cmd.py +89 -0
- erk/cli/commands/artifact/show.py +62 -0
- erk/cli/commands/artifact/sync_cmd.py +39 -0
- erk/cli/commands/branch/__init__.py +26 -0
- erk/cli/commands/branch/assign_cmd.py +152 -0
- erk/cli/commands/branch/checkout_cmd.py +357 -0
- erk/cli/commands/branch/create_cmd.py +161 -0
- erk/cli/commands/branch/list_cmd.py +82 -0
- erk/cli/commands/branch/unassign_cmd.py +197 -0
- erk/cli/commands/cc/__init__.py +15 -0
- erk/cli/commands/cc/jsonl_cmd.py +20 -0
- erk/cli/commands/cc/session/AGENTS.md +30 -0
- erk/cli/commands/cc/session/CLAUDE.md +1 -0
- erk/cli/commands/cc/session/__init__.py +15 -0
- erk/cli/commands/cc/session/list_cmd.py +167 -0
- erk/cli/commands/cc/session/show_cmd.py +175 -0
- erk/cli/commands/completion.py +89 -0
- erk/cli/commands/completions.py +165 -0
- erk/cli/commands/config.py +327 -0
- erk/cli/commands/docs/__init__.py +1 -0
- erk/cli/commands/docs/group.py +16 -0
- erk/cli/commands/docs/sync.py +121 -0
- erk/cli/commands/docs/validate.py +102 -0
- erk/cli/commands/doctor.py +243 -0
- erk/cli/commands/down.py +171 -0
- erk/cli/commands/exec/__init__.py +1 -0
- erk/cli/commands/exec/group.py +164 -0
- erk/cli/commands/exec/scripts/AGENTS.md +79 -0
- erk/cli/commands/exec/scripts/CLAUDE.md +1 -0
- erk/cli/commands/exec/scripts/__init__.py +5 -0
- erk/cli/commands/exec/scripts/add_reaction_to_comment.py +69 -0
- erk/cli/commands/exec/scripts/add_remote_execution_note.py +68 -0
- erk/cli/commands/exec/scripts/check_impl.py +152 -0
- erk/cli/commands/exec/scripts/ci_update_pr_body.py +294 -0
- erk/cli/commands/exec/scripts/create_extraction_branch.py +138 -0
- erk/cli/commands/exec/scripts/create_extraction_plan.py +242 -0
- erk/cli/commands/exec/scripts/create_issue_from_session.py +103 -0
- erk/cli/commands/exec/scripts/create_plan_from_context.py +103 -0
- erk/cli/commands/exec/scripts/create_worker_impl_from_issue.py +93 -0
- erk/cli/commands/exec/scripts/detect_trunk_branch.py +121 -0
- erk/cli/commands/exec/scripts/exit_plan_mode_hook.py +777 -0
- erk/cli/commands/exec/scripts/extract_latest_plan.py +49 -0
- erk/cli/commands/exec/scripts/extract_session_from_issue.py +150 -0
- erk/cli/commands/exec/scripts/find_project_dir.py +214 -0
- erk/cli/commands/exec/scripts/generate_pr_summary.py +112 -0
- erk/cli/commands/exec/scripts/get_closing_text.py +98 -0
- erk/cli/commands/exec/scripts/get_embedded_prompt.py +62 -0
- erk/cli/commands/exec/scripts/get_plan_metadata.py +95 -0
- erk/cli/commands/exec/scripts/get_pr_body_footer.py +70 -0
- erk/cli/commands/exec/scripts/get_pr_discussion_comments.py +149 -0
- erk/cli/commands/exec/scripts/get_pr_review_comments.py +155 -0
- erk/cli/commands/exec/scripts/impl_init.py +158 -0
- erk/cli/commands/exec/scripts/impl_signal.py +375 -0
- erk/cli/commands/exec/scripts/impl_verify.py +49 -0
- erk/cli/commands/exec/scripts/issue_title_to_filename.py +34 -0
- erk/cli/commands/exec/scripts/list_sessions.py +296 -0
- erk/cli/commands/exec/scripts/mark_impl_ended.py +188 -0
- erk/cli/commands/exec/scripts/mark_impl_started.py +188 -0
- erk/cli/commands/exec/scripts/marker.py +163 -0
- erk/cli/commands/exec/scripts/objective_save_to_issue.py +109 -0
- erk/cli/commands/exec/scripts/plan_save_to_issue.py +269 -0
- erk/cli/commands/exec/scripts/plan_update_issue.py +147 -0
- erk/cli/commands/exec/scripts/post_extraction_comment.py +237 -0
- erk/cli/commands/exec/scripts/post_or_update_pr_summary.py +133 -0
- erk/cli/commands/exec/scripts/post_pr_inline_comment.py +143 -0
- erk/cli/commands/exec/scripts/post_workflow_started_comment.py +168 -0
- erk/cli/commands/exec/scripts/preprocess_session.py +777 -0
- erk/cli/commands/exec/scripts/quick_submit.py +32 -0
- erk/cli/commands/exec/scripts/rebase_with_conflict_resolution.py +260 -0
- erk/cli/commands/exec/scripts/reply_to_discussion_comment.py +173 -0
- erk/cli/commands/exec/scripts/resolve_review_thread.py +170 -0
- erk/cli/commands/exec/scripts/session_id_injector_hook.py +52 -0
- erk/cli/commands/exec/scripts/setup_impl_from_issue.py +159 -0
- erk/cli/commands/exec/scripts/slot_objective.py +102 -0
- erk/cli/commands/exec/scripts/tripwires_reminder_hook.py +20 -0
- erk/cli/commands/exec/scripts/update_dispatch_info.py +116 -0
- erk/cli/commands/exec/scripts/user_prompt_hook.py +113 -0
- erk/cli/commands/exec/scripts/validate_plan_content.py +98 -0
- erk/cli/commands/exec/scripts/wrap_plan_in_metadata_block.py +34 -0
- erk/cli/commands/implement.py +695 -0
- erk/cli/commands/implement_shared.py +649 -0
- erk/cli/commands/info/__init__.py +14 -0
- erk/cli/commands/info/release_notes_cmd.py +128 -0
- erk/cli/commands/init.py +801 -0
- erk/cli/commands/land_cmd.py +690 -0
- erk/cli/commands/log_cmd.py +137 -0
- erk/cli/commands/md/__init__.py +5 -0
- erk/cli/commands/md/check.py +118 -0
- erk/cli/commands/md/group.py +14 -0
- erk/cli/commands/navigation_helpers.py +430 -0
- erk/cli/commands/objective/__init__.py +16 -0
- erk/cli/commands/objective/list_cmd.py +47 -0
- erk/cli/commands/objective_helpers.py +132 -0
- erk/cli/commands/plan/__init__.py +32 -0
- erk/cli/commands/plan/check_cmd.py +174 -0
- erk/cli/commands/plan/close_cmd.py +69 -0
- erk/cli/commands/plan/create_cmd.py +120 -0
- erk/cli/commands/plan/docs/__init__.py +18 -0
- erk/cli/commands/plan/docs/extract_cmd.py +53 -0
- erk/cli/commands/plan/docs/unextract_cmd.py +38 -0
- erk/cli/commands/plan/docs/unextracted_cmd.py +72 -0
- erk/cli/commands/plan/extraction/__init__.py +16 -0
- erk/cli/commands/plan/extraction/complete_cmd.py +101 -0
- erk/cli/commands/plan/extraction/create_raw_cmd.py +63 -0
- erk/cli/commands/plan/get.py +71 -0
- erk/cli/commands/plan/list_cmd.py +754 -0
- erk/cli/commands/plan/log_cmd.py +440 -0
- erk/cli/commands/plan/start_cmd.py +459 -0
- erk/cli/commands/planner/__init__.py +40 -0
- erk/cli/commands/planner/configure_cmd.py +73 -0
- erk/cli/commands/planner/connect_cmd.py +96 -0
- erk/cli/commands/planner/create_cmd.py +148 -0
- erk/cli/commands/planner/list_cmd.py +51 -0
- erk/cli/commands/planner/register_cmd.py +105 -0
- erk/cli/commands/planner/set_default_cmd.py +23 -0
- erk/cli/commands/planner/unregister_cmd.py +43 -0
- erk/cli/commands/pr/__init__.py +23 -0
- erk/cli/commands/pr/check_cmd.py +112 -0
- erk/cli/commands/pr/checkout_cmd.py +165 -0
- erk/cli/commands/pr/fix_conflicts_cmd.py +82 -0
- erk/cli/commands/pr/parse_pr_reference.py +10 -0
- erk/cli/commands/pr/submit_cmd.py +360 -0
- erk/cli/commands/pr/sync_cmd.py +181 -0
- erk/cli/commands/prepare_cwd_recovery.py +60 -0
- erk/cli/commands/project/__init__.py +16 -0
- erk/cli/commands/project/init_cmd.py +91 -0
- erk/cli/commands/run/__init__.py +17 -0
- erk/cli/commands/run/list_cmd.py +189 -0
- erk/cli/commands/run/logs_cmd.py +54 -0
- erk/cli/commands/run/shared.py +19 -0
- erk/cli/commands/shell_integration.py +29 -0
- erk/cli/commands/slot/__init__.py +23 -0
- erk/cli/commands/slot/check_cmd.py +277 -0
- erk/cli/commands/slot/common.py +314 -0
- erk/cli/commands/slot/init_pool_cmd.py +157 -0
- erk/cli/commands/slot/list_cmd.py +228 -0
- erk/cli/commands/slot/repair_cmd.py +190 -0
- erk/cli/commands/stack/__init__.py +23 -0
- erk/cli/commands/stack/consolidate_cmd.py +470 -0
- erk/cli/commands/stack/list_cmd.py +79 -0
- erk/cli/commands/stack/move_cmd.py +309 -0
- erk/cli/commands/stack/split_old/README.md +64 -0
- erk/cli/commands/stack/split_old/__init__.py +5 -0
- erk/cli/commands/stack/split_old/command.py +233 -0
- erk/cli/commands/stack/split_old/display.py +116 -0
- erk/cli/commands/stack/split_old/plan.py +216 -0
- erk/cli/commands/status.py +58 -0
- erk/cli/commands/submit.py +768 -0
- erk/cli/commands/up.py +154 -0
- erk/cli/commands/upgrade.py +82 -0
- erk/cli/commands/wt/__init__.py +29 -0
- erk/cli/commands/wt/checkout_cmd.py +110 -0
- erk/cli/commands/wt/create_cmd.py +998 -0
- erk/cli/commands/wt/current_cmd.py +35 -0
- erk/cli/commands/wt/delete_cmd.py +573 -0
- erk/cli/commands/wt/list_cmd.py +332 -0
- erk/cli/commands/wt/rename_cmd.py +66 -0
- erk/cli/config.py +242 -0
- erk/cli/constants.py +29 -0
- erk/cli/core.py +65 -0
- erk/cli/debug.py +9 -0
- erk/cli/ensure-conversion-tasks.md +288 -0
- erk/cli/ensure.py +628 -0
- erk/cli/github_parsing.py +96 -0
- erk/cli/graphite.py +81 -0
- erk/cli/graphite_command.py +80 -0
- erk/cli/help_formatter.py +345 -0
- erk/cli/output.py +361 -0
- erk/cli/presets/dagster.toml +12 -0
- erk/cli/presets/generic.toml +12 -0
- erk/cli/prompt_hooks_templates/README.md +68 -0
- erk/cli/script_output.py +32 -0
- erk/cli/shell_integration/bash_wrapper.sh +32 -0
- erk/cli/shell_integration/fish_wrapper.fish +39 -0
- erk/cli/shell_integration/handler.py +338 -0
- erk/cli/shell_integration/zsh_wrapper.sh +32 -0
- erk/cli/shell_utils.py +171 -0
- erk/cli/subprocess_utils.py +92 -0
- erk/cli/uvx_detection.py +59 -0
- erk/core/__init__.py +0 -0
- erk/core/claude_executor.py +511 -0
- erk/core/claude_settings.py +317 -0
- erk/core/command_log.py +406 -0
- erk/core/commit_message_generator.py +234 -0
- erk/core/completion.py +10 -0
- erk/core/consolidation_utils.py +177 -0
- erk/core/context.py +570 -0
- erk/core/display/__init__.py +4 -0
- erk/core/display/abc.py +24 -0
- erk/core/display/real.py +30 -0
- erk/core/display_utils.py +526 -0
- erk/core/file_utils.py +87 -0
- erk/core/health_checks.py +1315 -0
- erk/core/health_checks_dogfooder/__init__.py +85 -0
- erk/core/health_checks_dogfooder/deprecated_dot_agent_config.py +64 -0
- erk/core/health_checks_dogfooder/legacy_claude_docs.py +69 -0
- erk/core/health_checks_dogfooder/legacy_config_locations.py +122 -0
- erk/core/health_checks_dogfooder/legacy_erk_docs_agent.py +61 -0
- erk/core/health_checks_dogfooder/legacy_erk_kits_folder.py +60 -0
- erk/core/health_checks_dogfooder/legacy_hook_settings.py +104 -0
- erk/core/health_checks_dogfooder/legacy_kit_yaml.py +78 -0
- erk/core/health_checks_dogfooder/legacy_kits_toml.py +43 -0
- erk/core/health_checks_dogfooder/outdated_erk_skill.py +43 -0
- erk/core/implementation_queue/__init__.py +1 -0
- erk/core/implementation_queue/github/__init__.py +8 -0
- erk/core/implementation_queue/github/abc.py +7 -0
- erk/core/implementation_queue/github/noop.py +38 -0
- erk/core/implementation_queue/github/printing.py +43 -0
- erk/core/implementation_queue/github/real.py +119 -0
- erk/core/init_utils.py +227 -0
- erk/core/output_filter.py +338 -0
- erk/core/plan_store/__init__.py +6 -0
- erk/core/planner/__init__.py +1 -0
- erk/core/planner/registry_abc.py +8 -0
- erk/core/planner/registry_fake.py +129 -0
- erk/core/planner/registry_real.py +195 -0
- erk/core/planner/types.py +7 -0
- erk/core/pr_utils.py +30 -0
- erk/core/release_notes.py +263 -0
- erk/core/repo_discovery.py +126 -0
- erk/core/script_writer.py +41 -0
- erk/core/services/__init__.py +1 -0
- erk/core/services/plan_list_service.py +94 -0
- erk/core/shell.py +51 -0
- erk/core/user_feedback.py +11 -0
- erk/core/version_check.py +55 -0
- erk/core/workflow_display.py +75 -0
- erk/core/worktree_pool.py +190 -0
- erk/core/worktree_utils.py +300 -0
- erk/data/CHANGELOG.md +438 -0
- erk/data/__init__.py +1 -0
- erk/data/claude/agents/devrun.md +180 -0
- erk/data/claude/commands/erk/__init__.py +0 -0
- erk/data/claude/commands/erk/create-extraction-plan.md +360 -0
- erk/data/claude/commands/erk/fix-conflicts.md +25 -0
- erk/data/claude/commands/erk/git-pr-push.md +345 -0
- erk/data/claude/commands/erk/implement-stacked-plan.md +96 -0
- erk/data/claude/commands/erk/land.md +193 -0
- erk/data/claude/commands/erk/objective-create.md +370 -0
- erk/data/claude/commands/erk/objective-list.md +34 -0
- erk/data/claude/commands/erk/objective-next-plan.md +220 -0
- erk/data/claude/commands/erk/objective-update-with-landed-pr.md +216 -0
- erk/data/claude/commands/erk/plan-implement.md +202 -0
- erk/data/claude/commands/erk/plan-save.md +45 -0
- erk/data/claude/commands/erk/plan-submit.md +39 -0
- erk/data/claude/commands/erk/pr-address.md +367 -0
- erk/data/claude/commands/erk/pr-submit.md +58 -0
- erk/data/claude/skills/dignified-python/SKILL.md +48 -0
- erk/data/claude/skills/dignified-python/cli-patterns.md +155 -0
- erk/data/claude/skills/dignified-python/dignified-python-core.md +1190 -0
- erk/data/claude/skills/dignified-python/subprocess.md +99 -0
- erk/data/claude/skills/dignified-python/versions/python-3.10.md +517 -0
- erk/data/claude/skills/dignified-python/versions/python-3.11.md +536 -0
- erk/data/claude/skills/dignified-python/versions/python-3.12.md +662 -0
- erk/data/claude/skills/dignified-python/versions/python-3.13.md +653 -0
- erk/data/claude/skills/erk-diff-analysis/SKILL.md +27 -0
- erk/data/claude/skills/erk-diff-analysis/references/commit-message-prompt.md +78 -0
- erk/data/claude/skills/learned-docs/SKILL.md +362 -0
- erk/data/github/actions/setup-claude-erk/action.yml +11 -0
- erk/data/github/prompts/dignified-python-review.md +125 -0
- erk/data/github/workflows/dignified-python-review.yml +61 -0
- erk/data/github/workflows/erk-impl.yml +251 -0
- erk/hooks/__init__.py +1 -0
- erk/hooks/decorators.py +319 -0
- erk/status/__init__.py +8 -0
- erk/status/collectors/__init__.py +9 -0
- erk/status/collectors/base.py +52 -0
- erk/status/collectors/git.py +76 -0
- erk/status/collectors/github.py +81 -0
- erk/status/collectors/graphite.py +80 -0
- erk/status/collectors/impl.py +145 -0
- erk/status/models/__init__.py +4 -0
- erk/status/models/status_data.py +404 -0
- erk/status/orchestrator.py +169 -0
- erk/status/renderers/__init__.py +5 -0
- erk/status/renderers/simple.py +322 -0
- erk/tui/AGENTS.md +193 -0
- erk/tui/CLAUDE.md +1 -0
- erk/tui/__init__.py +1 -0
- erk/tui/app.py +1404 -0
- erk/tui/commands/__init__.py +1 -0
- erk/tui/commands/executor.py +66 -0
- erk/tui/commands/provider.py +165 -0
- erk/tui/commands/real_executor.py +63 -0
- erk/tui/commands/registry.py +121 -0
- erk/tui/commands/types.py +36 -0
- erk/tui/data/__init__.py +1 -0
- erk/tui/data/provider.py +492 -0
- erk/tui/data/types.py +104 -0
- erk/tui/filtering/__init__.py +1 -0
- erk/tui/filtering/logic.py +43 -0
- erk/tui/filtering/types.py +55 -0
- erk/tui/jsonl_viewer/__init__.py +1 -0
- erk/tui/jsonl_viewer/app.py +61 -0
- erk/tui/jsonl_viewer/models.py +208 -0
- erk/tui/jsonl_viewer/widgets.py +204 -0
- erk/tui/sorting/__init__.py +6 -0
- erk/tui/sorting/logic.py +55 -0
- erk/tui/sorting/types.py +68 -0
- erk/tui/styles/dash.tcss +95 -0
- erk/tui/widgets/__init__.py +1 -0
- erk/tui/widgets/command_output.py +112 -0
- erk/tui/widgets/plan_table.py +276 -0
- erk/tui/widgets/status_bar.py +116 -0
- erk-0.4.5.dist-info/METADATA +376 -0
- erk-0.4.5.dist-info/RECORD +331 -0
- erk-0.4.5.dist-info/WHEEL +4 -0
- erk-0.4.5.dist-info/entry_points.txt +2 -0
- erk-0.4.5.dist-info/licenses/LICENSE.md +3 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"""Pure business logic for worktree consolidation operations.
|
|
2
|
+
|
|
3
|
+
This module contains the core logic for determining which worktrees can be
|
|
4
|
+
safely consolidated (removed) when consolidating a worktree stack into a
|
|
5
|
+
single worktree. All functions are pure and testable without filesystem I/O.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from erk_shared.git.abc import WorktreeInfo
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True)
|
|
15
|
+
class ConsolidationPlan:
|
|
16
|
+
"""Plan for consolidating worktrees in a stack.
|
|
17
|
+
|
|
18
|
+
Attributes:
|
|
19
|
+
stack_branches: Full list of branches in the stack (trunk to leaf)
|
|
20
|
+
stack_to_consolidate: Subset of branches being consolidated
|
|
21
|
+
worktrees_to_remove: Worktrees that will be removed
|
|
22
|
+
target_worktree_path: Path to the target worktree (where branches will be consolidated)
|
|
23
|
+
source_worktree_path: Path to source worktree (None if not creating new worktree)
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
stack_branches: list[str]
|
|
27
|
+
stack_to_consolidate: list[str]
|
|
28
|
+
worktrees_to_remove: list[WorktreeInfo]
|
|
29
|
+
target_worktree_path: Path
|
|
30
|
+
source_worktree_path: Path | None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def calculate_stack_range(
|
|
34
|
+
stack_branches: list[str],
|
|
35
|
+
end_branch: str | None,
|
|
36
|
+
) -> list[str]:
|
|
37
|
+
"""Calculate which portion of the stack to consolidate.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
stack_branches: Full stack from trunk to leaf (e.g., ['main', 'feat-1', 'feat-2', 'feat-3'])
|
|
41
|
+
end_branch: Optional branch name to consolidate up to (inclusive).
|
|
42
|
+
If None, consolidate entire stack.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
List of branches to consolidate, from trunk up to (and including) end_branch.
|
|
46
|
+
If end_branch is None, returns the full stack.
|
|
47
|
+
|
|
48
|
+
Raises:
|
|
49
|
+
ValueError: If end_branch is not in stack_branches
|
|
50
|
+
|
|
51
|
+
Examples:
|
|
52
|
+
>>> calculate_stack_range(['main', 'feat-1', 'feat-2', 'feat-3'], 'feat-2')
|
|
53
|
+
['main', 'feat-1', 'feat-2']
|
|
54
|
+
|
|
55
|
+
>>> calculate_stack_range(['main', 'feat-1', 'feat-2'], None)
|
|
56
|
+
['main', 'feat-1', 'feat-2']
|
|
57
|
+
"""
|
|
58
|
+
if end_branch is None:
|
|
59
|
+
return stack_branches
|
|
60
|
+
|
|
61
|
+
if end_branch not in stack_branches:
|
|
62
|
+
raise ValueError(f"Branch '{end_branch}' not in stack")
|
|
63
|
+
|
|
64
|
+
branch_index = stack_branches.index(end_branch)
|
|
65
|
+
return stack_branches[: branch_index + 1]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def identify_removable_worktrees(
|
|
69
|
+
all_worktrees: list[WorktreeInfo],
|
|
70
|
+
stack_to_consolidate: list[str],
|
|
71
|
+
target_worktree_path: Path,
|
|
72
|
+
source_worktree_path: Path | None = None,
|
|
73
|
+
) -> list[WorktreeInfo]:
|
|
74
|
+
"""Identify which worktrees can be safely removed during consolidation.
|
|
75
|
+
|
|
76
|
+
A worktree is removable if:
|
|
77
|
+
1. It has a branch checked out (not detached HEAD)
|
|
78
|
+
2. Its branch is in the consolidation range
|
|
79
|
+
3. It's not the target worktree
|
|
80
|
+
4. It's not the source worktree (if creating new worktree with --name)
|
|
81
|
+
5. It's not the root worktree (CRITICAL safety check)
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
all_worktrees: All worktrees in the repository
|
|
85
|
+
stack_to_consolidate: Branches being consolidated
|
|
86
|
+
target_worktree_path: Path to target worktree (will not be removed)
|
|
87
|
+
source_worktree_path: Path to source worktree when creating new worktree
|
|
88
|
+
(will be removed separately, not included in result)
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
List of worktrees that can be safely removed
|
|
92
|
+
|
|
93
|
+
Examples:
|
|
94
|
+
>>> worktrees = [
|
|
95
|
+
... WorktreeInfo(Path('/repo'), 'main', True, False),
|
|
96
|
+
... WorktreeInfo(Path('/repo/feat-1'), 'feat-1', False, False),
|
|
97
|
+
... WorktreeInfo(Path('/repo/feat-2'), 'feat-2', False, False),
|
|
98
|
+
... ]
|
|
99
|
+
>>> identify_removable_worktrees(
|
|
100
|
+
... worktrees,
|
|
101
|
+
... ['main', 'feat-1', 'feat-2'],
|
|
102
|
+
... Path('/repo/feat-1'),
|
|
103
|
+
... None
|
|
104
|
+
... )
|
|
105
|
+
[WorktreeInfo(path=Path('/repo/feat-2'), branch='feat-2', is_root=False, is_bare=False)]
|
|
106
|
+
"""
|
|
107
|
+
removable: list[WorktreeInfo] = []
|
|
108
|
+
target_resolved = target_worktree_path.resolve()
|
|
109
|
+
source_resolved = source_worktree_path.resolve() if source_worktree_path else None
|
|
110
|
+
|
|
111
|
+
for worktree in all_worktrees:
|
|
112
|
+
# Skip if no branch (detached HEAD)
|
|
113
|
+
if worktree.branch is None:
|
|
114
|
+
continue
|
|
115
|
+
|
|
116
|
+
# Skip if branch is not in the consolidation range
|
|
117
|
+
if worktree.branch not in stack_to_consolidate:
|
|
118
|
+
continue
|
|
119
|
+
|
|
120
|
+
# Skip the target worktree
|
|
121
|
+
if worktree.path.resolve() == target_resolved:
|
|
122
|
+
continue
|
|
123
|
+
|
|
124
|
+
# Skip source worktree if creating new worktree (will be removed separately)
|
|
125
|
+
if source_resolved is not None and worktree.path.resolve() == source_resolved:
|
|
126
|
+
continue
|
|
127
|
+
|
|
128
|
+
# Never remove root worktree (CRITICAL safety check)
|
|
129
|
+
if worktree.is_root:
|
|
130
|
+
continue
|
|
131
|
+
|
|
132
|
+
removable.append(worktree)
|
|
133
|
+
|
|
134
|
+
return removable
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def create_consolidation_plan(
|
|
138
|
+
all_worktrees: list[WorktreeInfo],
|
|
139
|
+
stack_branches: list[str],
|
|
140
|
+
end_branch: str | None,
|
|
141
|
+
target_worktree_path: Path,
|
|
142
|
+
source_worktree_path: Path | None = None,
|
|
143
|
+
) -> ConsolidationPlan:
|
|
144
|
+
"""Create a complete consolidation plan.
|
|
145
|
+
|
|
146
|
+
This is a convenience function that combines calculate_stack_range() and
|
|
147
|
+
identify_removable_worktrees() into a single operation.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
all_worktrees: All worktrees in the repository
|
|
151
|
+
stack_branches: Full stack from trunk to leaf
|
|
152
|
+
end_branch: Optional branch to consolidate up to (inclusive)
|
|
153
|
+
target_worktree_path: Path to target worktree
|
|
154
|
+
source_worktree_path: Path to source worktree (when creating new with --name)
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
Complete consolidation plan with all information needed for execution
|
|
158
|
+
|
|
159
|
+
Raises:
|
|
160
|
+
ValueError: If end_branch is not in stack_branches
|
|
161
|
+
"""
|
|
162
|
+
stack_to_consolidate = calculate_stack_range(stack_branches, end_branch)
|
|
163
|
+
|
|
164
|
+
worktrees_to_remove = identify_removable_worktrees(
|
|
165
|
+
all_worktrees,
|
|
166
|
+
stack_to_consolidate,
|
|
167
|
+
target_worktree_path,
|
|
168
|
+
source_worktree_path,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
return ConsolidationPlan(
|
|
172
|
+
stack_branches=stack_branches,
|
|
173
|
+
stack_to_consolidate=stack_to_consolidate,
|
|
174
|
+
worktrees_to_remove=worktrees_to_remove,
|
|
175
|
+
target_worktree_path=target_worktree_path,
|
|
176
|
+
source_worktree_path=source_worktree_path,
|
|
177
|
+
)
|