-
Notifications
You must be signed in to change notification settings - Fork 4
Description
branch: rewrite-ta-wt-python
Spec: Rewrite ta-wt in Python
Overview
Rewrite scripts/ta-wt (818 lines of zsh) to Python 3 using stdlib only (no pip dependencies). The script is a git worktree manager with 6 subcommands (list, create, remove, prune, status, merge). The rewrite replaces hand-rolled arg parsing with argparse, manual JSON construction with the json module, and parallel arrays with dataclasses.
The file stays named ta-wt (no .py extension) with a #!/usr/bin/env python3 shebang so the ta dispatcher and setup symlink mechanism work unchanged.
Constraints:
- stdlib only —
argparse,json,subprocess,pathlib,dataclasses,shutil,os,sys - Python 3.9+ (ships with macOS, available on all CI runners)
- Identical CLI interface — same subcommands, same flags, same exit codes
- Identical JSON output schema —
ta-workspacedepends on it - Identical text output column widths
Architecture
scripts/ta-wt (Python, ~500-600 lines)
├── Git class — subprocess wrapper (run, output methods)
├── Worktree — dataclass (path, branch)
├── Core functions
│ ├── parse_worktrees() — parse `git worktree list --porcelain`
│ ├── dirty_status() — parse `git status --porcelain`
│ ├── ahead_behind() — `git rev-list --left-right --count`
│ ├── classify_status() — multi-stage classification
│ ├── has_active_operation() — detect rebase/merge/cherry-pick
│ └── default_wt_path() — generate worktree path from branch
├── Subcommand handlers (cmd_list, cmd_create, cmd_remove, cmd_prune, cmd_status, cmd_merge)
├── build_parser() — argparse with subparsers
└── main()
Git class
class Git:
def __init__(self, cwd=None): ...
def run(self, *args, check=True, cwd=None) -> CompletedProcess:
"""Run git command. cwd overrides instance default."""
def output(self, *args, **kw) -> str:
"""Run and return stripped stdout."""Subcommand CLI mapping
| Subcommand | Positional args | Flags |
|---|---|---|
list |
— | --json, --full |
create |
branch, [path] |
--remote <name>, --from=<base> |
remove |
branch |
--force |
prune |
— | --apply |
status |
— | --json |
merge |
branch |
--target <branch>, --message <msg>, --message-file <path> |
JSON output schemas (must be preserved exactly)
list --json:
[{"branch": "...", "status": "...", "ahead": 0, "behind": 0, "path": "..."}]list --json --full:
[{"branch": "...", "status": "...", "ahead": 0, "behind": 0, "path": "...", "commit_message": "...", "commit_date": "..."}]status --json:
[{"branch": "...", "status": "...", "ahead": 0, "behind": 0, "dirty": "...", "path": "..."}]Note: ahead/behind must be integers (not strings). Field order must match the above.
Text output column widths
list: %-24s %-14s %-6s %-7s %s (BRANCH, STATUS, AHEAD, BEHIND, PATH)
list --full: %-24s %-14s %-6s %-7s %-30s %s (+ LAST COMMIT)
status: %-24s %-10s %s ahead, %s (BRANCH, classification, ahead, dirty)
Classification logic (classify_status)
Order matters — first match wins:
- current — worktree path matches CWD
- merged —
git merge-base --is-ancestor, orgit merge-tree --write-treetree comparison, orgh pr list --state merged(useshutil.which("gh")) - wip — dirty working tree
- conflict —
git merge-treedetects conflicts (old three-way form) - almost — ahead of base with unpushed commits
- ready — ahead of base, all pushed (or no upstream)
Cross-script interaction
cmd_remove calls ta-workspace kill <branch> for cleanup. Find the script via os.path.dirname(os.path.realpath(__file__)) (equivalent of zsh ${0:A:h}). Suppress all errors.
Exit codes
- 0: success
- 1: runtime error (branch not found, dirty worktree, merge conflict)
- 2: usage/argument error (argparse handles this automatically)
Error message prefix
All error messages use ta: prefix (e.g., ta: no worktree found for branch 'foo'), matching current behavior.
1. Core Functions
parse_worktrees(git) -> list[Worktree]
Parse git worktree list --porcelain. Skip bare worktrees. Handle detached HEAD as (detached). Return list of Worktree(path, branch) dataclass instances.
dirty_status(git, wt_dir) -> str
Parse git status --porcelain in the given directory. Count staged (S), modified (M), untracked (U). Return "clean" or "dirty(2S,1M,3U)" format.
ahead_behind(git, branch, base="main") -> tuple[int, int]
Run git rev-list --left-right --count {branch}...{base}. Return (ahead, behind). Return (0, 0) for detached or on error.
classify_status(git, wt_dir, branch, current_wt, base="main") -> str
Implement the multi-stage classification described above. For squash-merge detection:
git merge-tree --write-tree base branch— compare first line of output togit rev-parse "base^{tree}"gh pr list --head branch --state merged --json number --limit 1— check for non-empty result
has_active_operation(git, wt_dir) -> bool
Check for rebase-merge/, rebase-apply/ dirs and MERGE_HEAD, CHERRY_PICK_HEAD files in the git dir.
default_wt_path(git, branch) -> str
Get repo root via git rev-parse --show-toplevel. Sanitize branch (replace / with -). Return {parent}/{repo_name}-{sanitized_branch}.
2. Subcommands
cmd_list
Parse worktrees, compute dirty status and ahead/behind for each. Output as text table or JSON. With --full, include commit message (truncated to 25 chars + ... if > 28) and commit date (ISO 8601).
cmd_create
Two modes:
- Tracking (default): validate branch exists on remote via
git ls-remote, thengit worktree add --track -b. --from=<base>: create new local branch from base, or checkout existing branch if it's a descendant of base.
Remote resolution: --remote flag > upstream remote > origin remote.
cmd_remove
Find worktree by branch name (parse porcelain). Check dirty status unless --force. Remove worktree, delete local branch with git branch -D, call ta-workspace kill.
cmd_prune
Find merged worktrees (same three-stage detection as classify_status). Skip main, detached, current, dirty, and active-operation worktrees. Dry-run by default, --apply to execute.
cmd_status
Classify each non-main worktree. Output as text or JSON.
cmd_merge
Squash-merge branch into target (default: main). Validate both worktrees are clean. On conflict, abort and reset. Commit with --message, --message-file, or default message. Clean up worktree via cmd_remove --force.
Implementation Plan
Each step follows this structure:
- Implement — Write the code
- Test — Write BATS tests
- Verify — Run tests, fix failures until all pass
- Review — Code review for bugs, edge cases, and conventions
- Address feedback — Fix review findings, re-run tests, re-review until clean
- Update spec — Mark the step
[done]and record any decisions or deviations
Spec maintenance rules
- Mark each step
[done]when complete. - Record design decisions that emerged during implementation as notes under the step.
- Minor deviations (e.g. flag name changes, reordered logic) should be noted and the spec updated to match.
- Significant design changes (e.g. new subcommands, changed architecture, removed features) require pausing for user review before proceeding.
Step 1: Write Python ta-wt with core infrastructure and list subcommand [done]
Files:
scripts/ta-wt— Replace zsh with Python
Implement:
- Add
#!/usr/bin/env python3shebang - Implement
Gitclass withrun()andoutput()methods - Implement
Worktreedataclass - Implement
parse_worktrees(),dirty_status(),ahead_behind() - Implement
cmd_listwith text and JSON output (both--jsonand--full) - Implement
build_parser()withlistsubparser - Implement
main()with dispatch chmod +x scripts/ta-wt
Test: Run list-related bats tests.
Verify: Run bats tests/test_ta_wt.bats — list tests pass.
Review: Verify JSON schema matches exactly (field names, types, order). Verify text column widths match.
Address feedback: Fix all review findings. Re-run tests. Re-review if changes were substantial.
Step 2: Add create subcommand [done]
Files:
scripts/ta-wt— Add create subcommand
Implement:
- Implement
default_wt_path() - Implement
cmd_createwith both tracking and--frommodes - Add
createsubparser tobuild_parser()
Test: Run create-related bats tests.
Verify: Run bats tests/test_ta_wt.bats — create tests pass.
Review: Verify remote resolution logic (upstream > origin fallback). Verify branch validation and error messages.
Address feedback: Fix all review findings. Re-run tests. Re-review if changes were substantial.
Step 3: Add remove subcommand [done]
Files:
scripts/ta-wt— Add remove subcommand
Implement:
- Implement
cmd_removewith dirty check and--forceflag - Implement
ta-workspace killcleanup call usingos.path.realpath(__file__) - Add
removesubparser
Test: Run remove-related bats tests.
Verify: Run bats tests/test_ta_wt.bats — remove tests pass.
Review: Verify workspace cleanup is best-effort (errors suppressed). Verify branch deletion uses -D flag.
Address feedback: Fix all review findings. Re-run tests. Re-review if changes were substantial.
Step 4: Add status and prune subcommands [done]
Files:
scripts/ta-wt— Add status and prune subcommands
Implement:
- Implement
classify_status()with full multi-stage logic - Implement
has_active_operation() - Implement
cmd_statuswith text and JSON output - Implement
cmd_prunewith dry-run/apply modes - Add
statusandprunesubparsers
Test: Run status and prune bats tests.
Verify: Run bats tests/test_ta_wt.bats — status and prune tests pass.
Review: Verify classification order matches zsh version exactly. Verify squash-merge detection (merge-tree + gh fallback). Verify prune skips main/detached/current/dirty/active-op worktrees.
Address feedback: Fix all review findings. Re-run tests. Re-review if changes were substantial.
Step 5: Add merge subcommand
Files:
scripts/ta-wt— Add merge subcommand
Implement:
- Implement
cmd_mergewith squash merge, conflict detection, and cleanup - Support
--target,--message,--message-fileflags - Validate mutual exclusivity of
--messageand--message-file - Add
mergesubparser
Test: Run merge-related bats tests.
Verify: Run bats tests/test_ta_wt.bats — merge tests pass.
Review: Verify conflict detection aborts and resets cleanly. Verify --message-file validation. Verify worktree cleanup after successful merge.
Address feedback: Fix all review findings. Re-run tests. Re-review if changes were substantial.
Step 6: Update bats test invocations [done]
Files:
tests/test_ta_wt.bats— Update all test invocations
Implement:
- Replace all
run zsh "$TA_WT"withrun "$TA_WT"throughout the file - Verify no other zsh-specific invocation patterns remain
Test: Run all bats tests.
Verify: Run bats tests/test_ta_wt.bats — all 104 tests pass.
Review: Verify no zsh references remain in test invocations.
Address feedback: Fix all review findings. Re-run tests. Re-review if changes were substantial.
Step 7: Run all checks
Implement:
- Run the full test suite:
bats tests/test_ta_wt.bats - Run
python3 -m py_compile scripts/ta-wtto verify syntax - Fix any failures
Verify: All checks pass clean.
Step 8: Create commit
Implement:
- Stage
scripts/ta-wtandtests/test_ta_wt.bats - Create a commit:
Rewrite ta-wt in Python (stdlib only)
Verify: git log -1 shows the commit.
Conventions
- Language: Python 3.9+ (stdlib only)
- Tests: BATS (existing regression suite, updated invocations)
- Error messages: Prefix with
ta:(e.g.,ta: no worktree found for branch 'foo') - Exit codes: 0=success, 1=runtime error, 2=usage error