Skip to content

Instantly share code, notes, and snippets.

@andkirby
Last active December 22, 2025 16:49
Show Gist options
  • Select an option

  • Save andkirby/e44e984a061a6b61b02249721d11677b to your computer and use it in GitHub Desktop.

Select an option

Save andkirby/e44e984a061a6b61b02249721d11677b to your computer and use it in GitHub Desktop.
git worktree alias for markdown-ticket projects

Git Worktree Manager (git wt) - Complete Guide

Transform complex git worktree management into simple, ticket-based development. git wt 101 automatically creates isolated workspaces using dynamic path templates and project codes, replacing lengthy git commands with intelligent defaults.

Quick Start

1. First-time Setup

# Set your worktree location (choose one):
git config --global worktree.defaultPath ".gitWT/{worktree_name}"      # Inside repo
git config --global worktree.defaultPath "~/worktrees/{worktree_name}"  # Outside repo
git config worktree.defaultPath "~/home/markdown-ticket-{worktree_name}" # Custom location

# NEW: Include project directory name (e.g., super-mario for path /home/projects/super-mario)
git config --global worktree.defaultPath "/worktrees/{project_dir}-{worktree_name}"
git config --global worktree.defaultPath "../{project_dir}_{worktree_name}"

# Or set per-repo:
git config worktree.defaultPath ".gitWT/{worktree_name}"

2. Create Worktrees

# Just 3 digits (reads project code from .mdt-config.toml)
git wt 101

# Full ticket name
git wt MDT-101

# Custom ticket format
git wt PROJ-123

3. Remove Worktrees

# Remove by ticket number (automatically finds path)
git wt-rm 101
git wt-rm MDT-101
git wt-rm PROJ-123

# Manual removal (require full path)
git worktree remove path/to/worktree

4. Work with Worktrees

# List all worktrees
git worktree list

# Clean up stale worktrees
git worktree prune

Table of Contents

  1. Overview
  2. Installation
  3. Configuration
  4. Usage
  5. Worktree Management
  6. Advanced Features
  7. IDE Integration
  8. Troubleshooting
  9. Examples

Overview

git wt is a custom Git alias that simplifies creating worktrees for ticket-based development. It automatically:

  • Extracts ticket numbers from input
  • Reads project configuration from .mdt-config.toml
  • Creates worktrees in configurable locations
  • Generates IntelliJ IDEA scope files for focused development
  • Supports both relative and absolute paths

Key Benefits

  • Isolated Development: Each ticket gets its own worktree
  • IDE Scoping: Automatic IDEA scope creation for ticket-specific file filtering
  • Flexible Paths: Store worktrees inside or outside the repository
  • Smart Naming: Automatically constructs worktree names from project config

Installation

Prerequisites

  • Git 2.15+ (for worktree support)
  • Bash/Zsh shell
  • Optional: IntelliJ IDEA (for automatic scope support)

Install the Alias

Use the command from this gist file: https://gist.github.com/andkirby/e44e984a061a6b61b02249721d11677b#file-git_config_alias_worktree-sh


Configuration

Global Configuration

Set default worktree location for all repositories:

# Inside repository (recommended)
git config --global worktree.defaultPath ".gitWT/{worktree_name}"

# Outside repository
git config --global worktree.defaultPath "~/worktrees/{worktree_name}"

# Custom location with project prefix
git config --global worktree.defaultPath "~/home/markdown-ticket-{worktree_name}"

# Using $HOME directly (no ~ expansion needed)
git config --global worktree.defaultPath "$HOME/worktrees/{worktree_name}"

# NEW: Include project directory name (e.g., super-mario)
git config --global worktree.defaultPath "/worktrees/{project_dir}-{worktree_name}"
git config --global worktree.defaultPath "../{project_dir}_{worktree_name}"

Local Configuration

Set worktree location for current repository only:

# Relative to repository root
git config worktree.defaultPath ".gitWT/{worktree_name}"

# Absolute path
git config worktree.defaultPath "/Users/username/worktrees/{worktree_name}"

# Without placeholder (will append worktree name)
git config worktree.defaultPath ".gitWT"

# NEW: Include project directory name
git config worktree.defaultPath "../{project_dir}_{worktree_name}"
git config worktree.defaultPath "~/workspaces/{project_dir}-{worktree_name}"

Project Configuration

Configure project code in .mdt-config.toml:

[project]
name = "Markdown Ticket"
code = "MDT"
cr_path = "docs/CRs"

Usage

Basic Usage

# Create worktree with just ticket number
git wt 101
# Output: Creates MDT-101 if project code is MDT in .mdt-config.toml

# Create worktree with full ticket name
git wt MDT-101

# Create worktree with custom prefix
git wt PROJ-123

Checking Configuration

# Check current configuration
git config worktree.defaultPath          # Local config
git config --global worktree.defaultPath # Global config

# Check all git configs
git config --list | grep worktree

Managing Worktrees

# List all worktrees
git worktree list

# Switch to a worktree
cd path/to/worktree

# Remove a worktree (deletes branch unless used elsewhere)
git worktree remove path/to/worktree

# Remove worktree but keep branch
git worktree remove --force path/to/worktree

# Clean up stale worktree references
git worktree prune

Worktree Management

Creating Worktrees (git wt)

The git wt command creates isolated workspaces:

# Basic usage - just ticket number
git wt 101          # Creates worktree for MDT-101 (reads project code)

# Full ticket name
git wt MDT-101      # Creates worktree for MDT-101

# Custom project codes
git wt PROJ-123     # Creates worktree for PROJ-123

Features:

  • Automatically reads project code from .mdt-config.toml
  • Uses configurable path templates with placeholders
  • Creates branches automatically
  • Supports both relative and absolute paths

Removing Worktrees (git wt-rm)

The git wt-rm command safely removes worktrees using the same ticket logic:

# Remove by ticket number (automatically finds worktree path)
git wt-rm 101       # Removes MDT-101 worktree and branch
git wt-rm MDT-101   # Removes MDT-101 worktree and branch
git wt-rm PROJ-123  # Removes PROJ-123 worktree and branch

Safety Features:

  • Path Discovery: Automatically finds worktree using same config logic as git wt
  • Interactive Confirmation: Prompts before removing worktree and branch
  • Smart Branch Cleanup: Offers to delete branch if no other worktrees use it
  • Error Handling: Lists available worktrees if target not found

Removal Process:

  1. Finds worktree path using worktree.defaultPath configuration
  2. Confirms removal with user
  3. Removes worktree directory
  4. Optionally deletes the branch if safe

Example Output:

$ git wt-rm 101
Found worktree: /projects/markdown-ticket/.gitWT/MDT-101
Branch: MDT-101

Remove worktree and branch? [y/N] y
✓ Removed worktree: /projects/markdown-ticket/.gitWT/MDT-101
Delete branch "MDT-101"? [y/N] y
✓ Deleted branch: MDT-101
✓ Worktree removal completed

Manual Worktree Management

For advanced usage, you can still use native git commands:

# List all worktrees
git worktree list

# Manual removal (requires full path)
git worktree remove /path/to/worktree

# Remove worktree but keep branch
git worktree remove --force /path/to/worktree

# Clean up stale worktree references
git worktree prune

Advanced Features

Path Handling

The script automatically handles:

  1. Relative Paths: Relative to repository root

    • Config: .gitWT/{worktree_name}
    • Result: /repo/root/.gitWT/MDT-101
  2. Absolute Paths: Full paths from filesystem root

    • Config: ~/worktrees/{worktree_name}
    • Result: /Users/username/worktrees/MDT-101
  3. Home Directory: Both ~ and $HOME supported

    • Config: ~/worktrees/{worktree_name} or $HOME/worktrees/{worktree_name}
  4. Appending: If no {worktree_name} placeholder

    • Config: .gitWT
    • Result: .gitWT/MDT-101
  5. NEW: Project Folder Placeholder: Uses repository folder name

    • Config: /worktrees/{project_dir}-{worktree_name}

    • From repo /projects/super-mario, creates: /worktrees/super-mario-MDT-123

    • Config: ../{project_dir}_{worktree_name}

    • From repo /projects/super-mario, creates: /projects/super-mario_MDT-123

Available Placeholders

  • {worktree_name}: The branch/worktree name (e.g., MDT-123, PROJ-456)
  • {project_dir}: Basename of git repository (e.g., super-mario, markdown-ticket)

Error Handling

The script provides clear error messages:

# Missing ticket number
$ git wt abc
error: Must include 3-digit ticket number. E.g. "123" or "MDT-123"

# Worktree already exists
$ git wt 101
error: Worktree already exists at /path/to/worktree
To remove it: git worktree remove /path/to/worktree

# Branch exists without worktree
$ git wt 101
error: Branch "MDT-101" already exists but has no worktree
To create worktree for existing branch: git worktree add /path/to/worktree MDT-101

Interactive Setup

When worktree.defaultPath is not configured, the script:

  1. Shows warning with explanation
  2. Provides examples of different configurations
  3. Offers to set default configuration
  4. Falls back to .gitWT/{worktree_name} if declined

Examples

Example 1: Standard Setup

# Initial setup
cd ~/projects/markdown-ticket
git config --global worktree.defaultPath ".gitWT/{worktree_name}"

# Create worktrees
git wt 101  # Creates .gitWT/MDT-101
git wt 102  # Creates .gitWT/MDT-102

# Work on ticket 101
cd .gitWT/MDT-101
# (IDEA automatically has ticket 101 scope)

Example 2: External Worktrees

# Setup external location
mkdir -p ~/workspaces
git config --global worktree.defaultPath "~/workspaces/{worktree_name}"

# Create worktree
git wt 205  # Creates ~/workspaces/MDT-205

# List worktrees
git worktree list
# /Users/kirby/projects/markdown-ticket           abc1234 [main]
# /Users/kirby/workspaces/MDT-205                 def5678 [MDT-205]

Example 3: Multiple Projects

# Project A (MDT prefix)
cd ~/projects/project-a
git config worktree.defaultPath ".gitWT/{worktree_name}"
git wt 001  # Creates .gitWT/MDT-001

# Project B (PROJ prefix)
cd ~/projects/project-b
git config worktree.defaultPath ".gitWT/{worktree_name}"
git wt 001  # Creates .gitWT/PROJ-001

Example 4: Project Folder Integration

# From /projects/super-mario repository
git config --global worktree.defaultPath "/worktrees/{project_dir}-{worktree_name}"

# Creates /worktrees/super-mario-MDT-345
git wt 345

# Relative to parent directory
git config worktree.defaultPath "../{project_dir}_{worktree_name}"

# From /projects/super-mario, creates: /projects/super-mario_MDT-345
git wt 345

Example 5: Custom Path Structure

# Organized by year
git config --global worktree.defaultPath "~/worktrees/2025/{worktree_name}"

# Creates ~/worktrees/2025/MDT-345
git wt 345

# Combine project directory with organization
git config --global worktree.defaultPath "~/worktrees/{project_dir}/{worktree_name}"

# From /projects/super-mario, creates: ~/worktrees/super-mario/MDT-345
git wt 345

Example 6: Recover from Stale Worktree

# Worktree directory deleted but reference remains
git worktree list
# /Users/kirby/.gitWT/MDT-123  abcdef0 [MDT-123]

# Clean up stale reference
git worktree prune

# Recreate worktree
git wt 123
error: Branch "MDT-123" already exists but has no worktree
# Use the suggested command:
git worktree add .gitWT/MDT-123 MDT-123

Example 7: Worktree Removal with git wt-rm

# List worktrees
git worktree list
# /Users/kirby/projects/markdown-ticket          abc1234 [main]
# /Users/kirby/projects/markdown-ticket/.gitWT/MDT-101  def5678 [MDT-101]

# Remove by ticket number
git wt-rm 101
Found worktree: /Users/kirby/projects/markdown-ticket/.gitWT/MDT-101
Branch: MDT-101

Remove worktree and branch? [y/N] y
✓ Removed worktree: /Users/kirby/projects/markdown-ticket/.gitWT/MDT-101
Delete branch "MDT-101"? [y/N] y
✓ Deleted branch: MDT-101
✓ Worktree removal completed

# Try to remove non-existent worktree
git wt-rm 999
error: Worktree not found at /Users/kirby/projects/markdown-ticket/.gitWT/MDT-999
Listing existing worktrees:
# /Users/kirby/projects/markdown-ticket          abc1234 [main]

Best Practices

  1. Consistent Naming: Always use the same project prefix across repositories
  2. Regular Cleanup: Run git worktree prune periodically
  3. Backup Strategy: Worktrees share .git directory, backup the main repo
  4. IDE Integration: Use the automatic IDEA scopes for focused development
  5. Configuration: Use global config for personal preferences, local for team standards

Tips

  • Use git worktree add directly for existing branches
  • Combine with other git aliases: git config --global alias.sw 'cd $(git worktree list | grep $(git branch --show-current) | cut -f1)'
  • Create aliases for common operations: git config --global alias.wtlist 'git worktree list'
  • Use with git hooks for additional automation
git config --global alias.wt '!f() {
worktree="$1"
if [ -z "$worktree" ]; then
echo "Usage: git wt <ticket-number>"
echo "Example: git wt 101"
echo "Example: git wt ABC-101"
exit 1
fi
if [[ ! "$worktree" =~ [0-9][0-9][0-9] ]]; then
echo "error: Must include 3-digit ticket number. E.g. \"123\" or \"ABC-123\"" >&2
exit 3
fi
ticket_number=$(echo "$worktree" | grep -Eo "[0-9][0-9][0-9]")
# check integration with https://github.com/andkirby/markdown-ticket
if [[ "$worktree" =~ ^[0-9][0-9][0-9]$ ]]; then
dot_config="$(git rev-parse --show-toplevel)/.mdt-config.toml"
if [ -f "$dot_config" ]; then
project_code=$(grep "^code = " "$dot_config" | cut -d"=" -f2 | tr -d " \"")
if [ -n "$project_code" ]; then
worktree="${project_code}-${ticket_number}"
else
worktree="${ticket_number}"
fi
else
worktree="${ticket_number}"
fi
fi
# Check local config first, then global
default_path=$(git config worktree.defaultPath 2>/dev/null || git config --global worktree.defaultPath 2>/dev/null)
if [ -z "$default_path" ]; then
echo "warning: worktree.defaultPath is not configured" >&2
echo ""
echo "This setting defines where worktrees are created. Use placeholders: {worktree_name}, {project_dir}"
echo "If {worktree_name} is not in the path, it will be appended."
echo "Examples:"
echo " - Relative: .gitWT/{worktree_name} (creates worktrees inside repo)"
echo " - Relative: .gitWT (worktree_name will be appended: .gitWT/ABC-122)"
echo " - Absolute: ~/worktrees/{worktree_name} (creates worktrees outside repo)"
echo " - With project: /worktrees/{project_dir}-{worktree_name}"
echo " - Relative: ../{project_dir}_{worktree_name}"
echo ""
echo "Placeholders:"
echo " - {worktree_name}: The branch/worktree name (e.g., ABC-123)"
echo " - {project_dir}: Basename of git repository (e.g., super-mario)"
echo ""
echo "To set globally: git config --global worktree.defaultPath \".gitWT/{worktree_name}\""
echo "To set locally: git config worktree.defaultPath \"~/worktrees/{worktree_name}\""
echo ""
read -p "Set global config to default (.gitWT/{worktree_name})? [Y/n] " response
if [[ "$response" =~ ^[Yy]?$ ]] || [ -z "$response" ]; then
git config --global worktree.defaultPath ".gitWT/{worktree_name}"
default_path=".gitWT/{worktree_name}"
echo "Set global worktree.defaultPath to: $default_path"
else
worktree_path="$(git rev-parse --show-toplevel)/.gitWT/$worktree"
parent_dir=$(dirname "$worktree_path")
[ ! -d "$parent_dir" ] && mkdir -p "$parent_dir"
git worktree add "$worktree_path" -b "$worktree"
echo "Created worktree: $worktree_path"
echo "Branch: $worktree"
exit 0
fi
fi
# Get project directory name (basename of git root)
project_dir="$(basename "$(git rev-parse --show-toplevel)")"
# Replace {project_dir} placeholder first, then {worktree_name}
if [[ "$default_path" == *"{project_dir}"* ]]; then
default_path_with_project=$(echo "$default_path" | sed "s/{project_dir}/$project_dir/g")
else
default_path_with_project="$default_path"
fi
if [[ "$default_path_with_project" == *"{worktree_name}"* ]]; then
worktree_path=$(echo "$default_path_with_project" | sed "s/{worktree_name}/$worktree/g")
else
default_path_with_project="${default_path_with_project%/}"
worktree_path="$default_path_with_project/$worktree"
fi
# Expand ~ to $HOME
worktree_path="${worktree_path/#\~/$HOME}"
if [[ "$worktree_path" == /* ]]; then
relative_flag="--no-relative-paths"
else
repo_root="$(git rev-parse --show-toplevel)"
worktree_path="$repo_root/$worktree_path"
relative_flag="--relative-paths"
fi
if [ -d "$worktree_path" ]; then
echo "error: Worktree already exists at $worktree_path" >&2
echo "To remove it: git worktree remove $worktree_path" >&2
exit 1
fi
if git show-ref --verify --quiet "refs/heads/$worktree"; then
echo "error: Branch \"$worktree\" already exists but has no worktree" >&2
echo "To create worktree for existing branch: git worktree add $relative_flag \"$worktree_path\" \"$worktree\"" >&2
exit 2
fi
parent_dir=$(dirname "$worktree_path")
if [ ! -d "$parent_dir" ]; then
mkdir -p "$parent_dir"
fi
git worktree add $relative_flag "$worktree_path" -b "$worktree"
echo "Created worktree: $worktree_path"
echo "Branch: $worktree"
echo "Using config: worktree.defaultPath = $default_path"
}; f'
git config --global alias.wt-rm '!f() {
worktree="$1"
if [ -z "$worktree" ]; then
echo "Usage: git wt-rm <ticket-number>"
echo "Example: git wt-rm 101"
echo "Example: git wt-rm ABC-101"
exit 1
fi
if [[ ! "$worktree" =~ [0-9][0-9][0-9] ]]; then
echo "error: Must include 3-digit ticket number. E.g. \"123\" or \"ABC-123\"" >&2
exit 3
fi
ticket_number=$(echo "$worktree" | grep -Eo "[0-9][0-9][0-9]")
# check integration with https://github.com/andkirby/markdown-ticket
if [[ "$worktree" =~ ^[0-9][0-9][0-9]$ ]]; then
dot_config="$(git rev-parse --show-toplevel)/.mdt-config.toml"
if [ -f "$dot_config" ]; then
project_code=$(grep "^code = " "$dot_config" | cut -d"=" -f2 | tr -d " \"")
if [ -n "$project_code" ]; then
worktree="${project_code}-${ticket_number}"
else
worktree="${ticket_number}"
fi
else
worktree="${ticket_number}"
fi
fi
# Check local config first, then global
default_path=$(git config worktree.defaultPath 2>/dev/null || git config --global worktree.defaultPath 2>/dev/null)
if [ -z "$default_path" ]; then
echo "warning: worktree.defaultPath is not configured" >&2
echo "Using default path: .gitWT/{worktree_name}" >&2
default_path=".gitWT/{worktree_name}"
fi
# Get project directory name (basename of git root)
project_dir="$(basename "$(git rev-parse --show-toplevel)")"
# Replace {project_dir} placeholder first, then {worktree_name}
if [[ "$default_path" == *"{project_dir}"* ]]; then
default_path_with_project=$(echo "$default_path" | sed "s/{project_dir}/$project_dir/g")
else
default_path_with_project="$default_path"
fi
if [[ "$default_path_with_project" == *"{worktree_name}"* ]]; then
worktree_path=$(echo "$default_path_with_project" | sed "s/{worktree_name}/$worktree/g")
else
default_path_with_project="${default_path_with_project%/}"
worktree_path="$default_path_with_project/$worktree"
fi
# Expand ~ to $HOME
worktree_path="${worktree_path/#\~/$HOME}"
if [[ "$worktree_path" == /* ]]; then
# Absolute path, use as-is
:
else
# Relative path, convert to absolute from repo root
repo_root="$(git rev-parse --show-toplevel)"
worktree_path="$repo_root/$worktree_path"
fi
# Check if worktree exists
if [ ! -d "$worktree_path" ]; then
echo "error: Worktree not found at $worktree_path" >&2
echo "Listing existing worktrees:" >&2
git worktree list
exit 1
fi
# Confirm removal
echo "Found worktree: $worktree_path"
echo "Branch: $worktree"
echo ""
read -p "Remove worktree and branch? [y/N] " response
if [[ ! "$response" =~ ^[Yy]$ ]]; then
echo "Cancelled."
exit 0
fi
# Remove the worktree
if git worktree remove "$worktree_path"; then
echo "✓ Removed worktree: $worktree_path"
else
echo "error: Failed to remove worktree" >&2
echo "You may need to manually remove the directory and prune the worktree:" >&2
echo " rm -rf \"$worktree_path\"" >&2
echo " git worktree prune" >&2
exit 1
fi
# Check if branch is used elsewhere
if git show-ref --verify --quiet "refs/heads/$worktree"; then
# Branch exists, check if it has other worktrees
other_worktrees=$(git worktree list | grep -c "\[$worktree\]")
if [ "$other_worktrees" -eq 0 ]; then
read -p "Delete branch \"$worktree\"? [y/N] " branch_response
if [[ "$branch_response" =~ ^[Yy]$ ]]; then
git branch -D "$worktree"
echo "✓ Deleted branch: $worktree"
else
echo "Branch \"$worktree\" kept"
fi
fi
fi
echo "✓ Worktree removal completed"
}; f'
@andkirby
Copy link
Author

Usage example:

 $ git wt 100
Preparing worktree (new branch 'MDT-100')
HEAD is now at bfef478 docs(MDT-101): noted scope changes
Created IDEA scope file for ticket MDT-100
Created worktree: ~/home/markdown-ticket/.gitWT/MDT-100
Branch: MDT-100

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment