Which of your repos are dirty right now?
If you keep a folder full of them — services, packages, plugins — answering that means
cd-ing through every one. manygit is a single CLI tool for managing
multiple git repositories: every repo underneath lands on one screen —
ahead, behind, dirty, clean — then
fetch, pull, push, or switch branches on the one under your cursor, without leaving
the list.
Install
One command. A single binary, no runtime, no config to write.
macOS — Homebrew
The trust step is required from Homebrew 6 onwards. A cask in a
third-party tap can run arbitrary Ruby, so Homebrew refuses to load one until you
opt in. Use brew trust --cask rabeeh-ta/manygit/manygit to trust only
manygit rather than everything this tap may ever hold.
Homebrew owns the binary it installs, so manygit never replaces it. When a newer
release lands it says so — at most once a day, after you quit — and
leaves brew update && brew upgrade --cask manygit to you — the
update is what pulls the tap, without which brew still reads the old cask.
macOS and Linux — installer script
The installer picks the right build for your OS and architecture, and adds
~/.local/bin to your PATH if it isn't there already. This binary is
yours rather than a package manager's, so manygit checks for a newer release on
every launch and offers to update itself — the last time you'll run this.
Windows — installer script
PowerShell. Drops manygit.exe into
%LocalAppData%\manygit\bin and adds it to your PATH, with the same
self-update behavior as the script above. Already on Git Bash, MSYS2, Cygwin or
WSL? install.sh detects Windows there too and installs the same
binary.
With Go
Managed by the Go toolchain, so it behaves like the Homebrew build: manygit reports new releases but never replaces itself.
From source
Needs Go 1.24 or newer.
Then run it
$manygit --root ~/work # scan somewhere else
manygit walks the folder three levels deep, collects every git repo it finds, and groups them by parent directory.
Keys
Every action applies to the repo under the > cursor.
| KEY | ACTION |
|---|---|
| 1 2 3 | Focus Repos, Scripts, or Branches |
| 4 | Pull requests — a tab beside Branches |
| 5 6 7 | The bottom slot: commit graph, changed files, script output |
| tab | Cycle the panes. shift+tab cycles back |
| [ ] | Cycle the focused pane's tabs — 3⇄4, 5→6→7. The numbers still jump straight to any of them |
| j k | Move within the focused pane |
| ← → | Hop between Repos and that repo's Branches |
| enter | Select: open a repo's branches, check one out, run a script, or check out a PR |
| b | Check out the highlighted branch — what enter does in Branches |
| s / p | Sync (fetch, then fast-forward pull) / push |
| d / D | Discard changes, after a confirm — d tracked files only, D also deletes untracked ones |
| f / r | Fetch this repo / refetch all of them |
| / |
Filter the focused list by what it shows. Repos match on their name
and their current branch — /main finds every repo sitting on
main, and the tag too while t is on. Branches match on name, so
feat finds one among hundreds
|
| F | Show only repos that are changed, ahead, or behind |
| t | Show each repo's latest tag inline |
| g / n | Full-screen commit graph / full-screen news feed |
| esc | Back out one layer of state — the diff, then Changes, then zoom, then the / and F filters. One press, one layer |
| o / z | Open the repo in your editor / zoom the focused pane |
| : |
Plain-English git. Type a request — rebase current onto master,
sync everything in apps — and your AI harness turns it into git commands
against the whole tree. Repo, folder and branch names complete with tab,
and @script.sh sends that file along as context —
@scripts/update-all.sh only the frontend apps from this.
Nothing runs until you confirm
|
| ! |
Shell in the highlighted repo. Opens a prompt naming where you are —
$manygit:apps/api-gateway. Type a bash command —
git status -sb, npm test — and it runs there (falling back
to cmd.exe on Windows if bash isn't on PATH), streaming
into the Output pane. The prompt stays open, so it is a shell you sit
in: run, read, run again. The bottom bar shows which repo you are in
while you type. esc leaves the shell and lands on Repos;
ctrl+c is what stops a running command. Non-interactive, so
vim and top will not work
|
| ? | Help: the keybindings and the status legend, plus settings — themes, AI harness, news window, scan depth, glyphs, editor. tab or [ ] switch faces; ? or esc closes |
| q | Quit |
Status
The right-hand column of the Repos pane — every repo's git status at once, one glance each.
| ok | Up to date with upstream |
| ↑N | N commits ahead — something to push |
| ↓N | N commits behind — something to pull |
| ↑N ↓M | Diverged from upstream |
| *N | N files changed in the working tree |
| no-remote | A local-only repo, never pushed anywhere. s and p skip it |
| ! | The branch has no upstream, or git errored |
These stay live without being asked — a PR checkout, a script, or a : plan updates the rows it touches as it touches them.
If the arrows misalign in your terminal, switch to ASCII +/-
in the settings face — press ? then tab, in the demo above too.
Pull requests
If the gh CLI is signed in, a PRs tab appears next to Branches.
Press 4. One list is the pull requests you opened, m toggles to the ones
waiting on your review, and it opens on whichever has something in it. Each row shows
what merges where — base ← head — and the branch that clone is on.
enter checks the PR's branch out in the matching local clone and leaves you
where you are, so you can walk a PR that spans several repos. Needs
gh signed in.
Plain English
Press : and say what you want. It comes back as git commands you approve.
The prompt is named after your AI tool — claude: or codex: —
and it knows the tree it is looking at: every repo, its branch, whether it is dirty,
whether main is actually called master, and what the latest
tag is. So rebase current onto master rebases each repo onto its own main
branch, and sync everything in apps resolves the folder.
@ pulls a file in as context — point it at a script and ask for the part
you want. Repo, folder, branch and script names complete with tab, filling in
as far as they agree and waiting for the character that decides the rest.
↑/↓ recall what you asked earlier in the session.
Nothing runs until you say so. The exact commands are printed, and a
y runs them; anything else drops the plan. A batch stops at the first failure,
so a conflict leaves you one repo to sort out rather than five. It only ever runs
git — asked for anything else, it says so in a sentence — and it is
executed as an argument list, never through a shell, so force-pushes and remote
deletions are refused before you are even asked.
Each request stands alone — there is no chat to follow up in, so it never asks you a question. Given something ambiguous it picks the most likely reading and shows you the commands; the plan is the question, and y or N is the answer.
No AI tool installed? : says so and does nothing. Nothing else changes.
News
The top bar reads the last few days of commits back to you in English.
If you have claude or codex on your PATH, manygit hands them
the recent main-branch commits across every repo and rotates the summaries through the
top bar. Press n for all of them at once. Summaries are cached for four hours
and rewritten in place, so opening manygit doesn't re-summarize and the cache never
grows.
No AI tool installed? The top bar shows a repo count instead. Nothing else changes.
Config
Optional. The settings face (? then tab) writes this file for you.
| max_depth |
How many folders below the root to search for repos. Default 3.
Also a row in the settings face — picking a depth re-walks the tree straight away, and a
depth with no repos under it is refused rather than leaving you an empty list
|
| open_cmd |
What o runs in the repo — code, cursor,
code -r. A trailing . is fine
|
| theme |
default, serika_dark, dracula,
nord, catppuccin, 8008
|
| status_glyphs | unicode arrows, or ascii if they misalign |
manygit never writes to the folder you launch it from. On its own it never force-pushes, merges, or rebases — s is fetch plus a fast-forward-only pull, p is a plain push — and the one destructive action it offers on its own is discarding a repo's changes with d / D, which always asks first.
! is the deliberate exception: it runs the bash command you type, in the
highlighted repo (falling back to cmd.exe on Windows if bash isn't
on PATH), with no confirm and no blocklist — a shell escape that
second-guessed you would be useless. What it does instead is leave a record:
the command is echoed into the Output pane with the repo it ran in, before any
output arrives, and ctrl+c stops it mid-run.
: widens that on purpose, since merging and rebasing are the point of it. Every
command is shown before it runs and needs a y; git is executed with an argument
list rather than through a shell, so pipes and && cannot be
expressed; and force-pushes, remote deletions and the git flags that take a shell
command are refused before the confirm appears. A batch stops at the first failure.