diff --git a/CLAUDE.md b/CLAUDE.md index 5206a48..e34564b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -64,7 +64,7 @@ Test changes using this comprehensive checklist (from CONTRIBUTING.md): # Open in editor (if testing adapters) ./bin/gtr config set gtr.editor.default cursor -./bin/gtr open test-feature +./bin/gtr editor test-feature # Expected: Opens Cursor at worktree path # Run AI tool (if testing adapters) @@ -91,7 +91,7 @@ Test changes using this comprehensive checklist (from CONTRIBUTING.md): # Test shell completions with tab completion gtr new -gtr open +gtr editor # Expected: Shows available branches/worktrees # Test gtr go for main repo and worktrees @@ -171,7 +171,7 @@ git --version **Branch Name Mapping**: Branch names are sanitized to valid folder names (slashes and special chars → hyphens). For example, `feature/user-auth` becomes folder `feature-user-auth`. -**Special ID '1'**: The main repository is always accessible via ID `1` in commands (e.g., `gtr go 1`, `gtr open 1`). +**Special ID '1'**: The main repository is always accessible via ID `1` in commands (e.g., `gtr go 1`, `gtr editor 1`). **Configuration Storage**: All configuration is stored via `git config` (local, global, or system). No custom config files. This makes settings portable and follows git conventions. @@ -187,7 +187,7 @@ git --version Understanding how commands are dispatched through the system: 1. **Entry Point** (`bin/gtr:32-79`): Main dispatcher receives command and routes to appropriate handler -2. **Command Handlers** (`bin/gtr`): Each `cmd_*` function handles a specific command (e.g., `cmd_create`, `cmd_open`, `cmd_ai`) +2. **Command Handlers** (`bin/gtr`): Each `cmd_*` function handles a specific command (e.g., `cmd_create`, `cmd_editor`, `cmd_ai`) 3. **Library Functions** (`lib/*.sh`): Command handlers call reusable functions from library modules 4. **Adapters** (`adapters/*`): Dynamically loaded when needed via `load_editor_adapter` or `load_ai_adapter` @@ -202,11 +202,11 @@ bin/gtr main() → run_hooks_in() [lib/hooks.sh] ``` -**Example flow for `gtr open my-feature`:** +**Example flow for `gtr editor my-feature`:** ``` bin/gtr main() - → cmd_open() + → cmd_editor() → resolve_target() [lib/core.sh] → load_editor_adapter() → editor_open() [adapters/editor/*.sh] @@ -413,7 +413,7 @@ bash -c 'source adapters/editor/cursor.sh && editor_can_open && echo "Available" bash -c 'source adapters/ai/claude.sh && ai_can_start && echo "Available" || echo "Not found"' # Debug adapter loading with trace -bash -x ./bin/gtr open test-feature --editor cursor +bash -x ./bin/gtr editor test-feature --editor cursor # Shows full execution trace including adapter loading ``` diff --git a/README.md b/README.md index 775df6c..c6627ec 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ gtr config set gtr.ai.default claude # One-time setup # Daily workflow gtr new my-feature # Create worktree -gtr open my-feature # Open in editor +gtr editor my-feature # Open in editor gtr ai my-feature # Start AI tool gtr rm my-feature # Remove when done ``` @@ -50,7 +50,7 @@ While `git worktree` is powerful, it's verbose and manual. `gtr` adds quality-of | Task | With `git worktree` | With `gtr` | | ----------------- | ------------------------------------------ | ------------------------------------ | | Create worktree | `git worktree add ../repo-feature feature` | `gtr new feature` | -| Open in editor | `cd ../repo-feature && cursor .` | `gtr open feature` | +| Open in editor | `cd ../repo-feature && cursor .` | `gtr editor feature` | | Start AI tool | `cd ../repo-feature && aider` | `gtr ai feature` | | Copy config files | Manual copy/paste | Auto-copy via `gtr.copy.include` | | Run build steps | Manual `npm install && npm run build` | Auto-run via `gtr.hook.postCreate` | @@ -83,7 +83,7 @@ gtr config set gtr.ai.default claude # Daily workflow gtr new my-feature # Create worktree folder: my-feature -gtr open my-feature # Open in cursor +gtr editor my-feature # Open in cursor gtr ai my-feature # Start claude # Navigate to worktree @@ -175,13 +175,13 @@ gtr new my-feature --name descriptive-variant # Optional: custom name without - `--name `: Custom folder name suffix (optional, required with --force) - `--yes`: Non-interactive mode -### `gtr open [--editor ]` +### `gtr editor [--editor ]` Open worktree in editor (uses `gtr.editor.default` or `--editor` flag). ```bash -gtr open my-feature # Uses configured editor -gtr open my-feature --editor vscode # Override with vscode +gtr editor my-feature # Uses configured editor +gtr editor my-feature --editor vscode # Override with vscode ``` ### `gtr ai [--ai ] [-- args...]` @@ -220,7 +220,7 @@ gtr rm my-feature --delete-branch --force # Delete branch and force List all worktrees. Use `--porcelain` for machine-readable output. -### `gtr config {get|set|unset} [value] [--global]` +### `gtr config {get|set|add|unset} [value] [--global]` Manage configuration via git config. @@ -324,13 +324,13 @@ Copy files to new worktrees using glob patterns: ```bash # Add patterns to copy (multi-valued) -git config --add gtr.copy.include "**/.env.example" -git config --add gtr.copy.include "**/CLAUDE.md" -git config --add gtr.copy.include "*.config.js" +gtr config add gtr.copy.include "**/.env.example" +gtr config add gtr.copy.include "**/CLAUDE.md" +gtr config add gtr.copy.include "*.config.js" # Exclude patterns (multi-valued) -git config --add gtr.copy.exclude "**/.env" -git config --add gtr.copy.exclude "**/secrets.*" +gtr config add gtr.copy.exclude "**/.env" +gtr config add gtr.copy.exclude "**/secrets.*" ``` **⚠️ Security Note:** Be careful not to copy sensitive files. Use `.env.example` instead of `.env`. @@ -341,11 +341,11 @@ Run custom commands after worktree operations: ```bash # Post-create hooks (multi-valued, run in order) -git config --add gtr.hook.postCreate "npm install" -git config --add gtr.hook.postCreate "npm run build" +gtr config add gtr.hook.postCreate "npm install" +gtr config add gtr.hook.postCreate "npm run build" # Post-remove hooks -git config --add gtr.hook.postRemove "echo 'Cleaned up!'" +gtr config add gtr.hook.postRemove "echo 'Cleaned up!'" ``` **Environment variables available in hooks:** @@ -358,19 +358,19 @@ git config --add gtr.hook.postRemove "echo 'Cleaned up!'" ```bash # Node.js (npm) -git config --add gtr.hook.postCreate "npm install" +gtr config add gtr.hook.postCreate "npm install" # Node.js (pnpm) -git config --add gtr.hook.postCreate "pnpm install" +gtr config add gtr.hook.postCreate "pnpm install" # Python -git config --add gtr.hook.postCreate "pip install -r requirements.txt" +gtr config add gtr.hook.postCreate "pip install -r requirements.txt" # Ruby -git config --add gtr.hook.postCreate "bundle install" +gtr config add gtr.hook.postCreate "bundle install" # Rust -git config --add gtr.hook.postCreate "cargo build" +gtr config add gtr.hook.postCreate "cargo build" ``` ## Configuration Examples @@ -378,35 +378,35 @@ git config --add gtr.hook.postCreate "cargo build" ### Minimal Setup (Just Basics) ```bash -git config --local gtr.worktrees.prefix "wt-" -git config --local gtr.defaultBranch "main" +gtr config set gtr.worktrees.prefix "wt-" +gtr config set gtr.defaultBranch "main" ``` ### Full-Featured Setup (Node.js Project) ```bash # Worktree settings -git config --local gtr.worktrees.prefix "wt-" +gtr config set gtr.worktrees.prefix "wt-" # Editor -git config --local gtr.editor.default cursor +gtr config set gtr.editor.default cursor # Copy environment templates -git config --local --add gtr.copy.include "**/.env.example" -git config --local --add gtr.copy.include "**/.env.development" -git config --local --add gtr.copy.exclude "**/.env.local" +gtr config add gtr.copy.include "**/.env.example" +gtr config add gtr.copy.include "**/.env.development" +gtr config add gtr.copy.exclude "**/.env.local" # Build hooks -git config --local --add gtr.hook.postCreate "pnpm install" -git config --local --add gtr.hook.postCreate "pnpm run build" +gtr config add gtr.hook.postCreate "pnpm install" +gtr config add gtr.hook.postCreate "pnpm run build" ``` ### Global Defaults ```bash # Set global preferences -git config --global gtr.editor.default cursor -git config --global gtr.ai.default claude +gtr config set gtr.editor.default cursor --global +gtr config set gtr.ai.default claude --global ``` ## Advanced Usage @@ -425,11 +425,11 @@ git config --global gtr.ai.default claude ```bash # Terminal 1: Work on feature gtr new feature-a -gtr open feature-a +gtr editor feature-a # Terminal 2: Review PR gtr new pr/123 -gtr open pr/123 +gtr editor pr/123 # Terminal 3: Navigate to main branch (repo root) cd "$(gtr go 1)" # Special ID '1' = main repo @@ -448,7 +448,7 @@ gtr list # auth-feature ~/GitHub/frontend-worktrees/auth-feature # nav-redesign ~/GitHub/frontend-worktrees/nav-redesign -gtr open auth-feature # Open frontend auth work +gtr editor auth-feature # Open frontend auth work gtr ai nav-redesign # AI on frontend nav work # Backend repo (separate worktrees) @@ -459,12 +459,12 @@ gtr list # api-auth ~/GitHub/backend-worktrees/api-auth # websockets ~/GitHub/backend-worktrees/websockets -gtr open api-auth # Open backend auth work +gtr editor api-auth # Open backend auth work gtr ai websockets # AI on backend websockets # Switch back to frontend cd ~/GitHub/frontend -gtr open auth-feature # Opens frontend auth +gtr editor auth-feature # Opens frontend auth ``` **Key point:** Each repository has its own worktrees. Use branch names to identify worktrees. @@ -477,17 +477,17 @@ Create a `.gtr-setup.sh` in your repo: #!/bin/sh # .gtr-setup.sh - Project-specific gtr configuration -git config --local gtr.worktrees.prefix "dev-" -git config --local gtr.editor.default cursor +gtr config set gtr.worktrees.prefix "dev-" +gtr config set gtr.editor.default cursor # Copy configs -git config --local --add gtr.copy.include ".env.example" -git config --local --add gtr.copy.include "docker-compose.yml" +gtr config add gtr.copy.include ".env.example" +gtr config add gtr.copy.include "docker-compose.yml" # Setup hooks -git config --local --add gtr.hook.postCreate "docker-compose up -d db" -git config --local --add gtr.hook.postCreate "npm install" -git config --local --add gtr.hook.postCreate "npm run db:migrate" +gtr config add gtr.hook.postCreate "docker-compose up -d db" +gtr config add gtr.hook.postCreate "npm install" +gtr config add gtr.hook.postCreate "npm run db:migrate" ``` Then run: `sh .gtr-setup.sh` @@ -585,14 +585,14 @@ command -v cursor # or: code, zed gtr config get gtr.editor.default # Try opening again -gtr open 2 +gtr editor 2 ``` ### File Copying Issues ```bash # Check your patterns -git config --get-all gtr.copy.include +gtr config get gtr.copy.include # Test patterns with find cd /path/to/repo diff --git a/bin/gtr b/bin/gtr index 26542ce..57a9ede 100755 --- a/bin/gtr +++ b/bin/gtr @@ -28,6 +28,32 @@ resolve_script_dir() { . "$GTR_DIR/lib/copy.sh" . "$GTR_DIR/lib/hooks.sh" +# Generic adapter functions (used when no explicit adapter file exists) +# These will be overridden if an adapter file is sourced +# Globals set by load_editor_adapter: GTR_EDITOR_CMD, GTR_EDITOR_CMD_NAME +editor_can_open() { + command -v "$GTR_EDITOR_CMD_NAME" >/dev/null 2>&1 +} + +editor_open() { + # $GTR_EDITOR_CMD may contain arguments (e.g., "code --wait") + # Using eval here is necessary to handle multi-word commands properly + eval "$GTR_EDITOR_CMD \"\$1\"" +} + +# Globals set by load_ai_adapter: GTR_AI_CMD, GTR_AI_CMD_NAME +ai_can_start() { + command -v "$GTR_AI_CMD_NAME" >/dev/null 2>&1 +} + +ai_start() { + local path="$1" + shift + # $GTR_AI_CMD may contain arguments (e.g., "bunx @github/copilot@latest") + # Using eval here is necessary to handle multi-word commands properly + (cd "$path" && eval "$GTR_AI_CMD \"\$@\"") +} + # Main dispatcher main() { local cmd="${1:-help}" @@ -43,8 +69,8 @@ main() { go) cmd_go "$@" ;; - open) - cmd_open "$@" + editor) + cmd_editor "$@" ;; ai) cmd_ai "$@" @@ -184,7 +210,7 @@ cmd_create() { echo "Branch: $branch_name" # Create the worktree - if ! create_worktree "$base_dir" "$prefix" "$branch_name" "$from_ref" "$track_mode" "$skip_fetch" "$force" "$custom_name"; then + if ! worktree_path=$(create_worktree "$base_dir" "$prefix" "$branch_name" "$from_ref" "$track_mode" "$skip_fetch" "$force" "$custom_name"); then exit 1 fi @@ -210,7 +236,7 @@ cmd_create() { log_info "Worktree created: $worktree_path" echo "" echo "Next steps:" - echo " gtr open $branch_name # Open in editor" + echo " gtr editor $branch_name # Open in editor" echo " gtr ai $branch_name # Start AI tool" echo " cd \"\$(gtr go $branch_name)\" # Navigate to worktree" } @@ -332,8 +358,8 @@ cmd_go() { printf "%s\n" "$worktree_path" } -# Open command -cmd_open() { +# Editor command +cmd_editor() { local identifier="" local editor="" @@ -358,7 +384,7 @@ cmd_open() { done if [ -z "$identifier" ]; then - log_error "Usage: gtr open [--editor ]" + log_error "Usage: gtr editor [--editor ]" exit 1 fi @@ -737,7 +763,7 @@ cmd_config() { scope="global" shift ;; - get|set|unset) + get|set|unset|add) action="$1" shift ;; @@ -745,7 +771,7 @@ cmd_config() { if [ -z "$key" ]; then key="$1" shift - elif [ -z "$value" ] && [ "$action" = "set" ]; then + elif [ -z "$value" ] && { [ "$action" = "set" ] || [ "$action" = "add" ]; }; then value="$1" shift else @@ -775,6 +801,14 @@ cmd_config() { cfg_set "$key" "$value" "$scope" log_info "Config set: $key = $value ($scope)" ;; + add) + if [ -z "$key" ] || [ -z "$value" ]; then + log_error "Usage: gtr config add [--global]" + exit 1 + fi + cfg_add "$key" "$value" "$scope" + log_info "Config added: $key = $value ($scope)" + ;; unset) if [ -z "$key" ]; then log_error "Usage: gtr config unset [--global]" @@ -785,7 +819,7 @@ cmd_config() { ;; *) log_error "Unknown config action: $action" - log_error "Usage: gtr config {get|set|unset} [value] [--global]" + log_error "Usage: gtr config {get|set|add|unset} [value] [--global]" exit 1 ;; esac @@ -796,13 +830,27 @@ load_editor_adapter() { local editor="$1" local adapter_file="$GTR_DIR/adapters/editor/${editor}.sh" - if [ ! -f "$adapter_file" ]; then - log_error "Unknown editor: $editor" - log_info "Available editors: cursor, vscode, zed, idea, pycharm, webstorm, vim, nvim, emacs, sublime, nano, atom" + # Try loading explicit adapter first (allows special handling) + if [ -f "$adapter_file" ]; then + . "$adapter_file" + return 0 + fi + + # Generic fallback: check if command exists in PATH + # Extract first word (command name) from potentially multi-word string + local cmd_name="${editor%% *}" + + if ! command -v "$cmd_name" >/dev/null 2>&1; then + log_error "Editor '$editor' not found" + log_info "Built-in adapters: cursor, vscode, zed, idea, pycharm, webstorm, vim, nvim, emacs, sublime, nano, atom" + log_info "Or use any editor command available in your PATH (e.g., code-insiders, fleet)" exit 1 fi - . "$adapter_file" + # Set globals for generic adapter functions + # Note: $editor may contain arguments (e.g., "code --wait") + GTR_EDITOR_CMD="$editor" + GTR_EDITOR_CMD_NAME="$cmd_name" } # Load AI adapter @@ -810,13 +858,27 @@ load_ai_adapter() { local ai_tool="$1" local adapter_file="$GTR_DIR/adapters/ai/${ai_tool}.sh" - if [ ! -f "$adapter_file" ]; then - log_error "Unknown AI tool: $ai_tool" - log_info "Available AI tools: aider, claude, codex, cursor, continue" + # Try loading explicit adapter first (allows special handling) + if [ -f "$adapter_file" ]; then + . "$adapter_file" + return 0 + fi + + # Generic fallback: check if command exists in PATH + # Extract first word (command name) from potentially multi-word string + local cmd_name="${ai_tool%% *}" + + if ! command -v "$cmd_name" >/dev/null 2>&1; then + log_error "AI tool '$ai_tool' not found" + log_info "Built-in adapters: aider, claude, codex, cursor, continue" + log_info "Or use any AI tool command available in your PATH (e.g., bunx, gpt)" exit 1 fi - . "$adapter_file" + # Set globals for generic adapter functions + # Note: $ai_tool may contain arguments (e.g., "bunx @github/copilot@latest") + GTR_AI_CMD="$ai_tool" + GTR_AI_CMD_NAME="$cmd_name" } # Help command @@ -833,7 +895,7 @@ QUICK START: gtr config set gtr.editor.default cursor # One-time setup gtr config set gtr.ai.default claude # One-time setup gtr new my-feature # Creates worktree in folder "my-feature" - gtr open my-feature # Opens in cursor + gtr editor my-feature # Opens in cursor gtr ai my-feature # Starts claude gtr rm my-feature # Remove when done @@ -841,9 +903,9 @@ QUICK START: KEY CONCEPTS: • Worktree folders are named after the branch name - • Main repo is accessible via special ID '1' (e.g., gtr go 1, gtr open 1) + • Main repo is accessible via special ID '1' (e.g., gtr go 1, gtr editor 1) • Commands accept branch names to identify worktrees - Example: gtr open my-feature, gtr go feature/user-auth + Example: gtr editor my-feature, gtr go feature/user-auth ──────────────────────────────────────────────────────────────────────────────── @@ -859,7 +921,7 @@ CORE COMMANDS (daily workflow): --name : custom folder name suffix (e.g., backend, frontend) --yes: non-interactive mode - open [--editor ] + editor [--editor ] Open worktree in editor (uses gtr.editor.default or --editor) Special: use '1' to open repo root @@ -885,14 +947,19 @@ CORE COMMANDS (daily workflow): SETUP & MAINTENANCE: - config {get|set|unset} [value] [--global] + config {get|set|add|unset} [value] [--global] Manage configuration + - get: read a config value + - set: set a single value (replaces existing) + - add: add a value (for multi-valued configs like hooks, copy patterns) + - unset: remove a config value doctor Health check (verify git, editors, AI tools) adapter List available editor & AI tool adapters + Note: Any command in your PATH can be used (e.g., code-insiders, bunx) clean Remove stale/prunable worktrees @@ -911,18 +978,18 @@ WORKFLOW EXAMPLES: # Daily workflow gtr new feature/user-auth # Create worktree (folder: feature-user-auth) - gtr open feature/user-auth # Open in editor + gtr editor feature/user-auth # Open in editor gtr ai feature/user-auth # Start AI tool # Navigate to worktree directory cd "$(gtr go feature/user-auth)" # Override defaults with flags - gtr open feature/user-auth --editor vscode + gtr editor feature/user-auth --editor vscode gtr ai feature/user-auth --ai aider # Chain commands together - gtr new hotfix && gtr open hotfix && gtr ai hotfix + gtr new hotfix && gtr editor hotfix && gtr ai hotfix # When finished gtr rm feature/user-auth --delete-branch diff --git a/completions/_gtr b/completions/_gtr index 4fdb43d..29c8f9f 100644 --- a/completions/_gtr +++ b/completions/_gtr @@ -7,7 +7,7 @@ _gtr() { 'new:Create a new worktree' 'go:Navigate to worktree' 'rm:Remove worktree(s)' - 'open:Open worktree in editor' + 'editor:Open worktree in editor' 'ai:Start AI coding tool' 'ls:List all worktrees' 'list:List all worktrees' @@ -29,7 +29,7 @@ _gtr() { _describe 'commands' commands elif (( CURRENT == 3 )); then case "$words[2]" in - go|open|ai|rm) + go|editor|ai|rm) _describe 'branch names' all_options ;; new) diff --git a/completions/gtr.bash b/completions/gtr.bash index acb191b..5b844a1 100644 --- a/completions/gtr.bash +++ b/completions/gtr.bash @@ -9,13 +9,13 @@ _gtr_completion() { # Complete commands on first argument if [ "$cword" -eq 1 ]; then - COMPREPLY=($(compgen -W "new go open ai rm ls list clean doctor adapter config help version" -- "$cur")) + COMPREPLY=($(compgen -W "new go editor ai rm ls list clean doctor adapter config help version" -- "$cur")) return 0 fi # Commands that take branch names or '1' for main repo case "$cmd" in - go|open|ai|rm) + go|editor|ai|rm) if [ "$cword" -eq 2 ]; then # Complete with branch names and special ID '1' for main repo local branches all_options diff --git a/completions/gtr.fish b/completions/gtr.fish index 0b2b1c0..a7d1df0 100644 --- a/completions/gtr.fish +++ b/completions/gtr.fish @@ -4,7 +4,7 @@ complete -c gtr -f -n "__fish_use_subcommand" -a "new" -d "Create a new worktree" complete -c gtr -f -n "__fish_use_subcommand" -a "go" -d "Navigate to worktree" complete -c gtr -f -n "__fish_use_subcommand" -a "rm" -d "Remove worktree(s)" -complete -c gtr -f -n "__fish_use_subcommand" -a "open" -d "Open worktree in editor" +complete -c gtr -f -n "__fish_use_subcommand" -a "editor" -d "Open worktree in editor" complete -c gtr -f -n "__fish_use_subcommand" -a "ai" -d "Start AI coding tool" complete -c gtr -f -n "__fish_use_subcommand" -a "ls" -d "List all worktrees" complete -c gtr -f -n "__fish_use_subcommand" -a "list" -d "List all worktrees" @@ -52,4 +52,4 @@ function __gtr_worktree_branches end # Complete branch names for commands that need them -complete -c gtr -n "__fish_seen_subcommand_from go open ai rm" -f -a "(__gtr_worktree_branches)" +complete -c gtr -n "__fish_seen_subcommand_from go editor ai rm" -f -a "(__gtr_worktree_branches)" diff --git a/lib/core.sh b/lib/core.sh index bf00309..d52391f 100644 --- a/lib/core.sh +++ b/lib/core.sh @@ -298,8 +298,8 @@ create_worktree() { # Force use of remote branch if [ "$remote_exists" -eq 1 ]; then log_step "Creating worktree from remote branch origin/$branch_name" - if git worktree add $force_flag "$worktree_path" -b "$branch_name" "origin/$branch_name" 2>/dev/null || \ - git worktree add $force_flag "$worktree_path" "$branch_name" 2>/dev/null; then + if git worktree add $force_flag "$worktree_path" -b "$branch_name" "origin/$branch_name" >&2 || \ + git worktree add $force_flag "$worktree_path" "$branch_name" >&2; then log_info "Worktree created tracking origin/$branch_name" printf "%s" "$worktree_path" return 0 @@ -314,7 +314,7 @@ create_worktree() { # Force use of local branch if [ "$local_exists" -eq 1 ]; then log_step "Creating worktree from local branch $branch_name" - if git worktree add $force_flag "$worktree_path" "$branch_name" 2>/dev/null; then + if git worktree add $force_flag "$worktree_path" "$branch_name" >&2; then log_info "Worktree created with local branch $branch_name" printf "%s" "$worktree_path" return 0 @@ -328,7 +328,7 @@ create_worktree() { none) # Create new branch from from_ref log_step "Creating new branch $branch_name from $from_ref" - if git worktree add $force_flag "$worktree_path" -b "$branch_name" "$from_ref" 2>/dev/null; then + if git worktree add $force_flag "$worktree_path" -b "$branch_name" "$from_ref" >&2; then log_info "Worktree created with new branch $branch_name" printf "%s" "$worktree_path" return 0 @@ -350,21 +350,21 @@ create_worktree() { fi # Now add worktree using the tracking branch - if git worktree add $force_flag "$worktree_path" "$branch_name" 2>/dev/null; then + if git worktree add $force_flag "$worktree_path" "$branch_name" >&2; then log_info "Worktree created tracking origin/$branch_name" printf "%s" "$worktree_path" return 0 fi elif [ "$local_exists" -eq 1 ]; then log_step "Using existing local branch $branch_name" - if git worktree add $force_flag "$worktree_path" "$branch_name" 2>/dev/null; then + if git worktree add $force_flag "$worktree_path" "$branch_name" >&2; then log_info "Worktree created with local branch $branch_name" printf "%s" "$worktree_path" return 0 fi else log_step "Creating new branch $branch_name from $from_ref" - if git worktree add $force_flag "$worktree_path" -b "$branch_name" "$from_ref" 2>/dev/null; then + if git worktree add $force_flag "$worktree_path" -b "$branch_name" "$from_ref" >&2; then log_info "Worktree created with new branch $branch_name" printf "%s" "$worktree_path" return 0 diff --git a/lib/ui.sh b/lib/ui.sh index b9820b5..7e2d700 100644 --- a/lib/ui.sh +++ b/lib/ui.sh @@ -2,7 +2,7 @@ # UI utilities for logging and prompting log_info() { - printf "[OK] %s\n" "$*" + printf "[OK] %s\n" "$*" >&2 } log_warn() { @@ -14,7 +14,7 @@ log_error() { } log_step() { - printf "==> %s\n" "$*" + printf "==> %s\n" "$*" >&2 } log_question() {