convenience shell functions for git worktrees

If you like working with git worktrees, then you may like these following functions. They allow you to create, delete and checkout worktrees (also from upstream) from anywhere within the bare repository.

The following functions assume that standard git worktree structure (as far as I’m aware):

.
├── .git
├── worktree_1
├── worktree_2
└── worktree_n

As a bonus, here’s a function to clone a repo and setup this structure (assumes there is a dev and main branch in the remote):

gwsetup() {
    if [[ -z "$2" ]]; then
        echo "Args required: gwsetup <url> <project-name>"
        return
    fi
    repo=$1
    project=$2
    mkdir $project
    cd $project
    git clone --bare $repo .git
    mkdir dev
    mkdir main
    git worktree add ./dev dev
    git worktree add ./main main
}

What allows them to be executed from anywhere is the following function, which recursively moves upwards in the directory structure until it finds the .git directory of the bare repository. Put this in your .zshrc or wherever you’re setting functions:

get_git_root() {
    local p=$PWD
    while [ $p != "/" ]; do
        if [[ -d "$p/.git" ]]; then
            git_root=$p
            break
        fi
        p=${p:h}
    done
    echo $git_root
}

And then in the same file, place these functions. They need to be in the same file, because they call the get_git_root() function.

The below functions can be run from anywhere at or deeper than the bare repository root (where the .git directory is). When applicable, the functions use fzf to provide selection options. So you need fzf installed:

# create a new worktree
gwn() {
    local wt=$1
    git_root=$(get_git_root)
    git worktree add "$git_root/$wt" -b $wt
    cd $git_root && cd "$wt"
}

# remove a git worktree
gwr() {
    git_root=$(get_git_root)
    local worktrees=$(git worktree list --porcelain | grep worktree | sed 's/worktree //') && wt=$(echo "$worktrees" | fzf)

    for w in $wt; do
        # Get the branch associated with the worktree
        branch=$(git -C "$w" rev-parse --abbrev-ref HEAD)

        # Remove the worktree
        git worktree remove "$@" "$w"

        # Delete the branch (local only)
        if [[ "$branch" != "HEAD" && "$branch" != "main" && "$branch" != "master" ]]; then
            git branch -D "$branch"
        else
            echo "Not deleting branch: $branch"
        fi
    done
}

# checkout a branch as a worktree
gwc() {
    git_root=$(get_git_root)
    local branches branch
    branches=$(git branch -a) && branch=$(echo "$branches" | fzf --tac +s +m -e | sed "s/^[[:space:]]*//" | sed "s/^remotes\/[a-A]*\///")
    if [[ -z $branch ]]; then
        return
    fi

    git worktree add "$git_root/$branch" $branch
    cd $git_root && cd "$branch"
}


# remove a git worktree
gwr() {
    git_root=$(get_git_root)
    local worktrees=$(git worktree list --porcelain | grep worktree | sed 's/worktree //') && wt=$(echo "$worktrees" | fzf)

    for w in $wt; do
        # Get the branch associated with the worktree
        branch=$(git -C "$w" rev-parse --abbrev-ref HEAD)

        # Remove the worktree
        git worktree remove "$@" "$w"

        # Delete the branch (local only)
        if [[ "$branch" != "HEAD" && "$branch" != "main" && "$branch" != "master" ]]; then
            git branch -D "$branch"
        else
            echo "Not deleting branch: $branch"
        fi
    done
}