git-yawn 0.9.0

Yet Another Worktree Navigator
git-yawn-0.9.0 is not a library.

yawn — Yet Another Worktree Navigator

CI crates.io License: MIT

A fast project switcher and worktree manager for git.

Install

From source

cargo install git-yawn

Nix flake

inputs.yawn.url = "github:ComeBertrand/yawn";

# then in your packages:
inputs.yawn.packages.${system}.default

GitHub Releases

Download a binary from the releases page, or use the shell installer:

curl --proto '=https' --tlsv1.2 -LsSf https://github.com/ComeBertrand/yawn/releases/latest/download/yawn-installer.sh | sh

Usage

yawn list [path] [--json] [--raw] [--porcelain]  Discover git projects
yawn resolve <pretty-name> [-P <path>]  Map a pretty name back to an absolute path
yawn pick [-F <finder>] [path]  Interactively pick a project and open it
yawn open <path> [-c <command>] Open a terminal in the given directory
yawn create <name> [--source <base>] [--open] [--init]  Create a git worktree
yawn delete <name>              Remove a worktree
yawn init                       Initialize the current directory

Listing projects

Recursively discovers git projects under a directory. Takes an optional path, defaults to the current directory.

yawn list ~/projects           # pretty output (tree in terminal, flat when piped)
yawn list                      # discover projects under cwd
yawn list --porcelain          # force flat pretty names (stable for scripts)
yawn list --raw                # absolute paths, one per line
yawn list --json               # structured JSON output

By default, the output is human-friendly. In a terminal, projects are shown as a colored tree with worktrees grouped under their parent:

my-app
├─ fix-branch
└─ feature-x
dotfiles
notes (personal)
notes (work)

When piped (or with --porcelain), it falls back to flat pretty names for compatibility with tools like fzf:

my-app
fix-branch @my-app
feature-x @my-app
dotfiles
notes (personal)
notes (work)

The --raw flag outputs absolute paths, one per line:

/home/user/projects/my-app
/home/user/worktrees/my-app--fix-branch
/home/user/worktrees/my-app--feature-x
/home/user/projects/dotfiles

The --json flag outputs an array of objects with path, name, is_worktree, and worktree_of fields:

yawn list ~/projects --json
# [
#   { "path": "/home/user/projects/myapp", "name": "myapp", "is_worktree": false, "worktree_of": null },
#   { "path": "/home/user/projects/myapp--feature", "name": "feature @myapp", "is_worktree": true, "worktree_of": "myapp" }
# ]

If run inside a git repo (without a path), it lists the repo itself and its worktrees:

cd ~/projects/my-app
yawn list
/home/user/projects/my-app
/home/user/worktrees/my-app--fix-branch
/home/user/worktrees/my-app--feature-x

Interactive project switcher

Use yawn pick with any fuzzy finder:

# fzf
yawn pick -F fzf ~/projects

# rofi
yawn pick -F "rofi -dmenu -p project -i" ~

# from current directory
yawn pick -F fzf

This discovers projects, pipes pretty names into the finder, resolves the selection, and opens a terminal — all in one command. Easy to bind in i3/sway/hyprland.

The equivalent manual pipeline still works:

yawn open "$(yawn resolve -P ~ "$(yawn list ~ --pretty | fzf)")"

# override the configured open command for a single invocation
yawn open /path/to/project -c "code {dir}"

Worktrees

Worktrees are created under a configurable root directory (default: ~/worktrees) using the naming convention <project>--<name>. For example, running yawn create feature-x from inside a repo called my-app creates:

~/worktrees/my-app--feature-x

When listing with --pretty, the <project>-- prefix is stripped and the worktree is annotated:

feature-x @my-app

Branch resolution when creating a worktree follows this order:

  1. If <name> exists as a local branch, check it out.
  2. If <name> exists as origin/<name>, track it.
  3. If --source <base> is provided, create a new branch from <base>.
  4. Otherwise, create a new branch from the default branch (origin/HEAD, falling back to main then master).
# Create a worktree (new branch from default branch)
yawn create feature-x

# Create from a specific base branch
yawn create feature-x --source develop

# Create and immediately open a terminal in it
yawn create feature-x --open

# Create and run init (copy files + setup commands)
yawn create feature-x --init

# All flags combine
yawn create feature-x --source develop --init --open

# Delete a worktree
yawn delete feature-x

Initializing a project

yawn init sets up the current directory by copying files from the main repo and running setup commands. Configuration lives in .yawn.toml at the repo root:

[init]
include = [".env", ".env.local", "config/*.toml"]
commands = ["npm install", "cargo build"]
  • include — files and glob patterns to copy from the main repo into worktrees. Useful for local config files (.env, etc.) that aren't tracked by git.
  • commands — shell commands to run sequentially in the target directory. Stops on first failure.

When run in a worktree, yawn init copies include files from the main repo and then runs commands. When run in the main repo itself (e.g. after a fresh clone), it skips the copy step and only runs commands.

# Initialize the current directory
yawn init

# Create a worktree and initialize it in one step
yawn create feature-x --init

Configuration

There are two config files:

  • ~/.config/yawn/config.toml — global user config (discovery, session, worktree settings)
  • .yawn.toml — per-project config, lives in the repo root and should be committed to git (init settings)

.yawn.toml (per-project)

Key Type Description
init.include list of strings Files/glob patterns to copy from the main repo into worktrees.
init.commands list of strings Shell commands to run sequentially during init.
[init]
include = [".env", ".env.local", "config/*.toml"]
commands = ["npm install"]

~/.config/yawn/config.toml (global)

All fields are optional.

[discovery]

Key Type Default Description
max_depth integer 5 Maximum recursion depth when searching for git projects.
ignore list of strings [".*", "node_modules"] Glob patterns matched against directory names. Matching directories are skipped during discovery. Hidden directories (except .git itself) are ignored by default.

[session]

Key Type Default Description
opener string unset Command template to open a terminal session. Placeholders: {dir} (absolute path), {name} (directory basename). When unset, uses $TERMINAL, or falls back to Terminal.app on macOS and xterm on Linux.
finder string unset Default finder command for yawn pick (e.g. fzf, rofi -dmenu -p project -i). Can be overridden with -F.

[worktree]

Key Type Default Description
root string ~/worktrees Directory where worktrees are created. Supports ~ expansion.
auto_init boolean false Automatically run init after creating a worktree (equivalent to always passing --init).

Example

[discovery]
max_depth = 3
ignore = [".*", "node_modules", "target", "vendor"]

[session]
opener = "kitty --directory {dir} --title 'dev: {name}'"
finder = "fzf"

[worktree]
root = "~/worktrees"

Shell Completion

Bash

cp completions/yawn.bash ~/.local/share/bash-completion/completions/yawn

Zsh

cp completions/yawn.zsh ~/.local/share/zsh/site-functions/_yawn

Or place it anywhere in your $fpath.

Fish

cp completions/yawn.fish ~/.config/fish/completions/yawn.fish

Man Page

A man page is generated at build time. After building from source:

man target/*/build/git-yawn-*/out/man/yawn.1

License

MIT