Created
January 13, 2026 12:27
-
-
Save fclairamb/298de10d487fc50311dde692acec3776 to your computer and use it in GitHub Desktop.
Simple worktrees (+ claude & zed)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| import os | |
| import sys | |
| import subprocess as run | |
| import shutil | |
| # Get the directory where this script is located | |
| script_dir = os.path.dirname(os.path.abspath(__file__)) | |
| # Handle clean command directly | |
| if len(sys.argv) >= 2 and sys.argv[1] in ['--clean', '-c']: | |
| wt_cmd = [os.path.join(script_dir, 'wt'), '--clean'] | |
| run.run(wt_cmd) | |
| sys.exit(0) | |
| if len(sys.argv) < 2: | |
| print("Usage: claudewt <branch-name> [query...] | --clean") | |
| sys.exit(1) | |
| # Use wt with claude command | |
| branch = sys.argv[1] | |
| query_args = sys.argv[2:] # Everything after the branch name | |
| # Build the wt command with claude | |
| wt_cmd = [os.path.join(script_dir, 'wt'), branch] | |
| # Add claude command if available, otherwise just shell | |
| if shutil.which('claude'): | |
| wt_cmd.extend(['claude', '--dangerously-skip-permissions']) | |
| # Add query arguments if provided | |
| if query_args: | |
| wt_cmd.extend(query_args) | |
| run.run(wt_cmd) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| import os | |
| import sys | |
| import re | |
| import subprocess as run | |
| def clean_worktrees(): | |
| """Remove worktrees with no uncommitted files or unpushed commits""" | |
| git_root = run.check_output(['git', 'rev-parse', '--show-toplevel']).decode().strip() | |
| repo = os.path.basename(git_root) | |
| # List all worktrees | |
| worktrees = run.check_output(['git', 'worktree', 'list', '--porcelain']).decode().strip().split('\n\n') | |
| for worktree_info in worktrees: | |
| lines = worktree_info.strip().split('\n') | |
| if not lines: | |
| continue | |
| worktree_path = lines[0].replace('worktree ', '') | |
| # Skip main worktree | |
| if worktree_path == git_root: | |
| continue | |
| # Check if it's one of our managed worktrees | |
| if not os.path.basename(worktree_path).startswith(f"{repo}-"): | |
| continue | |
| print(f"Checking worktree: {worktree_path}") | |
| # Change to worktree directory | |
| try: | |
| os.chdir(worktree_path) | |
| except FileNotFoundError: | |
| print(f" Worktree path missing, removing: {worktree_path}") | |
| run.run(['git', 'worktree', 'remove', '--force', worktree_path]) | |
| continue | |
| # Check for uncommitted changes | |
| status = run.run(['git', 'status', '--porcelain'], capture_output=True).stdout.decode().strip() | |
| if status: | |
| print(" Has uncommitted changes, keeping") | |
| continue | |
| # Check for unpushed commits | |
| try: | |
| # Get current branch | |
| branch = run.check_output(['git', 'branch', '--show-current']).decode().strip() | |
| if not branch: | |
| print(" No current branch, removing") | |
| run.run(['git', 'worktree', 'remove', '--force', worktree_path]) | |
| continue | |
| # Check if branch has upstream | |
| upstream = run.run(['git', 'rev-parse', '--abbrev-ref', f'{branch}@{{upstream}}'], | |
| capture_output=True, text=True) | |
| if upstream.returncode != 0: | |
| # No upstream - check if branch has any commits beyond main | |
| try: | |
| result = run.run(['git', 'rev-list', '--count', f'main..{branch}'], | |
| capture_output=True, text=True) | |
| if result.returncode == 0: | |
| commits = int(result.stdout.strip()) | |
| if commits > 0: | |
| print(f" Has {commits} commits vs main, keeping") | |
| continue | |
| else: | |
| print(" No upstream and no new commits, removing") | |
| run.run(['git', 'worktree', 'remove', '--force', worktree_path]) | |
| continue | |
| except Exception: | |
| print(" No upstream, removing") | |
| run.run(['git', 'worktree', 'remove', '--force', worktree_path]) | |
| continue | |
| # Check for unpushed commits | |
| ahead = run.check_output(['git', 'rev-list', '--count', f'{branch}@{{upstream}}..{branch}']).decode().strip() | |
| if int(ahead) > 0: | |
| print(f" Has {ahead} unpushed commits, keeping") | |
| continue | |
| except Exception as e: | |
| print(f" Error checking push status: {e}, keeping") | |
| continue | |
| print(" Clean worktree, removing") | |
| run.run(['git', 'worktree', 'remove', '--force', worktree_path]) | |
| if len(sys.argv) < 2: | |
| print("Usage: wt <branch-name> [command] | --clean") | |
| print(" wt <branch-name> - Create/switch to worktree and start shell") | |
| print(" wt <branch-name> [command] - Create/switch to worktree and run command") | |
| print(" wt --clean - Clean up empty worktrees") | |
| sys.exit(1) | |
| # Handle clean command | |
| if sys.argv[1] in ['--clean', '-c']: | |
| clean_worktrees() | |
| sys.exit(0) | |
| # Get current state | |
| git_root = run.check_output(['git', 'rev-parse', '--show-toplevel']).decode().strip() | |
| rel_path = os.path.relpath(os.getcwd(), git_root) | |
| branch = re.sub(r'[^a-zA-Z0-9/_-]', '-', sys.argv[1]).strip('-') | |
| repo = os.path.basename(git_root) | |
| wt_path = f"{git_root}/../{repo}-{branch.replace('/', '-')}" | |
| # Check if branch already exists (before creating worktree) | |
| branch_exists = run.run(['git', 'show-ref', '--verify', '--quiet', f'refs/heads/{branch}'], | |
| capture_output=True).returncode == 0 | |
| # Create or switch to worktree | |
| if os.path.isdir(wt_path): | |
| print(f"Switching to existing worktree: {wt_path}") | |
| os.chdir(wt_path) | |
| # Ensure we're on the right branch | |
| if branch_exists: | |
| run.run(['git', 'checkout', branch]) | |
| else: | |
| run.run(['git', 'checkout', '-b', branch, 'origin/main']) | |
| else: | |
| if branch_exists: | |
| print(f"Creating worktree with existing branch '{branch}': {wt_path}") | |
| run.run(['git', 'worktree', 'add', wt_path, branch]) | |
| else: | |
| print(f"Creating worktree with new branch '{branch}' from origin/main: {wt_path}") | |
| run.run(['git', 'worktree', 'add', '-b', branch, wt_path, 'origin/main']) | |
| os.chdir(wt_path) | |
| # Navigate to same relative path | |
| target_path = f"{wt_path}/{rel_path}" | |
| if os.path.isdir(target_path): | |
| print(f"Navigating to: {target_path}") | |
| os.chdir(target_path) | |
| else: | |
| print(f"Warning: {target_path} not found, staying in worktree root") | |
| # Execute command if provided, otherwise start shell | |
| if len(sys.argv) > 2: | |
| # Run the provided command with all remaining arguments | |
| command = sys.argv[2:] | |
| run.run(command) | |
| else: | |
| # Start shell | |
| run.run([os.environ.get('SHELL', '/bin/sh')]) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| import os | |
| import sys | |
| import subprocess as run | |
| import shutil | |
| # Get the directory where this script is located | |
| script_dir = os.path.dirname(os.path.abspath(__file__)) | |
| # Handle clean command directly | |
| if len(sys.argv) >= 2 and sys.argv[1] in ['--clean', '-c']: | |
| wt_cmd = [os.path.join(script_dir, 'wt'), '--clean'] | |
| run.run(wt_cmd) | |
| sys.exit(0) | |
| if len(sys.argv) < 2: | |
| print("Usage: zedwt <branch-name> | --clean") | |
| sys.exit(1) | |
| # Use wt with zed command | |
| branch = sys.argv[1] | |
| # Build the wt command with zed | |
| wt_cmd = [os.path.join(script_dir, 'wt'), branch] | |
| # Add zed command if available, otherwise just shell | |
| if shutil.which('zed'): | |
| wt_cmd.extend(['zed', '.']) | |
| else: | |
| print("Warning: 'zed' command not found, starting shell instead") | |
| run.run(wt_cmd) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment