#!/usr/bin/env bash set -Eeuo pipefail script_name="git wt" usage() { cat < Creates a worktree for under /worktree/// and prints its path. Creates the branch if it does not exist. If is already checked out in a worktree, prints that path. If it is checked out in the main repository, that checkout is switched to the default branch (or detached) first, so the branch can move to its own worktree. Config: gj.root Directory where repositories are stored. Example: cd ($script_name pb/my-feature) EOF } # Prints the path of the worktree that has checked out, if any. worktree_for_branch() { git worktree list --porcelain | awk -v ref="branch refs/heads/$1" ' /^worktree / { path = substr($0, 10) } $0 == ref { print path; exit }' } # Frees from the worktree at so it can be checked out elsewhere. release_branch() { local branch=$1 path=$2 default [[ -z "$(git -C "$path" status --porcelain)" ]] || die "$branch is checked out in $path with uncommitted changes; commit or stash first" default=$(git -C "$path" default-branch 2>/dev/null || true) if [[ -n "$default" && "$default" != "$branch" ]] && git -C "$path" switch --quiet "$default" 2>/dev/null; then printf 'Switched %s to %s\n' "$path" "$default" >&2 else git -C "$path" switch --quiet --detach printf 'Detached HEAD in %s\n' "$path" >&2 fi } die() { printf '%s\n' "$*" >&2 exit 1 } main() { [[ $# -eq 1 ]] || { usage exit 2 } case "$1" in -h | --help) usage exit 0 ;; esac local branch=$1 root toplevel common main dest existing root=$(git config --global --get gj.root || true) [[ -n "$root" ]] || die "gj.root is not set in git config" root="${root/#\~/$HOME}" toplevel=$(git rev-parse --show-toplevel 2>/dev/null) || die "Not inside a git repository" [[ "$toplevel" == "$root"/* ]] || die "Repository is not under gj.root: $toplevel" # Use the main repository path even when run from inside a linked worktree. common=$(git rev-parse --path-format=absolute --git-common-dir) main=${common%/.git} if [[ "$toplevel" == "$root"/worktree/* ]]; then toplevel=$main fi dest="$root/worktree/${toplevel#"$root"/}/${branch//\//-}" existing=$(worktree_for_branch "$branch") if [[ -n "$existing" && "$existing" != "$main" ]]; then printf '%s\n' "$existing" return fi [[ -z "$existing" ]] || release_branch "$branch" "$existing" if [[ ! -d "$dest" ]]; then git fetch --prune --quiet if git show-ref --verify --quiet "refs/heads/$branch" || git show-ref --verify --quiet "refs/remotes/origin/$branch"; then git worktree add --quiet "$dest" "$branch" else git worktree add --quiet -b "$branch" "$dest" fi fi printf '%s\n' "$dest" } main "$@"