-
-
Save claylevering/f6bdbb1d01b141c1144458875494c0b4 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
| #! /usr/bin/env zsh | |
| # | |
| # Worktree helpers (zsh-only) | |
| # | |
| # --- User configuration ------------------------------------------------------- | |
| # Override these in your `.zshrc` before sourcing this file. | |
| # | |
| # Example: | |
| # export GIT_WT_HELPER_IDE_CMD="cursor" # e.g. cursor, code, idea, subl, etc. | |
| # export GIT_WT_HELPER_OPEN_IDE_DEFAULT="false" # true/false (default: false) | |
| : "${GIT_WT_HELPER_IDE_CMD:=}" | |
| : "${GIT_WT_HELPER_OPEN_IDE_DEFAULT:=false}" | |
| _git_wt_helper_ga_is_zsh() { [[ -n "${ZSH_VERSION:-}" ]]; } | |
| _git_wt_helper_ga_log() { print -r -- "$*"; } | |
| _git_wt_helper_ga_err() { print -ru2 -- "$*"; } | |
| _git_wt_helper_ga_is_truthy() { | |
| # Treat 1/true/yes/on as true (case-insensitive) | |
| local v="${1:-}" | |
| v="${v:l}" | |
| [[ "$v" == "1" || "$v" == "true" || "$v" == "yes" || "$v" == "on" ]] | |
| } | |
| _git_wt_helper_ga_safe_path() { | |
| local p="/usr/bin:/bin:/usr/sbin:/sbin" | |
| [[ -n "${PATH:-}" ]] && p="$p:$PATH" | |
| print -r -- "$p" | |
| } | |
| _git_wt_helper_ga_find_cmd() { | |
| # Usage: _git_wt_helper_ga_find_cmd <name> [fallback1] [fallback2] ... | |
| local name="$1" | |
| shift | |
| if command -v "$name" >/dev/null 2>&1; then | |
| command -v "$name" | |
| return 0 | |
| fi | |
| local candidate | |
| for candidate in "$@"; do | |
| [[ -x "$candidate" ]] && { | |
| print -r -- "$candidate" | |
| return 0 | |
| } | |
| done | |
| return 1 | |
| } | |
| _git_wt_helper_ga_require_zsh() { | |
| if ! _git_wt_helper_ga_is_zsh; then | |
| _git_wt_helper_ga_err "Error: this function requires zsh. Current shell: ${SHELL:-unknown}" | |
| return 1 | |
| fi | |
| return 0 | |
| } | |
| _git_wt_helper_ga_require_cmd() { | |
| # Usage: _git_wt_helper_ga_require_cmd <label> <cmd_path> | |
| local label="$1" | |
| local cmd="$2" | |
| if [[ -z "$cmd" ]]; then | |
| _git_wt_helper_ga_err "Error: $label not found." | |
| return 1 | |
| fi | |
| return 0 | |
| } | |
| # Create a new worktree and branch from within current git directory. | |
| ga() { | |
| _git_wt_helper_ga_require_zsh || return 1 | |
| local copy_env=1 | |
| local open_ide=0 | |
| local branch="" | |
| if _git_wt_helper_ga_is_truthy "${GIT_WT_HELPER_OPEN_IDE_DEFAULT:-false}"; then | |
| open_ide=1 | |
| fi | |
| while [[ $# -gt 0 ]]; do | |
| case "$1" in | |
| -n | --no-env) | |
| copy_env=0 | |
| shift | |
| ;; | |
| -o | --open-ide) | |
| open_ide=1 | |
| shift | |
| ;; | |
| --no-open-ide) | |
| open_ide=0 | |
| shift | |
| ;; | |
| -h | --help) | |
| _git_wt_helper_ga_err "Usage: ga [--no-env] [--open-ide|--no-open-ide] <branch>" | |
| _git_wt_helper_ga_err " --no-env: do not copy .env into the new worktree (default: copy if present)" | |
| _git_wt_helper_ga_err " -o, --open-ide: open your IDE after creating the worktree" | |
| _git_wt_helper_ga_err " --no-open-ide: do not open your IDE (overrides default setting)" | |
| _git_wt_helper_ga_err "" | |
| _git_wt_helper_ga_err "Config:" | |
| _git_wt_helper_ga_err " GIT_WT_HELPER_IDE_CMD: IDE command (e.g. cursor, code). If empty, auto-detect." | |
| _git_wt_helper_ga_err " GIT_WT_HELPER_OPEN_IDE_DEFAULT: true/false (default: false)" | |
| return 0 | |
| ;; | |
| --) | |
| shift | |
| break | |
| ;; | |
| -*) | |
| _git_wt_helper_ga_err "Error: unknown option: $1" | |
| _git_wt_helper_ga_err "Usage: ga [--no-env] [--open-ide|--no-open-ide] <branch>" | |
| return 1 | |
| ;; | |
| *) | |
| branch="$1" | |
| shift | |
| break | |
| ;; | |
| esac | |
| done | |
| if [[ -z "$branch" ]]; then | |
| _git_wt_helper_ga_err "Usage: ga [--no-env] [--open-ide|--no-open-ide] <branch>" | |
| return 1 | |
| fi | |
| [[ -x /bin/sh ]] || { | |
| _git_wt_helper_ga_err "Error: /bin/sh not found or not executable." | |
| return 1 | |
| } | |
| local safe_path | |
| safe_path="$(_git_wt_helper_ga_safe_path)" | |
| local git_cmd | |
| git_cmd="$(_git_wt_helper_ga_find_cmd git /usr/bin/git /usr/local/bin/git)" || true | |
| _git_wt_helper_ga_require_cmd "git" "$git_cmd" || return 1 | |
| local mise_cmd | |
| mise_cmd="$(_git_wt_helper_ga_find_cmd mise "$HOME/.local/bin/mise" /opt/homebrew/bin/mise /usr/local/bin/mise)" || true | |
| if [[ -z "$mise_cmd" ]]; then | |
| _git_wt_helper_ga_err "Error: mise not found." | |
| _git_wt_helper_ga_err "Install: https://mise.jdx.dev/getting-started.html" | |
| return 1 | |
| fi | |
| if ! "$git_cmd" rev-parse --git-dir >/dev/null 2>&1; then | |
| _git_wt_helper_ga_err "Error: not in a git repository." | |
| return 1 | |
| fi | |
| local base | |
| base="$(basename "$PWD")" | |
| local worktree_path="../${base}-${branch}" | |
| if [[ -d "$worktree_path" ]]; then | |
| _git_wt_helper_ga_err "Error: worktree path already exists: $worktree_path" | |
| return 1 | |
| fi | |
| _git_wt_helper_ga_log "Creating worktree '$branch' at '$worktree_path'..." | |
| if ! /usr/bin/env PATH="$safe_path" "$git_cmd" worktree add -b "$branch" "$worktree_path"; then | |
| _git_wt_helper_ga_err "Error: failed to create worktree." | |
| return 1 | |
| fi | |
| if [[ "$copy_env" -eq 1 ]]; then | |
| local repo_root | |
| repo_root="$("$git_cmd" rev-parse --show-toplevel 2>/dev/null)" | |
| if [[ -n "$repo_root" && -f "$repo_root/.env" ]]; then | |
| if [[ -e "$worktree_path/.env" ]]; then | |
| _git_wt_helper_ga_log ".env already exists in worktree; skipping copy." | |
| else | |
| _git_wt_helper_ga_log "Copying .env into worktree..." | |
| cp -p "$repo_root/.env" "$worktree_path/.env" 2>/dev/null || _git_wt_helper_ga_err "Warning: failed to copy .env (continuing)." | |
| fi | |
| fi | |
| fi | |
| _git_wt_helper_ga_log "Trusting mise in worktree..." | |
| "$mise_cmd" trust "$worktree_path" >/dev/null 2>&1 || _git_wt_helper_ga_err "Warning: mise trust failed (continuing)." | |
| if cd "$worktree_path"; then | |
| _git_wt_helper_ga_log "Now in: $(pwd)" | |
| else | |
| _git_wt_helper_ga_err "Error: failed to change directory to worktree." | |
| return 1 | |
| fi | |
| if [[ "$open_ide" -eq 1 ]]; then | |
| local ide_setting ide_cmd | |
| ide_setting="${GIT_WT_HELPER_IDE_CMD:-}" | |
| # Resolve IDE command. | |
| if [[ -n "$ide_setting" ]]; then | |
| # Allow absolute paths too. | |
| if [[ "$ide_setting" == */* ]]; then | |
| [[ -x "$ide_setting" ]] && ide_cmd="$ide_setting" | |
| else | |
| ide_cmd="$(command -v "$ide_setting" 2>/dev/null)" | |
| fi | |
| else | |
| # Auto-detect common IDE CLI commands. | |
| local candidate | |
| for candidate in cursor code idea subl; do | |
| ide_cmd="$(command -v "$candidate" 2>/dev/null)" && break | |
| done | |
| fi | |
| if [[ -n "$ide_cmd" ]]; then | |
| _git_wt_helper_ga_log "Opening IDE..." | |
| "$ide_cmd" . >/dev/null 2>&1 & disown | |
| else | |
| _git_wt_helper_ga_err "Warning: IDE command not found. Set GIT_WT_HELPER_IDE_CMD (e.g. 'cursor') to enable --open-ide." | |
| fi | |
| fi | |
| } | |
| # Remove worktree and branch from within active worktree directory. | |
| gd() { | |
| _git_wt_helper_ga_require_zsh || return 1 | |
| local safe_path | |
| safe_path="$(_git_wt_helper_ga_safe_path)" | |
| local git_cmd | |
| git_cmd="$(_git_wt_helper_ga_find_cmd git /usr/bin/git /usr/local/bin/git)" || true | |
| _git_wt_helper_ga_require_cmd "git" "$git_cmd" || return 1 | |
| if ! "$git_cmd" rev-parse --git-dir >/dev/null 2>&1; then | |
| _git_wt_helper_ga_err "Error: not in a git repository." | |
| return 1 | |
| fi | |
| local cwd | |
| cwd="$(pwd)" | |
| local branch | |
| branch="$("$git_cmd" rev-parse --abbrev-ref HEAD 2>/dev/null)" | |
| [[ -n "$branch" ]] || { | |
| _git_wt_helper_ga_err "Error: could not determine current branch." | |
| return 1 | |
| } | |
| # Worktrees have a .git file (not a directory) | |
| if [[ ! -f "$cwd/.git" ]]; then | |
| _git_wt_helper_ga_err "Error: current directory doesn't appear to be a git worktree." | |
| return 1 | |
| fi | |
| # Move to a safe directory BEFORE removing the worktree, so shell hooks (mise/fnm/etc) | |
| # don't run in a deleted directory. | |
| local common_git_dir repo_root | |
| common_git_dir="$("$git_cmd" rev-parse --git-common-dir 2>/dev/null)" | |
| if [[ -n "$common_git_dir" ]]; then | |
| # zsh: :A makes path absolute (resolves relative paths) | |
| common_git_dir="${common_git_dir:A}" | |
| repo_root="$(dirname "$common_git_dir")" | |
| fi | |
| if [[ -z "$repo_root" || ! -d "$repo_root" ]]; then | |
| # Fallback: parent directory of the worktree | |
| repo_root="$(dirname "$cwd")" | |
| fi | |
| if [[ -d "$repo_root" ]]; then | |
| cd "$repo_root" || true | |
| fi | |
| if ! command -v gum >/dev/null 2>&1; then | |
| _git_wt_helper_ga_err "Error: gum not found." | |
| _git_wt_helper_ga_err "Install: https://github.com/charmbracelet/gum?tab=readme-ov-file#installation" | |
| return 1 | |
| fi | |
| _git_wt_helper_ga_log "About to remove worktree '$cwd' and delete branch '$branch'." | |
| if ! gum confirm "Remove worktree and branch?"; then | |
| _git_wt_helper_ga_log "Cancelled." | |
| return 0 | |
| fi | |
| # Remove worktree (use safe PATH for any internal env/sh usage) | |
| if ! /usr/bin/env PATH="$safe_path" "$git_cmd" worktree remove "$cwd" --force; then | |
| _git_wt_helper_ga_err "Error: failed to remove worktree." | |
| return 1 | |
| fi | |
| # Delete branch after worktree removal | |
| if ! /usr/bin/env PATH="$safe_path" "$git_cmd" branch -D "$branch" >/dev/null 2>&1; then | |
| _git_wt_helper_ga_err "Warning: failed to delete branch '$branch'. It may be checked out in another worktree." | |
| _git_wt_helper_ga_err "Tip: run 'git worktree list' to find where it's still in use." | |
| return 1 | |
| fi | |
| _git_wt_helper_ga_log "Removed worktree and deleted branch '$branch'." | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment