Last Updated: 2024-02-06
Git worktrees allow you to check out multiple branches simultaneously in separate directories, while sharing a single .git
metadata store.
THere are some best practices, useful Git aliases, and shell functions for efficiently managing Git worktrees. It covers:
- Setting up Git aliases for worktree operations.
- Using shell functions for enhanced worktree management.
- Automating worktree status and updates.
- Best practices for keeping worktrees clean.
Example Structure:
~/Documents/Workspace/github/
└── communityORuser/
├── githubreponame/
│ ├── .git/ # Git metadata (bare repository)
│ ├── main/ # Primary branch worktree (main or master)
│ ├── feature-branch-1/ # Other branches as worktrees
│ ├── feature-branch-2/
These aliases help manage worktrees more efficiently.
[alias]
worktrees = "worktree list"
prunetrees = "worktree prune"
or
git config --global alias.worktrees "!git worktree list"
git config --global alias.prunetrees "!git worktree prune"
Alias | Command | Description |
---|---|---|
git worktrees |
git worktree list |
List all worktrees |
git prunetrees |
git worktree prune |
Clean up stale worktrees |
These aliases work globally in any Git repository
Some Git operations, such as creating or switching worktrees, work better with shell functions instead of Git aliases.
# Quickly switch to a worktree directory
swtree() {
local dir
dir=$(git worktree list | grep "$1" | awk '{print $1}')
if [[ -d "$dir" ]]; then
cd "$dir" || echo "❌ Worktree not found"
else
echo "❌ Worktree '$1' not found"
fi
}
# Create a new worktree
newtree() {
if [[ $# -ne 2 ]]; then
echo "Usage: newtree <directory> <branch>"
return 1
fi
git worktree add "$1" "$2"
}
# Remove a worktree
rmtree() {
if [[ $# -ne 1 ]]; then
echo "Usage: rmtree <directory>"
return 1
fi
git worktree remove "$1"
}
# Show status of all worktrees
worktree-status() {
for dir in $(git worktree list | awk '{print $1}' | tail -n +2); do
echo "📂 Checking status in: $dir"
(cd "$dir" && git status --short)
done
}
Command | Description |
---|---|
swtree feature-branch |
Switch to a specific worktree |
newtree feature-branch feature-branch |
Create a new worktree |
rmtree feature-branch |
Remove a worktree |
worktree-status |
Show status of all worktrees |
Works globally** after adding to ~/.zshrc
and running:
source ~/.zshrc
This script clones a GitHub repository using git worktree
, organizing main and branches as a tree.
#!/bin/zsh
# -----------------------------------------------------------------------------
# GitHub Repository Clone & Worktree Setup Script (Best Practices)
# -----------------------------------------------------------------------------
#
# This script clones a GitHub repository using `git worktree`, organizing it as:
#
# ~/Documents/Workspace/github/
# ├── communityORuser/ # GitHub user or organization
# │ ├── githubreponame/ # Repository folder
# │ │ ├── .git/ # Git metadata (bare repository)
# │ │ ├── main/ # Primary branch worktree (main or master)
# │ │ ├── feature-branch-1/ # Other branches as worktrees
# │ │ ├── feature-branch-2/
#
# -----------------------------------------------------------------------------
#
# USAGE:
# Run this script from ~/Documents/Workspace/github/:
#
# ./clone-worktree.zsh communityORuser/githubreponame
#
# DEPENDENCIES:
# - Requires `gh` (GitHub CLI) for detecting the default branch
# - Requires `git` for cloning and worktree operations
# - Uses `tree` for directory structure (falls back to `ls -A` if missing)
#
# FEATURES:
# - Automatically detects if the primary branch is `main` or `master`
# - Clones the repository as a bare repository
# - Creates worktrees for all branches
# - Ensures script is run from the correct directory
# - Errors out if the repository already exists
# - Provides clear error messages and fallbacks
#
# -----------------------------------------------------------------------------
# Exit immediately if any command fails
set -e
# Ensure GitHub CLI (`gh`) is installed
if ! command -v gh &> /dev/null; then
echo "❌ GitHub CLI (gh) not found. Please install it first."
exit 1
fi
# Ensure Git is installed
if ! command -v git &> /dev/null; then
echo "❌ Git not found. Please install Git first."
exit 1
fi
# Define the expected working directory
EXPECTED_DIR="$HOME/Documents/Workspace/github"
# Ensure script is run from ~/Documents/Workspace/github/
if [[ "$PWD" != "$EXPECTED_DIR" ]]; then
echo "❌ Please run this script from $EXPECTED_DIR"
exit 1
fi
# Check if a repository argument is provided
if [[ -z "$1" ]]; then
echo "❌ Usage: $0 communityORuser/githubreponame"
exit 1
fi
# Extract repository details from argument
REPO="$1"
COMMUNITY_OR_USER="${REPO%/*}" # Extracts 'communityORuser'
REPO_NAME="${REPO##*/}" # Extracts 'githubreponame'
REPO_DIR="$PWD/$COMMUNITY_OR_USER/$REPO_NAME"
# Ensure the parent directory for the repository exists
mkdir -p "$PWD/$COMMUNITY_OR_USER"
# Check if the repository directory already exists and prevent overwriting
if [[ -d "$REPO_DIR" ]]; then
echo "❌ Error: Repository $REPO_NAME already exists in $COMMUNITY_OR_USER/"
exit 1
fi
# Clone the repository as a bare repo inside ".git"
echo "🚀 Cloning $REPO as a bare repository..."
mkdir -p "$REPO_DIR"
git clone --bare "https://github.com/$REPO.git" "$REPO_DIR/.git"
# Change into repo directory
cd "$REPO_DIR" || exit 1
# Fetch all remote branches
echo "📥 Fetching branch list..."
git --git-dir=.git fetch --all --prune
# Determine the primary branch (handles both 'main' and 'master')
echo "🔎 Detecting default branch..."
# First, try using GitHub CLI (preferred method)
DEFAULT_BRANCH=$(gh repo view "$REPO" --json defaultBranchRef -q .defaultBranchRef.name 2>/dev/null)
# If GitHub CLI fails, fall back to `git symbolic-ref`
if [[ -z "$DEFAULT_BRANCH" ]]; then
DEFAULT_BRANCH=$(git --git-dir=.git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@')
fi
# If still not found, manually check for 'main' or 'master'
if [[ -z "$DEFAULT_BRANCH" ]]; then
DEFAULT_BRANCH=$(git --git-dir=.git branch -r | grep -E 'origin/main|origin/master' | head -n 1 | awk -F'/' '{print $2}')
fi
# If still not found, exit with an error
if [[ -z "$DEFAULT_BRANCH" ]]; then
echo "❌ Error: Could not determine the primary branch (main or master)."
exit 1
fi
echo "✅ Primary branch detected: $DEFAULT_BRANCH"
# Create the worktree for the primary branch
echo "📂 Creating worktree for main branch: $DEFAULT_BRANCH..."
git worktree add "main" "$DEFAULT_BRANCH"
# List all branches except the default branch
BRANCHES=$(git --git-dir=.git branch -r | grep -v "origin/$DEFAULT_BRANCH" | sed 's/origin\///')
# Create worktrees for each additional branch
for BRANCH in $BRANCHES; do
echo "📂 Creating worktree for branch: $BRANCH..."
git worktree add "$BRANCH" "$BRANCH"
done
# Display the directory structure
echo "✅ Worktree setup complete!"
echo "📂 Directory structure:"
# Check if 'tree' command is available; if not, use 'ls -A'
if command -v tree &> /dev/null; then
tree -a -L 2 "$REPO_DIR" # -a ensures hidden files (like .git) are shown
else
echo "⚠️ 'tree' command not found. Showing directory with 'ls -A':"
ls -A "$REPO_DIR"
fi
This script automatically fetches updates and rebases all worktrees.
#!/bin/zsh
# Fetch latest changes
echo "📥 Fetching latest changes from origin..."
git fetch --all --prune
# Get all worktrees
WORKTREES=$(git worktree list | awk '{print $1}' | tail -n +2)
# Iterate over each worktree and update it
for DIR in $WORKTREES; do
echo "🔄 Updating worktree: $DIR"
cd "$DIR" || continue
BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || echo "detached")
if [[ "$BRANCH" != "detached" ]]; then
git rebase origin/"$BRANCH"
fi
cd - > /dev/null
done
echo "✅ All worktrees updated!"
Run:
./update-worktrees.zsh
Keeps all worktrees in sync with the latest remote changes.**
- Remove a worktree manually:
git worktree remove feature-branch
- Automatically clean up old worktrees:
git worktree prune
- Prevent worktrees from being tracked:
Add this to
.gitignore
:/* !.git !main/ !feature-branch-1/ !feature-branch-2/
Feature | Command |
---|---|
List all worktrees | git worktrees |
Create a new worktree | newtree feature-branch feature-branch |
Switch to a worktree | swtree feature-branch |
Remove a worktree | rmtree feature-branch |
Clean up stale worktrees | git prunetrees |
Update all worktrees | ./update-worktrees.zsh |
Show worktree status | worktree-status |