Last active
January 17, 2026 05:22
-
-
Save FranDepascuali/c0b4912dc458439326dc450ebd87c4b9 to your computer and use it in GitHub Desktop.
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
| #!/bin/bash | |
| # ============================================================================ | |
| # Git Worktree Workflow for AI Agents | |
| # ============================================================================ | |
| # | |
| # Commands: | |
| # wa <branch> [base] Create worktree + branch, cd into it | |
| # wd [-f] Delete current worktree and branch | |
| # wm <branch> Rebase branch onto current, merge, delete worktree | |
| # wc <branch> Switch to a worktree by branch name | |
| # wl List all worktrees | |
| # ws Status overview | |
| # wp Prune stale worktree references | |
| # | |
| # Example workflow: | |
| # wa claude-refactor # create worktree from current branch | |
| # # ... AI does work, makes commits ... | |
| # wc main # switch back to main | |
| # wm claude-refactor # rebase, merge, cleanup | |
| # | |
| # Install: | |
| # Add to ~/.zshrc or ~/.bashrc: | |
| # source ~/path/to/agent-worktree.sh | |
| # | |
| # ============================================================================ | |
| # Create a new worktree and branch for an AI agent to work in | |
| # Usage: wa <branch-name> [base-branch] | |
| wa() { | |
| if [[ -z "$1" ]]; then | |
| echo "Usage: wa <branch-name> [base-branch]" | |
| echo " branch-name: name for the new branch" | |
| echo " base-branch: branch to base off of (default: current branch)" | |
| return 1 | |
| fi | |
| local branch="$1" | |
| local base_branch="${2:-HEAD}" | |
| local repo_name="$(basename "$(git rev-parse --show-toplevel 2>/dev/null)")" | |
| local worktree_path="../${repo_name}--${branch}" | |
| # Check if we're in a git repo | |
| if ! git rev-parse --git-dir > /dev/null 2>&1; then | |
| echo "Error: Not in a git repository" | |
| return 1 | |
| fi | |
| # Check if branch already exists | |
| if git show-ref --verify --quiet "refs/heads/$branch"; then | |
| echo "Error: Branch '$branch' already exists" | |
| return 1 | |
| fi | |
| # Create the worktree with a new branch | |
| git worktree add -b "$branch" "$worktree_path" "$base_branch" | |
| # Trust mise/asdf/direnv if available | |
| command -v mise > /dev/null && mise trust "$worktree_path" 2>/dev/null | |
| command -v direnv > /dev/null && direnv allow "$worktree_path" 2>/dev/null | |
| # Copy over any local env files that might be needed | |
| [[ -f .env ]] && cp .env "$worktree_path/" | |
| [[ -f .env.local ]] && cp .env.local "$worktree_path/" | |
| echo "" | |
| echo "✓ Worktree created at: $worktree_path" | |
| echo "✓ Branch: $branch" | |
| echo "" | |
| echo "To enter: cd $worktree_path" | |
| echo "To delete: wd (from within the worktree)" | |
| # Optionally cd into it | |
| cd "$worktree_path" | |
| } | |
| # Delete current worktree and its branch | |
| # Usage: wd [--force] | |
| wd() { | |
| local force=false | |
| [[ "$1" == "--force" || "$1" == "-f" ]] && force=true | |
| local cwd="$(pwd)" | |
| local worktree_dir="$(basename "$cwd")" | |
| # Check if this looks like a worktree (contains --) | |
| if [[ "$worktree_dir" != *"--"* ]]; then | |
| echo "Error: This doesn't look like a worktree directory" | |
| echo "Expected format: repo-name--branch-name" | |
| return 1 | |
| fi | |
| # Parse the directory name | |
| local repo_name="${worktree_dir%%--*}" | |
| local branch="${worktree_dir#*--}" | |
| local main_repo="../$repo_name" | |
| # Verify the main repo exists | |
| if [[ ! -d "$main_repo/.git" && ! -f "$main_repo/.git" ]]; then | |
| echo "Error: Cannot find main repository at $main_repo" | |
| return 1 | |
| fi | |
| # Confirm deletion unless --force | |
| if [[ "$force" != true ]]; then | |
| echo "This will remove:" | |
| echo " • Worktree: $cwd" | |
| echo " • Branch: $branch" | |
| echo "" | |
| echo -n "Continue? [y/N] " | |
| read -r REPLY | |
| if [[ ! $REPLY =~ ^[Yy]$ ]]; then | |
| echo "Cancelled" | |
| return 0 | |
| fi | |
| fi | |
| # Go to main repo and clean up | |
| cd "$main_repo" | |
| git worktree remove "$worktree_dir" --force | |
| git branch -D "$branch" | |
| echo "" | |
| echo "✓ Removed worktree and branch: $branch" | |
| } | |
| # List all worktrees | |
| # Usage: wl | |
| wl() { | |
| git worktree list --porcelain | awk '/^worktree /{path=$2} /^branch /{gsub("refs/heads/","",$2); print " " $2 "\t" path}' | |
| } | |
| # Quick status of all worktrees | |
| # Usage: ws | |
| ws() { | |
| echo "=== Worktrees ===" | |
| git worktree list | |
| echo "" | |
| echo "=== Branches with worktrees ===" | |
| git worktree list --porcelain | grep "^branch" | sed 's/branch refs\/heads\// /' | |
| } | |
| # Prune stale worktree references | |
| # Usage: wp | |
| wp() { | |
| git worktree prune -v | |
| } | |
| # Finish worktree: rebase onto current branch, merge, and delete worktree | |
| # Usage: wm <branch> | |
| wm() { | |
| if [[ -z "$1" ]]; then | |
| echo "Usage: wm <branch>" | |
| echo "" | |
| echo "Available worktrees:" | |
| git worktree list --porcelain | awk '/^worktree /{path=$2} /^branch /{gsub("refs/heads/","",$2); print " " $2 "\t" path}' | |
| return 1 | |
| fi | |
| local branch="$1" | |
| local target="$(git branch --show-current)" | |
| # Find the worktree path for this branch | |
| local worktree_path | |
| worktree_path=$(git worktree list --porcelain | grep -B2 "branch refs/heads/$branch$" | grep "^worktree " | sed 's/^worktree //') | |
| if [[ -z "$worktree_path" ]]; then | |
| echo "Error: No worktree found for branch '$branch'" | |
| return 1 | |
| fi | |
| # Check for uncommitted changes in the worktree | |
| if ! git -C "$worktree_path" diff-index --quiet HEAD -- 2>/dev/null; then | |
| echo "Error: Worktree has uncommitted changes. Commit or stash them first." | |
| return 1 | |
| fi | |
| echo "This will:" | |
| echo " 1. Rebase '$branch' onto '$target'" | |
| echo " 2. Fast-forward '$target' with commits from '$branch'" | |
| echo " 3. Delete worktree and branch" | |
| echo "" | |
| echo -n "Continue? [y/N] " | |
| read -r REPLY | |
| if [[ ! $REPLY =~ ^[Yy]$ ]]; then | |
| echo "Cancelled" | |
| return 0 | |
| fi | |
| # Rebase the worktree branch onto current branch | |
| git -C "$worktree_path" rebase "$target" | |
| if [[ $? -ne 0 ]]; then | |
| echo "Error: Rebase failed. Resolve conflicts in $worktree_path and try again." | |
| return 1 | |
| fi | |
| # Fast-forward merge | |
| git merge --ff-only "$branch" | |
| # Clean up worktree and branch | |
| local worktree_dir="$(basename "$worktree_path")" | |
| git worktree remove "$worktree_dir" --force | |
| git branch -D "$branch" | |
| echo "" | |
| echo "✓ Merged '$branch' onto '$target' and cleaned up" | |
| } | |
| # Switch to a worktree by branch name | |
| # Usage: wc <branch-name> | |
| wc() { | |
| if [[ -z "$1" ]]; then | |
| echo "Usage: wc <branch-name>" | |
| echo "" | |
| echo "Available worktrees:" | |
| git worktree list --porcelain | awk '/^worktree /{path=$2} /^branch /{gsub("refs/heads/","",$2); print " " $2 "\t" path}' | |
| return 1 | |
| fi | |
| local branch="$1" | |
| local worktree_path | |
| # Find the worktree path for this branch | |
| worktree_path=$(git worktree list --porcelain | grep -B2 "branch refs/heads/$branch$" | grep "^worktree " | sed 's/^worktree //') | |
| if [[ -z "$worktree_path" ]]; then | |
| echo "Error: No worktree found for branch '$branch'" | |
| echo "" | |
| echo "Available worktrees:" | |
| git worktree list --porcelain | awk '/^worktree /{path=$2} /^branch /{gsub("refs/heads/","",$2); print " " $2 "\t" path}' | |
| return 1 | |
| fi | |
| cd "$worktree_path" | |
| } |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Install it with: