devstack · Guide › Multi-repo git
A workspace usually spans several repositories. The ws group runs git across
all of them at once — clone them, fast-forward them, check their status, or run
an arbitrary git command over the set — in bounded parallel.
devstack ws clone # clone every workspace repo that isn't on disk yet
devstack ws sync # fetch + fast-forward pull every repo
devstack ws status # a cross-repo status tablews acts on the projects declared in workspace.yaml that carry a git: field:
# workspace.yaml
projects:
- { name: api, path: services/api, git: git@github.com:acme/api.git }
- { name: web, path: services/web, git: git@github.com:acme/web.git }
- { name: docs, path: docs } # no git: → skipped by wsIt shells out to your system git, so your existing SSH keys, credential
helper, and ~/.gitconfig all apply — there is nothing extra to authenticate.
Every subcommand takes an optional list of project names to narrow the set;
with none, it acts on all git-backed projects.
Parallelism is bounded by --jobs (default min(8, 2×CPUs)).
Clone workspace repos in parallel. Repos already present on disk are skipped, so this is safe to re-run.
devstack ws clone
devstack ws clone api web # only these two
devstack ws clone --jobs 4| Flag | Default | Description |
|---|---|---|
--jobs |
0 | max parallel workers (0 → min(8, 2×CPUs)) |
Fetch and fast-forward pull every repo. When a pull brings in new commits,
the project's postPull hooks run (e.g. reinstall deps, re-migrate).
devstack ws sync
devstack ws sync --no-hooks # skip postPull hooks
devstack ws sync api| Flag | Default | Description |
|---|---|---|
--jobs |
0 | max parallel workers |
--no-hooks |
false | skip the postPull hooks that fire on new commits |
postPull hooks are declared per project — see projects for hook
phases.
A cross-repo status table: branch, ahead/behind, and dirty state.
devstack ws status
devstack ws status --json
devstack ws status --check # exit non-zero if ANY repo is dirty (CI)| Flag | Default | Description |
|---|---|---|
--check |
false | exit non-zero if any repo has uncommitted changes (CI gate) |
--jobs |
0 | max parallel workers |
Run an arbitrary git command across repos. The -- separator splits the line:
before -- is the git command passed to every repo; after -- is an
optional subset of project names to run it against.
devstack ws git fetch --all # in every repo
devstack ws git checkout main # switch branch everywhere
devstack ws git checkout feature/x -- api web # only in api and web
devstack ws git log --oneline -5 -- api # git args + repo subset| Flag | Default | Description |
|---|---|---|
--jobs |
0 | max parallel workers |
Grammar: everything before -- is forwarded verbatim to git in each repo;
everything after -- selects which repos. Omit -- names... to run over all.
Onboard a fresh checkout
devstack ws clone # pull down every repo
devstack up # then bring the stack upUpdate everything each morning
devstack ws sync # fast-forward all; postPull hooks re-migrate/reinstallPut every repo on the same feature branch
devstack ws git fetch --all
devstack ws git checkout feature/new-authCI gate: fail if any repo has uncommitted changes
devstack ws status --check # non-zero exit ⇒ the build fails- Workspaces — declaring
projects[].gitinworkspace.yaml - Projects —
postPulland other lifecycle hooks - Lifecycle —
upclones/syncs as part of the saga (--skip-clone) - Command reference — every command, terse