multirepo

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.