diff --git a/dev-scripts/WORKTREE_QUICK_START.md b/dev-scripts/WORKTREE_QUICK_START.md new file mode 100644 index 0000000..2ec8bc5 --- /dev/null +++ b/dev-scripts/WORKTREE_QUICK_START.md @@ -0,0 +1,290 @@ +# Git Worktree Quick Start Guide + +**Note:** This guide uses `~/git/` as an example location for repositories. The worktree structure works in **any directory** you choose - `/projects/`, `/workspace/`, your home directory, etc. All worktrees stay **inside the repository directory**, keeping everything self-contained and portable. + +## TL;DR - Get Started + +### Option A: Fresh Clone (Recommended) + +```bash +# 1. Clone with worktree structure built-in +git clone-worktree git@github.com:dotCMS/core.git dotcms-core + +# 2. Create worktree for an issue (same command as traditional repos!) +cd dotcms-core +git issue-branch --issue 123 + +# 3. Open in your IDE +cd worktrees/issue-123-feature +cursor . # or code . or idea . +``` + +### Option B: Migrate Existing Repository + +```bash +# 1. Migrate your repository +cd ~/git/dotcms-core +git migrate-to-worktrees + +# 2. Create worktree for an issue (auto-detects worktree structure!) +git issue-branch --issue 123 + +# 3. Open in your IDE +cd worktrees/issue-123-feature +cursor . # or code . or idea . +``` + +Done! Now you can work on multiple issues simultaneously. + +## Essential Commands + +### Safe Branch Switching (IMPORTANT!) +```bash +git smart-switch feature-branch # Safe! Navigates to worktree +git checkout feature-branch # ⚠️ DANGEROUS in worktree! + +# The safe way - prevents accidental branch changes +git smart-switch issue-123 # Finds or creates worktree +git smart-switch main # Navigates to main worktree +``` + +### Create Worktree for Issue +```bash +# NO ISSUE NUMBER NEEDED - Interactive selection! +git issue-branch +# → Shows searchable list of your issues +# → Type to filter, arrow keys to select +# → Works in both traditional and worktree repos + +git issue-branch --issue 123 # Direct issue number (if you know it) +git issue-branch --open-ide cursor # Interactive + auto-open IDE +``` + +### Open IDE for Worktree +```bash +git worktree-ide # Interactive selection +git worktree-ide worktrees/issue-123 # Open specific worktree +``` + +### List Worktrees +```bash +git worktree list # Built-in git command +git worktree-cleanup --list # With merge status +``` + +### Clean Up Merged Worktrees +```bash +git worktree-cleanup # Interactive cleanup +git worktree-cleanup --dry-run # Preview +``` + +### Setup Aliases (Optional) +```bash +# Run once to set up short aliases +./setup-worktree-aliases.sh + +# Then use shorter commands: +git wti --issue 123 # wt-issue +git wts feature-branch # wt-switch +git wtc # wt-cleanup +``` + +## Workflow Comparison + +### Old Way (Branch Switching) +```bash +git checkout main +git checkout -b issue-123 +# work... +git checkout main # ❌ Lose context +git checkout -b issue-456 +# work... +git checkout issue-123 # ❌ Switch again +``` + +### New Way (Worktrees) +```bash +git issue-branch --issue 123 # Same command as before! +git issue-branch --issue 456 # Auto-detects worktree structure +# Just switch IDE windows! ✅ +# No git checkout needed +``` + +## Real-World Examples + +### Example 1: Multiple AI Agents +```bash +# Terminal 1: Agent working on backend +git issue-branch --issue 100 +cd worktrees/issue-100-backend-api +cursor . + +# Terminal 2: Agent working on frontend +git issue-branch --issue 101 +cd worktrees/issue-101-frontend-ui +cursor . + +# Both agents work in complete isolation ✅ +``` + +### Example 2: Emergency Hotfix During Feature Work +```bash +# Currently working on feature in worktrees/issue-123-feature/ +cursor . # IDE open here + +# Emergency hotfix needed! +git issue-branch --issue 999 --open-ide cursor +# New Cursor window opens for issue-999 +# Fix bug, commit, push + +# Close issue-999 window +# Return to issue-123 window - exactly where you left off ✅ +``` + +### Example 3: Code Review While Developing +```bash +# Working on your feature +cd worktrees/issue-123-my-feature + +# Colleague asks for code review +git issue-branch --issue 456 --open-ide code +# Review code in VS Code + +# Close review window +# Return to your feature window ✅ +``` + +## Directory Structure Visual + +``` +~/git/dotcms-core/ +├── .git/ # Shared git metadata +├── worktrees/ # All your work happens here +│ ├── main/ # ← Always available +│ ├── issue-123-add-auth/ # ← Current feature +│ ├── issue-456-fix-cache/ # ← Parallel work +│ └── issue-789-update-docs/ # ← Another task +├── docs/ # Optional: keep in main +└── README.md +``` + +## Common Scenarios + +### Migrating from ~/git/core2 +```bash +# If you have: +# ~/git/core-baseline/ (main clone) +# ~/git/core2/ (existing worktree) + +cd ~/git/core-baseline +git migrate-to-worktrees + +# Now delete core2: +rm -rf ~/git/core2 + +# Use worktrees instead (same command as before!): +git issue-branch --issue 123 +# Creates: ~/git/core-baseline/worktrees/issue-123-*/ +``` + +### Setting Default IDE +```bash +# Set once, use everywhere +git config --global worktree.defaultIde cursor + +# Now this automatically uses cursor: +git issue-worktree --issue 123 --open-ide +``` + +### Checking Disk Usage +```bash +# Worktrees are lightweight +du -sh .git/ # ~500MB (shared metadata) +du -sh worktrees/*/ # ~200MB each (just files) + +# Compare to cloning 3 times: +# 3 clones = 3 × 500MB = 1.5GB +# 3 worktrees = 500MB + 3 × 200MB = 1.1GB +``` + +## Troubleshooting + +### "worktree already exists" +```bash +# Remove existing worktree first +git worktree remove worktrees/issue-123-feature + +# Or remove orphaned entry +git worktree prune +``` + +### Lost uncommitted changes after migration +```bash +# Check stash +git stash list + +# Apply to worktree +cd worktrees/ +git stash pop +``` + +### IDE not opening +```bash +# Check IDE is installed and in PATH +which cursor # or: code, idea +``` + +## Pro Tips + +1. **Keep main worktree** - Always have `worktrees/main/` for quick reference +2. **Name IDE windows** - Cursor/Code let you name windows by branch +3. **Use separate terminals** - One terminal tab per worktree +4. **Weekly cleanup** - Run `git worktree-cleanup` to remove merged branches +5. **IDE bookmarks** - Bookmark `~/git/dotcms-core/worktrees/` for quick access + +## Need More Info? + +- Full documentation: [WORKTREE_WORKFLOW.md](./WORKTREE_WORKFLOW.md) +- Script source: `/Users/stevebolton/git/dotcms-utilities/dev-scripts/` +- Git documentation: `man git-worktree` + +## Safety Features + +### Why `git smart-switch`? + +**The Problem:** +```bash +cd ~/git/dotcms-core/worktrees/issue-123-feature/ +git checkout main # ⚠️ DANGER! Changes branch in worktree directory +# Now worktree/issue-123-feature contains main branch - CONFUSING! +``` + +**The Solution:** +```bash +cd ~/git/dotcms-core/worktrees/issue-123-feature/ +git smart-switch main # ✅ SAFE! Shows you main worktree path +# Prevents accidental branch change, guides you to correct worktree +``` + +### What `git smart-switch` Does: + +1. **Detects worktree structure** - Knows when you're in worktrees/ +2. **Prevents accidents** - Blocks accidental branch changes in worktrees +3. **Finds correct worktree** - Shows path to target branch's worktree +4. **Creates if missing** - Makes new worktree for existing branches +5. **Migrates old worktrees** - Automatically moves old worktrees to new structure +6. **Preserves safety** - Ensures worktree names match their branches + +## Quick Reference Card + +| Task | Command | +|------|---------| +| Clone with worktrees | `git clone-worktree [dir]` | +| Migrate repo | `git migrate-to-worktrees` | +| **Create branch/worktree** | **`git issue-branch --issue 123`** (adapts!) | +| **Safe branch switch** | **`git smart-switch `** | +| Open IDE | `git worktree-ide` | +| List worktrees | `git worktree list` | +| Clean up | `git worktree-cleanup` | +| Remove worktree | `git worktree remove worktrees/branch` | +| Get help | `git issue-branch --help` | \ No newline at end of file diff --git a/dev-scripts/WORKTREE_SCRIPT_ABSTRACTIONS.md b/dev-scripts/WORKTREE_SCRIPT_ABSTRACTIONS.md new file mode 100644 index 0000000..11ad3e0 --- /dev/null +++ b/dev-scripts/WORKTREE_SCRIPT_ABSTRACTIONS.md @@ -0,0 +1,407 @@ +# Git Extension Script Abstractions for Worktree Compatibility + +## Overview + +The dotCMS development scripts have been architected with **intelligent detection and delegation** to work seamlessly with both **traditional single-directory repositories** and **worktree-based multi-directory repositories**. This allows developers to use the same familiar commands regardless of repository structure, with minimal learning curve. + +## Design Philosophy + +**Core Principle:** Same commands, adaptive behavior. + +Users shouldn't need to learn new commands or workflows when switching between repository patterns. The scripts automatically detect the repository structure and adapt their behavior accordingly. + +## Key Abstraction Patterns + +### 1. Repository Pattern Detection + +All worktree-aware scripts implement two detection functions: + +#### `is_worktree_repo()` - Detects New Worktree Structure +```bash +is_worktree_repo() { + local git_common_dir=$(git rev-parse --git-common-dir 2>/dev/null) + local base_repo_root=$(dirname "$git_common_dir") + + # Check if worktrees/ directory exists with actual worktrees + if [[ -d "$base_repo_root/worktrees" ]]; then + if [ -n "$(find "$base_repo_root/worktrees" -mindepth 1 -maxdepth 1 -type d 2>/dev/null)" ]; then + return 0 # New worktree structure detected + fi + fi + return 1 +} +``` + +**Purpose:** Identifies repositories using the recommended `repo/worktrees/` structure. + +#### `is_old_worktree()` - Detects Legacy Worktree Pattern +```bash +is_old_worktree() { + local git_dir=$(git rev-parse --git-dir 2>/dev/null) + + # Check if we're in a worktree (git-dir points to .git/worktrees/*) + if [[ "$git_dir" == *".git/worktrees/"* ]]; then + # NOT part of new pattern (no worktrees/ subdirectory) + if [[ ! -d "$repo_root/../worktrees" ]] && [[ "$(basename "$(dirname "$repo_root")")" != "worktrees" ]]; then + return 0 # Old worktree pattern (separate directory) + fi + fi + return 1 +} +``` + +**Purpose:** Identifies repositories using the old scattered worktree pattern (e.g., `../repo-feature-branch/`). + +**Why Both?** Graceful migration path. Old pattern gets warnings and migration prompts, new pattern gets full functionality. + +--- + +### 2. Behavioral Delegation Pattern + +Scripts use **early detection + delegation** to route to the appropriate implementation: + +#### Example: `git-issue-branch` → `git-issue-worktree` + +```bash +# Early in git-issue-branch execution: + +# Detect NEW worktree structure and delegate +if is_worktree_repo; then + echo "Worktree structure detected - delegating to git-issue-worktree" + + # Map options appropriately + WORKTREE_ARGS=() + for arg in "${ORIGINAL_ARGS[@]}"; do + case "$arg" in + --from-current) + # Skip: doesn't make sense in worktree mode (isolated dirs) + ;; + *) + WORKTREE_ARGS+=("$arg") # Pass through + ;; + esac + done + + # Delegate with exec (replace current process) + exec "$script_dir/git-issue-worktree" "${WORKTREE_ARGS[@]}" + exit $? +fi + +# Continue with traditional branch workflow for non-worktree repos... +``` + +**Benefits:** +- **Single entry point:** Users always run `git issue-branch` +- **Transparent delegation:** Script automatically routes to appropriate implementation +- **Argument mapping:** Options are translated to equivalent worktree operations +- **Exec replacement:** No performance overhead, direct process replacement + +--- + +### 3. Option Compatibility Translation + +Some options don't make sense in worktree mode and are handled gracefully: + +| Traditional Option | Worktree Behavior | Reason | +|-------------------|-------------------|---------| +| `--from-current` | Ignored/Transformed | Each worktree is isolated; state copying handled differently | +| `--yes` | Passed through | Still useful for automation | +| `--dry-run` | Passed through | Preview works in both modes | +| `--issue NUMBER` | Passed through | Core functionality, works everywhere | + +**Example Transformation:** +```bash +case "$arg" in + --from-current) + # In worktree mode, this becomes part of create_worktree options + # Rather than branch switching context + ;; + *) + WORKTREE_ARGS+=("$arg") + ;; +esac +``` + +--- + +### 4. Graceful Fallback for Old Patterns + +When old worktree patterns are detected, scripts provide **warnings + guidance**, not errors: + +```bash +if is_old_worktree; then + echo "⚠️ Old worktree pattern detected!" + echo "You're in a separate worktree folder (not using worktrees/ subdirectory)" + echo "" + echo "Recommendation: Migrate to new structure for better organization" + echo "" + echo "To migrate:" + echo " 1. cd to the base repository" + echo " 2. Run: git migrate-to-worktrees" + echo "" + echo "Continuing with traditional branch workflow for now..." + echo "" +fi +``` + +**Key Points:** +- **Non-blocking:** Users can continue working +- **Educational:** Clear explanation of what's detected +- **Actionable:** Specific migration steps provided +- **Fallback:** Traditional workflow continues to work + +--- + +### 5. Path Resolution Abstraction + +Scripts intelligently resolve repository paths regardless of where they're executed: + +```bash +# Get base repository root (works from any worktree or base repo) +local git_common_dir=$(git rev-parse --git-common-dir 2>/dev/null) +local repo_root=$(dirname "$git_common_dir") +local worktrees_dir="$repo_root/worktrees" +``` + +**Why This Matters:** +- Works when run from base repository +- Works when run from any worktree subdirectory +- Consistently finds the canonical worktree storage location +- Prevents path confusion and wrong-directory operations + +--- + +### 6. Contextual Behavior in `git-smart-switch` + +`git-smart-switch` demonstrates the most sophisticated abstraction - it changes behavior based on **current context**: + +#### When run from Base Repository: +```bash +# User is in /repo/ (base directory) +if is_worktree_repo; then + echo "Creating/navigating to worktree for branch: $new_branch" + # Creates worktree at worktrees/branch-name/ + # Outputs path for wrapper function to cd to +fi +``` + +#### When run from Inside a Worktree: +```bash +# User is in /repo/worktrees/feature-123/ +if [[ "$CURRENT_DIR" == "$REPO_ROOT/worktrees/"* ]]; then + echo "You're in a worktree - creating new worktree instead of switching" + # Creates ANOTHER worktree (parallel work) + # Does NOT modify current directory +fi +``` + +#### When run from Traditional Repo: +```bash +# User is in regular single-directory repo +# Standard branch switching with WIP management +git checkout "$new_branch" +restore_wip +``` + +**Result:** Same command (`git smart-switch feature`), three different contextual behaviors, all appropriate to the situation. + +--- + +## Benefits to User Experience + +### 1. Zero Learning Curve +Users don't need to memorize new commands: +- `git issue-branch` works everywhere +- `git smart-switch` works everywhere +- `git issue-pr` works everywhere + +### 2. Progressive Enhancement +- Traditional repos: Full functionality as before +- Worktree repos: Enhanced with parallel work capability +- Migration: Smooth transition with clear guidance + +### 3. Safety Through Detection +Scripts prevent dangerous operations: +- Won't migrate from within a worktree (would corrupt structure) +- Won't create conflicting worktrees +- Won't lose uncommitted changes during transitions + +### 4. Consistent Developer Experience +Whether working on: +- Personal projects (traditional repos) +- Team projects migrated to worktrees +- Mixed environments (some repos migrated, some not) + +**Same muscle memory, same commands, appropriate behavior.** + +--- + +## Implementation Details + +### Shared Helper Functions + +All worktree-aware scripts source common detection logic: + +```bash +# Defined in git-issue-branch, git-smart-switch, etc. +is_worktree_repo() { ... } # Detects new structure +is_old_worktree() { ... } # Detects legacy pattern +find_worktree_for_branch() { ... } # Locates existing worktrees +``` + +### Consistent User Messaging + +All scripts use unified color-coded output: +- 🔵 **Blue:** Informational messages (structure detected, actions taken) +- 🟡 **Yellow:** Warnings (old patterns, migration suggestions) +- 🟢 **Green:** Success confirmations +- 🔴 **Red:** Errors (operation blocked, manual action needed) + +### Dry-Run Support Everywhere + +Both traditional and worktree modes support `--dry-run`: +```bash +git issue-branch --issue 123 --dry-run # Works in both modes +git smart-switch feature --dry-run # Shows what would happen +``` + +This helps users understand what the script will do **before** committing to an action. + +--- + +## Migration Path Considerations + +### Phase 1: Traditional Repositories +- All scripts work as they always have +- No worktree detection needed +- Full backward compatibility + +### Phase 2: Post-Migration (Worktree Structure) +- Scripts auto-detect new structure +- Delegate to worktree-optimized implementations +- Enhanced parallel work capabilities + +### Phase 3: Hybrid Environments +- Some repos migrated, some not +- Scripts adapt per-repository +- No mental overhead for developers +- Same commands everywhere + +--- + +## Example: Command Equivalence Table + +| Command | Traditional Repo Behavior | Worktree Repo Behavior | +|---------|--------------------------|------------------------| +| `git issue-branch` | Creates/switches local branch | Creates new worktree directory | +| `git issue-branch --from-current` | Branches from current state | Maps to state-copying option | +| `git smart-switch main` | Switches branch (WIP commits) | Navigates to main worktree directory | +| `git smart-switch new-feature` | Creates branch from main | Creates worktree from main | +| `git issue-pr` | Creates PR from current branch | Creates PR from current worktree's branch | + +**User perspective:** "I just run the same commands I always have." + +--- + +## Advanced: Argument Pass-Through Logic + +Scripts implement intelligent argument filtering and transformation: + +```bash +# Original args from user +ORIGINAL_ARGS=("$@") + +# Parse and potentially transform +for arg in "${ORIGINAL_ARGS[@]}"; do + case "$arg" in + --from-current) + # In worktree context, this becomes: + # "copy state from current worktree to new worktree" + # Not: "branch from current commit" + if is_worktree_repo; then + WORKTREE_ARGS+=("--copy-state") + else + TRAD_ARGS+=("--from-current") + fi + ;; + --issue) + # Universal - works same in both modes + WORKTREE_ARGS+=("$arg") + TRAD_ARGS+=("$arg") + ;; + esac +done +``` + +This ensures **semantic preservation** - the *intent* of the option is maintained, even if the implementation differs. + +--- + +## Testing Strategy + +Scripts are designed to be testable in both modes: + +```bash +# Test traditional mode +cd ~/test-repo-traditional/ +git issue-branch --issue 123 --dry-run + +# Test worktree mode +cd ~/test-repo-worktree/ +git issue-branch --issue 123 --dry-run + +# Same command, different preview output, both valid +``` + +--- + +## Summary: Why This Design Works + +1. **Single Source of Truth:** Core business logic (issue creation, PR creation) is shared +2. **Behavioral Polymorphism:** Implementation adapts to detected environment +3. **Progressive Disclosure:** Users only see complexity when needed (migration warnings) +4. **Fail-Safe Defaults:** When in doubt, scripts explain and prompt rather than error +5. **Idempotent Operations:** Running commands multiple times is safe +6. **Context-Aware Help:** Help text adapts to show relevant options for detected mode + +**Result:** Developers experience a **unified, intuitive workflow** that "just works" regardless of repository structure, with clear guidance when transitioning between patterns. + +--- + +## Comparison: Before vs After Abstraction + +### Before (Separate Commands, User Confusion) +```bash +# Traditional repo +git issue-branch --issue 123 # Works + +# After migration +git issue-branch --issue 123 # Error: not compatible with worktrees! + # Must use git issue-worktree instead! + +# User: "Wait, which command do I use where?" +``` + +### After (Unified Interface, Adaptive) +```bash +# Traditional repo +git issue-branch --issue 123 # Creates branch + +# After migration +git issue-branch --issue 123 # Auto-detects, delegates to worktree creation + +# User: "Same command, just works." +``` + +--- + +## Future Extensibility + +This abstraction pattern allows future enhancements without breaking changes: + +- **New repository patterns** (e.g., sparse checkouts): Add detection + delegation +- **Cloud-based workflows**: Abstract to support remote worktree creation +- **IDE integrations**: Scripts already output paths for wrapper functions +- **Multi-repo coordination**: Detection logic can expand to parent/child relationships + +The architecture is **open-closed principle compliant**: Open for extension (new patterns), closed for modification (existing behavior preserved). \ No newline at end of file diff --git a/dev-scripts/WORKTREE_WORKFLOW.md b/dev-scripts/WORKTREE_WORKFLOW.md new file mode 100644 index 0000000..c711d01 --- /dev/null +++ b/dev-scripts/WORKTREE_WORKFLOW.md @@ -0,0 +1,1020 @@ +# Git Worktree Workflow Guide + +## Overview + +This guide documents a comprehensive git worktree-based workflow that allows you to work on multiple issues/branches simultaneously without switching contexts. Each branch gets its own isolated working directory. + +## Benefits + +✅ **No more branch switching** - Each branch has its own directory +✅ **Parallel development** - Work on multiple issues simultaneously +✅ **Clean separation** - No accidental commits to wrong branch +✅ **IDE friendly** - Open multiple IDEs, each on different branch +✅ **AI agent friendly** - Perfect for running multiple AI coding agents +✅ **Fast context switching** - Just switch IDE windows, not branches + +## Directory Structure + +**Note:** This example uses `~/git/` as the repository root, but this structure works **anywhere you clone repositories** - your home directory, `/projects/`, `/workspace/`, etc. The key is that all worktrees are contained **within the repository directory**, keeping everything self-contained and portable. + +``` +~/git/ # Example location (use any directory you prefer) +├── dotcms-core/ # Main repository (completely self-contained) +│ ├── .git/ # All git metadata here +│ ├── worktrees/ # All working trees live INSIDE the repo +│ │ ├── main/ # Main branch worktree +│ │ ├── issue-123-add-feature/ # Issue #123 worktree +│ │ └── issue-456-fix-bug/ # Issue #456 worktree +│ ├── docs/ # Optional: docs in base directory +│ └── README.md +│ +└── dotcms-utilities/ # Another repo (regular clone, no worktrees) + └── dev-scripts/ +``` + +**Self-Contained Structure:** +- All worktrees are subdirectories within the repository +- Moving the repository directory moves all worktrees together +- No worktrees scattered across different filesystem locations +- Everything related to `dotcms-core` stays inside `dotcms-core/` + +**Git Configuration:** + +To prevent Git from trying to track the `worktrees/` directory and any documentation files in the base directory, we use `.git/info/exclude` instead of `.gitignore`: + +```bash +# After migration, the migration script adds to .git/info/exclude: +/worktrees/ +/README.md +``` + +**Why `.git/info/exclude` instead of `.gitignore`?** +- ✅ **Local to your repository** - Not tracked or committed +- ✅ **Works across all branches** - All worktrees see the same exclusions +- ✅ **No branch conflicts** - Switching worktrees doesn't change exclusion rules +- ✅ **Zero code changes** - Existing branches remain untouched +- ❌ `.gitignore` would need to be committed to each branch, causing merge conflicts + +This enables the worktree structure without modifying any code or branch files. + +## Scripts + +### 1. `git-migrate-to-worktrees` + +Converts an existing repository to use the worktree workflow. + +**Usage:** +```bash +# Preview migration +git migrate-to-worktrees --dry-run + +# Perform migration +git migrate-to-worktrees +``` + +**What it does:** +1. Creates `worktrees/` directory +2. Moves current branch to `worktrees//` +3. Preserves all uncommitted changes using git stash +4. Optionally creates worktree for main branch + +**Safety features:** +- Detects and preserves uncommitted changes +- Confirms before making changes +- Dry-run mode to preview +- Handles existing worktrees gracefully + +### 2. `git-issue-branch` (Unified Command) + +Creates branches OR worktrees from GitHub issues - automatically adapts to repository structure! + +**Usage:** +```bash +# Interactive issue selection - NO ISSUE NUMBER NEEDED! +git issue-branch +# → Shows searchable list of your assigned and recently created issues +# → Select with arrow keys or type to filter +# → Works in both traditional and worktree repos + +# Direct issue number (if you already know it) +git issue-branch --issue 123 + +# Interactive selection with IDE launch (worktree repos) +git issue-branch --open-ide cursor +# → Select issue from list, then IDE opens automatically + +# List all available issues +git issue-branch --list + +# Preview what would happen (dry run) +git issue-branch --issue 123 --dry-run + +# Automation mode (skip confirmations) +git issue-branch --issue 123 --yes +``` + +**No Issue Number Required:** +- Just run `git issue-branch` without any arguments +- Get an interactive, searchable list of issues +- Filter by typing (fuzzy search) +- Select with arrow keys or number +- Perfect when you don't remember the exact issue number! + +**Adaptive Behavior:** +- **Traditional repos:** Creates/switches branches (classic workflow) +- **Worktree repos:** Creates isolated worktree directories (parallel workflow) +- Automatically detects repository structure +- No need to remember separate commands! + +**Features:** +- **Interactive issue selection** - searchable list with fuzzy filtering +- Shows your assigned and recently created issues +- In worktree repos: Creates worktree in `worktrees/issue-{number}-{title}/` +- In traditional repos: Creates/switches to branch `issue-{number}-{title}` +- Automatically links branch to GitHub issue +- Optional custom branch suffix +- Handles naming conflicts with numbered suffixes +- Optional IDE launcher integration (worktree mode) + +### 3. `git-worktree-ide` + +Opens IDEs for specific worktrees. + +**Usage:** +```bash +# Interactive selection +git worktree-ide + +# Open specific worktree +git worktree-ide worktrees/issue-123-feature + +# Specify IDE +git worktree-ide worktrees/main --ide cursor +git worktree-ide worktrees/issue-123 --ide code +git worktree-ide worktrees/issue-456 --ide idea + +# List worktrees +git worktree-ide --list +``` + +**Supported IDEs:** +- `cursor` - Cursor IDE +- `code` - Visual Studio Code +- `idea` - IntelliJ IDEA +- `fleet` - JetBrains Fleet + +**Configuration:** +```bash +# Set default IDE +git config --global worktree.defaultIde cursor +git config --global worktree.defaultIde code +git config --global worktree.defaultIde idea +``` + +### 4. `git-worktree-cleanup` + +Cleans up merged and stale worktrees. + +**Usage:** +```bash +# Interactive cleanup +git worktree-cleanup + +# List worktrees with status +git worktree-cleanup --list + +# Dry run +git worktree-cleanup --dry-run + +# Auto-confirm (careful!) +git worktree-cleanup --yes + +# Remove all non-main worktrees +git worktree-cleanup --all +``` + +**Features:** +- Identifies merged branches +- Detects uncommitted changes +- Never removes main/master worktree +- Confirms before deletion +- Preserves git history (only removes worktrees) + +## Workflow Examples + +### Starting Fresh (New Repository) + +```bash +# 1. Clone repository normally +cd ~/git +git clone git@github.com:dotCMS/core.git dotcms-core +cd dotcms-core + +# 2. Migrate to worktrees +git migrate-to-worktrees + +# 3. Set default IDE (optional) +git config --global worktree.defaultIde cursor + +# 4. Done! Start working +``` + +### Daily Development Workflow + +```bash +# Start working on an issue (same command works for traditional or worktree repos!) +git issue-branch --issue 123 --open-ide cursor + +# In worktree repo: IDE opens at worktrees/issue-123-feature/ +# In traditional repo: Creates branch and switches to it + +# Start another task (in parallel in worktree repos!) +git issue-branch --issue 456 --open-ide cursor + +# In worktree repos, you now have TWO IDE windows: +# - Window 1: issue-123-feature +# - Window 2: issue-456-fix-bug + +# Switch between them by switching IDE windows +# No git checkout needed! +``` + +### Multiple AI Agents Workflow + +```bash +# Agent 1: Start working on backend feature +git issue-branch --issue 100 --yes +cd worktrees/issue-100-backend-api +cursor . # AI agent 1 works here + +# Agent 2: Start working on frontend feature +git issue-branch --issue 101 --yes +cd worktrees/issue-101-frontend-ui +cursor . # AI agent 2 works here + +# Both agents work in complete isolation +# No conflicts, no branch switching +``` + +### Cleanup After Merge + +```bash +# After merging a PR on GitHub + +# Option 1: Clean up specific worktree manually +git worktree remove worktrees/issue-123-feature + +# Option 2: Auto-cleanup all merged worktrees +git worktree-cleanup + +# Option 3: Just see what would be cleaned +git worktree-cleanup --list +``` + +## Migrating Existing Setup + +If you already have `~/git/core2` worktree: + +```bash +# Current state: +# ~/git/core-baseline/ (original) +# ~/git/core2/ (worktree) + +# Option 1: Keep core-baseline as main, remove core2 +cd ~/git/core-baseline +git migrate-to-worktrees +# Then delete ~/git/core2 + +# Option 2: Fresh start with worktrees +cd ~/git +mv core-baseline core-baseline.old +git clone git@github.com:dotCMS/core.git dotcms-core +cd dotcms-core +git migrate-to-worktrees +# Copy any uncommitted work from core-baseline.old +``` + +## Unified Command Works Everywhere + +The `git-issue-branch` command automatically adapts to your repository structure: + +- **Traditional repos** - Creates/switches branches (classic workflow) +- **Worktree repos** - Creates isolated worktree directories (parallel workflow) + +You use the SAME command everywhere! Example: + +```bash +# Worktree repo - creates worktree +cd ~/git/dotcms-core +git issue-branch --issue 123 +# → Creates worktrees/issue-123-*/ + +# Traditional repo - creates branch +cd ~/git/dotcms-utilities +git issue-branch --issue 456 +# → Creates and switches to branch issue-456-* + +# Same command, adaptive behavior! +``` + +## Native Git Commands vs. Helper Scripts + +**You have complete freedom to choose your workflow!** The helper scripts (`git issue-branch`, `git smart-switch`) are entirely optional. You can manage worktrees using standard git commands if you prefer. + +### Option A: Native Git Commands (More Work, Full Control) + +```bash +# Create a new worktree manually +git worktree add worktrees/issue-123-feature issue-123-feature + +# Navigate to it +cd worktrees/issue-123-feature + +# Link to GitHub issue (optional) +gh issue develop 123 --checkout + +# Open your IDE +cursor . + +# List all worktrees +git worktree list + +# Remove a worktree when done +git worktree remove worktrees/issue-123-feature + +# Clean up orphaned worktree metadata +git worktree prune +``` + +**When to use native commands:** +- You prefer manual control over every step +- You're comfortable with git worktree syntax +- You want to use custom naming conventions +- You're scripting your own workflows +- You don't need GitHub issue integration + +### Option B: Helper Scripts (Less Work, Convenience Features) + +```bash +# Create worktree with one command (interactive or direct) +git issue-branch # Interactive issue selection +git issue-branch --issue 123 # Direct issue number +git issue-branch --open-ide cursor # Create + open IDE + +# Navigate between worktrees safely +git smart-switch main # Creates/navigates to main worktree +git smart-switch issue-456 # Creates/navigates to issue-456 worktree + +# Clean up merged worktrees automatically +git worktree-cleanup +``` + +**What helper scripts provide:** + +1. **Consistency** - Standardized naming (`worktrees/issue-123-title/`) +2. **Safety** - Prevents common mistakes (wrong directory, naming conflicts) +3. **GitHub Integration** - Automatic issue linking, searches your assigned issues +4. **Time Savings** - One command instead of multiple steps +5. **Error Prevention** - Validates branch names, handles edge cases +6. **IDE Integration** - Automatically launches your preferred IDE +7. **Interactive Selection** - Searchable issue list when you don't know the number + +### Full Comparison: Native vs. Helper Scripts + +**Creating a worktree from GitHub issue #123:** + +```bash +# Native Git Way (5 steps, more typing) +git fetch origin +git worktree add worktrees/issue-123-add-dark-mode issue-123-add-dark-mode +cd worktrees/issue-123-add-dark-mode +gh issue develop 123 --checkout # Link to GitHub issue +cursor . # Open IDE + +# Helper Script Way (1 command) +git issue-branch --issue 123 --open-ide cursor +# ✅ Creates worktree with standard naming +# ✅ Links to GitHub issue automatically +# ✅ Opens IDE in one command +# ✅ Handles errors gracefully +``` + +**Switching to work on a different branch:** + +```bash +# Native Git Way +cd ~/git/dotcms-core/worktrees/main +# Or create new worktree if it doesn't exist: +git worktree add worktrees/main main +cd worktrees/main + +# Helper Script Way (from anywhere in the repo) +git smart-switch main +# ✅ Detects worktree structure +# ✅ Creates worktree if needed +# ✅ Shows you the path to navigate to +``` + +**Cleaning up merged worktrees:** + +```bash +# Native Git Way (manual for each worktree) +git worktree list # See all worktrees +git branch -d issue-123-feature # Delete branch +git worktree remove worktrees/issue-123-feature +# Repeat for each merged worktree... + +# Helper Script Way (automated) +git worktree-cleanup +# ✅ Detects merged branches automatically +# ✅ Shows which worktrees will be removed +# ✅ Confirms before deletion +# ✅ Preserves uncommitted changes +``` + +### Bottom Line: Your Choice! + +**Both approaches work perfectly!** Choose based on your preferences: + +- **Use native commands** if you want full manual control and don't need GitHub integration +- **Use helper scripts** if you want convenience, safety features, and time savings +- **Mix both** - use native commands for some tasks, helper scripts for others + +The worktree structure works the same regardless of which commands you use to create and manage worktrees. The scripts simply automate common patterns and add safety guardrails. + +## Critical Rules for Working with Worktrees + +### Rule 1: Never Work in the Base Repository Directory + +**After migration, the top-level directory is metadata only:** + +```bash +~/git/dotcms-core/ # ❌ DO NOT open this in your IDE +~/git/dotcms-core/ # ❌ DO NOT commit changes here +~/git/dotcms-core/worktrees/issue-123/ # ✅ ALWAYS work in worktree directories +``` + +**Why?** The base directory after migration contains only: +- `.git/` - Shared git metadata for all worktrees +- `worktrees/` - Your actual working directories +- Possibly documentation or configuration files + +**Important:** +- ❌ **DO NOT** open `~/git/dotcms-core/` in your IDE +- ❌ **DO NOT** commit changes from the base directory +- ✅ **ALWAYS** work inside `~/git/dotcms-core/worktrees//` + +### Rule 2: Keep Worktree Folders and Branches Matched + +**Each worktree directory should stay on its corresponding branch:** + +```bash +# ✅ CORRECT - Branch matches directory name +~/git/dotcms-core/worktrees/issue-123-feature/ → branch: issue-123-feature + +# ❌ WRONG - Branch doesn't match directory +~/git/dotcms-core/worktrees/issue-123-feature/ → branch: main # CONFUSING! +``` + +**Why this matters:** + +1. **Prevents confusion** - Directory name tells you what branch it contains +2. **Avoids conflicts** - Git prevents checking out the same branch in multiple worktrees +3. **Simplifies workflow** - No mental overhead tracking which directory is on which branch + +**What happens if you break this rule:** + +```bash +cd ~/git/dotcms-core/worktrees/issue-123-feature/ +git checkout main # ❌ BAD IDEA! + +# Now you have: +# - Directory named "issue-123-feature" +# - But it's on the "main" branch +# - Very confusing! + +# Worse: Try to create a new worktree for main +git worktree add worktrees/main main +# ERROR: 'main' is already checked out at 'worktrees/issue-123-feature' +``` + +**The right way to switch work:** + +```bash +# Instead of changing branches, create/navigate to worktrees +cd ~/git/dotcms-core/worktrees/issue-123-feature/ # Working on feature + +# Need to work on main? +git smart-switch main +# ✅ Creates/navigates to worktrees/main/ +# ✅ Keeps issue-123-feature worktree unchanged +``` + +### Rule 3: Use `git smart-switch` Instead of `git checkout` + +**The problem with `git checkout` in worktree repos:** + +```bash +# Traditional repo behavior (single directory) +git checkout feature-branch # Switches branch in current directory + +# Worktree repo behavior (should navigate instead!) +git checkout feature-branch # ❌ Changes branch in worktree directory (confusing!) +``` + +**The solution: `git smart-switch` adapts to repo structure:** + +| Repository Type | `git smart-switch` Behavior | +|----------------|----------------------------| +| **Traditional repo** | Backs up WIP, switches branch in current directory | +| **Worktree repo** | Creates/navigates to worktree for that branch | + +**Examples:** + +```bash +# Traditional repo +cd ~/git/dotcms-utilities +git smart-switch feature-branch +# ✅ Backs up current work, switches to feature-branch + +# Worktree repo +cd ~/git/dotcms-core/worktrees/issue-123/ +git smart-switch main +# ✅ Shows you: "Navigate to ~/git/dotcms-core/worktrees/main/" +# ✅ Creates worktree if it doesn't exist +# ✅ Current worktree stays on issue-123 branch +``` + +**Why `git smart-switch` is safer:** + +1. **Context-aware** - Detects worktree structure and adapts behavior +2. **Prevents accidents** - Won't let you change branches in worktree directories +3. **Consistent UX** - Same command works everywhere, does the right thing +4. **Migration-friendly** - Works before and after worktree migration + +### Rule 4: One Branch, One Worktree (Maximum) + +**Git enforces this rule automatically:** + +```bash +# Create first worktree for feature-branch +git worktree add worktrees/issue-123-feature issue-123-feature +# ✅ Success + +# Try to create another worktree for the same branch +git worktree add worktrees/issue-123-copy issue-123-feature +# ❌ ERROR: 'issue-123-feature' is already checked out +``` + +**Why this exists:** +- Prevents simultaneous edits to the same branch from different directories +- Avoids confusion about which worktree has the "real" state +- Ensures clean git history + +**What to do if you need to work on the same code in two places:** + +```bash +# Option 1: Use the existing worktree +cd worktrees/issue-123-feature/ + +# Option 2: Create a new branch +git worktree add worktrees/issue-123-variant issue-123-variant +# Base it on issue-123-feature or merge later + +# Option 3: Remove the old worktree first +git worktree remove worktrees/issue-123-feature +git worktree add worktrees/issue-123-feature issue-123-feature +``` + +### Summary: Worktree Best Practices + +| ✅ DO | ❌ DON'T | +|-------|---------| +| Open worktree directories in IDE | Open base repository directory | +| Commit from worktree directories | Commit from base directory | +| Keep branch matched to directory name | Change branches within worktrees | +| Use `git smart-switch` to navigate | Use `git checkout` to switch branches | +| Create one worktree per branch | Try to checkout same branch twice | +| Use `git issue-branch` for new issues | Manually manage worktree naming | + +**Golden Rule:** Treat each worktree directory as if it were a separate clone of the repository. Each one has its own branch, its own working state, and should be opened in its own IDE window. + +## Troubleshooting + +### Worktree is locked + +```bash +# Remove lock file +rm .git/worktrees//locked + +# Or force remove +git worktree remove --force worktrees/ +``` + +### Uncommitted changes in old location + +After migration, if you left uncommitted changes: + +```bash +# Check stash +git stash list + +# Apply stash to worktree +cd worktrees/ +git stash pop +``` + +### IDE doesn't recognize worktree + +Worktrees are full git repositories, just open the directory: + +```bash +# ✅ Correct +cursor worktrees/issue-123-feature/ + +# ❌ Wrong +cursor . # (from main repo directory) +``` + +### Disk space concerns + +Each worktree is a checkout, but they share `.git`: + +```bash +# Check worktree sizes +du -sh worktrees/* + +# Worktrees are lightweight (~same size as one checkout) +# .git directory is shared (no duplication) +``` + +## Advanced Usage + +### Custom worktree locations + +By default, worktrees go in `repo/worktrees/`. You can put them anywhere: + +```bash +# Worktrees outside repo (not recommended) +git worktree add ~/my-worktrees/issue-123 issue-123-feature + +# But then tools may not find them automatically +``` + +### Sharing worktrees with smart-switch + +The `git-smart-switch` script can detect and switch to existing worktrees: + +```bash +# If worktree exists, switches to it +# If not, creates branch normally +git smart-switch issue-123-feature +``` + +**Note:** Your shell prompt should already display the current branch name through standard git integration. Each worktree directory is a full git repository with its own branch, so existing git prompt configurations work automatically without additional setup. + +## Best Practices + +1. **Main branch worktree** - Always keep a main branch worktree for quick reference +2. **Regular cleanup** - Run `git worktree-cleanup` weekly to remove merged branches +3. **IDE windows** - Name your IDE windows by branch (Cursor/Code support this) +4. **Terminal tabs** - Use separate terminal tabs per worktree +5. **One worktree per issue** - Don't share worktrees between multiple issues + +## Comparison: Traditional vs Worktree Workflow + +### Traditional (Branch Switching) + +```bash +git checkout main +git pull +git checkout -b issue-123-feature +# ... work on feature ... + +git checkout main # ❌ Slow, loses IDE state +git checkout -b issue-456-bug +# ... work on bug ... + +git checkout issue-123-feature # ❌ More switching +# ... continue feature ... +``` + +### Worktree Workflow + +```bash +# One-time setup (same command as traditional, just detects worktree structure!) +git issue-branch --issue 123 +git issue-branch --issue 456 + +# Then just switch IDE windows! +# IDE 1: ~/git/dotcms-core/worktrees/issue-123-feature/ +# IDE 2: ~/git/dotcms-core/worktrees/issue-456-bug/ + +# ✅ No git checkout +# ✅ Instant context switching +# ✅ Independent IDE states +``` + +## FAQ + +**Q: Does this duplicate my repository?** +A: No! The `.git` directory is shared. Worktrees are just checkouts, similar to having multiple clones but more efficient. + +**Q: Do I need to learn new commands for worktrees?** +A: No! `git issue-branch` automatically detects worktree structure and adapts. Same command, everywhere! + +**Q: What happens to my existing branches?** +A: Nothing! Your branches stay intact. Worktrees are just another way to check them out. + +**Q: Can I commit/push from worktrees?** +A: Absolutely! Worktrees are full-featured git working directories. + +**Q: Do I need to migrate my entire repo?** +A: No! You can create worktrees manually with `git worktree add` or use `git issue-branch` (it will detect and create worktrees automatically in migrated repos). + +**Q: What about disk space?** +A: Worktrees use similar disk space to a single checkout (not per worktree). The `.git` directory is shared. + +## Git Worktree Command Reference + +For those who prefer using native git commands, here's a complete reference of standard git worktree operations: + +### Creating Worktrees + +```bash +# Create a new worktree from an existing branch +git worktree add +git worktree add worktrees/feature-branch feature-branch + +# Create a new worktree and new branch from current HEAD +git worktree add -b +git worktree add -b issue-123-fix worktrees/issue-123-fix + +# Create a new worktree and new branch from a specific commit/branch +git worktree add -b +git worktree add -b hotfix worktrees/hotfix-123 main + +# Create worktree without checking out files (bare worktree) +git worktree add --no-checkout + +# Create detached HEAD worktree at specific commit +git worktree add --detach +git worktree add --detach worktrees/test-commit abc123 +``` + +### Listing Worktrees + +```bash +# List all worktrees +git worktree list + +# List worktrees with detailed information (porcelain format) +git worktree list --porcelain + +# Example output: +# worktree /Users/user/git/dotcms-core +# HEAD 1234567890abcdef +# branch refs/heads/main +# +# worktree /Users/user/git/dotcms-core/worktrees/issue-123 +# HEAD abcdef1234567890 +# branch refs/heads/issue-123-feature +``` + +### Removing Worktrees + +```bash +# Remove a worktree (must have no uncommitted changes) +git worktree remove +git worktree remove worktrees/issue-123-feature + +# Force remove a worktree (even with uncommitted changes) +git worktree remove --force +git worktree remove -f worktrees/issue-123-feature +``` + +### Moving Worktrees + +```bash +# Move/rename a worktree to a new location +git worktree move +git worktree move worktrees/old-name worktrees/new-name +``` + +### Pruning Worktrees + +```bash +# Remove worktree information for deleted worktrees +git worktree prune + +# Dry run - show what would be pruned +git worktree prune --dry-run + +# Verbose output +git worktree prune --verbose +``` + +### Locking/Unlocking Worktrees + +**What are locked worktrees?** + +Locking a worktree prevents `git worktree prune` from automatically removing its administrative data, even if the worktree directory has been deleted or moved. This is useful when: + +- Working on a network drive that may be temporarily unavailable +- Moving worktrees to different locations (external drives, NAS, etc.) +- Protecting important long-running work from accidental cleanup +- Working on removable media (USB drives) + +**When Git locks worktrees automatically:** +- Worktrees on removable media are automatically locked +- When Git detects a worktree is on a different filesystem than the main repo + +```bash +# Lock a worktree (prevents automatic pruning) +git worktree lock +git worktree lock worktrees/important-feature + +# Optional: provide a reason (recommended for team repos) +git worktree lock worktrees/important-feature --reason "Work in progress, do not remove" + +# Unlock a worktree +git worktree unlock +git worktree unlock worktrees/important-feature + +# Check if a worktree is locked (shows in list output) +git worktree list +# Output shows: worktree /path/to/worktree locked +``` + +**Common scenarios:** + +```bash +# Moving worktree to external drive +git worktree lock worktrees/big-feature --reason "Moved to external SSD" +mv worktrees/big-feature /Volumes/ExternalSSD/ +# Later: unlock when back in standard location +git worktree unlock /Volumes/ExternalSSD/big-feature + +# Network drive temporarily unavailable +git worktree lock worktrees/shared-work --reason "Network drive may disconnect" +# Prevents pruning when network is down +# Unlock when network is stable +git worktree unlock worktrees/shared-work +``` + +### Repairing Worktrees + +```bash +# Repair worktree administrative files (if moved manually) +git worktree repair + +# Repair specific worktree +git worktree repair +``` + +### Working with Worktree Branches + +```bash +# Inside a worktree, all standard git commands work: +cd worktrees/issue-123-feature + +# Check current branch +git branch --show-current + +# Create a new branch from current worktree +git checkout -b new-branch + +# Switch branches (NOT recommended - breaks directory/branch matching) +git checkout other-branch # ⚠️ Avoid this in worktrees! + +# Commit, push, pull - all work normally +git add . +git commit -m "Fix bug" +git push origin issue-123-feature +``` + +### Checking Worktree Status + +```bash +# Check if current directory is a worktree +git rev-parse --git-dir +# Output: /path/to/repo/.git/worktrees/branch-name (if worktree) +# Output: .git (if main working tree) + +# Get the common git directory (shared across all worktrees) +git rev-parse --git-common-dir +# Output: /path/to/repo/.git + +# Get the root directory of current worktree +git rev-parse --show-toplevel +``` + +### Advanced Usage + +```bash +# Create worktree with specific initial commit +git worktree add + +# Create orphan branch in worktree (no commit history) +git worktree add --orphan + +# Create worktree for existing remote branch +git worktree add worktrees/feature origin/feature + +# Create worktree tracking a remote branch +git worktree add -b local-feature worktrees/feature origin/feature +``` + +### Common Workflows with Native Commands + +**Parallel Feature Development:** +```bash +# Work on multiple features simultaneously +git worktree add worktrees/feature-a feature-a +git worktree add worktrees/feature-b feature-b +git worktree add worktrees/main main + +# Switch between them by changing directories +cd worktrees/feature-a # Work on feature A +cd worktrees/feature-b # Work on feature B +cd worktrees/main # Check main branch +``` + +**Emergency Hotfix:** +```bash +# Create hotfix worktree from main +git worktree add -b hotfix-urgent worktrees/hotfix-urgent main +cd worktrees/hotfix-urgent +# Fix bug, commit, push +git add . +git commit -m "Fix critical bug" +git push origin hotfix-urgent +# Create PR, get it merged +cd ../.. +git worktree remove worktrees/hotfix-urgent +git branch -d hotfix-urgent +``` + +**Code Review in Separate Worktree:** +```bash +# Create temporary worktree for reviewing a PR +git fetch origin pull/123/head:pr-123 +git worktree add worktrees/pr-123-review pr-123 +cd worktrees/pr-123-review +# Review code, test locally +cd ../.. +git worktree remove worktrees/pr-123-review +git branch -d pr-123 +``` + +### Cleanup After Merging + +```bash +# After branch is merged on GitHub +git fetch --prune # Update remote branch info + +# Remove the worktree +git worktree remove worktrees/issue-123-feature + +# Delete the local branch +git branch -d issue-123-feature + +# If branch wasn't merged (force delete) +git branch -D issue-123-feature +``` + +### Troubleshooting Commands + +```bash +# List locked worktrees +git worktree list | grep locked + +# Manually unlock all worktrees +find .git/worktrees -name locked -delete + +# Check for corrupted worktrees +git worktree list --porcelain | grep -A 3 "^worktree" + +# Repair all worktrees +git worktree repair + +# Remove all worktree metadata (nuclear option) +rm -rf .git/worktrees/* +git worktree prune +``` + +### Configuration Options + +```bash +# Set default behavior for worktree creation +git config worktree.guessRemote true # Auto-track remote branches + +# Configure worktree paths +git config extensions.worktreeConfig true +``` + +## Resources + +- [Git Worktree Documentation](https://git-scm.com/docs/git-worktree) +- [dotcms-utilities Scripts](https://github.com/dotCMS/dotcms-utilities) +- Related scripts: `git-smart-switch`, `git-issue-branch`, `git-issue-create` \ No newline at end of file diff --git a/dev-scripts/git-clone-worktree b/dev-scripts/git-clone-worktree new file mode 100755 index 0000000..d3c8520 --- /dev/null +++ b/dev-scripts/git-clone-worktree @@ -0,0 +1,221 @@ +#!/bin/bash + +# git-clone-worktree - Clone repository with worktree structure from the start +# Usage: git clone-worktree [directory] [--main-branch BRANCH] + +# Determine the directory of the calling script +script_dir="$(dirname "$(realpath "$0")")" + +# Call the check_updates.sh script from the determined directory +"$script_dir/check_updates.sh" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +REPO_URL="" +TARGET_DIR="" +MAIN_BRANCH="" +CREATE_MAIN_WORKTREE=true + +show_help() { + echo -e "${BLUE}git-clone-worktree - Clone repository with worktree structure${NC}" + echo "" + echo -e "${YELLOW}Usage:${NC}" + echo " git clone-worktree [directory] [options]" + echo "" + echo -e "${YELLOW}Description:${NC}" + echo " Clones a git repository and immediately sets up worktree structure:" + echo " 1. Clones the repository" + echo " 2. Creates worktrees/ directory" + echo " 3. Creates worktree for main/master branch" + echo " 4. Ready to use git-issue-worktree for development" + echo "" + echo -e "${YELLOW}Arguments:${NC}" + echo " repository-url Git repository URL (required)" + echo " directory Target directory name (optional, inferred from URL)" + echo "" + echo -e "${YELLOW}Options:${NC}" + echo " --help, -h Show this help message" + echo " --main-branch BRANCH Specify main branch name (default: auto-detect)" + echo " --no-main-worktree Skip creating main branch worktree" + echo "" + echo -e "${YELLOW}Structure Created:${NC}" + echo " target-dir/" + echo " ├── .git/ # Git metadata" + echo " └── worktrees/" + echo " └── main/ # Main branch worktree" + echo "" + echo -e "${YELLOW}Examples:${NC}" + echo " git clone-worktree git@github.com:dotCMS/core.git" + echo " git clone-worktree git@github.com:dotCMS/core.git dotcms-core" + echo " git clone-worktree https://github.com/user/repo.git --main-branch develop" + echo " git clone-worktree git@github.com:org/repo.git --no-main-worktree" + echo "" + echo -e "${YELLOW}After Cloning:${NC}" + echo " cd target-dir" + echo " git issue-worktree --issue 123 # Create worktree for issue" + echo " git worktree-ide worktrees/main # Open main in IDE" +} + +# Parse command line arguments +POSITIONAL_ARGS=() +while [[ $# -gt 0 ]]; do + case $1 in + --help|-h) + show_help + exit 0 + ;; + --main-branch) + MAIN_BRANCH="$2" + shift + ;; + --no-main-worktree) + CREATE_MAIN_WORKTREE=false + ;; + -*) + echo -e "${RED}Error: Unknown option '$1'${NC}" + echo "Use --help for usage information" + exit 1 + ;; + *) + POSITIONAL_ARGS+=("$1") + ;; + esac + shift +done + +# Restore positional parameters +set -- "${POSITIONAL_ARGS[@]}" + +# Get repository URL (required) +if [[ $# -lt 1 ]]; then + echo -e "${RED}Error: Repository URL required${NC}" + echo "Usage: git clone-worktree [directory]" + echo "Use --help for more information" + exit 1 +fi + +REPO_URL="$1" + +# Get or infer target directory +if [[ $# -ge 2 ]]; then + TARGET_DIR="$2" +else + # Infer directory name from repository URL + # Examples: + # git@github.com:dotCMS/core.git -> core + # https://github.com/user/repo.git -> repo + TARGET_DIR=$(basename "$REPO_URL" .git) +fi + +# Validate inputs +if [[ -z "$REPO_URL" ]]; then + echo -e "${RED}Error: Repository URL cannot be empty${NC}" + exit 1 +fi + +if [[ -z "$TARGET_DIR" ]]; then + echo -e "${RED}Error: Could not determine target directory${NC}" + echo "Please specify directory explicitly" + exit 1 +fi + +# Check if target directory already exists +if [[ -d "$TARGET_DIR" ]]; then + echo -e "${RED}Error: Directory '$TARGET_DIR' already exists${NC}" + echo "Please specify a different directory name" + exit 1 +fi + +echo -e "${BLUE}Cloning with worktree structure:${NC}" +echo -e "${BLUE}Repository:${NC} $REPO_URL" +echo -e "${BLUE}Directory:${NC} $TARGET_DIR" +echo "" + +# Step 1: Clone the repository +echo -e "${BLUE}[1/3] Cloning repository...${NC}" +if ! git clone "$REPO_URL" "$TARGET_DIR"; then + echo -e "${RED}Error: Failed to clone repository${NC}" + exit 1 +fi + +echo -e "${GREEN}✓ Repository cloned${NC}" +echo "" + +# Change to repository directory +cd "$TARGET_DIR" || exit 1 + +# Determine main branch if not specified +if [[ -z "$MAIN_BRANCH" ]]; then + echo -e "${BLUE}[2/3] Detecting main branch...${NC}" + + # Try to get default branch from remote + MAIN_BRANCH=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@') + + if [[ -z "$MAIN_BRANCH" ]]; then + # Fallback: try common branch names + if git show-ref --verify --quiet refs/heads/main; then + MAIN_BRANCH="main" + elif git show-ref --verify --quiet refs/heads/master; then + MAIN_BRANCH="master" + elif git show-ref --verify --quiet refs/heads/develop; then + MAIN_BRANCH="develop" + else + echo -e "${RED}Error: Cannot determine main branch${NC}" + echo "Please specify with --main-branch option" + exit 1 + fi + fi + + echo -e "${GREEN}✓ Detected main branch: $MAIN_BRANCH${NC}" +else + echo -e "${BLUE}[2/3] Using specified main branch: $MAIN_BRANCH${NC}" +fi + +echo "" + +# Step 2: Create worktrees directory +echo -e "${BLUE}[3/3] Setting up worktree structure...${NC}" +mkdir -p worktrees + +# Step 3: Create main branch worktree (optional) +if [[ "$CREATE_MAIN_WORKTREE" == true ]]; then + echo -e "${BLUE}Creating worktree for $MAIN_BRANCH...${NC}" + + if git worktree add "worktrees/$MAIN_BRANCH" "$MAIN_BRANCH" 2>/dev/null; then + echo -e "${GREEN}✓ Worktree created at worktrees/$MAIN_BRANCH${NC}" + else + echo -e "${YELLOW}⚠ Could not create main branch worktree${NC}" + echo -e "${YELLOW}You can create it manually with:${NC}" + echo " git worktree add worktrees/$MAIN_BRANCH $MAIN_BRANCH" + fi +else + echo -e "${YELLOW}Skipping main branch worktree creation (--no-main-worktree)${NC}" +fi + +echo "" +echo -e "${GREEN}✓ Clone complete with worktree structure!${NC}" +echo "" + +# Show current worktrees +echo -e "${BLUE}Current worktrees:${NC}" +git worktree list +echo "" + +# Get absolute path for display +ABS_TARGET_DIR="$(pwd)" + +echo -e "${YELLOW}Next steps:${NC}" +echo " cd $TARGET_DIR" +if [[ "$CREATE_MAIN_WORKTREE" == true ]]; then + echo " cd worktrees/$MAIN_BRANCH # Work in main branch" +fi +echo " git issue-worktree --issue 123 # Create worktree for issue" +echo " git worktree-ide # Open IDE" +echo "" +echo -e "${BLUE}Worktree location:${NC} $ABS_TARGET_DIR/worktrees/" +echo "" \ No newline at end of file diff --git a/dev-scripts/git-issue-branch b/dev-scripts/git-issue-branch index 9fdf7d9..b39a797 100755 --- a/dev-scripts/git-issue-branch +++ b/dev-scripts/git-issue-branch @@ -3,6 +3,7 @@ # git-issue-branch - Work with GitHub issues and create/switch branches # Usage: git issue-branch [--help] [--json] [--list] # Supports both interactive and automated workflows with JSON output and issue discovery +# Automatically detects worktree structure and delegates to appropriate workflow # Determine the directory of the calling script script_dir="$(dirname "$(realpath "$0")")" @@ -26,6 +27,48 @@ BRANCH_FROM_CURRENT=false AUTO_CONFIRM=false DRY_RUN=false +# Check if repository is using the NEW worktree structure pattern +# (repo with worktrees/ subdirectory containing worktrees) +is_worktree_repo() { + local git_common_dir=$(git rev-parse --git-common-dir 2>/dev/null) + if [[ -z "$git_common_dir" ]]; then + return 1 + fi + + # Get the base repository root (parent of .git directory) + local base_repo_root=$(dirname "$git_common_dir") + + # Check if worktrees/ directory exists in the base repo and has at least one worktree + if [[ -d "$base_repo_root/worktrees" ]]; then + # Check if it has any subdirectories (actual worktrees) + if [ -n "$(find "$base_repo_root/worktrees" -mindepth 1 -maxdepth 1 -type d 2>/dev/null)" ]; then + return 0 + fi + fi + + return 1 +} + +# Check if we're in an OLD worktree pattern (separate worktree folder, not in worktrees/ subdirectory) +is_old_worktree() { + local git_dir=$(git rev-parse --git-dir 2>/dev/null) + local repo_root=$(git rev-parse --show-toplevel 2>/dev/null) + + if [[ -z "$git_dir" || -z "$repo_root" ]]; then + return 1 + fi + + # Check if we're in a worktree (git-dir points to .git/worktrees/*) + if [[ "$git_dir" == *".git/worktrees/"* ]]; then + # Check if this is NOT part of the new pattern (worktrees/ subdirectory) + if [[ ! -d "$repo_root/../worktrees" ]] && [[ "$(basename "$(dirname "$repo_root")")" != "worktrees" ]]; then + return 0 # Old worktree pattern detected + fi + fi + + return 1 +} + # Show help message show_help() { echo -e "${BLUE}git-issue-branch - Work with GitHub issues and create/switch branches${NC}" @@ -37,6 +80,12 @@ show_help() { echo " Interactive tool to select from your assigned or recently created GitHub issues" echo " and create/switch to branches. Shows issues with PR status indicators." echo "" + echo -e "${YELLOW}✨ Smart Worktree Detection:${NC}" + echo " • Automatically detects if repository uses worktree structure" + echo " • Non-worktree repos: Traditional branch switching (git checkout)" + echo " • Worktree repos: Creates isolated worktree directories" + echo " • Seamless experience - same command, works everywhere!" + echo "" echo -e "${YELLOW}Features:${NC}" echo " • Lists issues assigned to you or recently created by you" echo " • Shows [has PR] indicator for issues with existing pull requests" @@ -49,7 +98,7 @@ show_help() { echo " --json Output in JSON format (for automation)" echo " --list List issues without interactive selection" echo " --issue NUMBER Work with specific issue number (for automation)" - echo " --from-current Create branch from current working state (for automation)" + echo " --from-current Create branch from current working state (non-worktree only)" echo " --dry-run Preview operations without executing them" echo " --yes, -y Skip confirmation prompts (for automation)" echo " --with-pr-status Include PR status indicators (slower but more informative)" @@ -63,10 +112,10 @@ show_help() { echo " git issue-branch --issue 30 --dry-run # Preview branch creation without executing" echo " git issue-branch --with-pr-status # Include PR status indicators (slower)" echo "" - echo -e "${YELLOW}Branch Naming:${NC}" + echo -e "${YELLOW}Branch/Worktree Naming:${NC}" echo " • Format: issue-{number}-{sanitized-title}" echo " • Automatic conflict resolution with numbered suffixes" - echo " • Branches are automatically linked to GitHub issues" + echo " • Branches/Worktrees are automatically linked to GitHub issues" } sanitize_title() { @@ -128,6 +177,9 @@ create_and_switch_branch() { git smart-switch "$branch_name" } +# Store original arguments for delegation +ORIGINAL_ARGS=("$@") + # Parse command line arguments while [[ $# -gt 0 ]]; do case $1 in @@ -166,6 +218,56 @@ while [[ $# -gt 0 ]]; do shift done +# Check if we're in an old worktree pattern (warn but continue with traditional workflow) +if is_old_worktree; then + local repo_root=$(git rev-parse --show-toplevel 2>/dev/null) + echo -e "${YELLOW}⚠️ Old worktree pattern detected!${NC}" + echo -e "${YELLOW}You're in a separate worktree folder (not using worktrees/ subdirectory structure)${NC}" + echo "" + echo -e "${BLUE}Current location:${NC} $repo_root" + echo -e "${BLUE}Recommendation:${NC} Migrate to the new worktree structure for better organization" + echo "" + echo -e "${YELLOW}To migrate:${NC}" + echo " 1. cd to the base repository (not this worktree)" + echo " 2. Run: git migrate-to-worktrees" + echo " 3. Clean up old worktree folders like this one" + echo "" + echo -e "${BLUE}Continuing with traditional branch switching workflow for now...${NC}" + echo "" +fi + +# Detect NEW worktree structure and delegate if needed +if is_worktree_repo; then + echo -e "${BLUE}New worktree structure detected - delegating to git-issue-worktree${NC}" + echo "" + + # Map git-issue-branch options to git-issue-worktree options + WORKTREE_ARGS=() + + for arg in "${ORIGINAL_ARGS[@]}"; do + case "$arg" in + --from-current) + # In worktree mode, this doesn't make sense (each worktree is isolated) + # Skip this option + ;; + *) + # Pass through all other options + WORKTREE_ARGS+=("$arg") + ;; + esac + done + + # Delegate to git-issue-worktree + exec "$script_dir/git-issue-worktree" "${WORKTREE_ARGS[@]}" + exit $? +fi + +# Continue with traditional branch workflow for non-worktree repos +if ! is_old_worktree; then + echo -e "${BLUE}Traditional repository detected - using branch switching workflow${NC}" + echo "" +fi + # Get current GitHub user CURRENT_USER=$(gh api user --jq '.login') diff --git a/dev-scripts/git-issue-worktree b/dev-scripts/git-issue-worktree new file mode 100755 index 0000000..0a3a557 --- /dev/null +++ b/dev-scripts/git-issue-worktree @@ -0,0 +1,541 @@ +#!/bin/bash + +# git-issue-worktree - Create worktrees from GitHub issues +# Usage: git issue-worktree [--help] [--json] [--list] +# Parallel workflow to git-issue-branch that creates worktrees instead of switching branches + +# Determine the directory of the calling script +script_dir="$(dirname "$(realpath "$0")")" + +# Call the check_updates.sh script from the determined directory +"$script_dir/check_updates.sh" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Global variables +JSON_OUTPUT=false +LIST_MODE=false +SKIP_PR_STATUS=true +SPECIFIC_ISSUE="" +AUTO_CONFIRM=false +DRY_RUN=false +OPEN_IDE=false +IDE_CHOICE="" + +show_help() { + echo -e "${BLUE}git-issue-worktree - Create worktrees from GitHub issues${NC}" + echo "" + echo -e "${YELLOW}Usage:${NC}" + echo " git issue-worktree [options]" + echo "" + echo -e "${YELLOW}Description:${NC}" + echo " Interactive tool to create isolated worktrees for GitHub issues." + echo " Each issue gets its own working directory, eliminating branch switching." + echo "" + echo -e "${YELLOW}Features:${NC}" + echo " • Creates worktrees in repo/worktrees/issue-{number}-{title}/" + echo " • Lists issues assigned to you or recently created by you" + echo " • Automatic branch creation and linking to GitHub issues" + echo " • Optional IDE launcher integration" + echo " • Parallel workflow - work on multiple issues simultaneously" + echo "" + echo -e "${YELLOW}Options:${NC}" + echo " --help, -h Show this help message" + echo " --json Output in JSON format (for automation)" + echo " --list List issues without interactive selection" + echo " --issue NUMBER Work with specific issue number" + echo " --dry-run Preview operations without executing" + echo " --yes, -y Skip confirmation prompts" + echo " --with-pr-status Include PR status indicators (slower)" + echo " --open-ide [IDE] Open IDE after creation (cursor/code/idea)" + echo "" + echo -e "${YELLOW}Examples:${NC}" + echo " git issue-worktree # Interactive selection" + echo " git issue-worktree --issue 123 # Create worktree for issue #123" + echo " git issue-worktree --issue 123 --open-ide cursor # Create and open in Cursor" + echo " git issue-worktree --list --json # List issues in JSON" + echo " git issue-worktree --dry-run --issue 123 # Preview worktree creation" + echo "" + echo -e "${YELLOW}Worktree Structure:${NC}" + echo " repo/" + echo " └── worktrees/" + echo " ├── main/ # Main branch worktree" + echo " ├── issue-123-add-feature/ # Issue #123 worktree" + echo " └── issue-456-fix-bug/ # Issue #456 worktree" +} + +sanitize_title() { + local title="$1" + local sanitized=$(echo "$title" | tr '[:upper:]' '[:lower:]') + sanitized=$(echo "$sanitized" | sed -E 's/[[:space:]]+/-/g; s/[^a-z0-9-]//g; s/--+/-/g; s/^-|-$//g') + if [ ${#sanitized} -gt 50 ]; then + sanitized=$(echo "$sanitized" | cut -c1-50 | sed -E 's/-[^-]*$//g') + fi + sanitized=$(echo "$sanitized" | sed 's/-$//g') + echo "$sanitized" +} + +create_worktree() { + local issue_number="$1" + local issue_title="$2" + local custom_suffix="$3" + local from_current_state="$4" # New parameter for copying state + + # Get base repository root (not worktree root) + local git_common_dir=$(git rev-parse --git-common-dir 2>/dev/null) + if [[ -z "$git_common_dir" ]]; then + echo -e "${RED}Error: Could not determine git repository root${NC}" + return 1 + fi + local repo_root=$(dirname "$git_common_dir") + local worktrees_dir="$repo_root/worktrees" + + # Create worktrees directory if it doesn't exist + if [ ! -d "$worktrees_dir" ]; then + echo -e "${YELLOW}Warning: worktrees/ directory doesn't exist${NC}" + echo -e "${YELLOW}This repo hasn't been migrated to worktree structure yet${NC}" + echo "" + echo -e "${BLUE}Recommendation: Run migration first:${NC}" + echo -e "${GREEN} git migrate-to-worktrees${NC}" + echo "" + echo -e "${YELLOW}Or I can create worktrees/ now and continue...${NC}" + + if [[ "$AUTO_CONFIRM" != true ]]; then + read -p "Create worktrees/ and continue? (y/N) " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo -e "${YELLOW}Cancelled - please run git migrate-to-worktrees first${NC}" + exit 0 + fi + fi + + echo -e "${BLUE}Creating worktrees directory...${NC}" + mkdir -p "$worktrees_dir" + echo -e "${GREEN}✓ Created worktrees/${NC}" + echo "" + fi + + # Check if main branch worktree exists (best practice) + local main_branch=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@') + if [ -z "$main_branch" ]; then + if git show-ref --verify --quiet refs/heads/main; then + main_branch="main" + elif git show-ref --verify --quiet refs/heads/master; then + main_branch="master" + fi + fi + + if [ -n "$main_branch" ] && [ ! -d "$worktrees_dir/$main_branch" ]; then + echo -e "${YELLOW}Note: No worktree for '$main_branch' branch${NC}" + echo -e "${BLUE}Recommendation: Create main branch worktree for reference:${NC}" + echo -e "${GREEN} git worktree add worktrees/$main_branch $main_branch${NC}" + echo "" + fi + + # Determine branch/worktree name + local sanitized_suffix + if [ -n "$custom_suffix" ]; then + sanitized_suffix=$(sanitize_title "$custom_suffix") + else + sanitized_suffix=$(sanitize_title "$issue_title") + fi + + local base_name="issue-${issue_number}-${sanitized_suffix}" + local branch_name="$base_name" + local worktree_path="$worktrees_dir/$branch_name" + + # Check for existing worktrees with similar names + local max_index=0 + if [ -d "$worktrees_dir" ]; then + for dir in "$worktrees_dir"/${base_name}*; do + if [ -d "$dir" ]; then + if [[ $(basename "$dir") =~ ^${base_name}(-([0-9]+))?$ ]]; then + local index="${BASH_REMATCH[2]}" + if [[ "$index" -gt "$max_index" ]]; then + max_index="$index" + fi + fi + fi + done + fi + + # Add suffix if needed + if [[ $max_index -gt 0 ]] || [ -d "$worktree_path" ]; then + branch_name="${base_name}-$((max_index + 1))" + worktree_path="$worktrees_dir/$branch_name" + fi + + if [[ "$DRY_RUN" == true ]]; then + echo -e "${YELLOW}[DRY RUN] Would create worktree:${NC}" + echo -e " ${BLUE}Path:${NC} $worktree_path" + echo -e " ${BLUE}Branch:${NC} $branch_name" + echo -e " ${BLUE}Linked to:${NC} Issue #$issue_number" + echo "" + echo -e "${YELLOW}[DRY RUN] Commands that would run:${NC}" + echo " gh issue develop $issue_number --name \"$branch_name\"" + echo " git worktree add \"$worktree_path\" \"$branch_name\"" + if [[ "$OPEN_IDE" == true ]]; then + echo " git worktree-ide \"$worktree_path\" --ide $IDE_CHOICE" + fi + return 0 + fi + + echo -e "${GREEN}Creating worktree:${NC} $branch_name" + echo -e "${BLUE}Location:${NC} $worktree_path" + + # Create and link branch to issue + if ! gh issue develop "$issue_number" --name "$branch_name" 2>/dev/null; then + echo -e "${YELLOW}Warning: Could not link branch to issue (may already be linked)${NC}" + fi + + # Check if we should copy state from current worktree + local current_worktree_root=$(git rev-parse --show-toplevel 2>/dev/null) + local has_changes=false + local stash_created=false + local stash_ref="" + + if [[ "$from_current_state" == true ]]; then + # Check if there are uncommitted changes to copy + if ! git diff-index --quiet HEAD -- 2>/dev/null || ! git diff --cached --quiet 2>/dev/null || [[ -n "$(git status --porcelain --ignored 2>/dev/null)" ]]; then + has_changes=true + echo -e "${BLUE}Copying current working state to new worktree...${NC}" + # Stash current changes (stashes are shared across worktrees) + STASH_MSG="Pre-worktree-copy from $(basename "$current_worktree_root") ($(date +%Y%m%d-%H%M%S))" + if git stash push -u -m "$STASH_MSG" 2>/dev/null; then + stash_created=true + # Get the stash reference (most recent stash) + stash_ref=$(git stash list --format="%gd" -1 2>/dev/null) + echo -e "${GREEN}✓ Changes stashed${NC}" + fi + fi + fi + + # Create worktree + if git worktree add "$worktree_path" "$branch_name" 2>/dev/null; then + echo -e "${GREEN}✓ Worktree created successfully${NC}" + # Disable sparse checkout in the new worktree (only parent should be sparse) + cd "$worktree_path" + git config core.sparseCheckout false + git sparse-checkout disable 2>/dev/null || true + git read-tree -mu HEAD 2>/dev/null || true + + # Copy state if requested + if [[ "$from_current_state" == true && "$stash_created" == true && -n "$stash_ref" ]]; then + echo -e "${BLUE}Applying stashed changes to new worktree...${NC}" + if git stash pop "$stash_ref" 2>/dev/null; then + echo -e "${GREEN}✓ Changes applied to new worktree${NC}" + else + echo -e "${YELLOW}⚠ Could not automatically apply changes${NC}" + echo -e "${YELLOW}Your changes are in stash: $STASH_MSG${NC}" + echo -e "${YELLOW}Apply manually with: git stash pop${NC}" + fi + fi + + cd "$repo_root" + else + # Try force add if branch already exists + if git worktree add -b "$branch_name" "$worktree_path" 2>/dev/null; then + echo -e "${GREEN}✓ Worktree created with new branch${NC}" + # Disable sparse checkout in the new worktree (only parent should be sparse) + cd "$worktree_path" + git config core.sparseCheckout false + git sparse-checkout disable 2>/dev/null || true + git read-tree -mu HEAD 2>/dev/null || true + + # Copy state if requested + if [[ "$from_current_state" == true && "$stash_created" == true && -n "$stash_ref" ]]; then + echo -e "${BLUE}Applying stashed changes to new worktree...${NC}" + if git stash pop "$stash_ref" 2>/dev/null; then + echo -e "${GREEN}✓ Changes applied to new worktree${NC}" + else + echo -e "${YELLOW}⚠ Could not automatically apply changes${NC}" + echo -e "${YELLOW}Your changes are in stash: $STASH_MSG${NC}" + echo -e "${YELLOW}Apply manually with: git stash pop${NC}" + fi + fi + + cd "$repo_root" + else + echo -e "${RED}Error: Failed to create worktree${NC}" + return 1 + fi + fi + + echo "" + echo -e "${GREEN}✓ Ready to work on issue #${issue_number}${NC}" + echo -e "${BLUE}Worktree path:${NC} $worktree_path" + echo "" + + # Open IDE if requested + if [[ "$OPEN_IDE" == true ]]; then + echo -e "${BLUE}Opening IDE...${NC}" + git worktree-ide "$worktree_path" --ide "$IDE_CHOICE" + else + echo -e "${YELLOW}Next steps:${NC}" + echo " cd \"$worktree_path\"" + echo " # Or open in IDE:" + echo " git worktree-ide \"$worktree_path\"" + fi + + # Store worktree path for JSON output + CREATED_WORKTREE_PATH="$worktree_path" + + # Output path at the end for wrapper functions to cd to (like git-smart-switch) + echo "$worktree_path" +} + +# Parse command line arguments +while [[ $# -gt 0 ]]; do + case $1 in + --help|-h) + show_help + exit 0 + ;; + --json) + JSON_OUTPUT=true + ;; + --list) + LIST_MODE=true + ;; + --issue) + SPECIFIC_ISSUE="$2" + shift + ;; + --dry-run) + DRY_RUN=true + ;; + --yes|-y) + AUTO_CONFIRM=true + ;; + --with-pr-status) + SKIP_PR_STATUS=false + ;; + --open-ide) + OPEN_IDE=true + if [[ -n "$2" ]] && [[ ! "$2" =~ ^-- ]]; then + IDE_CHOICE="$2" + shift + fi + ;; + *) + echo -e "${RED}Error: Unknown option '$1'${NC}" + echo "Use --help for usage information" + exit 1 + ;; + esac + shift +done + +# Verify we're in a git repository +if ! git rev-parse --is-inside-work-tree &>/dev/null; then + echo -e "${RED}Error: Not in a git repository${NC}" + exit 1 +fi + +# Get current GitHub user +CURRENT_USER=$(gh api user --jq '.login') + +# Fetch issues +ASSIGNED_ISSUES=$(gh issue list --assignee "@me" --json number,title,author,updatedAt --jq '.[] | "\(.number) \(.title) [assigned] \(.updatedAt)"') +CREATED_ISSUES=$(gh issue list --author "@me" --limit 20 --json number,title,author,updatedAt --jq '.[] | "\(.number) \(.title) [created] \(.updatedAt)"') + +# Get PR status function +get_pr_status() { + local issue_num="$1" + local pr_count=$(timeout 5 gh pr list --search "linked:$issue_num" --json number --jq 'length' 2>/dev/null) + if [ $? -eq 0 ] && [ "$pr_count" -gt 0 ]; then + echo "[has PR]" + else + echo "" + fi +} + +# Combine and deduplicate issues +COMBINED_ISSUES=$(echo -e "$ASSIGNED_ISSUES\n$CREATED_ISSUES" | awk ' +{ + if ($0 ~ /^[0-9]+ .+ \[(assigned|created)\]/) { + issue_num = $1 + if ($0 ~ /\[assigned\]/) { + status = "assigned" + } else { + status = "created" + } + full_line = $0 + } else { + next + } + + if (issue_num in seen && seen[issue_num] ~ /\[assigned\]/) { + next + } + if (status == "assigned") { + seen[issue_num] = full_line + issues[issue_num] = full_line + } else if (!(issue_num in seen)) { + seen[issue_num] = full_line + issues[issue_num] = full_line + } +} +END { + for (num in issues) { + print issues[num] + } +}') + +# Add PR status if requested +if [[ "$SKIP_PR_STATUS" == true ]]; then + ISSUES_WITH_PR_STATUS="" + while IFS= read -r line; do + if [[ -n "$line" ]]; then + clean_line=$(echo "$line" | sed -E 's/ [0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$//') + ISSUES_WITH_PR_STATUS="${ISSUES_WITH_PR_STATUS}${clean_line}\n" + fi + done <<< "$COMBINED_ISSUES" +else + ISSUES_WITH_PR_STATUS="" + while IFS= read -r line; do + if [[ -n "$line" ]]; then + issue_num=$(echo "$line" | awk '{print $1}') + pr_status=$(get_pr_status "$issue_num") + clean_line=$(echo "$line" | sed -E 's/ [0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$//') + if [[ -n "$pr_status" ]]; then + ISSUES_WITH_PR_STATUS="${ISSUES_WITH_PR_STATUS}${clean_line} ${pr_status}\n" + else + ISSUES_WITH_PR_STATUS="${ISSUES_WITH_PR_STATUS}${clean_line}\n" + fi + fi + done <<< "$COMBINED_ISSUES" +fi + +COMBINED_ISSUES=$(echo -e "$ISSUES_WITH_PR_STATUS" | sed '$d') + +# Handle specific issue +if [[ -n "$SPECIFIC_ISSUE" ]]; then + if [[ ! "$SPECIFIC_ISSUE" =~ ^[0-9]+$ ]]; then + echo -e "${RED}Error: Issue number must be a positive integer${NC}" + exit 1 + fi + + echo -e "${BLUE}Fetching issue #$SPECIFIC_ISSUE...${NC}" + issue_data=$(gh issue view "$SPECIFIC_ISSUE" --json number,title,state 2>/dev/null) + if [[ $? -ne 0 ]]; then + echo -e "${RED}Error: Could not find issue #$SPECIFIC_ISSUE${NC}" + exit 1 + fi + + issue_title=$(echo "$issue_data" | jq -r '.title') + issue_state=$(echo "$issue_data" | jq -r '.state') + + if [[ "$issue_state" != "OPEN" ]]; then + echo -e "${YELLOW}Warning: Issue #$SPECIFIC_ISSUE is $issue_state${NC}" + fi + + echo -e "${GREEN}✓ Found: $issue_title${NC}" + echo "" + + if [[ "$JSON_OUTPUT" == true ]]; then + if [[ -n "$CREATED_WORKTREE_PATH" ]]; then + echo "{\"number\":$SPECIFIC_ISSUE,\"title\":\"$issue_title\",\"worktree\":\"$CREATED_WORKTREE_PATH\"}" + else + echo "{\"number\":$SPECIFIC_ISSUE,\"title\":\"$issue_title\"}" + fi + exit 0 + fi + + # For specific issue, default to clean branch unless --from-current is provided + # (We could add --from-current flag later if needed) + create_worktree "$SPECIFIC_ISSUE" "$issue_title" "" false + exit 0 +fi + +# Handle list/JSON mode +if [[ "$JSON_OUTPUT" == true || "$LIST_MODE" == true ]]; then + issues_json="[" + first=true + while IFS= read -r line; do + if [[ -n "$line" ]]; then + issue_num=$(echo "$line" | awk '{print $1}') + issue_title=$(echo "$line" | sed -E 's/^[0-9]+ (.+) \[(assigned|created)\]( \[has PR\])?$/\1/') + issue_status=$(echo "$line" | sed -E 's/^[0-9]+ .+ \[([^]]+)\]( \[has PR\])?$/\1/') + has_pr=$(echo "$line" | grep -q "\[has PR\]" && echo "true" || echo "false") + + if [[ "$first" == true ]]; then + first=false + else + issues_json+="," + fi + + issues_json+="{\"number\":$issue_num,\"title\":\"$issue_title\",\"status\":\"$issue_status\",\"has_pr\":$has_pr}" + fi + done <<< "$COMBINED_ISSUES" + issues_json+="]" + + if [[ "$JSON_OUTPUT" == true ]]; then + echo "$issues_json" | jq . + else + echo "$issues_json" | jq -r '.[] | "\(.number) \(.title) [\(.status)]" + (if .has_pr then " [has PR]" else "" end)' + fi + exit 0 +fi + +# Interactive selection +if command -v fzf >/dev/null 2>&1; then + SELECTED_ISSUE=$(echo "$COMBINED_ISSUES" | fzf --height 50% --reverse --header "Select issue for new worktree") +else + echo -e "${YELLOW}fzf not installed, using select${NC}" + echo -e "${BLUE}Select an issue:${NC}" + PS3="Select an issue: " + select issue_option in $COMBINED_ISSUES; do + SELECTED_ISSUE=$issue_option + break + done +fi + +if [[ -z "$SELECTED_ISSUE" ]]; then + echo -e "${YELLOW}No issue selected${NC}" + exit 0 +fi + +# Extract issue details +ISSUE_NUMBER=$(echo "$SELECTED_ISSUE" | awk '{print $1}') +ISSUE_TITLE=$(echo "$SELECTED_ISSUE" | sed -E 's/^[0-9]+ (.+) \[(assigned|created|other)\]( \[has PR\])?$/\1/') + +echo "" +echo -e "${BLUE}Creating worktree for issue #${ISSUE_NUMBER}${NC}" +echo -e "${BLUE}Title:${NC} $ISSUE_TITLE" +echo "" + +# Prompt for custom suffix unless auto-confirm +if [[ "$AUTO_CONFIRM" == true ]]; then + CUSTOM_SUFFIX="" + FROM_CURRENT_STATE=false +else + echo -e -n "${BLUE}Custom branch suffix (Enter for default):${NC} " + read CUSTOM_SUFFIX + + # Prompt whether to copy state from current worktree + echo "" + echo -e "${YELLOW}Choose how to create your new worktree:${NC}" + echo -e "${BLUE}1.${NC} Create clean worktree from main/master (default)" + echo -e "${BLUE}2.${NC} Create worktree based on current working state" + echo -e -n "${BLUE}Select option (1-2) [1]:${NC} " + read BRANCH_CHOICE + + # Default to option 1 if no choice is made + if [[ -z "$BRANCH_CHOICE" ]]; then + BRANCH_CHOICE=1 + fi + + if [[ "$BRANCH_CHOICE" == "2" ]]; then + FROM_CURRENT_STATE=true + else + FROM_CURRENT_STATE=false + fi +fi + +create_worktree "$ISSUE_NUMBER" "$ISSUE_TITLE" "$CUSTOM_SUFFIX" "$FROM_CURRENT_STATE" \ No newline at end of file diff --git a/dev-scripts/git-migrate-to-worktrees b/dev-scripts/git-migrate-to-worktrees new file mode 100755 index 0000000..eaaa355 --- /dev/null +++ b/dev-scripts/git-migrate-to-worktrees @@ -0,0 +1,1283 @@ +#!/bin/bash + +# git-migrate-to-worktrees - Convert existing repo to worktree-based structure +# Usage: git migrate-to-worktrees [--help] [--dry-run] + +# Determine the directory of the calling script +script_dir="$(dirname "$(realpath "$0")")" + +# Call the check_updates.sh script from the determined directory +"$script_dir/check_updates.sh" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +DRY_RUN=false + +show_help() { + echo -e "${BLUE}git-migrate-to-worktrees - Convert existing repo to worktree structure${NC}" + echo "" + echo -e "${YELLOW}Usage:${NC}" + echo " git migrate-to-worktrees [--help] [--dry-run]" + echo "" + echo -e "${YELLOW}Description:${NC}" + echo " Converts an existing git repository to use worktree-based workflow:" + echo " 1. Converts main repo to sparse checkout (git metadata only)" + echo " 2. ALWAYS creates worktree for default branch (main/master)" + echo " 3. Creates worktree for current branch (if different from default)" + echo " 4. Preserves all uncommitted changes in current branch worktree" + echo " 5. Leaves you in the worktree for the branch you were on" + echo "" + echo -e "${YELLOW}Structure After Migration:${NC}" + echo " repo/" + echo " ├── .git/ # Git metadata (sparse - minimal files)" + echo " └── worktrees/ # All work happens here" + echo " ├── main/ # Default branch (ALWAYS created)" + echo " └── feature-123/ # Your current branch (if not main)" + echo "" + echo -e "${YELLOW}What Happens:${NC}" + echo " • Main repo becomes sparse (only .git and minimal files)" + echo " • Default branch worktree is ALWAYS created" + echo " • Current branch worktree is created (if different)" + echo " • Uncommitted changes move to current branch worktree" + echo " • You end up in: worktrees//" + echo "" + echo -e "${YELLOW}Options:${NC}" + echo " --help, -h Show this help message" + echo " --dry-run Preview operations without executing them" + echo "" + echo -e "${YELLOW}Examples:${NC}" + echo " git migrate-to-worktrees # Migrate current repo" + echo " git migrate-to-worktrees --dry-run # Preview migration" + echo "" + echo -e "${YELLOW}After Migration:${NC}" + echo " cd worktrees/main # Work on main branch" + echo " git issue-worktree --issue 123 # Create worktree for issue" + echo " git smart-switch feature # Navigate to feature worktree" +} + +# Parse command line arguments +while [[ $# -gt 0 ]]; do + case $1 in + --help|-h) + show_help + exit 0 + ;; + --dry-run) + DRY_RUN=true + ;; + *) + echo -e "${RED}Error: Unknown option '$1'${NC}" + echo "Use --help for usage information" + exit 1 + ;; + esac + shift +done + +# Verify we're in a git repository +if ! git rev-parse --is-inside-work-tree &>/dev/null; then + echo -e "${RED}Error: Not in a git repository${NC}" + exit 1 +fi + +# Check if we're in an old worktree (prevent migration from worktree) +GIT_DIR=$(git rev-parse --git-dir 2>/dev/null) +if [[ "$GIT_DIR" == *".git/worktrees/"* ]]; then + REPO_ROOT=$(git rev-parse --show-toplevel) + + # Check if this is NOT part of the new pattern + if [[ "$(basename "$(dirname "$REPO_ROOT")")" != "worktrees" ]]; then + echo -e "${RED}Error: Cannot migrate from within a worktree!${NC}" + echo "" + echo -e "${YELLOW}You're currently in a worktree directory:${NC} $REPO_ROOT" + echo "" + echo -e "${BLUE}To migrate, you must run this command from the base repository.${NC}" + echo "" + echo -e "${YELLOW}Steps to migrate:${NC}" + echo " 1. Find your base repository (look for .git directory, not .git file)" + echo " 2. cd to the base repository" + echo " 3. Run: git migrate-to-worktrees" + echo "" + echo -e "${YELLOW}After migration:${NC}" + echo " • Your old worktree folders (like this one) will no longer be needed" + echo " • Remove them manually to prevent confusion" + echo " • New worktrees will be in: base-repo/worktrees/" + echo "" + exit 1 + fi +fi + +# Get repository root +REPO_ROOT=$(git rev-parse --show-toplevel) +cd "$REPO_ROOT" || exit 1 + +echo -e "${BLUE}Repository:${NC} $REPO_ROOT" + +# Check if already using worktrees - run validation mode FIRST +if [ -d "$REPO_ROOT/worktrees" ]; then + echo -e "${GREEN}✓ Repository is already using worktree structure${NC}" + echo "" + + # Determine main branch for validation mode + MAIN_BRANCH=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@') + if [ -z "$MAIN_BRANCH" ]; then + # Fallback: try common branch names + if git show-ref --verify --quiet refs/heads/main; then + MAIN_BRANCH="main" + elif git show-ref --verify --quiet refs/heads/master; then + MAIN_BRANCH="master" + else + MAIN_BRANCH="main" # Default fallback + fi + fi + + echo -e "${BLUE}═══════════════════════════════════════════════════════${NC}" + echo -e "${BLUE} WORKTREE STRUCTURE VALIDATION${NC}" + echo -e "${BLUE}═══════════════════════════════════════════════════════${NC}" + echo "" + + # Validate structure + echo -e "${BLUE}[1/4] Checking worktree structure...${NC}" + + # List all worktrees + echo "" + echo -e "${YELLOW}All worktrees:${NC}" + git worktree list + echo "" + + # Check for old worktrees outside worktrees/ directory + echo -e "${BLUE}[2/4] Checking for old worktree pattern...${NC}" + OLD_WORKTREES=$(git worktree list --porcelain | awk -v repo_root="$REPO_ROOT" ' + /^worktree/ { path = $2 } + /^branch/ { + branch = $2 + gsub("refs/heads/", "", branch) + if (path !~ /\/worktrees\//) { + if (path != repo_root) { + print path " [" branch "]" + } + } + } + ') + + if [[ -n "$OLD_WORKTREES" ]]; then + echo -e "${YELLOW}⚠️ Found old worktrees outside worktrees/ directory:${NC}" + echo "$OLD_WORKTREES" + echo "" + HAS_ISSUES=true + + # Offer to migrate old worktrees + echo -e "${YELLOW}Would you like to migrate these to the new structure?${NC}" + echo "" + echo -e "${YELLOW}Options:${NC}" + echo " 1. Migrate to new structure (RECOMMENDED - preserves everything)" + echo " 2. Remove old worktrees (loses uncommitted changes)" + echo " 3. Skip (leave as-is)" + echo "" + read -p "Choose option (1/2/3): " -n 1 -r + echo "" + echo "" + + case $REPLY in + 1) + echo -e "${BLUE}Migrating old worktrees to new structure...${NC}" + echo "" + + # Parse and migrate each old worktree + while IFS= read -r line; do + OLD_PATH=$(echo "$line" | sed 's/ \[.*$//') + OLD_BRANCH=$(echo "$line" | sed 's/.*\[//;s/\]$//') + + if [[ -n "$OLD_PATH" && -d "$OLD_PATH" ]]; then + NEW_PATH="$REPO_ROOT/worktrees/$OLD_BRANCH" + + echo -e "${BLUE}Migrating: $OLD_BRANCH${NC}" + echo -e " From: $OLD_PATH" + echo -e " To: $NEW_PATH" + + # Check for uncommitted changes and stash if needed + cd "$OLD_PATH" + STASHED=false + if ! git diff-index --quiet HEAD -- 2>/dev/null; then + echo -e "${YELLOW} ⚠️ Uncommitted changes found - stashing...${NC}" + STASH_MSG="Pre-migration backup from $OLD_PATH ($(date +%Y%m%d-%H%M%S))" + git stash push -m "$STASH_MSG" 2>/dev/null + STASHED=true + echo -e "${GREEN} ✓ Changes stashed${NC}" + fi + + cd "$REPO_ROOT" + + # Move the directory + mkdir -p "$REPO_ROOT/worktrees" + mv "$OLD_PATH" "$NEW_PATH" + + # Update git worktree metadata + git worktree prune 2>/dev/null + + # Fix the .git file in the worktree + if [[ -f "$NEW_PATH/.git" ]]; then + echo "gitdir: $REPO_ROOT/.git/worktrees/$OLD_BRANCH" > "$NEW_PATH/.git" + fi + + # Fix the gitdir file in .git/worktrees/ + if [[ -d "$REPO_ROOT/.git/worktrees/$OLD_BRANCH" ]]; then + echo "$NEW_PATH" > "$REPO_ROOT/.git/worktrees/$OLD_BRANCH/gitdir" + fi + + # Restore stashed changes if any + if [[ "$STASHED" == true ]]; then + cd "$NEW_PATH" + if git stash list 2>/dev/null | grep -q "$STASH_MSG"; then + git stash pop 2>/dev/null + echo -e "${GREEN} ✓ Changes restored${NC}" + fi + fi + + echo -e "${GREEN} ✓ Migration complete${NC}" + echo "" + fi + done <<< "$OLD_WORKTREES" + + echo -e "${GREEN}✓ All old worktrees migrated!${NC}" + echo "" + + # Re-run validation to show clean state + echo -e "${BLUE}Updated worktree list:${NC}" + git worktree list + echo "" + + # Re-check for remaining old worktrees after migration + REMAINING_OLD=$(git worktree list --porcelain | awk -v repo_root="$REPO_ROOT" ' + /^worktree/ { path = $2 } + /^branch/ { + branch = $2 + gsub("refs/heads/", "", branch) + if (path !~ /\/worktrees\//) { + if (path != repo_root) { + print path + } + } + } + ') + if [[ -z "$REMAINING_OLD" ]]; then + # Clear the issue flag if no old worktrees remain + HAS_ISSUES="" + fi + ;; + 2) + echo -e "${BLUE}Removing old worktrees...${NC}" + echo "" + + while IFS= read -r line; do + OLD_PATH=$(echo "$line" | sed 's/ \[.*$//') + OLD_BRANCH=$(echo "$line" | sed 's/.*\[//;s/\]$//') + + if [[ -n "$OLD_PATH" && -d "$OLD_PATH" ]]; then + echo -e "${YELLOW}Removing: $OLD_PATH${NC}" + + if git worktree remove "$OLD_PATH" --force 2>/dev/null; then + echo -e "${GREEN} ✓ Worktree removed${NC}" + else + rm -rf "$OLD_PATH" + git worktree prune + echo -e "${GREEN} ✓ Manually removed and pruned${NC}" + fi + fi + done <<< "$OLD_WORKTREES" + + echo "" + echo -e "${GREEN}✓ All old worktrees removed${NC}" + echo "" + + # Re-check for remaining old worktrees after removal + REMAINING_OLD=$(git worktree list --porcelain | awk -v repo_root="$REPO_ROOT" ' + /^worktree/ { path = $2 } + /^branch/ { + branch = $2 + gsub("refs/heads/", "", branch) + if (path !~ /\/worktrees\//) { + if (path != repo_root) { + print path + } + } + } + ') + if [[ -z "$REMAINING_OLD" ]]; then + # Clear the issue flag if no old worktrees remain + HAS_ISSUES="" + fi + ;; + 3|*) + echo -e "${YELLOW}Skipped - old worktrees left as-is${NC}" + echo "" + ;; + esac + else + echo -e "${GREEN}✓ No old worktrees found${NC}" + fi + echo "" + + # Check for broken worktree metadata (e.g., manually moved directories) + echo -e "${BLUE}[2.5/4] Checking worktree metadata integrity...${NC}" + + # Try to detect broken worktrees by checking if git worktree list has errors + BROKEN_WORKTREES=$(git worktree list 2>&1 | grep -i "error\|warning" || true) + + # Also check if worktree directories exist where git thinks they should be + NEEDS_REPAIR=false + while IFS= read -r line; do + if [[ $line == *"/"* ]]; then + WORKTREE_PATH=$(echo "$line" | awk '{print $1}') + if [[ ! -d "$WORKTREE_PATH" ]]; then + NEEDS_REPAIR=true + echo -e "${YELLOW}⚠️ Worktree path in git metadata doesn't exist: $WORKTREE_PATH${NC}" + fi + fi + done < <(git worktree list 2>/dev/null || true) + + # Check for worktree directories that exist but aren't in git's metadata + if [[ -d "$REPO_ROOT/worktrees" ]]; then + for dir in "$REPO_ROOT/worktrees"/*; do + if [[ -d "$dir" && -f "$dir/.git" ]]; then + DIR_PATH="$dir" + # Check if this directory is in git worktree list + if ! git worktree list 2>/dev/null | grep -q "^$DIR_PATH "; then + NEEDS_REPAIR=true + echo -e "${YELLOW}⚠️ Worktree directory exists but not in git metadata: $DIR_PATH${NC}" + fi + fi + done + fi + + if [[ "$NEEDS_REPAIR" == true ]] || [[ -n "$BROKEN_WORKTREES" ]]; then + echo -e "${YELLOW}Detected metadata inconsistencies - running git worktree repair...${NC}" + + cd "$REPO_ROOT" + if git worktree repair 2>/dev/null; then + echo -e "${GREEN}✓ Worktree metadata repaired${NC}" + echo "" + echo -e "${BLUE}Updated worktree list:${NC}" + git worktree list + else + echo -e "${RED}✗ Failed to repair worktree metadata${NC}" + echo -e "${YELLOW}You may need to manually fix worktrees with:${NC}" + echo " git worktree remove " + echo " git worktree add " + HAS_ISSUES=true + fi + else + echo -e "${GREEN}✓ Worktree metadata is intact${NC}" + fi + echo "" + + # Check for worktree/branch name mismatches + echo -e "${BLUE}[3/4] Validating worktree/branch alignment...${NC}" + MISMATCHES="" + + while IFS= read -r line; do + if [[ $line == "$REPO_ROOT/worktrees/"* ]]; then + # Extract worktree path and branch from git worktree list output + WORKTREE_PATH=$(echo "$line" | awk '{print $1}') + + # Extract branch name from [branch] format, skip if no brackets found + if [[ $line == *"["*"]"* ]]; then + BRANCH=$(echo "$line" | sed 's/.*\[\([^]]*\)\].*/\1/') + else + BRANCH="" + fi + + # Get the worktree directory name + WORKTREE_NAME=$(basename "$WORKTREE_PATH") + + # Check if worktree name matches branch name + if [[ -n "$BRANCH" && "$WORKTREE_NAME" != "$BRANCH" ]]; then + MISMATCHES="${MISMATCHES} Mismatch: $WORKTREE_PATH has branch [$BRANCH]\n" + HAS_ISSUES=true + fi + fi + done < <(git worktree list) + + if [[ -n "$MISMATCHES" ]]; then + echo -e "${YELLOW}⚠️ Found worktree/branch name mismatches:${NC}" + echo -e "$MISMATCHES" + echo -e "${YELLOW}Recommendation: Worktree folder names should match branch names${NC}" + else + echo -e "${GREEN}✓ All worktree names match their branches${NC}" + fi + echo "" + + # Check for invalid/orphaned directories in worktrees/ folder + echo -e "${BLUE}[3.5/4] Checking for invalid directories in worktrees/...${NC}" + INVALID_DIRS="" + REPAIRABLE_DIRS="" + + if [[ -d "$REPO_ROOT/worktrees" ]]; then + # Get list of all directories in worktrees/ + for dir in "$REPO_ROOT/worktrees"/*; do + if [[ -d "$dir" ]]; then + DIR_NAME=$(basename "$dir") + + # Check if this directory is registered as a git worktree + if ! git worktree list 2>/dev/null | grep -q "^$dir "; then + # Not in worktree list - check if it has a .git file + if [[ -f "$dir/.git" ]]; then + # Has .git file - check if it's a valid worktree that can be repaired + GITDIR=$(cat "$dir/.git" 2>/dev/null | sed 's/gitdir: //') + + # Check if the branch exists in the repo + if git show-ref --verify --quiet "refs/heads/$DIR_NAME" 2>/dev/null; then + # Branch exists - this is repairable + REPAIRABLE_DIRS="${REPAIRABLE_DIRS} ${BLUE}• $DIR_NAME/ (valid branch - can be restored)${NC}\n" + else + # Branch doesn't exist - truly orphaned + INVALID_DIRS="${INVALID_DIRS} ${YELLOW}• $DIR_NAME/ (orphaned - branch doesn't exist)${NC}\n" + HAS_ISSUES=true + fi + else + # No .git file - likely created by build process or mistake + INVALID_DIRS="${INVALID_DIRS} ${YELLOW}• $DIR_NAME/ (not a worktree - no .git file)${NC}\n" + HAS_ISSUES=true + fi + fi + fi + done + fi + + if [[ -n "$REPAIRABLE_DIRS" ]]; then + echo -e "${BLUE}Found repairable worktree directories:${NC}" + echo -e "$REPAIRABLE_DIRS" + echo -e "${BLUE}These can be restored with: git worktree add --force worktrees/ ${NC}" + echo "" + fi + + if [[ -n "$INVALID_DIRS" ]]; then + echo -e "${YELLOW}⚠️ Found invalid directories in worktrees/:${NC}" + echo -e "$INVALID_DIRS" + echo -e "${YELLOW}Recommendation: These directories should be removed or moved${NC}" + echo -e "${BLUE}Possible causes:${NC}" + echo " • Build processes creating folders above repo root" + echo " • Old worktree remnants after manual branch deletion" + echo " • Mistakenly created directories" + echo "" + echo -e "${BLUE}To fix:${NC}" + echo " • For non-worktree dirs: mv or rm the directory" + echo " • Check build configs to prevent creating folders above repo root" + fi + + if [[ -z "$INVALID_DIRS" ]] && [[ -z "$REPAIRABLE_DIRS" ]]; then + echo -e "${GREEN}✓ All directories in worktrees/ are valid worktrees${NC}" + fi + echo "" + + # Check base repo status + echo -e "${BLUE}[4/4] Checking base repository status...${NC}" + CURRENT_BRANCH=$(git branch --show-current 2>/dev/null) + + if [[ -z "$CURRENT_BRANCH" ]]; then + echo -e "${GREEN}✓ Base repo is in detached HEAD state${NC}" + echo -e "${YELLOW}Recommendation: Create 'worktree-parent' branch for clarity${NC}" + echo -e "${BLUE}To fix: git branch -f worktree-parent main && git checkout worktree-parent${NC}" + elif [[ "$CURRENT_BRANCH" == "worktree-parent" ]]; then + echo -e "${GREEN}✓ Base repo is on 'worktree-parent' branch (correct state)${NC}" + else + echo -e "${YELLOW}⚠️ Base repo is on branch: $CURRENT_BRANCH${NC}" + echo -e "${YELLOW}Recommendation: Base repo should be on 'worktree-parent' branch${NC}" + echo -e "${BLUE}To fix: cd $REPO_ROOT && git branch -f worktree-parent main && git checkout worktree-parent${NC}" + HAS_ISSUES=true + fi + + # Check if base repo is properly sparse (should have minimal files) + TRACKED_FILES=$(git ls-files 2>/dev/null | wc -l | tr -d ' ') + if [[ "$TRACKED_FILES" -gt 0 ]]; then + echo -e "${YELLOW}⚠️ Base repo has $TRACKED_FILES checked out files (should be 0)${NC}" + echo -e "${YELLOW}Recommendation: Convert to fully sparse checkout${NC}" + echo -e "${BLUE}To fix: git sparse-checkout set --no-cone \"\" && git read-tree -mu HEAD${NC}" + HAS_ISSUES=true + else + echo -e "${GREEN}✓ Base repo is sparse (no checked out files)${NC}" + fi + + # Check for untracked directories in base repo (excluding dot folders for IDE/config) + # Exclude all dot folders (.git, .claude, .idea, .vscode, etc.) - these should stay in base repo + UNTRACKED_LARGE=$(find "$REPO_ROOT" -maxdepth 1 -type d ! -path "$REPO_ROOT" ! -name "." ! -name ".git" ! -name "worktrees" ! -name ".*" 2>/dev/null) + if [[ -n "$UNTRACKED_LARGE" ]]; then + echo -e "${YELLOW}⚠️ Base repo has untracked directories:${NC}" + UNTRACKED_DIRS_LIST="" + echo "$UNTRACKED_LARGE" | while read dir; do + DIR_SIZE=$(du -sh "$dir" 2>/dev/null | awk '{print $1}') + DIR_NAME=$(basename "$dir") + echo -e " ${YELLOW}• $DIR_NAME/ ($DIR_SIZE)${NC}" + done + echo -e "${YELLOW}Recommendation: These directories should be in worktrees, not base repo${NC}" + echo -e "${BLUE}Note: This is often due to IDE being pointed at base repo during migration${NC}" + echo -e "${BLUE}To fix: Re-run this script and choose cleanup option${NC}" + HAS_ISSUES=true + fi + echo "" + + # Summary + echo -e "${BLUE}═══════════════════════════════════════════════════════${NC}" + if [[ -z "$HAS_ISSUES" ]]; then + echo -e "${GREEN}✓ Worktree structure is healthy!${NC}" + echo "" + echo -e "${BLUE}Quick commands:${NC}" + echo " git issue-worktree --issue 123 # Create new worktree" + echo " git smart-switch main # Navigate to worktree" + echo " git worktree-cleanup # Clean up merged worktrees" + else + echo -e "${YELLOW}⚠️ Issues found${NC}" + echo "" + + # Offer to fix invalid directories in worktrees/ + if [[ -n "$INVALID_DIRS" ]]; then + echo -e "${YELLOW}Would you like to clean up invalid worktrees/ directories?${NC}" + echo "" + echo -e "${YELLOW}This will:${NC}" + echo -e "$INVALID_DIRS" + echo "" + read -p "Remove these invalid directories? (y/N) " -n 1 -r + echo "" + echo "" + + if [[ $REPLY =~ ^[Yy]$ ]]; then + echo -e "${BLUE}Cleaning up invalid worktrees/ directories...${NC}" + echo "" + + for dir in "$REPO_ROOT/worktrees"/*; do + if [[ -d "$dir" ]]; then + DIR_NAME=$(basename "$dir") + + # Check if this directory is NOT registered as a git worktree + if ! git worktree list 2>/dev/null | grep -q "^$dir "; then + # Check if it has .git file and branch exists (repairable) + if [[ -f "$dir/.git" ]] && git show-ref --verify --quiet "refs/heads/$DIR_NAME" 2>/dev/null; then + # Skip repairable worktrees - don't delete them + echo -e " ${BLUE}Skipping $DIR_NAME/ (can be restored)${NC}" + continue + fi + + echo -e " ${YELLOW}Removing $DIR_NAME/...${NC}" + + # Check if it has .git file (orphaned worktree with deleted branch) + if [[ -f "$dir/.git" ]]; then + # Try git worktree remove first + if git worktree remove --force "$dir" 2>/dev/null; then + echo -e " ${GREEN}✓ Removed via git worktree${NC}" + else + # Fallback to manual removal + rm -rf "$dir" + echo -e " ${GREEN}✓ Removed manually${NC}" + fi + else + # Not a worktree, just remove the directory + rm -rf "$dir" + echo -e " ${GREEN}✓ Removed${NC}" + fi + fi + fi + done + + git worktree prune 2>/dev/null + echo "" + echo -e "${GREEN}✓ Invalid directories cleanup complete!${NC}" + echo "" + fi + fi + + # Offer to fix base repo cleanliness issues + if [[ "$TRACKED_FILES" -gt 0 ]] || [[ -n "$UNTRACKED_LARGE" ]]; then + echo -e "${YELLOW}Would you like to clean up the base repository now?${NC}" + echo "" + echo -e "${YELLOW}This will:${NC}" + if [[ "$TRACKED_FILES" -gt 0 ]]; then + echo " • Remove all $TRACKED_FILES checked out files (convert to sparse)" + fi + if [[ -n "$UNTRACKED_LARGE" ]]; then + echo " • Delete build artifacts: $(echo "$UNTRACKED_LARGE" | xargs -n1 basename | tr '\n' ' ')" + fi + echo "" + read -p "Clean up base repo? (y/N) " -n 1 -r + echo "" + echo "" + + if [[ $REPLY =~ ^[Yy]$ ]]; then + echo -e "${BLUE}Cleaning up base repository...${NC}" + echo "" + + # Convert to sparse checkout + if [[ "$TRACKED_FILES" -gt 0 ]]; then + echo -e "${BLUE}Converting to sparse checkout...${NC}" + git sparse-checkout set --no-cone "" 2>/dev/null + git read-tree -mu HEAD 2>/dev/null + REMAINING=$(git ls-files 2>/dev/null | wc -l | tr -d ' ') + if [[ "$REMAINING" -eq 0 ]]; then + echo -e "${GREEN}✓ Removed all checked out files${NC}" + else + echo -e "${YELLOW}⚠️ $REMAINING files remain${NC}" + fi + fi + + # Handle untracked directories - offer to move or delete + if [[ -n "$UNTRACKED_LARGE" ]]; then + echo -e "${BLUE}What should we do with untracked directories?${NC}" + echo "" + echo -e "${YELLOW}Options:${NC}" + echo " 1. Move to main branch worktree (recommended if unsure)" + echo " 2. Delete (if these are just build artifacts)" + echo " 3. Skip (leave as-is)" + echo "" + read -p "Choose option (1/2/3): " -n 1 -r + echo "" + echo "" + + case $REPLY in + 1) + echo -e "${BLUE}Moving untracked directories to main worktree...${NC}" + MAIN_WORKTREE="$REPO_ROOT/worktrees/$MAIN_BRANCH" + + if [[ ! -d "$MAIN_WORKTREE" ]]; then + echo -e "${YELLOW}Main worktree doesn't exist, creating it...${NC}" + git worktree add "$MAIN_WORKTREE" "$MAIN_BRANCH" 2>/dev/null + # Disable sparse checkout in the new worktree (only parent should be sparse) + if [[ -d "$MAIN_WORKTREE" ]]; then + cd "$MAIN_WORKTREE" + git config core.sparseCheckout false + git sparse-checkout disable 2>/dev/null || true + git read-tree -mu HEAD 2>/dev/null || true + cd "$REPO_ROOT" + fi + fi + + echo "$UNTRACKED_LARGE" | while read dir; do + if [[ -d "$dir" ]]; then + DIR_NAME=$(basename "$dir") + echo -e " ${YELLOW}Moving $DIR_NAME/ to main worktree...${NC}" + + # Check if destination already exists + if [[ -d "$MAIN_WORKTREE/$DIR_NAME" ]]; then + echo -e " ${YELLOW}⚠️ $DIR_NAME/ already exists in main worktree, merging...${NC}" + cp -R "$dir"/* "$MAIN_WORKTREE/$DIR_NAME/" 2>/dev/null + rm -rf "$dir" + else + mv "$dir" "$MAIN_WORKTREE/" 2>/dev/null + fi + + echo -e " ${GREEN}✓ Moved${NC}" + fi + done + echo "" + echo -e "${GREEN}✓ Untracked directories moved to: $MAIN_WORKTREE${NC}" + ;; + 2) + echo -e "${BLUE}Deleting untracked directories...${NC}" + echo "$UNTRACKED_LARGE" | while read dir; do + if [[ -d "$dir" ]]; then + echo -e " ${YELLOW}Removing $(basename "$dir")...${NC}" + rm -rf "$dir" + echo -e " ${GREEN}✓ Removed${NC}" + fi + done + ;; + 3|*) + echo -e "${YELLOW}Skipped - untracked directories left as-is${NC}" + ;; + esac + echo "" + fi + + echo "" + echo -e "${GREEN}✓ Base repository cleanup complete!${NC}" + echo "" + echo -e "${BLUE}Base repo status:${NC}" + du -sh . 2>/dev/null | awk '{print " Total size: " $1}' + echo "" + fi + fi + + # After cleanup, check if we need to create worktree-parent branch + CURRENT_BRANCH=$(git branch --show-current 2>/dev/null) + if [[ -z "$CURRENT_BRANCH" ]] || [[ "$CURRENT_BRANCH" != "worktree-parent" ]]; then + echo "" + echo -e "${YELLOW}Base repo is not on 'worktree-parent' branch${NC}" + read -p "Create and switch to 'worktree-parent' branch for clarity? (Y/n) " -n 1 -r + echo "" + echo "" + + if [[ ! $REPLY =~ ^[Nn]$ ]]; then + echo -e "${BLUE}Creating worktree-parent branch...${NC}" + git branch -f worktree-parent "$MAIN_BRANCH" 2>/dev/null + git checkout worktree-parent >/dev/null 2>&1 + echo -e "${GREEN}✓ Base repo now on 'worktree-parent' branch${NC}" + echo -e "${YELLOW}Note: This local branch exists only to avoid detached HEAD confusion${NC}" + echo "" + fi + fi + + # Other recommendations + if [[ -n "$(git worktree list --porcelain | awk -v repo_root="$REPO_ROOT" ' + /^worktree/ { path = $2 } + /^branch/ { + branch = $2 + gsub("refs/heads/", "", branch) + if (path !~ /\/worktrees\//) { + if (path != repo_root) { + print path + } + } + } + ')" ]]; then + echo -e "${BLUE}Still have old worktrees?${NC}" + echo " • Re-run this script to migrate them interactively" + echo "" + fi + fi + echo -e "${BLUE}═══════════════════════════════════════════════════════${NC}" + echo "" + + exit 0 +fi + +# Check for old worktrees that may need cleanup (BEFORE migration starts) +echo -e "${BLUE}Checking for old worktrees...${NC}" +OLD_WORKTREES=$(git worktree list --porcelain | awk -v repo_root="$REPO_ROOT" ' + /^worktree/ { path = $2 } + /^branch/ { + branch = $2 + gsub("refs/heads/", "", branch) + } + /^$/ { + if (path != "" && path !~ /\/worktrees\// && path != repo_root) { + print path " [" branch "]" + } + path = "" + branch = "" + } +') + +if [[ -n "$OLD_WORKTREES" ]]; then + echo -e "${YELLOW}⚠️ Old worktree pattern detected!${NC}" + echo "" + echo -e "${YELLOW}Existing worktrees outside worktrees/ directory:${NC}" + echo "$OLD_WORKTREES" + echo "" + echo -e "${RED}⚠️ CRITICAL: These old worktrees MUST be removed before migration!${NC}" + echo "" + echo -e "${BLUE}Why?${NC}" + echo " • Each branch can only be checked out in ONE place" + echo " • Old worktrees block creation of new worktrees for same branches" + echo " • This would cause migration to fail" + echo "" + echo -e "${YELLOW}Options:${NC}" + echo " 1. Auto-remove old worktrees (RECOMMENDED - changes will be preserved)" + echo " 2. Manual cleanup (you handle it yourself)" + echo " 3. Cancel migration" + echo "" + read -p "Choose option (1/2/3): " -n 1 -r + echo "" + echo + + case $REPLY in + 1) + echo -e "${BLUE}Auto-removing old worktrees...${NC}" + echo "" + + # Parse and remove each old worktree + while IFS= read -r line; do + # Extract path from line (before the [branch] part) + OLD_PATH=$(echo "$line" | sed 's/ \[.*$//') + OLD_BRANCH=$(echo "$line" | sed 's/.*\[//;s/\]$//') + + if [[ -n "$OLD_PATH" && -d "$OLD_PATH" ]]; then + echo -e "${YELLOW}Removing: $OLD_PATH${NC}" + + # Check for uncommitted changes in old worktree + cd "$OLD_PATH" + if ! git diff-index --quiet HEAD -- 2>/dev/null; then + echo -e "${YELLOW} ⚠️ Uncommitted changes found - stashing...${NC}" + STASH_MSG="Pre-migration backup from $OLD_PATH ($(date +%Y%m%d-%H%M%S))" + git stash push -m "$STASH_MSG" 2>/dev/null + echo -e "${GREEN} ✓ Changes stashed: $STASH_MSG${NC}" + echo -e "${BLUE} → Restore later in new worktree with: git stash list${NC}" + fi + + cd "$REPO_ROOT" + + # Remove the worktree via git (safer than rm -rf) + if git worktree remove "$OLD_PATH" --force 2>/dev/null; then + echo -e "${GREEN} ✓ Worktree removed via git${NC}" + else + # Fallback: manual removal if git command fails + echo -e "${YELLOW} Attempting manual removal...${NC}" + rm -rf "$OLD_PATH" + git worktree prune + echo -e "${GREEN} ✓ Manually removed and pruned${NC}" + fi + fi + done <<< "$OLD_WORKTREES" + + echo "" + echo -e "${GREEN}✓ All old worktrees removed${NC}" + echo -e "${BLUE}You can restore any stashed changes after migration${NC}" + echo "" + ;; + 2) + echo -e "${YELLOW}Manual cleanup selected${NC}" + echo "" + echo -e "${BLUE}Steps to clean up manually:${NC}" + echo " 1. For each old worktree, stash uncommitted changes" + echo " 2. Remove worktree: git worktree remove --force" + echo " 3. Or manually: rm -rf && git worktree prune" + echo " 4. Re-run this migration script" + echo "" + exit 0 + ;; + 3|*) + echo -e "${YELLOW}Migration cancelled${NC}" + exit 0 + ;; + esac +fi + +# Get current branch +CURRENT_BRANCH=$(git branch --show-current) +if [ -z "$CURRENT_BRANCH" ]; then + echo -e "${RED}Error: Not on a branch (detached HEAD)${NC}" + echo "Please checkout a branch first: git checkout main" + exit 1 +fi + +echo -e "${BLUE}Current branch:${NC} ${GREEN}$CURRENT_BRANCH${NC}" + +# Check for uncommitted changes +if ! git diff-index --quiet HEAD --; then + echo -e "${YELLOW}Warning: You have uncommitted changes${NC}" + git status --short + echo "" + if [[ "$DRY_RUN" == false ]]; then + read -p "These changes will be preserved. Continue? (y/N) " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo -e "${YELLOW}Migration cancelled${NC}" + exit 0 + fi + fi +fi + +# Determine main branch +MAIN_BRANCH=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@') +if [ -z "$MAIN_BRANCH" ]; then + # Fallback: try common branch names + if git show-ref --verify --quiet refs/heads/main; then + MAIN_BRANCH="main" + elif git show-ref --verify --quiet refs/heads/master; then + MAIN_BRANCH="master" + else + echo -e "${RED}Error: Cannot determine main branch${NC}" + exit 1 + fi +fi + +echo -e "${BLUE}Main branch:${NC} ${GREEN}$MAIN_BRANCH${NC}" +echo "" + +if [[ "$DRY_RUN" == true ]]; then + echo -e "${YELLOW}=== DRY RUN MODE ===${NC}" + echo "" + echo -e "${YELLOW}Would perform these operations:${NC}" + echo "" + echo -e "${BLUE}1. Create worktrees directory:${NC}" + echo " mkdir -p \"$REPO_ROOT/worktrees\"" + echo "" + echo -e "${BLUE}2. Create worktree for current branch ($CURRENT_BRANCH):${NC}" + echo " git worktree add \"$REPO_ROOT/worktrees/$CURRENT_BRANCH\" \"$CURRENT_BRANCH\"" + echo "" + echo -e "${BLUE}3. List existing branches to potentially convert:${NC}" + git branch --format=' %(refname:short)' + echo "" + echo -e "${BLUE}4. Suggested next steps:${NC}" + echo " - Create worktree for $MAIN_BRANCH if not current branch" + echo " - Create worktrees for other active branches" + echo " - Use git-issue-worktree for future branches" + echo "" + echo -e "${GREEN}Run without --dry-run to execute migration${NC}" + exit 0 +fi + +# Important pre-migration warnings +echo -e "${RED}═══════════════════════════════════════════════════════${NC}" +echo -e "${RED} ⚠️ IMPORTANT: CLOSE ALL IDEs BEFORE PROCEEDING${NC}" +echo -e "${RED}═══════════════════════════════════════════════════════${NC}" +echo "" +echo -e "${YELLOW}Before migrating, please:${NC}" +echo " 1. ${RED}Close all IDEs${NC} (IntelliJ, VS Code, Eclipse, etc.)" +echo " 2. Save any open work" +echo " 3. Wait for any running builds to complete" +echo "" +echo -e "${YELLOW}Why?${NC}" +echo " • IDEs pointed at base repo will create files during migration" +echo " • This can leave artifacts in the wrong location" +echo " • May cause conflicts or data loss" +echo "" +echo -e "${BLUE}After migration:${NC}" +echo " • ${RED}DO NOT${NC} reopen IDEs at: ${RED}$REPO_ROOT${NC}" +echo " • ${GREEN}INSTEAD${NC} open at: ${GREEN}$REPO_ROOT/worktrees/$CURRENT_BRANCH/${NC}" +echo " • A WORKTREE-README.md will be created with detailed instructions" +echo "" +echo -e "${RED}═══════════════════════════════════════════════════════${NC}" +echo "" + +# Confirm IDEs are closed +read -p "Have you closed all IDEs? (y/N) " -n 1 -r +echo +if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo -e "${YELLOW}Please close all IDEs and run this command again${NC}" + exit 0 +fi +echo "" + +# Confirm migration +echo -e "${YELLOW}This will convert your repository to use worktrees:${NC}" +echo " 1. Convert main repo to sparse checkout (git metadata only)" +echo " 2. Create worktree for $MAIN_BRANCH branch" +if [ "$CURRENT_BRANCH" != "$MAIN_BRANCH" ]; then + echo " 3. Create worktree for $CURRENT_BRANCH branch" + echo " 4. Preserve all uncommitted changes in $CURRENT_BRANCH worktree" +else + echo " 3. Preserve all uncommitted changes in $MAIN_BRANCH worktree" +fi +echo "" +read -p "Proceed with migration? (y/N) " -n 1 -r +echo +if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo -e "${YELLOW}Migration cancelled${NC}" + exit 0 +fi + +echo "" +echo -e "${BLUE}Starting migration...${NC}" +echo "" + +# Step 1: Identify untracked files/directories to move +echo -e "${BLUE}[1/6] Scanning for untracked files and directories...${NC}" + +# Get list of untracked files/directories (excluding .git, dot folders, and future worktrees/) +# Exclude: .git (repo metadata), .claude/.idea/.vscode (IDE config), .DS_Store (macOS), worktrees/ (future location) +UNTRACKED_ITEMS=$(git ls-files --others --exclude-standard 2>/dev/null | grep -v "^\.DS_Store$") +UNTRACKED_DIRS=$(find . -maxdepth 1 -type d ! -name "." ! -name ".git" ! -name "worktrees" ! -name ".*" 2>/dev/null) + +if [[ -n "$UNTRACKED_ITEMS" ]] || [[ -n "$UNTRACKED_DIRS" ]]; then + echo -e "${YELLOW}Found untracked content to migrate:${NC}" + if [[ -n "$UNTRACKED_DIRS" ]]; then + echo "$UNTRACKED_DIRS" | while read dir; do + DIR_SIZE=$(du -sh "$dir" 2>/dev/null | awk '{print $1}') + echo " • $(basename "$dir")/ ($DIR_SIZE)" + done + fi + UNTRACKED_COUNT=$(echo "$UNTRACKED_ITEMS" | wc -l | tr -d ' ') + if [[ "$UNTRACKED_COUNT" -gt 0 ]]; then + echo " • $UNTRACKED_COUNT untracked files" + fi +else + echo -e "${GREEN}✓ No untracked content to migrate${NC}" +fi +echo "" + +# Step 2: Create worktrees directory and exclude from git +echo -e "${BLUE}[2/6] Creating worktrees directory...${NC}" +mkdir -p "$REPO_ROOT/worktrees" + +# Add worktrees/ to .git/info/exclude (local ignore, no commits needed) +EXCLUDE_FILE="$REPO_ROOT/.git/info/exclude" +if [[ -f "$EXCLUDE_FILE" ]]; then + if ! grep -q "^worktrees/$" "$EXCLUDE_FILE" 2>/dev/null; then + echo "" >> "$EXCLUDE_FILE" + echo "# Worktree directories (git worktree)" >> "$EXCLUDE_FILE" + echo "worktrees/" >> "$EXCLUDE_FILE" + echo -e "${GREEN}✓ Added worktrees/ to .git/info/exclude${NC}" + else + echo -e "${GREEN}✓ worktrees/ already in .git/info/exclude${NC}" + fi +else + # Should always exist, but create if missing + mkdir -p "$(dirname "$EXCLUDE_FILE")" + echo "# Worktree directories (git worktree)" > "$EXCLUDE_FILE" + echo "worktrees/" >> "$EXCLUDE_FILE" + echo -e "${GREEN}✓ Added worktrees/ to .git/info/exclude${NC}" +fi + +echo -e "${GREEN}✓ Created worktrees/ directory${NC}" + +# Step 3: Handle uncommitted changes before creating worktrees +HAS_CHANGES=false +echo -e "${BLUE}[3/6] Checking for uncommitted changes...${NC}" +if ! git diff-index --quiet HEAD --; then + HAS_CHANGES=true + # Stash changes temporarily + STASH_MESSAGE="git-migrate-to-worktrees temporary stash $(date +%s)" + git stash push -m "$STASH_MESSAGE" 2>/dev/null + echo -e "${GREEN}✓ Changes stashed temporarily${NC}" +else + echo -e "${GREEN}✓ No uncommitted changes${NC}" +fi + +# Step 3.5: Detach HEAD to prepare for worktree creation +# This prevents "already checked out" errors when creating worktrees +# We detach at the main branch's HEAD for a clean neutral state +echo -e "${BLUE}Detaching HEAD to prepare for worktree creation...${NC}" +git checkout --detach "$MAIN_BRANCH" >/dev/null 2>&1 +echo -e "${GREEN}✓ HEAD detached at $MAIN_BRANCH (neutral state)${NC}" +echo "" + +# Step 4: Create main branch worktree (always) +echo -e "${BLUE}[4/6] Creating worktree for $MAIN_BRANCH (default branch)...${NC}" +if git worktree add "$REPO_ROOT/worktrees/$MAIN_BRANCH" "$MAIN_BRANCH" 2>/dev/null; then + echo -e "${GREEN}✓ Worktree created at worktrees/$MAIN_BRANCH${NC}" + # Disable sparse checkout in the new worktree (only parent should be sparse) + cd "$REPO_ROOT/worktrees/$MAIN_BRANCH" + git config core.sparseCheckout false + git sparse-checkout disable 2>/dev/null || true + git read-tree -mu HEAD 2>/dev/null || true + cd "$REPO_ROOT" +else + echo -e "${RED}Error: Could not create worktree for $MAIN_BRANCH${NC}" + echo -e "${YELLOW}This may indicate an issue with the repository${NC}" + exit 1 +fi + +# Step 5: Create worktree for current branch if different from main +DESTINATION_WORKTREE="$REPO_ROOT/worktrees/$CURRENT_BRANCH" + +if [ "$CURRENT_BRANCH" != "$MAIN_BRANCH" ]; then + echo -e "${BLUE}[5/6] Creating worktree for $CURRENT_BRANCH (current branch)...${NC}" + if git worktree add "$DESTINATION_WORKTREE" "$CURRENT_BRANCH" 2>/dev/null; then + echo -e "${GREEN}✓ Worktree created at worktrees/$CURRENT_BRANCH${NC}" + # Disable sparse checkout in the new worktree (only parent should be sparse) + cd "$DESTINATION_WORKTREE" + git config core.sparseCheckout false + git sparse-checkout disable 2>/dev/null || true + git read-tree -mu HEAD 2>/dev/null || true + cd "$REPO_ROOT" + else + echo -e "${RED}Error: Could not create worktree for $CURRENT_BRANCH${NC}" + echo -e "${YELLOW}Hint: Try manually with: git checkout --detach && git worktree add worktrees/$CURRENT_BRANCH $CURRENT_BRANCH${NC}" + exit 1 + fi +else + DESTINATION_WORKTREE="$REPO_ROOT/worktrees/$MAIN_BRANCH" +fi +echo "" + +# Step 5.5: Move untracked files/directories to current branch worktree +if [[ -n "$UNTRACKED_ITEMS" ]] || [[ -n "$UNTRACKED_DIRS" ]]; then + echo -e "${BLUE}[5.5/6] Moving untracked content to $CURRENT_BRANCH worktree...${NC}" + + # Move directories (like target/, installs/, node_modules/, etc.) + if [[ -n "$UNTRACKED_DIRS" ]]; then + echo "$UNTRACKED_DIRS" | while read dir; do + if [[ -d "$dir" && "$dir" != "./worktrees" ]]; then + DIR_NAME=$(basename "$dir") + echo -e " ${YELLOW}Moving $DIR_NAME/ ...${NC}" + mv "$dir" "$DESTINATION_WORKTREE/" 2>/dev/null && echo -e " ${GREEN}✓ Moved${NC}" + fi + done + fi + + # Move untracked files + if [[ -n "$UNTRACKED_ITEMS" ]]; then + echo "$UNTRACKED_ITEMS" | while read file; do + if [[ -f "$file" ]]; then + DIR=$(dirname "$file") + if [[ "$DIR" != "." ]]; then + mkdir -p "$DESTINATION_WORKTREE/$DIR" + fi + mv "$file" "$DESTINATION_WORKTREE/$file" 2>/dev/null + fi + done + echo -e " ${GREEN}✓ Moved untracked files${NC}" + fi + echo "" +fi + +# Step 6: Restore uncommitted changes to the appropriate worktree +if [[ "$HAS_CHANGES" == true ]]; then + echo -e "${BLUE}[6/6] Restoring uncommitted changes to $CURRENT_BRANCH worktree...${NC}" + cd "$DESTINATION_WORKTREE" + + # Apply the stash to the worktree + if git stash pop 2>/dev/null; then + echo -e "${GREEN}✓ Uncommitted changes restored${NC}" + else + echo -e "${YELLOW}⚠ Could not automatically restore changes${NC}" + echo -e "${YELLOW}Your changes are still in the stash: $STASH_MESSAGE${NC}" + echo -e "${YELLOW}You can manually apply them with: cd '$DESTINATION_WORKTREE' && git stash pop${NC}" + fi + echo "" +fi + +# Convert main repo to sparse checkout (remove all working tree files) +echo -e "${BLUE}Converting main repo to sparse checkout...${NC}" +cd "$REPO_ROOT" + +# Create README in base repo to explain worktree structure +cat > "$REPO_ROOT/WORKTREE-README.md" << 'EOF' +# Git Worktree Repository + +This repository uses **git worktrees** for managing multiple branches. + +## ⚠️ IMPORTANT: Update Your IDE + +**DO NOT open this directory in your IDE!** + +This base directory contains only git metadata. All actual work happens in the `worktrees/` subdirectories. + +### Update Your IDE Path: + +**Before:** `/path/to/repo/` +**After:** `/path/to/repo/worktrees/your-branch-name/` + +### Examples: +- IntelliJ IDEA: Close project, then open `worktrees/main/` +- VS Code: File → Open Folder → Select `worktrees/main/` +- Eclipse: File → Open Projects from File System → Select `worktrees/main/` + +## Structure + +``` +repo/ ← DO NOT open this in IDE (on 'worktree-parent' branch) +├── .git/ ← Git metadata only +├── WORKTREE-README.md ← This file +└── worktrees/ ← Open these in your IDE + ├── main/ ← Full working copy of main branch + ├── feature-123/ ← Full working copy of feature-123 branch + └── bugfix-456/ ← Full working copy of bugfix-456 branch +``` + +**Note:** The base repository is on a local 'worktree-parent' branch. This is intentional to avoid confusion +with detached HEAD states. Do not commit or push from this directory - all work should happen in worktrees/. + +## Working with Worktrees + +### Create new worktree: +```bash +git issue-worktree --issue 123 +# or +git worktree add worktrees/branch-name branch-name +``` + +### Switch between worktrees: +```bash +cd worktrees/main/ +# or use helper: +git smart-switch main +``` + +### List all worktrees: +```bash +git worktree list +``` + +### Remove worktree: +```bash +git worktree remove worktrees/branch-name +``` + +## Why Worktrees? + +- **Multiple branches simultaneously** - Work on multiple features/bugs without switching +- **Faster branch switching** - No need to stash changes +- **Cleaner workspace** - Each branch has its own directory +- **Build artifacts isolated** - No conflicts between branches + +## More Info + +Run `git migrate-to-worktrees` to validate your worktree structure. +EOF + +# Add README to git exclude so it doesn't get committed +echo "WORKTREE-README.md" >> .git/info/exclude + +echo -e "${GREEN}✓ Created WORKTREE-README.md (explains IDE path changes)${NC}" + +# Initialize sparse-checkout in cone mode +git sparse-checkout init --cone 2>/dev/null + +# Set to completely empty - no files checked out in base repo +# This makes the base repo contain ONLY .git directory +git sparse-checkout set --no-cone "" 2>/dev/null + +# Force refresh to remove all files +git read-tree -mu HEAD 2>/dev/null + +# Verify - count remaining non-git files +REMAINING_FILES=$(find . -maxdepth 1 -type f ! -name ".git*" ! -name "worktrees" 2>/dev/null | wc -l | tr -d ' ') +REMAINING_DIRS=$(find . -maxdepth 1 -type d ! -name "." ! -name ".git" ! -name "worktrees" 2>/dev/null | wc -l | tr -d ' ') + +if [[ "$REMAINING_FILES" -gt 0 ]] || [[ "$REMAINING_DIRS" -gt 0 ]]; then + echo -e "${YELLOW}⚠️ Some files/directories remain in base repo (expected: .git and worktrees only)${NC}" + echo -e "${YELLOW}This is normal for files not tracked by git (like target/, node_modules/, etc.)${NC}" + echo -e "${YELLOW}You can safely delete them: rm -rf target/ installs/ node_modules/ etc.${NC}" +else + echo -e "${GREEN}✓ Base repo is now completely sparse (only .git and worktrees/)${NC}" +fi + +echo -e "${GREEN}✓ Main repo converted to sparse checkout${NC}" + +# Create a clear local branch to replace confusing detached HEAD state +echo -e "${BLUE}Creating worktree-parent branch for base repo...${NC}" +# Create local branch at main branch's HEAD (doesn't track remote) +git branch -f worktree-parent "$MAIN_BRANCH" 2>/dev/null +git checkout worktree-parent >/dev/null 2>&1 +echo -e "${GREEN}✓ Base repo now on 'worktree-parent' branch (do not modify)${NC}" +echo -e "${YELLOW}Note: This branch exists only in the base repo to avoid confusion${NC}" + +echo "" +echo -e "${GREEN}✓ Migration complete!${NC}" +echo "" + +# Show current worktrees +echo -e "${BLUE}Current worktrees:${NC}" +git worktree list + +echo "" +echo -e "${YELLOW}Next steps:${NC}" +echo " 1. Update your IDE to open worktrees/$CURRENT_BRANCH" +echo " 2. Consider creating worktrees for other active branches:" +echo "" + +# Show branches that don't have worktrees yet +echo -e "${BLUE}Branches without worktrees:${NC}" +git branch --format='%(refname:short)' | while read branch; do + if [ ! -d "$REPO_ROOT/worktrees/$branch" ]; then + echo " git worktree add worktrees/$branch $branch" + fi +done + +echo "" +echo " 3. Use 'git issue-worktree' for future issue-based branches" +echo " 4. Use 'git worktree-ide' to open IDEs for specific worktrees" +echo "" +echo -e "${GREEN}Your shell is now in: $DESTINATION_WORKTREE${NC}" +echo "" + +# Change to the worktree directory +# Note: This only works if the script is sourced, not executed +# We'll output a message for the user to manually cd +if [[ "$0" == "${BASH_SOURCE[0]}" ]] || [[ "$0" == "$SHELL" ]]; then + # Script was executed (not sourced), can't change directory + echo -e "${YELLOW}To complete the migration, please run:${NC}" + echo -e "${GREEN} cd \"$DESTINATION_WORKTREE\"${NC}" + echo "" + + # Output the path for easy copying or scripting + echo "$DESTINATION_WORKTREE" +else + # Script was sourced, we can change directory + cd "$DESTINATION_WORKTREE" + echo -e "${GREEN}✓ Changed directory to $CURRENT_BRANCH worktree${NC}" +fi \ No newline at end of file diff --git a/dev-scripts/git-smart-switch b/dev-scripts/git-smart-switch index b905bc3..96c0d4d 100755 --- a/dev-scripts/git-smart-switch +++ b/dev-scripts/git-smart-switch @@ -87,23 +87,196 @@ NC='\033[0m' # No Color # Function to select a branch using fzf or a fallback method, now only showing local branches select_branch() { + # Check if we're in a worktree-enabled repo + local in_worktree_repo=false + if is_worktree_repo; then + in_worktree_repo=true + fi + if [ "$FZF_AVAILABLE" -eq 1 ]; then - # Using fzf to select a local branch, highlighting the current branch - echo "Select a branch (current branch is highlighted):" >&2 - selected_branch=$(git branch --sort=-committerdate | fzf --height 20% --reverse | sed -e 's/^[\* ]*//') + # Using fzf to select a local branch + if [ "$in_worktree_repo" = true ]; then + echo "Select a branch (* = current, [worktree] = has worktree, sorted by recent activity):" >&2 + + # Get all branches with worktree info and modification times for sorting + local branch_list=$( + # Get all local branches with their reflog timestamps + git for-each-ref --sort=-committerdate --format='%(refname:short)' refs/heads/ | while read -r branch; do + # Skip worktree-parent + if [[ "$branch" == "worktree-parent" ]]; then + continue + fi + + # Check if branch has a worktree and get its path + local worktree_path=$(git worktree list --porcelain | awk -v branch="$branch" ' + /^worktree/ { path = $2 } + /^branch/ { + br = $2 + gsub("refs/heads/", "", br) + if (br == branch) { print path; exit } + } + ' 2>/dev/null) + + local has_worktree=false + if [[ -n "$worktree_path" && -d "$worktree_path" ]]; then + has_worktree=true + fi + + # Get most recent reflog timestamp (last time branch was used) + # Get the commit hash from the most recent reflog entry, then get its commit date + local latest_hash=$(git reflog show "$branch" 2>/dev/null | head -1 | awk '{print $1}') + if [[ -n "$latest_hash" ]]; then + reflog_time=$(git show -s --format=%ct "$latest_hash" 2>/dev/null) + fi + + # If no reflog but has worktree, use worktree creation time + if [[ -z "$reflog_time" && "$has_worktree" == true ]]; then + reflog_time=$(stat -f "%B" "$worktree_path" 2>/dev/null || stat -c "%W" "$worktree_path" 2>/dev/null) + # If birth time not available (stat %W returns 0 on some systems), use modification time + if [[ -z "$reflog_time" || "$reflog_time" == "0" ]]; then + reflog_time=$(stat -f "%m" "$worktree_path" 2>/dev/null || stat -c "%Y" "$worktree_path" 2>/dev/null) + fi + fi + + # If still no reflog, use commit date as fallback + if [[ -z "$reflog_time" ]]; then + reflog_time=$(git log -1 --format=%ct "$branch" 2>/dev/null) + fi + + # If still no timestamp, use epoch 0 + if [[ -z "$reflog_time" ]]; then + reflog_time=0 + fi + + if [[ "$has_worktree" == true ]]; then + echo "$reflog_time|worktree|$branch" + else + echo "$reflog_time|nobranch|$branch" + fi + done | sort -t'|' -k1 -rn | while IFS='|' read -r timestamp type branch; do + local prefix="" + local current_branch=$(git branch --show-current 2>/dev/null) + + # Mark current branch + if [[ "$branch" == "$current_branch" ]]; then + prefix="* " + fi + + # Mark if has worktree + if [[ "$type" == "worktree" ]]; then + echo "${prefix}${branch} [worktree]" + else + echo "${prefix}${branch}" + fi + done) + + selected_branch=$(echo "$branch_list" | fzf --height 40% --reverse | sed -e 's/^[\* ]*//' -e 's/ \[worktree\]$//') + else + echo "Select a branch (current branch is highlighted):" >&2 + selected_branch=$(git branch --sort=-committerdate | fzf --height 20% --reverse | sed -e 's/^[\* ]*//') + fi else - # Fallback to using select, only showing local branches, highlighting the current branch + # Fallback to using select echo "fzf not found, using fallback method to select branch." >&2 - echo "Select a branch (current branch is marked with '->'):" >&2 - select branch in $(git branch --sort=-committerdate | sed "s/$current_branch/-> $current_branch/"); do - if [ -n "$branch" ]; then - # Remove any formatting added for current branch indication - selected_branch=$(echo $branch | sed 's/-> //' | sed -e 's/^[[:space:]]*//') - break - else - echo "Invalid selection. Please try again." >&2 - fi - done + + if [ "$in_worktree_repo" = true ]; then + echo "Select a branch (-> = current, [W] = has worktree, sorted by recent activity):" >&2 + + # Build sorted list of branches with worktree markers + local branches=() + local current_branch=$(git branch --show-current 2>/dev/null) + + # Get sorted list (same logic as fzf version - using reflog) + while IFS='|' read -r timestamp type branch; do + local display="$branch" + + # Mark current branch + if [[ "$branch" == "$current_branch" ]]; then + display="-> $display" + fi + + # Mark if has worktree + if [[ "$type" == "worktree" ]]; then + display="$display [W]" + fi + + branches+=("$display") + done < <( + # Get all local branches with their reflog timestamps + git for-each-ref --sort=-committerdate --format='%(refname:short)' refs/heads/ | while read -r branch; do + # Skip worktree-parent + if [[ "$branch" == "worktree-parent" ]]; then + continue + fi + + # Check if branch has a worktree and get its path + local worktree_path=$(git worktree list --porcelain | awk -v branch="$branch" ' + /^worktree/ { path = $2 } + /^branch/ { + br = $2 + gsub("refs/heads/", "", br) + if (br == branch) { print path; exit } + } + ' 2>/dev/null) + + local has_worktree=false + if [[ -n "$worktree_path" && -d "$worktree_path" ]]; then + has_worktree=true + fi + + # Get most recent reflog timestamp (last time branch was used) + # Get the commit hash from the most recent reflog entry, then get its commit date + local latest_hash=$(git reflog show "$branch" 2>/dev/null | head -1 | awk '{print $1}') + if [[ -n "$latest_hash" ]]; then + reflog_time=$(git show -s --format=%ct "$latest_hash" 2>/dev/null) + fi + + # If no reflog but has worktree, use worktree creation time + if [[ -z "$reflog_time" && "$has_worktree" == true ]]; then + reflog_time=$(stat -f "%B" "$worktree_path" 2>/dev/null || stat -c "%W" "$worktree_path" 2>/dev/null) + # If birth time not available (stat %W returns 0 on some systems), use modification time + if [[ -z "$reflog_time" || "$reflog_time" == "0" ]]; then + reflog_time=$(stat -f "%m" "$worktree_path" 2>/dev/null || stat -c "%Y" "$worktree_path" 2>/dev/null) + fi + fi + + # If still no reflog, use commit date as fallback + if [[ -z "$reflog_time" ]]; then + reflog_time=$(git log -1 --format=%ct "$branch" 2>/dev/null) + fi + + # If still no timestamp, use epoch 0 + if [[ -z "$reflog_time" ]]; then + reflog_time=0 + fi + + if [[ "$has_worktree" == true ]]; then + echo "$reflog_time|worktree|$branch" + else + echo "$reflog_time|nobranch|$branch" + fi + done | sort -t'|' -k1 -rn + ) + + select branch in "${branches[@]}"; do + if [ -n "$branch" ]; then + selected_branch=$(echo "$branch" | sed 's/-> //' | sed 's/ \[W\]$//' | sed -e 's/^[[:space:]]*//') + break + else + echo "Invalid selection. Please try again." >&2 + fi + done + else + echo "Select a branch (current branch is marked with '->'):" >&2 + select branch in $(git branch --sort=-committerdate | sed "s/$current_branch/-> $current_branch/"); do + if [ -n "$branch" ]; then + selected_branch=$(echo $branch | sed 's/-> //' | sed -e 's/^[[:space:]]*//') + break + else + echo "Invalid selection. Please try again." >&2 + fi + done + fi fi echo $selected_branch } @@ -137,6 +310,180 @@ select_commit() { echo $selected_commit } + # Function to check if we're in a worktree-enabled repository +is_worktree_repo() { + # Get the git common dir (base repo .git), then get parent directory + local git_common_dir=$(git rev-parse --git-common-dir 2>/dev/null) + local repo_root=$(dirname "$git_common_dir") + [[ -d "$repo_root/worktrees" ]] && return 0 || return 1 +} + +# Function to find if a branch already has a worktree (anywhere) +find_worktree_for_branch() { + local branch="$1" + + # Parse worktree list to find where this branch is checked out + git worktree list --porcelain | awk -v branch="$branch" ' + /^worktree/ { path = $2 } + /^branch/ { + br = $2 + gsub("refs/heads/", "", br) + if (br == branch) { + print path + exit + } + } + ' +} + +# Function to find if a branch is checked out in an old worktree +find_old_worktree_for_branch() { + local branch="$1" + local git_common_dir=$(git rev-parse --git-common-dir 2>/dev/null) + local repo_root=$(dirname "$git_common_dir") + + # Parse worktree list to find branches checked out outside worktrees/ directory + git worktree list --porcelain | awk -v branch="$branch" -v repo_root="$repo_root" ' + /^worktree/ { path = $2 } + /^branch/ { + br = $2 + gsub("refs/heads/", "", br) + if (br == branch && path !~ /\/worktrees\//) { + # Found branch in old worktree pattern (not in worktrees/ subdirectory) + # Exclude the base repo itself + if (path != repo_root) { + print path + exit + } + } + } + ' +} + +# Function to migrate old worktree to new structure +migrate_old_worktree() { + local branch="$1" + local old_path="$2" + local git_common_dir=$(git rev-parse --git-common-dir 2>/dev/null) + local repo_root=$(dirname "$git_common_dir") + local new_path="$repo_root/worktrees/$branch" + + echo -e "${YELLOW}⚠️ Branch '$branch' is checked out in old worktree pattern${NC}" + echo -e "${BLUE}Old location:${NC} $old_path" + echo -e "${BLUE}New location:${NC} $new_path" + echo "" + echo -e "${YELLOW}Options:${NC}" + echo " 1. Migrate worktree to new structure (RECOMMENDED - preserves everything)" + echo " 2. Remove old worktree and create fresh one (loses uncommitted changes)" + echo " 3. Cancel operation" + echo "" + read -p "Choose option (1/2/3): " -n 1 -r + echo "" + echo + + case $REPLY in + 1) + echo -e "${BLUE}Migrating worktree to new structure...${NC}" + echo "" + + # Create worktrees directory if it doesn't exist + mkdir -p "$repo_root/worktrees" + + # Check for uncommitted changes in old worktree + cd "$old_path" + if ! git diff-index --quiet HEAD -- 2>/dev/null; then + echo -e "${YELLOW} Uncommitted changes detected - stashing for safety...${NC}" + STASH_MSG="Pre-migration backup from $old_path ($(date +%Y%m%d-%H%M%S))" + git stash push -m "$STASH_MSG" 2>/dev/null + echo -e "${GREEN} ✓ Changes stashed: $STASH_MSG${NC}" + fi + + cd "$repo_root" + + # Move the directory + echo -e "${BLUE} Moving: $old_path → $new_path${NC}" + mv "$old_path" "$new_path" + + # Update git worktree metadata + echo -e "${BLUE} Updating git worktree metadata...${NC}" + + # Remove old worktree entry + git worktree prune 2>/dev/null + + # Re-add worktree at new location + # This updates .git/worktrees/ to point to the new location + cd "$new_path" + + # Fix the .git file to point to correct worktree metadata + local worktree_git_file="$new_path/.git" + if [[ -f "$worktree_git_file" ]]; then + echo "gitdir: $repo_root/.git/worktrees/$branch" > "$worktree_git_file" + fi + + # Update worktree metadata paths + local worktree_meta="$repo_root/.git/worktrees/$branch" + if [[ -d "$worktree_meta" ]]; then + echo "$new_path" > "$worktree_meta/gitdir" + fi + + # Restore stashed changes if any + if git stash list 2>/dev/null | grep -q "$STASH_MSG"; then + echo -e "${BLUE} Restoring uncommitted changes...${NC}" + git stash pop 2>/dev/null + fi + + echo "" + echo -e "${GREEN}✓ Worktree migrated successfully!${NC}" + echo -e "${BLUE}New location:${NC} $new_path" + echo "" + + # Update the path for the caller + echo "$new_path" + return 0 + ;; + 2) + echo -e "${YELLOW}Removing old worktree and creating fresh one...${NC}" + echo "" + + # Remove old worktree + if git worktree remove "$old_path" --force 2>/dev/null; then + echo -e "${GREEN} ✓ Old worktree removed${NC}" + else + echo -e "${YELLOW} Manual removal...${NC}" + rm -rf "$old_path" + git worktree prune + echo -e "${GREEN} ✓ Manually removed and pruned${NC}" + fi + + # Create new worktree + mkdir -p "$repo_root/worktrees" + echo -e "${BLUE} Creating new worktree at: $new_path${NC}" + git worktree add "$new_path" "$branch" + + # Disable sparse checkout in the new worktree (only parent should be sparse) + if [[ -d "$new_path" ]]; then + cd "$new_path" + git config core.sparseCheckout false + git sparse-checkout disable 2>/dev/null || true + git read-tree -mu HEAD 2>/dev/null || true + cd "$repo_root" + fi + + echo "" + echo -e "${GREEN}✓ Fresh worktree created!${NC}" + echo -e "${RED}⚠️ Note: Uncommitted changes from old worktree were lost${NC}" + echo "" + + echo "$new_path" + return 0 + ;; + 3|*) + echo -e "${YELLOW}Operation cancelled${NC}" + return 1 + ;; + esac +} + # Function to move only working state move_working_state() { local target_branch="$1" @@ -908,11 +1255,130 @@ if [ "$dry_run" = true ]; then exit 0 fi +# Prevent switching to worktree-parent branch (it's only for base repo) +if [[ "$new_branch" == "worktree-parent" ]]; then + echo "" + echo -e "${RED}Error: Cannot switch to 'worktree-parent' branch${NC}" + echo -e "${YELLOW}This branch is a placeholder for the base repository and should not be used for work.${NC}" + echo "" + echo -e "${BLUE}Use a regular branch instead:${NC}" + echo " git smart-switch main" + echo " git smart-switch " + echo "" + exit 1 +fi + # Create WIP commits if necessary create_wip git fetch origin +# Check if we're in a worktree-enabled repo +if is_worktree_repo; then + # First check if branch already has ANY worktree (new or old structure) + EXISTING_WORKTREE_PATH=$(find_worktree_for_branch "$new_branch") + + if [[ -n "$EXISTING_WORKTREE_PATH" ]]; then + GIT_COMMON_DIR=$(git rev-parse --git-common-dir 2>/dev/null) + REPO_ROOT=$(dirname "$GIT_COMMON_DIR") + + # Check if it's in the new structure (worktrees/) or old structure + if [[ "$EXISTING_WORKTREE_PATH" == "$REPO_ROOT/worktrees/"* ]]; then + # Already in new structure - just navigate there + echo "" + echo -e "${GREEN}✓ Worktree already exists for branch '$new_branch'${NC}" + echo -e "${BLUE}Location:${NC} $EXISTING_WORKTREE_PATH" + echo "" + echo -e "${YELLOW}Navigate to it with:${NC}" + echo -e "${GREEN} cd \"$EXISTING_WORKTREE_PATH\"${NC}" + echo "" + + # Output path for easy cd + echo "$EXISTING_WORKTREE_PATH" + exit 0 + else + # Old worktree pattern - offer migration + echo "" + echo -e "${YELLOW}════════════════════════════════════════════════════${NC}" + echo -e "${YELLOW} OLD WORKTREE PATTERN DETECTED${NC}" + echo -e "${YELLOW}════════════════════════════════════════════════════${NC}" + echo "" + + # Offer to migrate the old worktree + MIGRATED_PATH=$(migrate_old_worktree "$new_branch" "$EXISTING_WORKTREE_PATH") + MIGRATION_STATUS=$? + + if [[ $MIGRATION_STATUS -eq 0 ]]; then + echo -e "${GREEN}✓ Worktree migration completed${NC}" + echo -e "${BLUE}Navigate to:${NC} cd \"$MIGRATED_PATH\"" + echo "" + echo -e "${YELLOW}Note: You're still in your current worktree${NC}" + echo -e "${YELLOW}To work on the migrated worktree, cd to the path above${NC}" + + # Output path for easy cd + echo "$MIGRATED_PATH" + exit 0 + else + echo -e "${RED}Migration cancelled or failed${NC}" + exit 1 + fi + fi + fi + + # If we get here, branch doesn't have a worktree yet + # Check if we're currently inside a worktree directory + CURRENT_DIR=$(pwd) + GIT_COMMON_DIR=$(git rev-parse --git-common-dir 2>/dev/null) + REPO_ROOT=$(dirname "$GIT_COMMON_DIR") + + if [[ "$CURRENT_DIR" == "$REPO_ROOT/worktrees/"* ]]; then + # We're inside a worktree directory - create new worktree instead of checking out + echo "" + echo -e "${YELLOW}⚠️ You're in a worktree structure${NC}" + echo -e "${YELLOW}Creating new worktree for '$new_branch' instead of checking out here${NC}" + echo "" + + NEW_WORKTREE_PATH="$REPO_ROOT/worktrees/$new_branch" + + # Check if branch exists + if git rev-parse --verify "$new_branch" >/dev/null 2>&1; then + # Branch exists - create worktree from it + echo -e "${BLUE}Creating worktree for existing branch '$new_branch'${NC}" + git worktree add "$NEW_WORKTREE_PATH" "$new_branch" + else + # Branch doesn't exist - create new branch and worktree + echo -e "${BLUE}Creating new branch '$new_branch' and worktree${NC}" + + if [ "$keep_current_state" = true ]; then + echo "Creating from current state..." + git worktree add -b "$new_branch" "$NEW_WORKTREE_PATH" + else + echo "Creating from origin/main..." + git worktree add -b "$new_branch" "$NEW_WORKTREE_PATH" origin/main + fi + fi + + if [[ $? -eq 0 ]]; then + # Disable sparse checkout in the new worktree (only parent should be sparse) + echo -e "${BLUE}Disabling sparse checkout in worktree...${NC}" + cd "$NEW_WORKTREE_PATH" + git config core.sparseCheckout false + git sparse-checkout disable 2>/dev/null || true + git read-tree -mu HEAD 2>/dev/null || true + cd "$REPO_ROOT" + + echo "" + echo -e "${GREEN}✓ Worktree created${NC}" + echo "" + + # Output path for wrapper function to cd to + echo "$NEW_WORKTREE_PATH" + fi + + exit 0 + fi +fi + # Switch to the new branch, creating it if necessary if git rev-parse --verify "$new_branch" >/dev/null 2>&1; then # Branch already exists - switch to it diff --git a/dev-scripts/git-smart-switch.sh b/dev-scripts/git-smart-switch.sh new file mode 100755 index 0000000..fd2a04f --- /dev/null +++ b/dev-scripts/git-smart-switch.sh @@ -0,0 +1,37 @@ +#!/bin/bash +# Shell function wrapper for git-smart-switch that enables automatic directory navigation +# Source this in your .zshrc or .bashrc + +# Override git-smart-switch function (takes priority over PATH executables in zsh) +function git-smart-switch() { + # Run the actual git-smart-switch command and capture output + local temp_file=$(mktemp) + /Users/stevebolton/.dotcms/dev-scripts/git-smart-switch "$@" 2>&1 | tee "$temp_file" + local exit_code=${PIPESTATUS[0]} + + # Get the last line (potential worktree path) + local last_line=$(tail -n 1 "$temp_file") + rm -f "$temp_file" + + # Check if last line is a valid directory path + if [[ -d "$last_line" ]]; then + echo "" + echo "Changing directory to worktree..." + cd "$last_line" || return 1 + fi + + return $exit_code +} + +# Override git smart-switch as well (for when called via git) +function git() { + if [[ "$1" == "smart-switch" ]]; then + shift + git-smart-switch "$@" + else + command git "$@" + fi +} + +# Short alias +alias gss='git-smart-switch' diff --git a/dev-scripts/git-worktree-cleanup b/dev-scripts/git-worktree-cleanup new file mode 100755 index 0000000..e262591 --- /dev/null +++ b/dev-scripts/git-worktree-cleanup @@ -0,0 +1,325 @@ +#!/bin/bash + +# git-worktree-cleanup - Clean up merged and stale worktrees +# Usage: git worktree-cleanup [--help] [--dry-run] [--all] + +# Determine the directory of the calling script +script_dir="$(dirname "$(realpath "$0")")" + +# Call the check_updates.sh script from the determined directory +"$script_dir/check_updates.sh" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +DRY_RUN=false +REMOVE_ALL=false +INTERACTIVE=true +AUTO_CONFIRM=false + +show_help() { + echo -e "${BLUE}git-worktree-cleanup - Clean up merged and stale worktrees${NC}" + echo "" + echo -e "${YELLOW}Usage:${NC}" + echo " git worktree-cleanup [options]" + echo "" + echo -e "${YELLOW}Description:${NC}" + echo " Identifies and removes worktrees for merged branches." + echo " Helps maintain a clean worktrees/ directory by removing:" + echo " • Worktrees whose branches have been merged to main" + echo " • Stale worktrees (optionally)" + echo " • Orphaned worktree directories" + echo "" + echo -e "${YELLOW}Options:${NC}" + echo " --help, -h Show this help message" + echo " --dry-run Preview operations without executing" + echo " --all Remove all worktrees except main/master" + echo " --yes, -y Skip confirmation prompts" + echo " --list List worktrees with merge status" + echo "" + echo -e "${YELLOW}Safety Features:${NC}" + echo " • Never removes main/master worktrees" + echo " • Warns about uncommitted changes" + echo " • Confirms before deletion (unless --yes)" + echo " • Preserves git history (only removes worktrees, not branches)" + echo "" + echo -e "${YELLOW}Examples:${NC}" + echo " git worktree-cleanup # Interactive cleanup" + echo " git worktree-cleanup --dry-run # Preview cleanup operations" + echo " git worktree-cleanup --list # List worktrees with status" + echo " git worktree-cleanup --yes # Auto-confirm deletions" + echo "" + echo -e "${YELLOW}Cleanup Process:${NC}" + echo " 1. Scans all worktrees in repo/worktrees/" + echo " 2. Checks merge status against main branch" + echo " 3. Identifies worktrees with/without uncommitted changes" + echo " 4. Prompts for confirmation (unless --yes)" + echo " 5. Removes worktree and updates git metadata" +} + +get_main_branch() { + local main_branch=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@') + if [ -z "$main_branch" ]; then + if git show-ref --verify --quiet refs/heads/main; then + echo "main" + elif git show-ref --verify --quiet refs/heads/master; then + echo "master" + else + echo "" + fi + else + echo "$main_branch" + fi +} + +is_merged() { + local branch="$1" + local main_branch="$2" + + # Check if branch is merged into main + local merge_base=$(git merge-base "$branch" "$main_branch" 2>/dev/null) + local branch_commit=$(git rev-parse "$branch" 2>/dev/null) + + if [[ "$merge_base" == "$branch_commit" ]]; then + return 0 # Merged + else + return 1 # Not merged + fi +} + +has_uncommitted_changes() { + local worktree_path="$1" + + # Check for uncommitted changes in worktree + if [ ! -d "$worktree_path" ]; then + return 1 # Worktree doesn't exist + fi + + cd "$worktree_path" || return 1 + + # Check for staged or unstaged changes + if ! git diff-index --quiet HEAD -- 2>/dev/null; then + return 0 # Has changes + fi + + # Check for untracked files + if [ -n "$(git ls-files --others --exclude-standard)" ]; then + return 0 # Has untracked files + fi + + return 1 # Clean +} + +list_worktrees() { + local main_branch=$(get_main_branch) + local repo_root=$(git rev-parse --show-toplevel) + + echo -e "${BLUE}Worktrees Status:${NC}" + echo "" + + git worktree list --porcelain | awk -v main="$main_branch" ' + /^worktree/ { path = $2 } + /^branch/ { + branch = $2 + gsub("refs/heads/", "", branch) + } + /^$/ && path != "" { + print path " " branch + path = "" + branch = "" + } + ' | while read -r worktree_path branch; do + if [[ "$branch" == "$main_branch" ]]; then + status="${GREEN}[main]${NC}" + elif is_merged "$branch" "$main_branch" 2>/dev/null; then + status="${YELLOW}[merged]${NC}" + else + status="${BLUE}[active]${NC}" + fi + + if has_uncommitted_changes "$worktree_path"; then + changes="${RED}[uncommitted changes]${NC}" + else + changes="${GREEN}[clean]${NC}" + fi + + # Make path relative to repo root + relative_path=${worktree_path#$repo_root/} + + echo -e " ${GREEN}$branch${NC} $status $changes" + echo -e " ${BLUE}Path:${NC} $relative_path" + echo "" + done +} + +remove_worktree() { + local worktree_path="$1" + local branch="$2" + local force="$3" + + if [[ "$DRY_RUN" == true ]]; then + echo -e "${YELLOW}[DRY RUN] Would remove worktree:${NC}" + echo -e " ${BLUE}Path:${NC} $worktree_path" + echo -e " ${BLUE}Branch:${NC} $branch" + if [[ "$force" == "true" ]]; then + echo -e " ${YELLOW}Force remove (has uncommitted changes)${NC}" + fi + return 0 + fi + + # Remove worktree + if [[ "$force" == "true" ]]; then + git worktree remove --force "$worktree_path" 2>/dev/null + else + git worktree remove "$worktree_path" 2>/dev/null + fi + + if [ $? -eq 0 ]; then + echo -e "${GREEN}✓ Removed worktree: $branch${NC}" + return 0 + else + echo -e "${RED}✗ Failed to remove worktree: $branch${NC}" + return 1 + fi +} + +# Parse arguments +while [[ $# -gt 0 ]]; do + case $1 in + --help|-h) + show_help + exit 0 + ;; + --dry-run) + DRY_RUN=true + ;; + --all) + REMOVE_ALL=true + ;; + --yes|-y) + AUTO_CONFIRM=true + ;; + --list) + list_worktrees + exit 0 + ;; + *) + echo -e "${RED}Error: Unknown option '$1'${NC}" + echo "Use --help for usage information" + exit 1 + ;; + esac + shift +done + +# Verify we're in a git repository +if ! git rev-parse --is-inside-work-tree &>/dev/null; then + echo -e "${RED}Error: Not in a git repository${NC}" + exit 1 +fi + +# Get main branch +MAIN_BRANCH=$(get_main_branch) +if [ -z "$MAIN_BRANCH" ]; then + echo -e "${RED}Error: Cannot determine main branch${NC}" + exit 1 +fi + +echo -e "${BLUE}Scanning worktrees...${NC}" +echo -e "${BLUE}Main branch:${NC} ${GREEN}$MAIN_BRANCH${NC}" +echo "" + +# Collect worktrees to remove +WORKTREES_TO_REMOVE=() +REPO_ROOT=$(git rev-parse --show-toplevel) + +git worktree list --porcelain | awk ' + /^worktree/ { path = $2 } + /^branch/ { + branch = $2 + gsub("refs/heads/", "", branch) + } + /^$/ && path != "" { + print path " " branch + path = "" + branch = "" + } +' | while read -r worktree_path branch; do + # Skip main branch + if [[ "$branch" == "$MAIN_BRANCH" ]] || [[ "$branch" == "master" ]] || [[ "$branch" == "main" ]]; then + continue + fi + + # Determine if should be removed + should_remove=false + reason="" + force_remove=false + + if [[ "$REMOVE_ALL" == true ]]; then + should_remove=true + reason="manual cleanup (--all)" + elif is_merged "$branch" "$MAIN_BRANCH" 2>/dev/null; then + should_remove=true + reason="merged to $MAIN_BRANCH" + fi + + if [[ "$should_remove" == true ]]; then + # Check for uncommitted changes + if has_uncommitted_changes "$worktree_path"; then + echo -e "${YELLOW}Found merged worktree with uncommitted changes:${NC}" + echo -e " ${BLUE}Branch:${NC} $branch" + echo -e " ${BLUE}Path:${NC} ${worktree_path#$REPO_ROOT/}" + echo -e " ${YELLOW}Reason:${NC} $reason" + echo -e " ${RED}Warning: Has uncommitted changes${NC}" + echo "" + + if [[ "$AUTO_CONFIRM" == false ]]; then + read -p "Remove anyway (changes will be lost)? (y/N) " -n 1 -r + echo + if [[ $REPLY =~ ^[Yy]$ ]]; then + force_remove=true + else + echo -e "${YELLOW}Skipped${NC}" + echo "" + continue + fi + else + force_remove=true + fi + else + echo -e "${GREEN}Found worktree to remove:${NC}" + echo -e " ${BLUE}Branch:${NC} $branch" + echo -e " ${BLUE}Path:${NC} ${worktree_path#$REPO_ROOT/}" + echo -e " ${YELLOW}Reason:${NC} $reason" + echo "" + fi + + # Confirm removal + if [[ "$AUTO_CONFIRM" == false ]] && [[ "$force_remove" == false ]]; then + read -p "Remove this worktree? (y/N) " -n 1 -r + echo + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + echo -e "${YELLOW}Skipped${NC}" + echo "" + continue + fi + fi + + # Remove worktree + remove_worktree "$worktree_path" "$branch" "$force_remove" + echo "" + fi +done + +if [[ "$DRY_RUN" == true ]]; then + echo -e "${YELLOW}Dry run complete. Run without --dry-run to execute removals.${NC}" +else + echo -e "${GREEN}Cleanup complete!${NC}" + echo "" + echo -e "${BLUE}Remaining worktrees:${NC}" + git worktree list +fi \ No newline at end of file diff --git a/dev-scripts/git-worktree-ide b/dev-scripts/git-worktree-ide new file mode 100755 index 0000000..b9e43db --- /dev/null +++ b/dev-scripts/git-worktree-ide @@ -0,0 +1,240 @@ +#!/bin/bash + +# git-worktree-ide - Open IDEs for specific worktrees +# Usage: git worktree-ide [worktree-path] [--ide cursor|code|idea] + +# Determine the directory of the calling script +script_dir="$(dirname "$(realpath "$0")")" + +# Call the check_updates.sh script from the determined directory +"$script_dir/check_updates.sh" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +WORKTREE_PATH="" +IDE_CHOICE="" +LIST_MODE=false + +show_help() { + echo -e "${BLUE}git-worktree-ide - Open IDEs for specific worktrees${NC}" + echo "" + echo -e "${YELLOW}Usage:${NC}" + echo " git worktree-ide [worktree-path] [--ide IDE]" + echo " git worktree-ide --list" + echo "" + echo -e "${YELLOW}Description:${NC}" + echo " Opens your preferred IDE for a specific worktree directory." + echo " Supports Cursor, VS Code, and IntelliJ IDEA." + echo "" + echo -e "${YELLOW}Options:${NC}" + echo " --help, -h Show this help message" + echo " --ide IDE Specify IDE: cursor, code, idea, or fleet" + echo " --list List all available worktrees" + echo " worktree-path Path to worktree (or select interactively)" + echo "" + echo -e "${YELLOW}Supported IDEs:${NC}" + echo " cursor - Cursor IDE" + echo " code - Visual Studio Code" + echo " idea - IntelliJ IDEA" + echo " fleet - JetBrains Fleet" + echo "" + echo -e "${YELLOW}Examples:${NC}" + echo " git worktree-ide # Interactive selection" + echo " git worktree-ide worktrees/issue-123-feature # Open specific worktree" + echo " git worktree-ide --list # List all worktrees" + echo " git worktree-ide worktrees/main --ide cursor # Open in Cursor" + echo " git worktree-ide worktrees/issue-456 --ide code # Open in VS Code" + echo "" + echo -e "${YELLOW}Configuration:${NC}" + echo " Set default IDE via git config:" + echo " git config --global worktree.defaultIde cursor" + echo " git config --global worktree.defaultIde code" + echo " git config --global worktree.defaultIde idea" +} + +detect_ide_command() { + local ide="$1" + case "$ide" in + cursor) + if command -v cursor &>/dev/null; then + echo "cursor" + else + echo "" + fi + ;; + code) + if command -v code &>/dev/null; then + echo "code" + else + echo "" + fi + ;; + idea) + # Check for IntelliJ IDEA command line launcher + if command -v idea &>/dev/null; then + echo "idea" + elif [ -f "/Applications/IntelliJ IDEA.app/Contents/MacOS/idea" ]; then + echo "/Applications/IntelliJ IDEA.app/Contents/MacOS/idea" + else + echo "" + fi + ;; + fleet) + if command -v fleet &>/dev/null; then + echo "fleet" + else + echo "" + fi + ;; + *) + echo "" + ;; + esac +} + +get_default_ide() { + # Check git config for default IDE + local default_ide=$(git config --global --get worktree.defaultIde 2>/dev/null) + if [[ -n "$default_ide" ]]; then + echo "$default_ide" + return + fi + + # Auto-detect available IDEs in order of preference + for ide in cursor code idea fleet; do + if [[ -n $(detect_ide_command "$ide") ]]; then + echo "$ide" + return + fi + done + + echo "" +} + +open_in_ide() { + local worktree_path="$1" + local ide="$2" + + if [ ! -d "$worktree_path" ]; then + echo -e "${RED}Error: Worktree path does not exist: $worktree_path${NC}" + return 1 + fi + + local ide_cmd=$(detect_ide_command "$ide") + if [[ -z "$ide_cmd" ]]; then + echo -e "${RED}Error: IDE '$ide' not found or not installed${NC}" + echo "" + echo -e "${YELLOW}Available IDEs:${NC}" + for available_ide in cursor code idea fleet; do + local cmd=$(detect_ide_command "$available_ide") + if [[ -n "$cmd" ]]; then + echo " ✓ $available_ide" + else + echo " ✗ $available_ide (not installed)" + fi + done + return 1 + fi + + echo -e "${GREEN}Opening worktree in ${ide}...${NC}" + echo -e "${BLUE}Path:${NC} $worktree_path" + + # Open IDE + "$ide_cmd" "$worktree_path" &>/dev/null & + + echo -e "${GREEN}✓ IDE launched${NC}" +} + +# Parse arguments +while [[ $# -gt 0 ]]; do + case $1 in + --help|-h) + show_help + exit 0 + ;; + --ide) + IDE_CHOICE="$2" + shift + ;; + --list) + LIST_MODE=true + ;; + -*) + echo -e "${RED}Error: Unknown option '$1'${NC}" + echo "Use --help for usage information" + exit 1 + ;; + *) + WORKTREE_PATH="$1" + ;; + esac + shift +done + +# Verify we're in a git repository +if ! git rev-parse --is-inside-work-tree &>/dev/null; then + echo -e "${RED}Error: Not in a git repository${NC}" + exit 1 +fi + +# List mode +if [[ "$LIST_MODE" == true ]]; then + echo -e "${BLUE}Available worktrees:${NC}" + echo "" + git worktree list | while read -r line; do + worktree=$(echo "$line" | awk '{print $1}') + branch=$(echo "$line" | awk '{print $3}' | sed 's/\[//;s/\]//') + echo -e " ${GREEN}$branch${NC}" + echo -e " ${BLUE}Path:${NC} $worktree" + done + exit 0 +fi + +# Get default IDE if not specified +if [[ -z "$IDE_CHOICE" ]]; then + IDE_CHOICE=$(get_default_ide) + if [[ -z "$IDE_CHOICE" ]]; then + echo -e "${RED}Error: No IDE found${NC}" + echo "Please install one of: cursor, code, idea, fleet" + echo "Or specify IDE with --ide option" + exit 1 + fi + echo -e "${BLUE}Using default IDE:${NC} $IDE_CHOICE" +fi + +# Interactive worktree selection if no path provided +if [[ -z "$WORKTREE_PATH" ]]; then + echo -e "${BLUE}Select worktree to open:${NC}" + echo "" + + # Get list of worktrees + worktree_list=$(git worktree list --porcelain | awk '/^worktree/ {print $2}') + + if command -v fzf >/dev/null 2>&1; then + WORKTREE_PATH=$(echo "$worktree_list" | fzf --height 50% --reverse --header "Select worktree to open") + else + echo -e "${YELLOW}fzf not installed, using select${NC}" + PS3="Select a worktree: " + select wt in $worktree_list; do + WORKTREE_PATH=$wt + break + done + fi + + if [[ -z "$WORKTREE_PATH" ]]; then + echo -e "${YELLOW}No worktree selected${NC}" + exit 0 + fi +fi + +# Resolve relative path to absolute +if [[ "$WORKTREE_PATH" != /* ]]; then + WORKTREE_PATH="$(pwd)/$WORKTREE_PATH" +fi + +open_in_ide "$WORKTREE_PATH" "$IDE_CHOICE" \ No newline at end of file diff --git a/dev-scripts/setup-worktree-aliases.sh b/dev-scripts/setup-worktree-aliases.sh new file mode 100755 index 0000000..c07399c --- /dev/null +++ b/dev-scripts/setup-worktree-aliases.sh @@ -0,0 +1,107 @@ +#!/bin/bash + +# setup-worktree-aliases.sh - Configure git aliases for worktree workflow +# Makes worktree commands shorter and integrates with existing workflow + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +echo -e "${BLUE}Setting up git aliases for worktree workflow...${NC}" +echo "" + +# Determine script directory (where the actual scripts are) +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# Add aliases to git config +echo -e "${BLUE}Configuring git aliases...${NC}" + +# Core worktree aliases +git config --global alias.wt-clone "!f() { '$SCRIPT_DIR/git-clone-worktree' \"\$@\"; }; f" +git config --global alias.wt-migrate "!f() { '$SCRIPT_DIR/git-migrate-to-worktrees' \"\$@\"; }; f" +git config --global alias.wt-issue "!f() { '$SCRIPT_DIR/git-issue-worktree' \"\$@\"; }; f" +git config --global alias.wt-ide "!f() { '$SCRIPT_DIR/git-worktree-ide' \"\$@\"; }; f" +git config --global alias.wt-cleanup "!f() { '$SCRIPT_DIR/git-worktree-cleanup' \"\$@\"; }; f" + +# Short aliases +git config --global alias.wti "!f() { '$SCRIPT_DIR/git-issue-worktree' \"\$@\"; }; f" # wt-issue +git config --global alias.wtc "!f() { '$SCRIPT_DIR/git-worktree-cleanup' \"\$@\"; }; f" # wt-cleanup + +# Note: git-smart-switch is already available as 'git smart-switch' - no alias needed + +# Convenience aliases +git config --global alias.wt-list "worktree list" +git config --global alias.wt-remove "worktree remove" +git config --global alias.wt-prune "worktree prune" + +echo -e "${GREEN}✓ Git aliases configured${NC}" +echo "" + +# Show configured aliases +echo -e "${BLUE}Configured aliases:${NC}" +echo "" +echo -e "${YELLOW}Core Commands:${NC}" +echo " git wt-clone [dir] - Clone with worktree structure" +echo " git wt-migrate - Migrate existing repo to worktrees" +echo " git wt-issue [--issue N] - Create worktree from GitHub issue" +echo " git wt-ide [worktree] - Open IDE for worktree" +echo " git smart-switch - Safe branch switching (worktree-aware)" +echo " git wt-cleanup - Clean up merged worktrees" +echo "" +echo -e "${YELLOW}Short Aliases:${NC}" +echo " git wti - Same as wt-issue" +echo " git wtc - Same as wt-cleanup" +echo "" +echo -e "${YELLOW}Built-in Git:${NC}" +echo " git wt-list - List all worktrees" +echo " git wt-remove - Remove a worktree" +echo " git wt-prune - Clean up worktree info" +echo "" + +# Check if PATH includes script directory +if [[ ":$PATH:" != *":$SCRIPT_DIR:"* ]]; then + echo -e "${YELLOW}Note: For direct command access (without 'git' prefix), add to PATH:${NC}" + echo "" + echo " export PATH=\"\$PATH:$SCRIPT_DIR\"" + echo "" + echo "Add to your ~/.bashrc or ~/.zshrc for permanent access" + echo "" +fi + +# Setup git-smart-switch shell function for automatic cd +echo -e "${BLUE}Setting up git-smart-switch wrapper...${NC}" + +SHELL_RC="" +if [[ -n "$ZSH_VERSION" ]]; then + SHELL_RC="$HOME/.zshrc" +elif [[ -n "$BASH_VERSION" ]]; then + SHELL_RC="$HOME/.bashrc" +fi + +if [[ -n "$SHELL_RC" ]]; then + if ! grep -q "git-smart-switch.sh" "$SHELL_RC" 2>/dev/null; then + echo "" >> "$SHELL_RC" + echo "# Git smart-switch wrapper for automatic directory navigation" >> "$SHELL_RC" + echo "source $SCRIPT_DIR/git-smart-switch.sh" >> "$SHELL_RC" + echo -e "${GREEN}✓ Added git-smart-switch wrapper to $SHELL_RC${NC}" + echo -e "${YELLOW}Note: Run 'source $SHELL_RC' or restart your shell to activate${NC}" + else + echo -e "${GREEN}✓ git-smart-switch wrapper already configured${NC}" + fi +else + echo -e "${YELLOW}⚠️ Could not detect shell type. Manually add to your shell RC:${NC}" + echo " source $SCRIPT_DIR/git-smart-switch.sh" +fi +echo "" + +echo -e "${GREEN}✓ Setup complete!${NC}" +echo "" +echo -e "${YELLOW}Quick Start:${NC}" +echo " git wt-clone git@github.com:dotCMS/core.git # Clone new repo" +echo " git wt-migrate # Or migrate existing" +echo " git wti --issue 123 # Create worktree for issue" +echo " git smart-switch feature-branch # Switch safely" +echo "" \ No newline at end of file