<div align="center">

# `rv` — repoverse

**One workspace for many git repos. Branch, test, commit, and roll changes
up the dependency graph — in one command each.**

[![crates.io](https://img.shields.io/crates/v/repoverse?logo=rust&color=E05D44)](https://crates.io/crates/repoverse)
[![CI](https://github.com/madhavajay/repoverse/actions/workflows/ci.yml/badge.svg)](https://github.com/madhavajay/repoverse/actions/workflows/ci.yml)
[![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](./LICENSE)
![Platforms](https://img.shields.io/badge/platform-macOS%20%7C%20Linux%20%7C%20Windows-555)

<img src="docs/demo/rv-demo.svg" alt="animated rv demo: status tree, parallel fetch with live spinners, check, and rv next" width="760">

</div>

---

Some codebases can't be a monorepo: separate orgs, public forks with private
consumers, independent release cadence, OSS upstreams you don't control.
You end up with nested submodules, duplicate checkouts of the same library,
and a day of `cd`-and-`git` archaeology every time a leaf dependency changes.

`rv` makes a set of independent git repos behave like one workspace:

- **One config, one lock.** `.repoverse.yaml` declares the repos, branches,
  and the dependency graph; `.repoverse.lock` pins exact SHAs so a fresh
  clone is reproducible.
- **One physical copy of shared code.** When five repos vendor the same
  library, `rv` checks out one copy and overlays the rest with symlinks —
  edit once, every consumer sees it, git history stays intact.
- **Rollup.** Change a leaf library and `rv rollup` cascades it up the
  graph: test → commit → push → bump each consumer's pin → repeat, in
  dependency order, until the root is green.
- **Plain git underneath.** Every repo stays a normal clone. `rv git -- …`
  runs any git command across repos safely; you can always drop back to
  raw git.

> [!NOTE]
> **Everything rv commits is standard git submodule state.** Dependency pins
> are ordinary submodule **gitlinks** (mode `160000` tree entries) plus
> `.gitmodules` — nothing proprietary lands in your history. Anyone can
> `git clone --recursive` any repo in the workspace and build it like a
> normal submodule project, **without rv installed**: collaborators, CI, and
> downstream users are never locked in. The shared-checkout symlink overlays
> are local-only worktree state (`update-index --skip-worktree`), never
> committed.

## Quickstart

```sh
# macOS / Linux
curl -fsSL https://raw.githubusercontent.com/madhavajay/repoverse/main/install.sh | sh

# Windows
irm https://raw.githubusercontent.com/madhavajay/repoverse/main/install.ps1 | iex

# or with cargo
cargo install repoverse --bin rv
```

Then either **adopt** an existing nested-submodule project, or **init** from
a config someone already committed:

```sh
rv adopt          # wizard: scan submodules, propose flat layout + dedupe, emit config
rv init           # fresh machine: clone everything from .repoverse.yaml, run setup
rv                # status tree: every repo, branch, dirty/ahead/behind
```

Day-to-day, the loop looks like:

```sh
rv branch fix/proto-v2     # branch the dirty repos, record intent in config
rv check                   # lint + test the dirty repos, in parallel
rv commit -m "proto v2"    # same commit message across affected repos
rv rollup --direct         # cascade the change up to every consumer
```

## How rollup works

`rv plan` shows the dependency DAG; `rv rollup` walks it leaves-first.
Each node only proceeds when the node below it is merged and pinned:

```mermaid
flowchart BT
    protos[shared-protos<br/>test → commit → push]
    auth[auth-service<br/>bump pin → test → push]
    billing[billing<br/>bump pin → test → push]
    web[web-app<br/>bump pin → test → push]
    platform[platform root<br/>lock updated, green]
    protos --> auth --> platform
    protos --> billing --> platform
    protos --> web
    web --> platform
```

Stateless by design: `rv next --json` always tells you (or your agent) the
single next actionable step, reconstructed from git/GitHub state — there is
no daemon and no hidden state to corrupt. `rv rollup --step` executes
exactly one node; `rv rollup --direct` runs the whole local cascade.

## How it compares

| | Approach | Where it shines | Where `rv` differs |
|---|---|---|---|
| **git submodules** | Pin nested repos by SHA | Simple vendoring | `rv` keeps submodules but adds branch intent, a workspace-level lock, deduped shared checkouts, and cross-repo commands — no more detached-HEAD surprises or N duplicate copies of one library. |
| **google `repo`** | XML manifest + sync scripts | AOSP-scale checkout | `rv import manifest.xml` migrates directly. `rv` adds a lockfile, dependency-ordered rollup, and CI-derived lint/test — not just sync. |
| **Bazel / Buck** | One build graph, usually one tree | Hermetic builds inside a monorepo | Different layer: Bazel assumes the code can live together and replaces your build system. `rv` is for when repos *must* stay independent (orgs, forks, licenses, release cadence) and each keeps its own native build/CI. They compose — `rv` manages the repos, your build tool builds them. |
| **meta / gita / mu-repo** | Run git commands across many clones | Light fan-out | `rv` also fans out, but adds the dependency graph, lock reproducibility, shared-checkout overlays, and the rollup state machine. |
| **Monorepo migration** | Merge everything into one repo | If you can, do it | When you can't — external upstreams, split ownership — `rv` gives a monorepo-like daily workflow without moving any code. |

## Built for agents

Every long-running command animates progress on a TTY and degrades to
stable, plain, line-oriented output when piped — same text, no ANSI. Multi-repo
operations exit non-zero if **any** repo fails. `--json` variants exist for
status, plan, check, pr, and next.

```sh
rv next --json      # → { "repo": "...", "action": "...", "blocked_by": [...] }
rv rollup --step    # execute exactly that node, then stop
```

This is the agent loop: ask `next`, do the work, `--step`, repeat until
`{"done": true}`. See [AGENTS.md](./AGENTS.md).

## Commands

### Setup / onboarding

| Command | What it does |
|---|---|
| `rv adopt [--yes] [--dry-run]` | Wizard: walk an existing nested-submodule project, propose a flat layout, dedupe repos (per-conflict fork/branch resolution), detect CI lint/test jobs → emit `.repoverse.yaml` + pinned `.repoverse.lock`. Non-destructive. |
| `rv import <manifest.xml>` | Migrate a Google-repo `manifest.xml` into `.repoverse.yaml`. |
| `rv init [--https\|--ssh]` | Clone/initialize the workspace from `.repoverse.yaml`. Scheme auto-resolves (local SSH, CI HTTPS+token). |
| `rv setup [targets...]` | Install project dependencies per repo using its package manager (resolved from CI / `.repoverse/setup` / autodetect). Auto-runs after `rv init`. |

### Inspect / sync

| Command | What it does |
|---|---|
| `rv` / `rv status [--json]` | Tree of all repos with branch + dirty/ahead/behind. |
| `rv status --dirty` | File-level working-tree status across the root and all configured repos. |
| `rv fetch [-j N]` | Parallel fetch all remotes; show ahead/behind. |
| `rv pull [--rebase]` | Pull each repo on its branch. |
| `rv sync [--force]` | Check the workspace out to `.repoverse.lock`. `--force` discards local changes. |
| `rv branch-sync [--pull]` | Check repos out to `.repoverse.yaml` branches (`revision`, falling back to `main_branch`/defaults); `--pull` pulls them. |
| `rv pin` | Write `.repoverse.lock` = current HEADs. |
| `rv lock-update` | Move the lock forward to current tips of the yaml's branches. |
| `rv doctor [--fix]` | Check workspace health; apply safe repairs. |
| `rv update` | Update the installed `rv` binary. Uses Cargo when the active binary was installed with Cargo; otherwise uses the release installer. |

### Branching

| Command | What it does |
|---|---|
| `rv branch <name> [targets...]` | Smart mode: create/switch a branch in dirty repos and update `.repoverse.yaml` `revision` plus matching `links[].branch`. Use `--all` for every targeted repo or `--no-write-config` for local-only branches. |
| `rv switch [-b] <branch> <targets...>` | Checkout/create a branch across selected repos. |
| `rv checkout <rev> <target> [--reset]` | Checkout a revision in one repo / all. |
| `rv remote <ssh\|https>` | Re-point existing clones to either scheme. |

### Workflow

| Command | What it does |
|---|---|
| `rv commit -m "msg" [targets...]` | Atomic cross-repo commit, same message. |
| `rv push` | Push repos on feature branches. |
| `rv git -- <args> [targets...]` | Run any git command per selected repo, restoring overlays around it (stash, cherry-pick, revert, reset, log, ...). |
| `rv lint [--force] [targets...]` | Run lint (from CI mapping) in dirty repos, parallel, quiet on success. |
| `rv test [--force] [targets...]` | Same model, test job. |
| `rv check [targets...] [--json]` | Lint + test in parallel, aggregated pass/fail report. |
| `rv pr` | Open PRs: sub-repos first, then parent with cross-links. |
| `rv release` | Pin + commit lock + tag. |

`projects[].revision` is the branch/ref the workspace checks out and locks
from. `projects[].main_branch` is the stable PR base/trunk for that repo; it
falls back to `defaults.main_branch`, then `revision`.

### Shared-checkout overlays

| Command | What it does |
|---|---|
| `rv layout` | Show the workspace's shared checkout layout. |
| `rv link` | Re-establish shared-dep symlinks from config (run after a fresh clone). |
| `rv unlink [repo]` | Restore overlays to real submodules before git history surgery; `rv link` re-applies after. |
| `rv merge <args> [targets...]` | `git merge` per repo with overlays detached/relinked around it. |
| `rv rebase <args> [targets...]` | Same, for `git rebase`. |
| `rv gitlinks [--commit]` | Check or stamp committed gitlinks from current checkouts. |
| `rv upstream fetch` | Fetch configured upstream source remotes without merging. |

### Rollup

| Command | What it does |
|---|---|
| `rv plan [--json]` | Show the full ordered dependency DAG with per-node status (pending/dirty/branched/pr-open/ci-running/merged/blocked). |
| `rv next [--json]` | The single next actionable node: what to do, why, what's blocking it, exact commands. Stateless — agent loop primitive. |
| `rv rollup [--from <repo>] [--slug <name>] [--dry-run]` | Dependency-ordered cascade: leaves first → setup/lint/test → commit → push → PR → CI-green + auto-merge → bump consumers to merged SHA → recurse to root. |
| `rv rollup --step [<repo>]` | Execute exactly one node (the `rv next` one) and stop. Agent-in-the-loop. |
| `rv rollup --direct` | Same cascade, local fast-path: test locally → push → bump parent, no PRs. |

### Help

| Command | What it does |
|---|---|
| `rv --help` / `rv help <cmd>` | Grouped command list; complete per-command help with examples. |

## Rust workspaces — cargo path-dep identity

Symlinking duplicate submodule checkouts is necessary but not
sufficient for cargo: it identifies path deps by literal path string
and won't dedup symlinks. The pattern is to declare each shared dep
as a `git = "<url>"` source in every fork and `[patch.<url>]` it to
a single local path at the parent workspace. rv's symlinks then
ensure that path resolves to one physical checkout. Each fork's CI
mirrors the same `[patch]` against its own submodule. Full design and
the upstream `.gitmodules` constraint live in
[TODO.md → Cargo path-dep identity](./TODO.md#cargo-path-dep-identity-rust-workspaces).

## Development

```sh
./rv <command>      # run the debug build, e.g. ./rv status
./coverage.sh       # tests with coverage
python3 docs/demo/gen_demo.py   # regenerate the README demo SVG
```

Design notes and roadmap live in [TODO.md](./TODO.md). Agent guidance lives
in [AGENTS.md](./AGENTS.md).

## License

[Apache-2.0](./LICENSE)
