A native GUI for managing git worktrees, built in Rust with GPUI (Zed's GPU-accelerated UI framework).
Website, tutorials, and examples →
Lists every worktree of a repository with its branch, status (dirty files, ahead/behind vs upstream), and offers create / remove / prune operations plus quick actions (open in terminal, show in file manager, copy path). Keyboard-first.
Prebuilt packages for every platform are attached to each GitHub Release (built by CI on native runners):
| Platform | Package |
|---|---|
| macOS (universal arm64 + x86_64) | …-macos-universal.dmg — open and drag Worktree Tool onto Applications (a plain .app zip is attached too) |
| Linux x86_64 / aarch64 | …-linux-<arch>.tar.gz — extract the binary; runtime deps are libxcb, libxkbcommon(+x11) and a Vulkan stack (standard on any desktop) |
| Windows x86_64 | …-windows-x86_64.zip — extract and run worktree-tool.exe |
| FreeBSD x86_64 | …-freebsd-x86_64.tar.gz — runtime needs libxcb/libxkbcommon from ports and LD_LIBRARY_PATH=/usr/local/lib |
The macOS build is ad-hoc signed, not notarized — on first launch Gatekeeper may ask you to confirm via right-click → Open. When launched from Finder the app starts at the path picker (there is no terminal cwd to detect); when run from a terminal it auto-detects the repository from the working directory.
To package locally: scripts/package-macos.sh 0.1.0 produces
dist/Worktree Tool.app and the zip.
| Platform | Status |
|---|---|
| macOS 26+ (Apple silicon / Intel) | built, tested, and manually verified — including on macOS 27 (Xcode 27 SDK) |
| Linux (X11 / Wayland) | compiles and unit-tested via CI; rendering uses Vulkan through blade-graphics, linking needs libxcb, libxkbcommon, libxkbcommon-x11, libstdc++ |
| Windows | compiles and unit-tested via CI (MSVC toolchain) |
| FreeBSD | compiles via CI (GPUI gates its X11/Wayland backend to linux + freebsd); least-tested platform |
CI (.github/workflows/ci.yml) runs clippy, tests, and a release build on
macOS, Linux, Windows, and FreeBSD on every push once a remote is added.
For local cross-checks without target hardware:
cargo zigbuild --target x86_64-unknown-linux-gnu --lib (also
x86_64-pc-windows-gnu, x86_64-unknown-freebsd) — every crate compiles
cross-platform; only the final Linux binary link needs native X11 libraries.
- Stable Rust toolchain
giton yourPATH- macOS 26 or newer: Xcode command line tools (
xcode-select --install) for the Metal backend - Linux: the X11/xkbcommon dev packages above plus a Vulkan-capable driver stack
Run from inside any git repository — the app detects the repo from the current working directory:
cd ~/git/myrepo
cargo run --releaseFor step-by-step tutorials (first worktree, terminal setup, cleanup) and example workflows (hotfix mid-feature, PR review checkouts, parallel test runs), see the website.
| Key | Action |
|---|---|
n / cmd-n |
New worktree |
R |
Manage remotes (list, add, remove) |
/ |
Focus search |
r / cmd-r |
Refresh |
up / down |
Move selection |
enter |
Open selected worktree's detail view |
t |
Open in terminal |
backspace / delete |
Remove selected worktree |
esc |
Clear search / close dialog / back to worktree list |
Detail view (per worktree):
| Key | Action |
|---|---|
1 / 2 / 3 |
Switch section: Working Copy ↔ History ↔ Branches |
up / down |
Move file selection (diff pane focused: move hunk cursor) |
tab |
Toggle file list ↔ diff pane |
s |
Stage / unstage selected file (diff pane focused: stage hovered hunk) |
S |
Stage all remaining changes |
d |
Discard selected file's changes (confirmation) |
c |
Commit staged changes in the in-app editor (cmd/ctrl+enter commits) |
C |
Commit via your $EDITOR (git's own resolution order) |
g |
Continue the paused merge / rebase / cherry-pick / revert |
K |
Skip the current step (paused rebase / cherry-pick) |
A |
Abort the paused operation, restoring the pre-operation state |
t |
Open worktree in terminal |
r |
Refresh working copy |
esc |
Back to worktree list |
History section (2 from the detail view):
| Key | Action |
|---|---|
up / down |
Move commit selection (files pane focused: move file selection) |
tab |
Toggle commit list ↔ commit files |
y |
Copy the selected commit's full hash to the clipboard |
x |
Check out the selected commit (detached; refused while the working copy has changes) |
p |
Cherry-pick the selected commit onto this branch |
v |
Revert the selected commit (creates a revert commit) |
R |
Interactive rebase from the selected commit to HEAD — pick/drop/fixup rows in the dialog |
w |
Open a new worktree at the selected commit |
L |
Load 500 more commits |
r / t / esc |
Refresh / terminal / back — same as the Working Copy section |
Branches section (3 from the detail view):
| Key | Action |
|---|---|
up / down |
Move the cursor in the active list (branches or stashes) |
tab / s |
Toggle between the branch list and the stash list |
enter / x |
Switch to the selected branch (refused on a dirty working copy) |
m |
Merge the selected branch into the current branch |
R |
Rebase the current branch onto the selected branch |
d |
Delete the selected branch (refuses the current branch) |
n |
Create a new branch at HEAD |
M |
Rename the selected branch |
y |
Copy the selected branch name to the clipboard |
z |
Stash the working copy's changes (tracked + untracked) |
p / a |
Pop (apply + drop) / apply the selected stash |
D |
Drop the selected stash |
f |
Fetch all remotes (with prune) |
u |
Push the current branch (sets upstream on first push) |
l |
Pull the current branch (fast-forward only) |
F |
Force-push the current branch with lease — press twice to confirm |
r / t / esc |
Refresh / terminal / back — same as the Working Copy section |
Remote-tracking branches (origin/…) are listed after the locals; switching
and deleting refuse them, and merging/rebasing from them works. A conflicted
merge or rebase pauses (see Working Copy above): resolve and press g
in the Working Copy section, or A to back out.
Press enter on a worktree to drill into it. Changes are grouped into
Conflicts, Staged, Unstaged, and Untracked; the diff pane
shows the selected file's unified diff. Stage or unstage with s (all at
once with S), and discard a file's unstaged changes or an untracked file
with d — always with a confirmation. Discarding the unstaged part never
touches staged changes.
With the diff pane focused, ↑/↓ move a cursor between the file's hunks
and s stages just the hovered hunk (git apply --cached — the worktree
file is never touched). Binary and untracked files stage whole-file only.
Press c to write a commit in the app: a multi-line editor opens
pre-filled with commented hints (which never reach the message — lines
starting with # are stripped, honoring core.commentChar). Enter is a
newline; cmd/ctrl+enter or the Commit button commits, esc cancels and
keeps your staged changes.
C instead opens your own editor exactly like git commit does,
resolving it in git's order — $GIT_EDITOR, core.editor, $VISUAL,
$EDITOR, then a platform default. Save a message to commit; empty the
message to abort. Conflicted files show their raw content — resolve them
in your own editor, then press s to mark them resolved.
A conflicted merge, rebase, cherry-pick, or revert pauses instead of
backing out: a banner names the operation (with rebase progress, e.g.
"rebasing 2/5"), and the paused state is real git state — other git
clients see it too. Resolve the files in your own editor, stage with s,
then g continues with the stored message (no editor round trip). K
skips the current step of a rebase or cherry-pick; A aborts and
restores the pre-operation state. While paused, mutating actions in the
other sections (branch switch, history checkout) refuse until you
continue or abort.
R (or the Remotes toolbar button) opens the repo's remote list:
name, fetch URL, and the push URL when it differs. a adds a remote
(name + URL — a name may not start with -, contain whitespace, or look
like a path), d removes the selected one after a confirmation (its
origin/* tracking refs go with it), and esc closes. Fetching and
pushing live in the Branches section of each worktree (f fetch, u
push, l pull).
The Settings button (toolbar) lists the terminals detected on this machine; clicking one persists the choice immediately. The config file lives at an XDG path on every platform:
$XDG_CONFIG_HOME/worktree-tool/settings.toml # e.g. ~/.config/worktree-tool/settings.toml# worktree-tool settings
# terminal: one of the ids below (or unset for auto-detect)
terminal = "iterm"Terminal resolution order: settings.toml → $TERMCMD env var (app name,
Zed convention) → first detected terminal.
Report a bug (Settings → Report a bug) opens a prefilled GitHub issue
in your browser. It includes only the app version and platform — nothing
else is collected, and you review it before submitting. CLI terminals
launch with the worktree as their working directory; Windows Terminal gets an
explicit -d flag because its profiles override the inherited directory.
Supported terminals (auto-detected; the settings dialog only lists installed ones):
| Platform | Terminals, in auto-detect preference order |
|---|---|
| macOS | Terminal, iTerm2, WezTerm, Ghostty, Alacritty, Kitty, Warp, Hyper |
| Linux / BSD | xdg-terminal-exec, GNOME Terminal, Konsole, Xfce Terminal, foot, Tilix, Kitty, Ghostty, Alacritty, WezTerm, xterm |
| Windows | Windows Terminal, PowerShell (7), Windows PowerShell, Command Prompt, Alacritty, WezTerm, Ghostty |
cargo build # first build takes a few minutes (shader compilation)
cargo test # unit + git integration tests
cargo clippy -- -D warningsBuilding for any target — native or cross (Windows .exe from macOS,
Linux via Docker, FreeBSD, any Rust triplet) — is wrapped by:
scripts/build-target.sh <target> # host | macos-universal | x86_64-pc-windows-gnu | …See docs/BUILDING.md for the full per-platform guide and the measured cross-compilation matrix.
Note: text inputs accept raw key events; IME/marked-text input is not supported in v1.
Apache-2.0 — see LICENSE.
