Skip to content

PNRxA/aw

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

22 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

aw - Git Worktree Manager

Git worktrees let you check out multiple branches of the same repo simultaneously, each in its own directory, without cloning the repo multiple times. They're perfect for running AI coding agents in parallel — each agent gets its own worktree so they can work on separate branches without stepping on each other, while sharing the same git history and object store.

The built-in git worktree commands are verbose and require managing full paths. aw wraps them with short, ergonomic commands: aw feat-login creates a worktree and cd's into it immediately, aw with no args takes you back to the main repo, and aw rm lets you interactively clean up when you're done.

Dependencies

  • Git
  • One of: bash, zsh, fish, or PowerShell

Install

Download a pre-built binary from the latest release, extract it, and place it somewhere on your PATH. Then add the shell wrapper from the Setup section.

Linux / macOS:

# Download and extract (replace TARGET with your platform)
# Targets: x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu,
#          x86_64-apple-darwin, aarch64-apple-darwin
curl -L https://github.com/PNRxA/aw/releases/latest/download/aw-TARGET.tar.gz | tar xz
sudo mv aw /usr/local/bin/

Windows:

Download aw-x86_64-pc-windows-msvc.zip from the latest release, extract aw.exe, and move it to a directory on your PATH.

Alternatively, you can build from source.

Setup

Add the shell wrapper to your shell config so aw can cd into worktrees:

bash (add to ~/.bashrc):

eval "$(aw init bash)"

zsh (add to ~/.zshrc):

eval "$(aw init zsh)"

fish (add to ~/.config/fish/config.fish):

aw init fish | source

PowerShell (add to $PROFILE):

aw init powershell | Invoke-Expression

Without the shell wrapper, commands that change directories will print the path instead.

How the shell wrapper works

A subprocess can't change its parent shell's working directory. To work around this, aw init bash prints a small shell function named aw that wraps the binary. eval "$(…)" evaluates that output, defining the function in your current shell.

When you run aw add feat-login, the wrapper calls the real aw binary and reads its stdout line by line. The wrapper intercepts lines with special prefixes and acts on them in your shell:

Prefix Action
__AW_CD__:/some/path Runs cd /some/path in your shell
__AW_EXEC_ARG__:arg Collects an argument for a pending command
__AW_EXEC_RUN__ Executes the collected arguments as a command

All other lines are printed normally.

The __AW_CD__ protocol is the only way a CLI tool can change your shell's directory. The __AW_EXEC__ protocol works similarly — when you run aw feat-login claude, the binary emits __AW_EXEC_ARG__:claude followed by __AW_EXEC_RUN__, and the wrapper executes claude in the new worktree directory. This ensures the command runs as a direct child of your shell rather than as a subprocess of aw, which matters for interactive programs that need proper terminal access.

Commands

Command Description
aw Return to the main worktree (or show help)
aw <name> [CMD...] Create worktree and cd into it, optionally run CMD
aw add <name> [--base REF] [CMD...] Create worktree with optional base ref, optionally run CMD
aw list / aw ls List all worktrees with indices
aw go [name|#] Switch to a worktree by name, index, or interactively
aw rm [name|#] [-f] Remove a worktree by name, index, or interactively
aw merge [name|#] [--into name|#] Merge a worktree's branch into the main worktree (or --into target)
aw path [name|#] Print a worktree's absolute path by name, index, or interactively
aw prune Clean up stale worktree entries
aw init <shell> Print shell wrapper (bash, zsh, fish, powershell)
aw version / aw -v Print version
aw help Show help

Usage

Return to the main worktree

aw

Running aw with no arguments from inside a non-main worktree cd's you back to the main worktree. From the main worktree it shows help.

Runs git worktree list --porcelain to find the main worktree path.

Create a worktree

aw feat-login          # shorthand for 'aw add feat-login'
aw add feat-login
aw add feat-login --base develop

Running aw feat-login inside /code/myproject creates a new worktree at /code/myproject-feat-login with a branch named feat-login, then cd's into it.

Slashes in names become dashes in directory names (feature/foo becomes myproject-feature-foo).

Runs git worktree add -b <branch> <path> (or git worktree add -b <branch> <path> <base> with --base).

Run a command after creating a worktree

aw feat-login claude                  # create worktree and launch claude
aw feat-login npm install             # create worktree and install deps
aw add feat-login --base develop npm install

Anything after the worktree name (and optional --base REF) is run as a command in the new worktree directory. The exit code reflects the command's result. Use -- to separate flags from the command if needed: aw feat-login -- --some-tool.

List worktrees

aw list    # or 'aw ls'

Runs git worktree list --porcelain.

Output includes an index and marks the current worktree with *:

* 0  myproject [main]
  1  myproject-feat-login [feat-login]
  2  myproject-bugfix-42 [bugfix-42]

Switch to a worktree

aw go feat-login       # switch by name
aw go 1                # switch by index (from 'aw list')
aw go                  # interactive: shows worktrees, prompts for index

Runs git worktree list --porcelain to resolve the target, then cd's into it.

Remove a worktree

aw rm feat-login       # remove by name
aw rm 1                # remove by index (from 'aw list')
aw rm                  # interactive: shows worktrees, prompts for index
aw rm feat-login -f    # remove even with uncommitted changes

Runs git status --porcelain to check for uncommitted changes (unless --force), then git worktree remove <path> (or git worktree remove --force <path>).

Merge a worktree branch

aw merge feat-login             # merge feat-login into main worktree
aw merge 1                      # merge worktree at index 1 into main worktree
aw merge feat-login --into dev  # merge feat-login into the 'dev' worktree
aw merge                        # interactive: prompts for source worktree

Merges the source worktree's branch into the target worktree (default: main worktree at index 0). On success, prompts to remove the source worktree and delete its branch. In interactive mode, if you're currently inside a worktree it is pre-selected as the default — just press Enter to confirm.

Runs git status --porcelain on both worktrees to check for uncommitted changes, then git merge <branch> --no-edit from the target worktree. On cleanup: git worktree remove <path> and git branch -d <branch>.

Safety checks

aw merge runs several checks before performing the merge to prevent data loss:

  1. Self-merge — refuses to merge a worktree into itself.
  2. Detached HEAD / bare — refuses if either the source or target worktree has no branch (detached HEAD or bare repository).
  3. Dirty target — refuses if the target worktree has uncommitted changes. You must commit or stash them first. There is no override for this.
  4. Dirty source — warns if the source worktree has uncommitted changes (these won't be included in the merge) and asks for confirmation before continuing.
  5. Merge conflicts — if the merge produces conflicts, aw cd's you into the target worktree and prints instructions for manual resolution (git add + git commit).

Post-merge cleanup uses git branch -d (safe delete), which refuses to delete a branch that isn't fully merged, so no work can be silently lost.

Print a worktree path

aw path feat-login   # print path by name
aw path 1            # print path by index (from 'aw list')
aw path              # interactive: shows worktrees, prompts for index

Runs git worktree list --porcelain to resolve the target and prints its path.

Useful for scripting, e.g. cp file $(aw path feat-login)/.

Clean up stale entries

aw prune

Runs git worktree prune -v.

Help

aw help

Uninstall

  1. Remove the eval "$(aw init …)" / aw init fish | source / aw init powershell | Invoke-Expression line from your shell config (~/.bashrc, ~/.zshrc, ~/.config/fish/config.fish, or $PROFILE).
  2. Remove the binary (wherever you placed it during install, e.g. /usr/local/bin/aw):
    sudo rm /usr/local/bin/aw
  3. Any worktrees you created with aw are regular git worktrees. Clean them up with git worktree remove <path> or just delete the directories and run git worktree prune from the main repo.

Path convention

Worktrees are created as siblings of the repo root:

/code/
  myproject/           # main worktree
  myproject-feat-login/
  myproject-bugfix-42/

On Windows the layout is the same, just with Windows paths (e.g. C:\code\myproject\).

Development

Building from source

Requires Cargo (Rust toolchain).

cargo install --path .

Cargo installs binaries to ~/.cargo/bin (or %USERPROFILE%\.cargo\bin on Windows). Make sure this directory is on your PATH.

To uninstall:

cargo uninstall aw

Publishing a release

Releases are automated with GitHub Actions. To publish a new release:

  1. Update the version in Cargo.toml
  2. Commit the version bump:
    git add Cargo.toml
    git commit -m "v0.2.0"
  3. Tag the commit and push:
    git tag v0.2.0
    git push origin main v0.2.0

This triggers the release workflow which builds binaries for all supported platforms (Linux x86_64/aarch64, macOS x86_64/aarch64, Windows x86_64), generates SHA256 checksums, and creates a GitHub Release with the artifacts attached.

About

Simplifying git worktree management

Resources

Stars

Watchers

Forks

Packages

No packages published

Languages