Skip to content

QiTianDaSh3ng/hop

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🐇 hop - Git Worktree Manager

An interactive TUI for managing git worktrees. Hop between branches instantly without stashing or committing.

Terminology

Term What it is
Trunk Your original repo directory. Can checkout any branch normally.
Leaf 🍃 A worktree directory. Locked to one specific branch.
myproject/              ← trunk (original repo, currently on 'main')
myproject--feature/     ← 🍃 leaf (branch 'feature', forked from main)
myproject--bugfix/      ← 🍃 leaf (branch 'bugfix', forked from develop)

The Problem

You're working on a feature branch. A bug report comes in. You need to:

  1. Stash your changes
  2. Switch branches
  3. Fix the bug
  4. Switch back
  5. Pop your stash
  6. Remember what you were doing...

The Solution

With hop, each branch lives in its own directory (a leaf). Switch instantly:

hop                    # Open selector, pick a leaf
hop bugfix             # Jump to bugfix leaf

Your feature work is exactly where you left it—no stashing, no context switching.

Quick Start

# Download hop
curl -sL https://raw.githubusercontent.com/QiTianDaSh3ng/hop/main/hop.rb -o ~/.local/bin/hop
chmod +x ~/.local/bin/hop

# Set up your shell (interactive)
hop setup

That's it! Now run hop from any git repository.

Creating Leaves

Basic: hop add <leaf>

hop add feature

What happens:

  1. Creates a new branch called feature
  2. The branch forks from your current HEAD (whatever commit you're on)
  3. Creates a leaf directory myproject--feature/
  4. cds into the new leaf
# Before (trunk on 'main' @ abc123):
myproject/              ← you are here

# After:
myproject/              ← trunk (still on main @ abc123)
myproject--feature/     ← 🍃 you are here (leaf 'feature', forked from abc123)

With --stay: Create but don't cd

hop add lm-fix --stay

Creates the leaf but keeps you in your current directory. Perfect for spawning sandboxes:

🍃 Leaf created:
  Leaf:   lm-fix
  Path:   /home/you/myproject--lm-fix
  Branch: ← main @ abc123
  Trunk:  /home/you/myproject

With --cutting: Include ALL current files 🌱

hop add experiment --cutting

A "cutting" in gardening is snipping off a piece of a plant including its current growth. The --cutting flag copies all files from wherever you currently are:

What gets copied Without --cutting With --cutting
Committed files
Modified files (not staged)
Staged files
Untracked files
Works with no commits (orphan) ❌ Empty leaf ✅ Full copy

Key behavior: --cutting copies from your current directory, whether that's trunk or another leaf:

# In trunk
hop add first --cutting     # Copies from trunk → "cutting from trunk"

# In 'first' leaf, make more changes...
hop add second --cutting    # Copies from first → "cutting from first"

This enables iterative workflows where each leaf can spawn new leaves with its latest state.

Use case: You're mid-work and want a language model to continue from your exact state:

hop add lm-fix --stay --cutting
# Language model gets your exact working directory to build on

Commands

Command Description
hop Interactive leaf selector
hop [query] Selector with initial filter
hop add <leaf> Create leaf, cd into it
hop add <leaf> --stay Create leaf, stay in current dir
hop add <leaf> --cutting Create leaf with all current files
hop rm Remove current leaf (from inside)
hop rm <leaf> Remove named leaf (from anywhere)
hop list List all leaves
hop boom Remove ALL leaves 💥 (with confirmation)
hop boom --YES Remove ALL leaves (skip confirmation)
hop setup Interactive first-time shell setup
hop init Print shell wrapper (for manual setup)
hop --help Show help

Scripting Flags

Flag Works with Description
--json add --stay, rm, list, boom Machine-readable JSON output
--quiet, -q all commands Suppress emojis and decorative text

Headless / Scripting Mode

For language models and automation, hop provides 100% headless operation with machine-readable output.

Scripting Flags

Flag Description
--json Output machine-readable JSON
--quiet, -q Suppress decorative output (emojis, colors)

Headless Commands

All these work without any TTY or user interaction:

# Create a leaf (returns path info)
ruby hop.rb add task-123 --stay --cutting --json
# Output: {"leaf":"task-123","path":"/path/to/repo--task-123",...}

# List all leaves as JSON
ruby hop.rb list --json
# Output: {"trunk":{...},"leaves":[...]}

# Remove a leaf
ruby hop.rb rm task-123 --json
# Output: {"removed":"task-123","path":"/path/to/repo--task-123"}

# Remove all leaves (no confirmation needed)
ruby hop.rb boom --YES --json
# Output: {"removed":[...]}

Example: Language Model Integration

# Create sandbox with current files
output=$(ruby hop.rb add lm-fix --stay --cutting --json)
leaf_path=$(echo "$output" | jq -r '.path')

# Work in the sandbox
cd "$leaf_path"
# ... make changes ...

# Clean up
ruby hop.rb rm lm-fix --quiet

Interactive Workflow

For human users with the shell wrapper installed:

# Create a sandbox for the language model with your current state
hop add lm-fix --stay --cutting
# Output:
#   🍃 Leaf created:
#     Leaf:   lm-fix
#     Path:   /home/you/myproject--lm-fix
#     Branch: ← main @ abc123
#     Trunk:  /home/you/myproject
#     Files:  🌱 main @ abc123

# Language model works in the leaf...
# You keep working in trunk...

# When done:
hop rm lm-fix       # Remove from anywhere
# or
hop boom --YES      # Nuke all leaves

TUI Display

The interactive selector shows a color-coded table:

🐇 Hop  /home/you/myproject  •  3 leaves
──────────────────────────────────────────────────────────────────────────
Search: █
──────────────────────────────────────────────────────────────────────────
  NAME              TIME                      FROM
──────────────────────────────────────────────────────────────────────────
→ 🏠 main            Tue Dec 31 10:15 [2m]     —
  🌿 feature         Tue Dec 31 10:10 [7m]     main abc123  fresh
  🌿 lm-fix          Tue Dec 31 10:14 [3m]     main abc123  cutting
  🌿 attempt2        Tue Dec 31 10:16 [1m]     main abc123  cutting [lm-fix] def456
──────────────────────────────────────────────────────────────────────────
fresh = clean branch   cutting = includes uncommitted   [leaf] = from leaf
                    ↑↓ Navigate   Enter Select   Ctrl-D Delete   Esc Cancel

Legend

Element Meaning
🏠 Trunk (main repository)
🌿 Leaf (worktree)
No origin info (trunk)
branch commit Where leaf was forked from
fresh Clean fork from HEAD
cutting Includes uncommitted changes
[leaf-name] Cutting was taken from another leaf

How Forking Works

When you run hop add feature:

You're on HEAD points to New leaf feature starts at
main commit abc123 abc123
develop commit def456 def456
detached HEAD commit 789xyz 789xyz
no commits (orphan) empty (use --cutting to copy files)

The new branch always forks from wherever HEAD is pointing. The trunk's branch doesn't change.

Keyboard Shortcuts

Key Action
/ Ctrl-P Move up
/ Ctrl-N Move down
Enter Select / Create
Ctrl-D Mark for deletion
Esc Cancel / Exit delete mode
Ctrl-A Beginning of line
Ctrl-E End of line
Ctrl-W Delete word backward
Ctrl-K Delete to end of line

Naming Convention

Leaves are created as sibling directories:

~/code/myproject/              ← trunk
~/code/myproject--feature/     ← 🍃 leaf (branch: feature)
~/code/myproject--bugfix/      ← 🍃 leaf (branch: bugfix)
~/code/myproject--experiment/  ← 🍃 leaf (branch: experiment)

Data Storage

Hop stores leaf metadata in ~/.local/share/hop/:

~/.local/share/hop/
  a1b2c3d4/              ← repo ID (hash of path)
    feature.meta         ← metadata for 'feature' leaf
    lm-fix.meta          ← metadata for 'lm-fix' leaf

Each .meta file contains:

  • parent_branch - Branch the leaf was forked from
  • parent_commit - Commit hash at fork time
  • mode - fresh or cutting
  • copy_source - If cutting: trunk or leaf name
  • copy_source_commit - If cutting from leaf: that leaf's commit
  • created - Timestamp
  • repo_path - Full path to trunk

This keeps your repo and leaves clean—no hidden files added.

Installation

Option 1: Quick Setup (Recommended)

curl -sL https://raw.githubusercontent.com/QiTianDaSh3ng/hop/main/hop.rb -o ~/.local/bin/hop
chmod +x ~/.local/bin/hop
~/.local/bin/hop setup

Option 2: Make Install

git clone https://github.com/QiTianDaSh3ng/hop.git
cd hop
make install    # Installs to ~/.local/bin/
hop setup

Option 3: Manual Setup (using hop init)

Use hop init when you need manual control:

  • Custom shell config location
  • Non-standard shell setup
  • Scripted/automated installation
  • Inspecting or customizing the wrapper

hop init prints the shell wrapper function to stdout. Add to your shell config:

Bash/Zsh:

eval "$(hop init)"

Fish:

eval (hop init | string collect)

Option 4: Nix / Home Manager

{
  inputs.hop.url = "github:QiTianDaSh3ng/hop";
  imports = [ inputs.hop.homeManagerModules.default ];
  programs.hop.enable = true;
}

Development

make test       # Run test suite (70 tests)
make lint       # Check Ruby syntax
make help       # Show all available targets

Requirements

  • Ruby (standard library only, no gems)
  • Git

macOS comes with Ruby pre-installed. Most Linux distributions have it available.

How It Works

  1. hop setup adds a shell wrapper function
  2. When you select a leaf, hop outputs a shell script (cd /path/to/leaf)
  3. The wrapper evals this script, changing your directory

This is the same pattern used by z, zoxide, fzf, and other directory-jumping tools.

Compared to Plain Git Worktrees

Task Git hop
List worktrees git worktree list hop (interactive!)
Create worktree git worktree add ../path -b branch hop add leaf
Switch to worktree cd ../project--branch hop → select
Remove worktree git worktree remove path && git branch -D branch hop rm

Compared to try

hop is inspired by try, but focused on git worktrees:

Feature try hop
Purpose Ephemeral experiment directories Git worktree management
Naming YYYY-MM-DD-name {trunk}--{leaf}
Location Central directory (~/src/tries) Sibling to repo
Git integration Optional detached worktrees Core feature (named branches)

License

Do Whatever The Fuck You Want


Hop between leaves like a rabbit. 🐇🍃

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published