Last active
January 22, 2026 14:25
-
-
Save gadenbuie/6a9d1b8088f6bc9154b6c534896bbd25 to your computer and use it in GitHub Desktop.
tiny bash script to quickly spin up a new worktree and open a new ide session
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 bash | |
| set -e | |
| help() { | |
| echo "Usage: create-worktree <branch-name> [--base <base-branch>] [--open [auto|false|<cmd>]]" | |
| echo "" | |
| echo "Creates a git worktree with a new branch." | |
| echo "" | |
| echo "Options:" | |
| echo " --base <branch> Base branch to create from (default: current branch)" | |
| echo " --open [mode] How to open the worktree folder:" | |
| echo " auto - detect editor from environment (default)" | |
| echo " false - don't open" | |
| echo " <cmd> - use specified command (e.g., 'code', 'cursor')" | |
| echo " (bare) - use 'open .' (Finder on macOS)" | |
| echo " --no-setup Skip running setup commands (npm, uv, make)" | |
| echo " -h, --help Show this help message" | |
| echo "" | |
| echo "To delete a worktree later:" | |
| echo " git worktree remove <path> # removes worktree and branch" | |
| echo " git worktree list # list all worktrees" | |
| } | |
| auto_detect_editor() { | |
| # Check for Positron first, since it is based on VS Code | |
| if [[ "${POSITRON:-}" == "1" ]]; then | |
| echo "positron" | |
| return | |
| fi | |
| # Check for Cursor (VS Code fork) - has its own env vars | |
| if [[ -n "${CURSOR_TRACE_ID:-}" || -n "${CURSOR_SESSION_ID:-}" ]]; then | |
| echo "cursor" | |
| return | |
| fi | |
| # Check for Windsurf (Codeium's VS Code fork) | |
| if [[ -n "${WINDSURF:-}" || -n "${CODEIUM_WIND_SURF:-}" ]]; then | |
| echo "windsurf" | |
| return | |
| fi | |
| # Check for Zed | |
| if [[ -n "${ZED_SESSION:-}" || "${TERM_PROGRAM:-}" == "zed" ]]; then | |
| echo "zed" | |
| return | |
| fi | |
| # Check TERM_PROGRAM (set by many terminal-integrated editors) | |
| # Note: Cursor/Windsurf may report as "vscode", so check them first above | |
| case "${TERM_PROGRAM:-}" in | |
| vscode) | |
| # Could be VS Code or a fork - check for Insiders | |
| if [[ "${VSCODE_GIT_IPC_HANDLE:-}" == *"Code - Insiders"* ]]; then | |
| echo "code-insiders" | |
| else | |
| echo "code" | |
| fi | |
| return | |
| ;; | |
| esac | |
| # Fallback: check for VS Code via other env vars | |
| if [[ -n "${VSCODE_GIT_IPC_HANDLE:-}" || -n "${VSCODE_PID:-}" ]]; then | |
| if [[ "${VSCODE_GIT_IPC_HANDLE:-}" == *"Code - Insiders"* ]]; then | |
| echo "code-insiders" | |
| else | |
| echo "code" | |
| fi | |
| return | |
| fi | |
| # No editor detected | |
| echo "" | |
| } | |
| # Parse arguments | |
| BRANCH_NAME="" | |
| BASE_BRANCH="" | |
| OPEN_MODE="auto" | |
| RUN_SETUP=true | |
| while [[ $# -gt 0 ]]; do | |
| case $1 in | |
| --base) | |
| BASE_BRANCH="$2" | |
| shift 2 | |
| ;; | |
| --no-setup) | |
| RUN_SETUP=false | |
| shift | |
| ;; | |
| --open) | |
| # Check if next arg exists and is not a flag | |
| if [[ -z "${2:-}" || "$2" == -* ]]; then | |
| # Bare --open flag | |
| OPEN_MODE="finder" | |
| shift | |
| else | |
| OPEN_MODE="$2" | |
| shift 2 | |
| fi | |
| ;; | |
| -h|--help) | |
| help | |
| exit 0 | |
| ;; | |
| -*) | |
| echo "Unknown option: $1" | |
| echo "" | |
| help | |
| exit 1 | |
| ;; | |
| *) | |
| if [[ -z "$BRANCH_NAME" ]]; then | |
| BRANCH_NAME="$1" | |
| else | |
| echo "Unexpected argument: $1" | |
| echo "" | |
| help | |
| exit 1 | |
| fi | |
| shift | |
| ;; | |
| esac | |
| done | |
| if [[ -z "$BRANCH_NAME" ]]; then | |
| help | |
| exit 1 | |
| fi | |
| # Get repo root and name | |
| REPO_ROOT=$(git rev-parse --show-toplevel) | |
| REPO_NAME=$(basename "$REPO_ROOT") | |
| # Default base branch to current branch | |
| if [[ -z "$BASE_BRANCH" ]]; then | |
| BASE_BRANCH=$(git branch --show-current) | |
| fi | |
| # Calculate worktree path | |
| WORKTREE_DIR="$(dirname "$REPO_ROOT")/${REPO_NAME}.worktrees/${BRANCH_NAME}" | |
| # Create the branch from base (if it doesn't already exist) | |
| if git show-ref --verify --quiet "refs/heads/$BRANCH_NAME"; then | |
| echo "Branch '$BRANCH_NAME' already exists, using existing branch..." | |
| else | |
| echo "Creating branch '$BRANCH_NAME' from '$BASE_BRANCH'..." | |
| git branch "$BRANCH_NAME" "$BASE_BRANCH" | |
| fi | |
| # Create the worktree | |
| echo "Creating worktree at '$WORKTREE_DIR'..." | |
| mkdir -p "$(dirname "$WORKTREE_DIR")" | |
| git worktree add "$WORKTREE_DIR" "$BRANCH_NAME" | |
| # Symlink _dev if it exists | |
| if [[ -d "$REPO_ROOT/_dev" ]]; then | |
| echo "Creating symlink to _dev folder..." | |
| ln -s "$REPO_ROOT/_dev" "$WORKTREE_DIR/_dev" | |
| fi | |
| # Symlink .claude if it exists in the source repo | |
| if [[ -d "$REPO_ROOT/.claude" ]]; then | |
| if [[ ! -d "$WORKTREE_DIR/.claude" ]]; then | |
| # .claude folder doesn't exist in worktree, symlink the entire folder | |
| echo "Creating symlink to .claude folder..." | |
| ln -s "$REPO_ROOT/.claude" "$WORKTREE_DIR/.claude" | |
| else | |
| # .claude exists in worktree, check for settings.local.json | |
| if [[ -f "$REPO_ROOT/.claude/settings.local.json" ]] && [[ ! -f "$WORKTREE_DIR/.claude/settings.local.json" ]]; then | |
| echo "Creating symlink to .claude/settings.local.json..." | |
| ln -s "$REPO_ROOT/.claude/settings.local.json" "$WORKTREE_DIR/.claude/settings.local.json" | |
| fi | |
| fi | |
| fi | |
| echo "Worktree created at: $WORKTREE_DIR" | |
| # Change to worktree directory | |
| cd "$WORKTREE_DIR" | |
| # Run setup commands if applicable | |
| if [[ "$RUN_SETUP" == true ]]; then | |
| if [[ -f "package.json" ]] && command -v npm &> /dev/null; then | |
| echo "Running npm install..." | |
| npm install | |
| fi | |
| if [[ -f "pyproject.toml" ]] && command -v uv &> /dev/null; then | |
| echo "Running uv sync --all-groups..." | |
| uv sync --all-groups | |
| fi | |
| if [[ -f "Makefile" ]] && grep -q '^setup:' Makefile; then | |
| echo "Running make setup..." | |
| make setup | |
| fi | |
| fi | |
| # Determine open command | |
| OPEN_CMD="" | |
| case "$OPEN_MODE" in | |
| false) | |
| # Don't open | |
| ;; | |
| auto) | |
| EDITOR=$(auto_detect_editor) | |
| if [[ -n "$EDITOR" ]]; then | |
| OPEN_CMD="$EDITOR ." | |
| fi | |
| ;; | |
| finder) | |
| OPEN_CMD="open ." | |
| ;; | |
| *) | |
| # Custom command | |
| OPEN_CMD="$OPEN_MODE ." | |
| ;; | |
| esac | |
| if [[ -n "$OPEN_CMD" ]]; then | |
| echo "Opening with: $OPEN_CMD" | |
| $OPEN_CMD | |
| fi | |
| echo "" | |
| echo "To enter the worktree, run:" | |
| echo " cd \"$WORKTREE_DIR\"" | |
| echo "" | |
| echo "To delete this worktree later:" | |
| echo " git worktree remove \"$WORKTREE_DIR\"" |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Installation
Make sure
~/.local/binis in yourPATH. Add this to your shell profile if needed: