A hands-on guide to multirepo
multirepo manages a set of git repositories as one workspace, described by a
single .code-workspace file. It clones and updates them, lets you jump between
them by name, and keeps .claudeignore / .geminiignore in step so AI tools
only see the repos you want.
This guide walks through the things you’ll actually do, in the order you’ll hit
them. For install, see the
README; for the
full behavior and the file format, see
plan.md.
You already have the repos cloned
Use this when a directory already holds several git checkouts and you want to manage them together.
cd ~/code/myproject # holds api/, web/, infra/ — each a git clone
multirepo workspace create # writes ~/code/myproject/myproject.code-workspace
workspace create scans the directory, reads each repo’s origin remote, and
writes a workspace file listing them. It works out the source from the remote
itself: a github.com repo becomes a github source when gh is set up, a
gitlab.com repo a gitlab source, anything else a plain git source with the
exact URL. Non-git folders are skipped with a note, and so are repos with no
origin.
By default it opens a picker with everything checked, so you can drop a repo before writing. To take all of them without prompting:
multirepo workspace create --all # or: multirepo workspace create --yes
multirepo workspace create ~/code/other out.code-workspace # scan elsewhere, name the file
It only writes the file. Step into the directory (or run multirepo sync) to
start using it.
Someone handed you a workspace file
Use this when you have a .code-workspace file and need the repos on disk.
multirepo sync ~/path/to/myproject.code-workspace
sync with a file sets up a fresh copy: it asks where to put it, clones every
repo listed inside, and drops you into the new workspace. Pass -y to take the
defaults without prompts.
Working in a workspace
Once you’re in a workspace directory, repos are reachable by name and a few commands cover the day-to-day:
@api # cd into the api repo
@web # cd into the web repo
multirepo status # git state across every repo, at a glance
multirepo sync # with no file: reconcile — fetch, fast-forward,
# rebase what diverged, leave dirty repos alone
status reads local git state only, so it’s fast and works offline. sync
is the one that touches the network. Both are safe to run as often as you like.
You cloned a repo into the workspace
Use this when you git cloned a repo into the workspace directory by hand and
want multirepo to manage it too.
multirepo select # the combined screen lists untracked repos as (new)
In the combined screen, any untracked git repo sitting in the workspace dir shows
up as a (new) row. Check it and press Enter to adopt it: multirepo reads its
origin remote to work out its source (the same way workspace create does),
adds it to the .code-workspace, and wires up the folders[] entry and the
AI-tool ignore files.
sync and status nudge you too: when a git repo is in the workspace dir but
not in the config, they print a one-line note pointing here.
To go the other way, drop a repo from the workspace. The entry leaves the
.code-workspace and the AI-tool files, but the clone on disk stays put. In the
screen, highlight the repo and press Ctrl-d ((✗) … [drop]); from a script,
use multirepo select --rm <dir>.
Choose what AI tools see
Use this when you want to hide some repos from Claude, Gemini, or the editor’s file tree without deleting anything.
multirepo select # pick the enabled repos in the combined screen
Enabled state is the single source of truth. From it, multirepo regenerates
.claudeignore, .geminiignore, and the workspace’s folders[]. A disabled
repo drops out of the editor’s Explorer and out of what the AI tools see, all at
once. (It’s only hidden, not removed: it stays on disk and multirepo keeps
managing it.) For scripts, skip the screen:
multirepo select --disable infra
multirepo select --enable infra,web
multirepo select --set api,web # set the enabled list exactly
Keep the repos current
multirepo sync # fetch + fast-forward/rebase where it's safe
multirepo reset # clean repos back to their main branch, then pull
multirepo prune # delete local branches whose upstream is gone
None of these touch a dirty or ahead repo; they never throw away work.
To update every workspace at once, not just the current one:
multirepo sync --all # clone missing, fetch, ff/rebase across all workspaces
--all reconciles every registered workspace’s repos in place. It doesn’t
activate or cd anywhere, so you stay where you are.
Work on a feature across repos
Use this when one piece of work spans several repos and you want it isolated from your main checkouts, without juggling branches by hand.
multirepo feature create auth api web # or `multirepo feat create …`
This picks a subset of the workspace’s repos (a checklist if you don’t name them)
and checks each one out as its own git worktree on a shared feature/auth
branch, under .multirepo/feature/auth/. Each worktree starts from that repo’s
latest origin default branch. It also writes an auth.code-workspace you can
open in VSCode/Cursor to see just those repos, and drops you into the feature.
Inside a feature, the everyday commands act on its worktrees, not the main checkouts:
multirepo status # state of the feature's worktrees
multirepo sync # fetch + fast-forward/rebase the worktrees
To hop back into a feature later — or jump straight from one to another — run
multirepo feature select:
multirepo feature select auth # cd into the feature and activate it
multirepo feature select # no name: an fzf-style picker (type to filter, enter to pick)
It works from the parent workspace or from inside a sibling feature. (You can
still just cd into .multirepo/feature/auth/ by hand — the shell hook
activates it either way.)
To add or remove repos in a feature, run multirepo feature edit auth from the
parent workspace. It lists the parent’s repos with the feature’s current members
checked: check a repo and it gets a worktree, uncheck one and its worktree is
removed (after the same safety checks as teardown). For scripts, use
--add/--rm/--set. Your main checkouts are untouched the whole time.
When the work is merged, tear the feature down:
multirepo feature rm auth
It checks every worktree first and refuses to discard work. If anything is
dirty, unpushed, or unmerged, it lists what and stops (pass --force, or confirm,
to override). The worktrees are removed but the feature/auth branches are kept.
If a branch’s upstream is already gone (the PR merged, the remote branch deleted),
it points you at multirepo prune to delete those defunct local branches — and,
if you haven’t set fetch.prune, suggests git config --global fetch.prune true
so stale origin/* refs clean themselves up on every fetch.
To see features at a glance, multirepo feature list (alias feature ls) shows
the current workspace’s features as a nested list: each feature’s branch and the
directory you cd into, then a row per repo with its worktree and the branch that
worktree is on (a missing worktree is flagged). multirepo workspace list -a
nests every workspace’s repos and features. And from your main checkouts,
multirepo status prints a one-line note when the workspace has features, so you
won’t forget they’re there. To step back into a feature later, cd into its
directory (.multirepo/feature/<name>/); the shell hook activates it, just like
a workspace.
Juggle several workspaces
multirepo remembers every workspace you’ve set up on this machine.
multirepo workspace list # list them (the current one is marked; alias: ws ls)
multirepo select # one screen: switch workspace, toggle its repos, save all
multirepo workspace rm # unregister one — the files on disk stay put
workspace has a short alias, ws: multirepo ws ls is the same command.
Run multirepo select with no name and it opens one screen over all your
workspaces. ←/→ switches between them (the current one is preselected, or the
first alphabetically) and refreshes the repo list below; type to filter that
list fzf-style, ↑/↓ move, and space toggles which of that workspace’s repos
are enabled. Edits to every workspace you touch are kept. Press Enter and it
saves all of them, then activates the highlighted workspace and cds in. Already
know where you’re going? multirepo select <name> switches straight there
(short alias multirepo sel).
Keep multirepo itself up to date
Use this when you want to know whether you’re on the latest multirepo, or to update.
You don’t have to watch for releases. Once a day, in the background, multirepo checks the published release index. When there’s a newer version, your next command prints a one-line note on stderr — it never updates itself or gets in the way of a command’s output. To take the update:
multirepo upgrade # re-runs the installer in place
Then restart your shell (or re-source the plugin) so the shell side matches the
new binary — if it lags, multirepo says so on the next command. To opt out of the
check entirely, set MULTIREPO_NO_UPDATE_CHECK=1.
Hacking on multirepo itself? From inside a checkout, multirepo dev builds it and
swaps your shell onto that binary and shim, so you can try changes without
installing. multirepo dev --release builds the release profile; multirepo dev --off reverts (restart your shell to fully restore the installed plugin). A dev
session silences the update and drift notes.
The .code-workspace file
Everything lives in one file, so there’s no separate config to keep in sync. It’s
a normal VSCode .code-workspace with one extra multirepo block:
{
"folders": [
{ "path": "api" },
{ "path": "web" }
],
"multirepo": {
"version": 1,
"repositories": {
"api": { "source": "github", "repo": "your-org/api", "path": "api", "enabled": true },
"web": { "source": "git", "url": "git@github.com:your-org/web.git", "path": "web", "enabled": true }
}
}
}
multirepo workspace create writes this for you from repos you already have; you
can also hand-write it and run multirepo sync <file>. To rename how a repo shows up in
the editor, set "name" on its repositories[] entry; it sticks even while the
repo is disabled. The full schema (sources, aliases, the derived files, and the
shell eval contract) is in
plan.md.