The finder is built in
No ghq list | fzf | cd pipeline to assemble. The best match is the row at the bottom, next to the prompt, so the usual choice costs zero keystrokes.

ghq-style repository manager · built-in fuzzy finder · written in Rust
gm clones into one predictable host/user/repo tree, and Ctrl+G takes you to any of them — ranked by how well they match and how often you go there.
brew install jedipunkz/gm/gmLive demo with sample data — type to filter, ↑↓ to move, Enter to jump,/help for commands, and click ctrl-w for worktrees. The ranking is the same algorithm gm uses.
Why gm
An existing ghq tree works untouched. gm adds the parts you would otherwise glue together yourself.
No ghq list | fzf | cd pipeline to assemble. The best match is the row at the bottom, next to the prompt, so the usual choice costs zero keystrokes.
A fuzzy score first — a literal substring in the repository name beats letters scattered across the path — then frecency to break ties.
Path, remote, branch, working-tree status, visit count and the last three commits with their refs, so similar clones are easy to tell apart.
Ctrl-W swaps the list for the git worktrees of the repository under the cursor, Ctrl-L for its branches and Ctrl-J for its open pull requests: Enter checks one out as a worktree and goes there.
Roots, theme and key bindings live in gm.toml. $GHQ_ROOT and ghq.root are still honored, so an existing ghq tree needs no migration.
A new repository gets git init and its origin remote in one step — something ghq leaves to you.
Not implemented, deliberately: Mercurial / Subversion / Darcs cloning (they are still listed), bare clones, partial clones, parallel import, --vcs, and per-URL ghq.<url>.root roots.
Get started
Homebrew on macOS or Linux downloads a prebuilt binary; no Rust needed.
brew install jedipunkz/gm/gmOr build from source with Rust 1.95 or newer:
cargo install --locked --git https://github.com/jedipunkz/gmgm prints the chosen path, so it composes with $(...). One line in your rc file turns that into a cd on Ctrl+G.
gm shell fish | sourceeval "$(gm shell zsh)"eval "$(gm shell bash)"Clones land under the root; press Ctrl+G anywhere to jump.
gm get jedipunkz/gm
# then press Ctrl-G and type gmRequirements: git on $PATH, and a true-color terminal for the themes to look as intended.gm version says which version you have.
Usage
Ctrl+W swaps the repository list for the git worktrees of the repository under the cursor, andCtrl+L for its branches; the same key swaps it back. Going back keeps the query, the cursor and the highlights as they were. The branch list holds the local branches and the remote ones with no local namesake; branches a remote has that the last fetch did not bring join the top once git ls-remoteanswers in the background, with git's own credentials and no gh. Enter on a branch goes to its worktree, making one first when there is none — a remote branch becomes a local one that tracks it, fetched first if it has not been.Ctrl+J lists the open pull requests, drafts marked, through theGitHub CLI; Enter has gh pr checkout --worktree make the worktree. While gm waits on git or GitHub, the line under the prompt shows a spinner, what it is waiting on and the seconds so far.
| Key | Repository list | Worktree list | Branch list | Pull request list |
|---|---|---|---|---|
| any character | Filter | Filter | Filter | Filter |
| ↑ / Ctrl-P | Move up | Move up | Move up | Move up |
| ↓ / Ctrl-N | Move down | Move down | Move down | Move down |
| Enter | Print the repository path and exit | Print the worktree path and exit | Check the branch out as a worktree, print its path and exit | Check the pull request out as a worktree, print its path and exit |
| Ctrl-W | Show the worktrees of the selected repository | Back to the repositories | Show the worktrees | Show the worktrees |
| Ctrl-L | Show the branches of the selected repository | Show the branches | Back to the repositories | Show the branches |
| Ctrl-J | Show the open pull requests of the selected repository | Show the pull requests | Show the pull requests | Back to the repositories |
| Ctrl-Alt-B | Open the remote in a browser | Open the remote in a browser | Open the remote in a browser | Open the remote in a browser |
| Ctrl-G | — | Back to the repositories | Back to the repositories | Back to the repositories |
| Esc | Clear the query, then the filter, then quit | Back to the repositories | Back to the repositories | Back to the repositories |
| Ctrl-C | Quit without printing | Quit without printing | Quit without printing | Quit without printing |
Everything else is ordinary text editing (Ctrl-A, Ctrl-E, Ctrl-U…), exceptCtrl-W, which no longer deletes the word before the cursor. Esc undoes one layer of narrowing at a time — the worktree list, then the query, then the filter — and only quits once there is nothing left to undo.
A / at the start of the input types a command instead of a filter; Tab completes it. To act on a repository you just searched for, end the query with ; and type the command after it: gm;/remove selects gm and removes it./create and /remove ask first, in a panel over the list, and do the work without leaving the finder.
| Command | What it does |
|---|---|
/help | Show the command list; q or Esc closes it |
/dirty | Show repositories with uncommitted work; combines with /unpushed |
/unpushed | Show repositories with unpushed commits; combines with /dirty |
/create [--ssh] <repo> | Create a repository, after asking; adds it to the list |
/create <branch> | In the worktree list: check that branch out as a worktree |
/get <repo> | Same as gm get, then go to the clone |
/remove | Remove the selected repository, or worktree, after asking |
/expire <days>d | In the worktree list: remove every worktree idle that long, after asking |
/worktrees | Same as Ctrl-W |
/branches | Same as Ctrl-L |
/prs | Same as Ctrl-J |
/browse | Same as Ctrl-Alt-B |
The branch name is the only thing to type. /create <branch> starts the branch fromHEAD if it does not exist yet. The leading dot keeps worktrees out of the repository list: the walk never descends into a dotted directory. The main worktree cannot be removed from the worktree list.
~/gm/github.com/jedipunkz/gm/ the repository
~/gm/.worktrees/github.com/jedipunkz/gm/feat/login<repo> accepts a full URL, git@host:user/repo.git, host/user/repo,user/repo, or a bare repo (resolved against git config github.user).
| Command | What it does |
|---|---|
gm | Open the fuzzy finder; print the selected path |
gm get [-u] [--ssh] [--shallow] [--no-recursive] [-b <branch>] [-s] [-l] <repo>... | Clone into the tree; -u updates an existing clone |
gm list [-p] [-e] [--unique] [<query>] | List repositories (-p full paths, -e exact match, --unique shortest unambiguous name) |
gm status [--dirty] [--unpushed] [-a] [-p] | List unfinished work across repositories and worktrees; never fetches |
gm remove [--dry-run] [-y] <repo>... | Remove a repository and its worktrees after confirming, pruning empty parents (gm rm also works) |
gm create [--ssh] <repo> | Create and git init a repository with origin already set |
gm wt create|remove [--dry-run] [-y] <repo> <branch> | Add or remove a worktree from a script; the finder is better for doing it by hand |
gm wt expire [--dry-run] [-y] <repo> <days>d | Remove the worktrees idle that long, and their merged branches |
gm migrate [--dry-run] [-y] [-r] <dir>... | Move an existing clone into the tree, using its origin remote; -r searches the directory |
gm root [--all] | Print the root directory |
gm shell <fish|zsh|bash> | Print the launch_key binding (Ctrl-G by default) |
gm version | Print the installed version |
Configuration
~/.config/gm/gm.toml (or $XDG_CONFIG_HOME/gm/gm.toml). A file that cannot be parsed, or that holds a key gm does not know, is an error rather than a file half ignored.
root = "~/ghq" # or ["~/ghq", "~/src"], searched in order
theme = "tokyonight"
launch_key = "ctrl-g" # the shell key that opens gm
worktree_key = "ctrl-w" # the finder key that lists worktrees
branch_key = "ctrl-l" # the finder key that lists branches
pr_key = "ctrl-j" # the finder key that lists pull requests
issue_key = "ctrl-[" # the finder key that lists issues
remote_key = "ctrl-alt-b" # the finder key that opens the remoteThe first one set wins, so an existing ghq tree works untouched.
$GM_ROOTroot in gm.tomlgit config --get-all gm.root$GHQ_ROOTgit config --get-all ghq.root~/ghqlaunch_key is the chord gm shell binds and must be a plain Ctrl chord;worktree_key, branch_key, pr_key, issue_key and remote_key are the finder's own and also take alt andshift. Ctrl is written ctrl-, ctrl+, c- or ^. After changing launch_key, re-run gm shell <shell>.
| Chord | Reaches gm |
|---|---|
ctrl-<letter> | Everywhere |
ctrl-alt-<letter> | Nearly everywhere: Alt is sent as an ESC prefix |
ctrl-shift-<letter> | Only with the Kitty keyboard protocol — Ghostty, kitty, WezTerm, foot, recent Alacritty |
Set theme in gm.toml. An unknown name is an error listing the valid ones. The palettes below are read from gm's source when this site is built.
Ranking
Typing filters by fuzzy match, scored so that a literal substring beats a subsequence pieced together from elsewhere, and the repository name counts for more than the user name. The host is shared by nearly every repository, so matches there cost points instead of earning them.
Frecency only breaks ties between equally good matches. Visits are recorded in$XDG_STATE_HOME/gm/frecency.json (default ~/.local/state/gm/frecency.json); delete it to start over.
| Last visit | Score = visits |
|---|---|
| within the last hour | × 4 |
| within the last day | × 2 |
| within the last week | × 0.5 |
| older | × 0.25 |