# yawn — Yet Another Worktree Navigator
[](https://github.com/ComeBertrand/yawn/actions/workflows/ci.yml)
[](https://crates.io/crates/git-yawn)
[](LICENSE)
A fast project switcher and worktree manager for git.
## Install
### From source
```bash
cargo install git-yawn
```
### Nix flake
```nix
inputs.yawn.url = "github:ComeBertrand/yawn";
# then in your packages:
inputs.yawn.packages.${system}.default
```
### GitHub Releases
Download a binary from the [releases page](https://github.com/ComeBertrand/yawn/releases), or use the shell installer:
```bash
## 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.
```bash
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:
```bash
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:
```bash
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:
```bash
# 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:
```bash
# 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`).
```bash
# 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:
```toml
[init]
include = [".env", ".env.local", "config/*.toml"]
commands = ["npm install", "cargo build"]
```
- **`include`** — files, directories, and glob patterns to copy from the main repo into worktrees. Directories are copied recursively. 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.
```bash
# 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)
| `init.include` | list of strings | Files, directories, or glob patterns to copy from the main repo into worktrees. Directories are copied recursively. |
| `init.commands` | list of strings | Shell commands to run sequentially during init. |
```toml
[init]
include = [".env", ".env.local", "config/*.toml"]
commands = ["npm install"]
```
### `~/.config/yawn/config.toml` (global)
All fields are optional.
### `[discovery]`
| `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]`
| `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]`
| `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
```toml
[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
```bash
cp completions/yawn.bash ~/.local/share/bash-completion/completions/yawn
```
### Zsh
```bash
cp completions/yawn.zsh ~/.local/share/zsh/site-functions/_yawn
```
Or place it anywhere in your `$fpath`.
### Fish
```bash
cp completions/yawn.fish ~/.config/fish/completions/yawn.fish
```
## Man Page
A man page is generated at build time. After building from source:
```bash
man target/*/build/git-yawn-*/out/man/yawn.1
```
## License
MIT