Skip to content

vadimvolk/git-worktree-wrapper

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

55 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

English | Русский

⚠️ Warning: GWW is not heavily tested, use at your own risk!

πŸš€ GWW - Git Worktree Wrapper

CI

A CLI tool that wraps git worktree functionality with configurable path templates, condition-based routing, and project-specific actions.

✨ Features

  • πŸ“ Configurable path templates: Dynamic path generation using templates with functions like path(n), branch(), norm_branch(), tag()
  • πŸ”„ Condition-based routing: Route repositories to different locations based on URI conditions (host, path, protocol, tags)
  • 🏷️ Tag support: Pass custom tags via --tag option for conditional routing and path organization
  • βš™οΈ Project actions: Execute custom actions (file copies, commands) after clone or worktree creation
  • 🐚 Shell completion: Bash, Zsh, and Fish completion support

πŸ“‹ Requirements

  • 🐍 Python 3.11+
  • πŸ”§ Git
  • πŸ–₯️ Unix-like system (Linux, macOS)

πŸ“¦ Installation

Install the CLI (recommended)

Using uv

uv tool install "git+https://github.com/vadimvolk/git-worktree-wrapper.git"
gww --help

Using pipx

pipx install "git+https://github.com/vadimvolk/git-worktree-wrapper.git"
gww --help

From source (development)

# Clone the repository
git clone git@github.com:vadimvolk/git-worktree-wrapper.git
cd git-worktree-wrapper

# Install with uv
uv sync

# Run gww
uv run gww --help

From source using pip

# From a local checkout
cd git-worktree-wrapper
python -m pip install .
gww --help

πŸš€ Quick Start

1. βš™οΈ Initialize Configuration

gww init config

This creates a default configuration file at ~/.config/gww/config.yml (Linux) or ~/Library/Application Support/gww/config.yml (macOS). Edit these 2 values: default_sources and default_worktrees. Check the tutorial section for routing details.

2. 🐚 Initialize Shell Integration

gww init shell zsh  # or bash, or fish

This installs shell completion and aliases (gwc, gwa, gwr) for easier workflow. Follow the instructions printed by the command to enable them in your shell.

3. πŸ“₯ Clone a Repository

gwc https://github.com/user/repo.git
# Prompts: "Navigate to ~/Developer/sources/github/user/repo? [Y/n]"
# Navigates if you confirm (default: yes)

4. βž• Add a Worktree

cd ~/Developer/sources/github/user/repo
gwa feature-branch
# Prompts: "Navigate to ~/Developer/worktrees/github/user/repo/feature-branch? [Y/n]"
# Navigates if you confirm (default: yes)

5. βž– Remove a Worktree

gwr feature-branch
# If worktree has uncommitted changes or untracked files:
#   Prompts: "Force removal? [y/N]"
#   Removes with --force if you confirm
# Otherwise: Removes worktree immediately
# Output: Removed worktree: ~/Developer/worktrees/github/user/repo/feature-branch

6. πŸ”„ Update Source Repository

gww pull
# Output: Updated source repository: ~/Developer/sources/github/user/repo

Note: gww pull updates the source repository even from a worktree, as long as the source is clean and has main or master checked out. Useful for merge/rebase workflows.

gww pull # from any repository worktree
git rebase main # rebase your current changes to updated main branch

7. 🚚 Migrate Repositories

Create a backup first!

gww migrate ~/old-repos --dry-run
# Output:
# Would migrate 5 repositories:
#   ~/old-repos/repo1 -> ~/Developer/sources/github/user/repo1
#   ...

gww migrate ~/old-repos
# Copy (default): list, copy sources then worktrees, repair, summary

gww migrate ~/old-repos --inplace
# Move worktrees then sources, repair, clean empty folders

The migrate command scans one or more directories for git repositories and migrates them to locations based on your current configuration. It's useful when:

  • You've updated your configuration and want to reorganize existing repositories
  • You're moving from manual repository management to GWW
  • You need to consolidate repositories from different locations

Options:

  • --dry-run, -n: Show what would be migrated without making changes
  • --copy (default): Copy repositories to new locations; list, validate, copy sources then worktrees, run git worktree repair, then report summary. No folder cleanup.
  • --inplace: Move repositories in place (worktrees first, then sources), run git worktree repair, then recursively clean empty source folders.

Behavior:

  • Accepts one or more paths; scans each and merges repo lists (deduplicated)
  • Classifies each repo as source or worktree; uses source path template for sources and worktree path template for worktrees
  • --inplace: Two passes (worktrees then sources), move and repair, then remove vacated dirs and empty parents up to input roots
  • --copy: List sources and worktrees, validate destinations, copy sources then worktrees, repair relations, report summary
  • Skips repositories without remotes, detached HEAD worktrees, or already at target

Tutorial

A minimal config file looks like:

# Folder where all sources are checked out with gwc. path(-2)/path(-1) generates 2-level subfolders based on repository URI. Like https://github.com/user/repo.git -> ~/Developer/sources/user/repo
default_sources: ~/Developer/other/sources/path(-2)/path(-1)
# Folder where all worktrees are checked out with gwa. norm_branch() works better with remote branches, e.g. origin/remote-branch -> origin-remote-branch
default_worktrees: ~/Developer/other/worktrees/path(-2)/path(-1)/norm_branch()

The generated file will have more options commented out, including the functions reference.

Checkout based on where repository is hosted

Useful to separate e.g. open source projects (where you learn or get inspired) from your work projects.

# Still needed in case the config fails to find a section. You may prefer a non-nested sources structure, but make sure the result folder is unique
default_sources: ~/Developer/sources/host()-path(-2)-path(-1)
default_worktrees: ~/Developer/worktrees/host()-path(-2)-path(-1)-norm_branch()
sources:
  # ... other rules
  work:
    when: "your.org.host" in host()
    sources: ~/Developer/work/sources/path(-2)-path(-1)
    worktrees: ~/Developer/work/sources/path(-2)-path(-1)-norm_branch()
  

That's enough to separate work sources from all others, but you can create more sections with various rules. The library uses simpleeval to evaluate templates, so you can use its operators and functions below to get necessary routing.

🌐 URI Functions (available in templates and when conditions)

Function Description Example
uri() Get full URI string uri() β†’ "https://loca-repo-manager.com:8081/user/repo.git"
host() Get URI hostname host() β†’ "loca-repo-manager.com"
port() Get URI port (empty string if not specified) port() β†’ "8081" or "" usually
protocol() Get URI protocol/scheme protocol() β†’ "https" / "ssh" / git
path(n) Get URI path segment by index (0-based, negative for reverse) path(-1) β†’ "repo", path(0) β†’ "user"

🌿 Branch Functions (available in templates)

Function Description Example
branch() Get current branch name branch() β†’ "feature/new/ui"
norm_branch(replacement) Branch name with / replaced (default: "-") norm_branch() β†’ "feature-new-ui", norm_branch("_") β†’ "feature_new_ui"

Need to checkout temporary projects separately? Add this to your config:

sources:
  # ... other rules
  temp:
    when: tag_exist("temp")  # See [tags section](#-tags) for details about tags
    sources: ~/Downloads/temp/sources/time_id()-host()-path(-2)-path(-1) 
    worktrees: ~/Downloads/temp/worktrees/time_id()-host()-path(-2)-path(-1)-norm-branch()

time_id(fmt) generates a datetime-based identifier (cached per template evaluation). Default format is "20260120-2134.03" (short, seconds accuracy unique). Use format codes for more detailed/nested results. Works properly if used multiple times.

worktrees: ~/Downloads/temp/worktrees/time_id("%Y")/time_id("%m")/time_id("%H-%M$.%S")/host()-path(-2)-path(-1)-norm-branch()

Generates nested structure: YYYY/HH-MM.ss/host()-path(-2)-path(-1)-norm-branch()

βš™οΈ Actions (available in actions section)

Run actions after checking out a repository or adding a worktree. Common example: copying local.properties for Gradle projects.

actions:
  - when: file_exists("settings.gradle") # Check if it's actually a Gradle project
    after_clone:
      - abs_copy: ["~/sources/default-local.properties", "local.properties"] # Copies your default file right after cloning the repo
    after_add: 
      - rel_copy: ["local.properties"] # Inherit existing repository file to worktree

You can have multiple when subsections in actions. After clone/add, the library goes top-to-bottom and executes all actions with matching when conditions. Other functions available in the actions section:

Action Description Example
abs_copy Copy file from absolute path to relative destination in target directory abs_copy: ["~/sources/default-local.properties", "local.properties"]
rel_copy Copy file from source repository to worktree (relative paths) rel_copy: ["local.properties"] or rel_copy: ["config.template", "config"]
command Execute external command (runs in destination directory, template functions available) command: "npm install" or command: "claude init"

πŸ“ Actions Functions (available in command actions and when conditions)

Function Description Example
source_path() Get absolute path to source repository or worktree root source_path() β†’ "/path/to/repo"
dest_path() Get absolute path to destination (clone target or worktree) dest_path() β†’ "/path/to/worktree"
file_exists(path) Check if file exists relative to source repository file_exists("local.properties") β†’ True
dir_exists(path) Check if directory exists relative to source repository dir_exists("config") β†’ True
path_exists(path) Check if path exists (file or directory) relative to source repository path_exists("local.properties") β†’ True

🏷️ Tags

Still not flexible enough? Here comes tags. Tags specified using command line param -t <tag-name>[=optional value] (or --tag) for clone / add commands. Tags available in configuration with:

Function Description Example
tag(name) Get tag value by name (returns empty string if not set) tag("env") β†’ "prod"
tag_exist(name) Check if tag exists (returns boolean) tag_exist("env") β†’ True

🏷️ Tag Usage Example:

sources:
  # Temporary checkout: Clone repositories to ~/Downloads/temp for quick access
  # Usage: gwc <uri> -t temp
  temp:
    when: 'tag_exist("temp")'
    sources: ~/Downloads/temp/time_id()-host()-path(-1)
    worktrees: ~/Downloads/temp/time_id()-host()-path(-1)/norm_branch()

  # Code review worktrees: Add worktrees to ~/Developer/worktree/code-review for review tasks
  # Usage: gwa <branch> --tag review
  review:
    when: 'tag_exist("review")'
    worktrees: ~/Developer/review/worktree/path(-1)/norm_branch()
    # If used during clone, default source path is used

```bash
# Clone to temporary location
gwc https://github.com/user/repo.git -t temp
# Output: ~/Downloads/temp/repo

# Add worktree for code review
cd ~/Developer/sources/github/user/repo
gwa feature-branch --tag review
# Output: ~/Developer/worktree/code-review/repo/feature-branch

πŸ“– Commands

Command Description
gwc <uri> [--tag key=value]... πŸ“₯ Clone repository to configured location (tags available in templates/conditions)
gwa <branch> [-c] [--tag key=value]... βž• Add worktree for branch (optionally create branch, tags available in templates/conditions)
gwr <branch|path> [-f] βž– Remove worktree
gww pull πŸ”„ Update source repository (works from worktrees if source is clean and on main/master)
gww migrate <path>... [--dry-run] [--copy | --inplace] 🚚 Migrate repositories to new locations
gww init config βš™οΈ Create default configuration file
gww init shell <shell> 🐚 Install shell completion (bash/zsh/fish)

Note: gwc, gwa, and gwr are convenient shell aliases for gww clone, gww add, and gww remove respectively. They provide the same functionality with automatic navigation prompts. Install them with gww init shell <shell>.

Common Options:

  • --tag, -t: Tag in the format key=value or just key (can be specified multiple times).

πŸ”„ Update

Using uv

# Re-run the install command to update to the latest version
uv tool install "git+https://github.com/vadimvolk/git-worktree-wrapper.git"

# Or use the update command (if available)
uv tool update gww

Using pipx

pipx upgrade gww

Using pip

python -m pip install --upgrade gww

πŸ—‘οΈ Uninstall

Using uv

uv tool uninstall gww

Using pipx

pipx uninstall gww

Using pip

python -m pip uninstall gww

πŸ› οΈ Development

πŸ§ͺ Running Tests

# Run all tests
uv run pytest

# Run with coverage
uv run pytest --cov

# Run only unit tests
uv run pytest tests/unit/

# Run only integration tests
uv run pytest tests/integration/

πŸ” Type Checking

uv run mypy src/gww

πŸ“„ License

MIT

About

CLI wrapper for git worktree that clones repos into configurable template paths, routes by URI predicates and tags, and runs project-specific actions after clone/worktree creation. Includes bash/zsh/fish shell completion.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors