Skip to content

Latest commit

 

History

History
718 lines (582 loc) · 33.8 KB

File metadata and controls

718 lines (582 loc) · 33.8 KB

orbito — Design & Specification

Status: v0.1.0 (initial implementation) Audience: contributors, integrators writing manifests/tooling around orbito Companion docs: README.md for user-facing quickstart


1. Overview

orbito is a command-line tool for managing workspaces made of many git repositories, described by a single manifest file. It exists to do the job of Google's repo and Fuchsia's jiri — clone/sync dozens or hundreds of repositories into a coherent local tree, driven by version control rather than manual scripting — but as a single static Rust binary with a manifest format designed to be read and diffed by humans.

1.1 Problem statement

Projects that span many repositories (an OS distribution, an SDK with vendored dependencies, a monorepo-that-isn't) need a way to answer: what repos make up this product, at what revisions, and how do I get a working checkout of all of them? repo and jiri both solve this with an XML manifest and a sync tool. orbito solves the same problem with:

  • a TOML manifest (structured, comments, no XML tooling required),
  • a single native binary (no Python interpreter, no Go runtime bootstrap),
  • parallel sync using OS threads,
  • a small, auditable command surface (7 subcommands, no plugin system).

1.2 Non-goals (for v0.1.0)

  • Code-review integration (repo upload / Gerrit push). The review URL is parsed and surfaced (§4.2) but no upload command exists yet.
  • Manifest snapshotting/freezing to exact commits (repo manifest -r).
  • A merge/rebase-across-projects workflow beyond what forall gives for free by shelling out.
  • Non-git version control (Mercurial, Perforce, etc.) — git only.
  • A plugin/extension system. New behavior means new Rust code.

These are explicitly deferred, not rejected; see §9 (Roadmap).


2. Design principles

  1. Shell out to git, don't reimplement it. orbito has zero VCS logic of its own — every git operation is a call to the system git binary (§6). This means credential helpers, SSH config, .gitconfig, corporate proxy setups, and git version quirks all behave exactly as the user already expects them to, for free.
  2. The manifest is the source of truth, and it's just data. No Turing-complete manifest language, no templating, no conditionals. TOML in, a flat list of resolved Project structs out. This keeps manifests diffable and reviewable in a PR the same way Cargo.lock or package.json are.
  3. Fail loud, fail specific. Every fallible operation carries anyhow::Context naming what was being attempted (which project, which file, which URL) so a sync failure across 200 projects doesn't require re-running with -v to find out which one broke.
  4. No dependency you can't explain in one sentence. clap for CLI parsing, serde/toml for the manifest, anyhow/thiserror for errors, colored for output, walkdir for filesystem walks. HTTP fetch shells out to curl (§6.4) rather than pulling in a TLS stack, for the same reason git operations shell out to git.
  5. Portable by construction. Linux release binaries link against musl (no glibc-version coupling); Windows binaries are native MSVC. Every release target builds on hardware matching its own architecture (§8) rather than cross-compiling, trading a little CI complexity for binaries nobody has to second-guess.

3. Architecture

3.1 Module map

src/
├── main.rs              entry point: parse CLI, dispatch, print top-level errors
├── cli.rs                clap Parser/Subcommand definitions (the CLI surface, §5)
├── manifest.rs            manifest data model + TOML parsing + import resolution (§4)
├── workspace.rs            .orbito/ directory layout, config load/save, discovery (§7)
├── git.rs                  thin wrapper around the system `git` binary (§6.1–6.3)
├── http.rs                 thin wrapper around `curl`, for URL-based imports (§6.4)
└── commands/
    ├── mod.rs               re-exports
    ├── init.rs               `orbito init`
    ├── sync.rs               `orbito sync` (parallel clone/fetch/checkout)
    ├── status.rs             `orbito status`
    ├── list.rs                `orbito list`
    ├── forall.rs              `orbito forall`
    ├── branch.rs              `orbito branch`
    └── manifest_cmd.rs        `orbito manifest`

3.2 Data flow

                    ┌─────────────────┐
                    │  manifest repo   │   (a git repo, cloned into
                    │  (git, remote)   │    .orbito/manifest-repo/)
                    └────────┬─────────┘
                             │ contains manifest.toml (+ maybe [[import]]s)
                             ▼
                    ┌─────────────────┐
                    │  Manifest::load  │   recursive: follows [[import]]
                    │  (manifest.rs)   │   entries, local + URL-based (§4.3)
                    └────────┬─────────┘
                             │ produces
                             ▼
                    ┌─────────────────┐
                    │ Vec<Project>      │   flat, deduplicated by local path,
                    │ (fully resolved)  │   sorted — the one true in-memory
                    └────────┬─────────┘   representation every command reads
                             │
              ┌──────────────┼──────────────┬───────────────┬─────────────┐
              ▼              ▼              ▼               ▼             ▼
           sync           status          list           forall        branch
      (clone/fetch/    (git status    (print table    (run shell     (git checkout
       checkout, -j     per project)   or TOML)         cmd per       -b per
       parallel)                                        project)      project)

Every command re-runs Manifest::load from scratch — there is no cached, serialized "resolved manifest" on disk. This is a deliberate simplicity trade-off (§10.2): resolution is cheap (local file reads + a handful of git/curl calls for imports) relative to the sync operations that follow it, so caching would add state-invalidation complexity for negligible speedup.

3.3 Error handling

All fallible internal APIs return anyhow::Result<T>. Context is attached at each layer boundary via .with_context(|| ...) so error chains read top-down as a breadcrumb trail, e.g.:

error: resolving manifest at .orbito/manifest-repo/manifest.toml

Caused by:
    0: importing manifest from https://raw.githubusercontent.com/org/x/main/extra.toml
    1: fetching manifest from https://raw.githubusercontent.com/org/x/main/extra.toml
    2: curl failed fetching '...' (exit 22):
       curl: (22) The requested URL returned error: 404

main.rs is the single place that formats a top-level error ({:#} for the full chain) and sets the process exit code; no other module calls std::process::exit.


4. Manifest specification

4.1 File format

A manifest is a UTF-8 TOML document. orbito does not require a specific filename (the workspace config records which file to read, default manifest.toml) or that it live at the repo root — -m in orbito init takes any path within the manifest repo.

4.2 Schema

Manifest         := RemoteTable* DefaultTable? ProjectTable* ImportTable*

[[remote]]                       (RemoteTable, 0 or more)
  name        : string           required, unique among remotes in this file
  fetch       : string           required, base URL; project name is joined onto it
  review      : string           optional, Gerrit-style code-review URL (§10.1)

[default]                        (DefaultTable, 0 or 1)
  remote      : string           optional, fallback for project.remote
  revision    : string           optional, fallback for project.revision
                                  (if entirely absent, revision falls back to "main")

[[project]]                      (ProjectTable, 0 or more)
  name        : string           required; also the path segment joined onto
                                  remote.fetch to form the clone URL
  path        : string           optional, local checkout path; defaults to `name`
  remote      : string           optional, must name a [[remote]]; falls back to
                                  default.remote; error if neither is set
  revision    : string           optional; falls back to default.revision, then "main"
  hooks       : array<string>    optional, default []; shell commands run once,
                                  immediately after first clone (§4.5)
  pinned      : bool             optional, default false; if true, `sync` never
                                  moves this project off its current HEAD (§4.6)

[[import]]                       (ImportTable, 0 or more)
  manifest    : string           optional (see resolution table below)
  url         : string           optional (see resolution table below)
  revision    : string           optional, default "main"; only meaningful
                                  when url is a git repo (i.e. manifest is also set)

At least one of import.manifest / import.url must be set; an import with neither is a parse-time error (bail! in resolve_import).

4.3 Import resolution

[[import]] has three forms, distinguished by which of manifest/url are present:

manifest url Meaning
set absent Local/relative import. Resolved relative to the importing manifest: if that manifest came from disk, join as a filesystem path; if it came from a URL, join as a URL path segment (http::join, §6.4).
set set Git-hosted import. url is a git remote; orbito clones it (or fetches+checks out revision if already cloned) into .orbito/imports/<slug>/, then reads manifest as a path inside that checkout. Enables nested imports within that repo, resolved relative to it.
absent set Direct file import. url points straight at a manifest file, fetched over HTTP(S) via curl and parsed as-is — no git, no local cache. Re-fetched on every orbito sync / orbito manifest / etc.

Import resolution is implemented as a Source enum internal to manifest.rs:

enum Source {
    File(PathBuf),   // a manifest file that exists on disk
    Url(String),      // a manifest file fetched over HTTP(S)
}

Manifest::load_into recurses on Source, so an HTTP-fetched manifest can itself declare further imports (of any of the three forms above) and they resolve correctly relative to where that manifest came from, not where the top-level manifest started. There is no depth limit; a cyclic import graph will recurse until the process runs out of stack (see §9, "known limitations" — cycle detection is not yet implemented).

Cache directory naming (git-hosted imports only): the clone directory name is slug(url), computed as: keep [a-zA-Z0-9._-], replace everything else with _, truncate to 60 characters, then append -<16 hex digit hash> (std::hash::DefaultHasher over the full URL) so two URLs that sanitize to the same prefix never collide.

4.4 Project resolution & deduplication

Every [[project]] across the top-level manifest and all its imports is resolved independently (name/path/remote/revision/hooks/pinned, with defaults applied per-file — a project's default.revision comes from its own manifest file, not the top-level one) and inserted into a HashMap<PathBuf, Project> keyed by local checkout path. This means:

  • Two projects that resolve to the same path are not both kept — the later one (in file-then-import-order, depth-first) silently overwrites the earlier one. This mirrors how imports are meant to be used ("override a vendored project's revision") but means naming collisions fail silently rather than erroring. See §9.
  • The final Manifest.projects list is sorted by path, so command output (orbito list, orbito status, ...) is stable and deterministic regardless of import order.

4.5 Hooks

project.hooks is a list of shell command strings (sh -c "<hook>"), run once, immediately after that project's first clone, in the project's own directory, in list order. Hooks do not re-run on subsequent orbito sync calls for an already-cloned project. A failing hook (non-zero exit) aborts that project's sync with an error; it does not roll back the clone.

⚠️ Hooks execute arbitrary shell commands from the manifest with no sandboxing. See §10.3 (Security considerations) — a manifest is as trusted as a Makefile you'd run, not as trusted as data.

4.6 Pinned projects

project.pinned = true means orbito sync will clone it once (if not present) but will never fetch/checkout/move it thereafter — it's the escape hatch for "I have local changes here and don't want them clobbered" or "this dependency is intentionally frozen." orbito status still reports its dirty/clean state normally; only sync's mutation is suppressed.

4.7 Worked example

[[remote]]
name = "origin"
fetch = "https://github.com/my-org"
review = "https://gerrit.my-org.com"

[default]
remote = "origin"
revision = "main"

[[project]]
name = "platform/core"
path = "core"

[[project]]
name = "platform/tools/build"
path = "tools/build"
revision = "release/stable"
hooks = ["./tools/build/bootstrap.sh"]

[[project]]
name = "vendor/thirdparty-lib"
path = "third_party/lib"
pinned = true

[[import]]
url = "https://github.com/my-org/other-manifests"
manifest = "extra.toml"
revision = "main"

Resolves (assuming extra.toml is empty) to:

name path remote_url revision pinned
platform/core core https://github.com/my-org/platform/core main false
platform/tools/build tools/build https://github.com/my-org/platform/tools/build release/stable false
vendor/thirdparty-lib third_party/lib https://github.com/my-org/vendor/thirdparty-lib main true

5. Command specification

All commands except init require a workspace: they call Workspace::discover(), which walks up from the current directory looking for .orbito/, the same way git looks for .git. Every command that resolves the manifest passes Workspace::imports_cache_dir(&ws.root) (.orbito/imports/) as the import cache.

orbito init <manifest-url> [-m <file>] [-b <branch>]

Clones <manifest-url> (git) into .orbito/manifest-repo/, checking out <branch> (default main). Writes .orbito/config.toml:

manifest_url = "<manifest-url>"
manifest_file = "<file>"     # default "manifest.toml"
branch = "<branch>"          # default "main"

Idempotent: if .orbito/manifest-repo/ already exists, init skips the clone (prints a note) but still (re)writes the config — this lets you re-point manifest_file/branch without re-cloning. Does not run sync; that's a separate, explicit step.

orbito sync [-j <n>] [--manifest-only]

  1. git fetch + checkout the manifest repo to its tracked branch (always; not parallelized, there's only one).
  2. Unless --manifest-only: resolve the manifest (§4), then sync every project, <n> at a time (default 4; see §5.1 for the concurrency model).

Per-project outcome (clone / update / pinned-skip / error) is printed as it completes, not in project-list order — interleaving across workers is expected and each line is self-contained (project name always included). A per-project failure is reported and does not stop other workers' projects (sync is best-effort across the whole set); the process still exits non-zero if any project failed... (implementation note: currently sync_projects prints lines but does not propagate a non-zero exit code for individual project failures — see §9, this is a known gap relative to forall, which does propagate.)

5.1 Concurrency model

sync_projects statically partitions the project list round-robin across jobs OS threads (std::thread::scope), not a work-stealing pool: project[i] goes to worker[i % jobs]. Each worker processes its slice strictly sequentially and sends (name, Result<String>) back over an mpsc::channel; the main thread drains the channel and prints as results arrive. This means:

  • Total wall time is bounded by the slowest worker's total, not the slowest single project — an unlucky round-robin split (e.g. one worker getting all the large repos) is possible with very uneven project sizes and small jobs counts.
  • Output ordering is nondeterministic (arrival order), which is intentional — it reflects real progress rather than manifest order.

orbito status

For each resolved project: not-checked-out / not-a-git-repo / clean / dirty, plus current branch name (git rev-parse --abbrev-ref HEAD) and, if dirty, a count of changed paths (git status --short line count). Purely read-only — makes no git calls that mutate state. Prints a trailing note if any project is dirty.

orbito list [--toml]

Default: an aligned table (NAME, REMOTE, REVISION) sized to the longest project name, plus a trailing count. --toml: re-emits each project as a standalone [[project]] block (name/path/remote/revision) — useful for piping into other tools, though note this is a simplified re-serialization (no remote/import structure, no hooks/pinned/review), not a round-trippable manifest.

orbito forall -c "<command>" [-p]

Runs sh -c "<command>" in every currently checked-out project directory (silently skips projects whose path doesn't exist yet — run sync first), sequentially, in resolved-manifest order. Exposes ORBITO_PROJECT_NAME and ORBITO_PROJECT_PATH as environment variables to the command. Without -p/--keep-going, stops at the first failing project (non-zero exit) and returns an error immediately; with -p, runs every project regardless and reports the total failure count at the end, still exiting non-zero if any failed.

orbito branch <name>

For every currently checked-out project: git checkout -b <name>. Prints / per project and continues past individual failures (e.g. branch already exists in one project). Like sync (§5, exit-code note), branch always returns success at the process level regardless of how many per-project checkouts failed — the lines are the only failure signal. This is the same gap noted in §9, not an intentional difference from sync; forall is currently the only command that reflects per-project failure in its own exit code.

orbito manifest

Prints the fully-resolved manifest (post-import, post-default-resolution) as a sequence of [[project]] blocks with name, path, remote_url (the fully joined clone URL, not just the remote name), remote_review (only if the remote declared one), revision, and pinned. This is the "what will sync actually do" view — the closest thing to repo manifest or jiri resolve — useful for debugging import resolution and reviewing what a manifest change will actually check out before running it.


6. External process interfaces

orbito has two "escape hatches" to the outside world, both implemented as thin wrappers that shell out rather than link a library, on the philosophy that CLI-tool behavior should match what's already installed and configured on the machine.

6.1 git.rs — command construction

git::run(dir: Option<&Path>, args: &[&str]) -> Result<GitOutput> spawns git <args> with current_dir set if given, captures stdout/stderr, and turns a non-zero exit into an anyhow error embedding stderr (or stdout if stderr was empty). Every other function in the module is a thin, named wrapper over run:

Function git invocation
is_repo(dir) (no git call) — checks dir/.git exists
clone(url, dest, revision) git clone --origin origin --branch <revision> <url> <dest>
fetch(dir, remote) git fetch <remote> --prune --tags
checkout_revision(dir, remote, rev) tries git rev-parse --verify <remote>/<rev>; if that resolves, git checkout -B <rev> <remote>/<rev> (branch case); else git checkout --detach <rev> (tag/commit case)
current_branch(dir) git rev-parse --abbrev-ref HEAD
head_commit(dir) git rev-parse --short HEAD
status_short(dir) git status --short
create_branch(dir, name) git checkout -b <name>
clone_or_update(url, dest, rev) is_repo ? fetch + checkout_revision : clone

6.2 Revision resolution semantics

checkout_revision's branch-vs-tag/commit branching means: if <remote>/<revision> exists as a remote-tracking ref, orbito treats revision as a branch and creates/resets a local branch of that name tracking it (so git log, git push etc. behave normally for a developer working in that checkout). Otherwise it's treated as a tag or raw commit SHA and checked out detached. There is no manifest-level way to force one interpretation over the other — it's inferred from what's fetchable.

6.3 Clone semantics

clone always names the remote origin and always passes --branch. This means tags and raw commit SHAs cannot be the initial clone revisiongit clone --branch <tag-or-sha> fails for arbitrary non-branch refs on many git versions/hosts. A manifest that pins a fresh (never-before-cloned) project to a tag or SHA will fail on first sync. This is a known gap; see §9.

6.4 http.rs — URL-based imports

http::get(url) -> Result<String> shells out to curl -fsSL <url> and returns stdout as a UTF-8 string, erroring (with stderr embedded) on non-2xx/network failure. http::join(base_url, relative) does naive "drop everything after the last / in base_url, append relative" resolution (or returns relative unchanged if it's already absolute) — deliberately not a full RFC 3986 URL resolver, since manifest import paths are simple filenames/subpaths in practice, not ..-laden relative references.


7. Workspace layout

<workspace root>/
├── .orbito/
│   ├── config.toml            manifest_url, manifest_file, branch
│   ├── manifest-repo/           full git checkout of the manifest repo
│   │   └── <manifest_file>        (e.g. manifest.toml)
│   └── imports/                 cache for git-hosted [[import]] entries
│       └── <slug(url)>-<hash>/    one clone per distinct imported repo URL
├── <project.path>/              one directory per resolved project
│   └── ...                        (a normal git checkout, origin = project's remote)
└── ...

Workspace::discover() walks up from cwd looking for .orbito/ (analogous to git's .git walk), so any command can be run from within a project subdirectory, not just the workspace root.


8. CI/CD & release engineering

8.1 .github/workflows/ci.yml

On every push/PR to main: cargo build --locked + cargo test --locked on ubuntu-latest and windows-latest (required checks). A separate fmt/clippy job runs cargo fmt --all -- --check and cargo clippy --all-targets --all-features -- -D warnings with continue-on-error: true — informational, not blocking, on the theory that lint failures shouldn't gate a PR the same way a broken build does, but should still be visible.

8.2 .github/workflows/release.yml

Triggered by a tag push matching v*.*.* or manual workflow_dispatch. Builds four portable, self-contained archives, each on a GitHub-hosted runner matching its own target architecture — no Docker, no cross-compilation toolchain:

Target Runner Toolchain
x86_64-pc-windows-msvc windows-latest native MSVC
aarch64-pc-windows-msvc windows-11-arm native MSVC
x86_64-unknown-linux-musl ubuntu-latest musl-tools (apt)
aarch64-unknown-linux-musl ubuntu-24.04-arm musl-tools (apt)

Each archive (orbito-<version>-<platform>.zip/.tar.gz) bundles the binary, README.md, LICENSE-MIT, LICENSE-APACHE, and examples/manifest.toml, plus a .sha256 checksum file. A final release job downloads all four artifact sets, concatenates the checksums into SHA256SUMS.txt, and publishes everything to a GitHub Release via softprops/action-gh-release with auto-generated release notes.

windows-11-arm and ubuntu-24.04-arm are GitHub's newer Arm-native hosted runner labels; if unavailable on a given plan/repo visibility, the fallback is cross-targeting aarch64-pc-windows-msvc from windows-latest (works out of the box, MSVC ships ARM64 libs) and building aarch64-unknown-linux-musl via the Docker-based cross tool from ubuntu-latest.


9. Known limitations / non-implemented edges

These are things the current implementation does not handle, called out explicitly rather than left implicit:

  • No import-cycle detection. A manifest that (transitively) imports itself will recurse until stack overflow, not a clean error.
  • Silent path collisions. Two projects resolving to the same local path (whether within one manifest or across an import) silently overwrite rather than erroring — see §4.4.
  • Tags/commits can't be a first-clone revisionclone() always passes --branch <revision>, which only works for actual branches on first clone (see §6.3). Re-syncing an existing checkout to a tag/SHA works fine via checkout_revision's fallback path.
  • sync and branch's per-project failures don't set a non-zero process exit. Both print per failing project but always return Ok(()) at the command level. forall, by contrast, does propagate failures into its own exit code (immediately by default, or as a summarized count with --keep-going). Bringing sync/branch in line with forall here is tracked as a v0.2 fix.
  • Hooks and forall commands run with no sandboxing and inherit the user's full shell environment and permissions — see §10.3.
  • No --dry-run on sync. orbito manifest is the closest substitute (shows what would be synced) but doesn't diff against current on-disk state.
  • HTTP imports have no integrity pinning (no checksum/signature field in the [[import]] url = "..." form) — anyone who can serve that URL controls what gets merged into your manifest on every sync. See §10.3.

10. Cross-cutting concerns

10.1 Parity with repo/jiri: what's kept, what's dropped

Concept repo (Google) jiri (Fuchsia) orbito
Manifest format XML XML TOML
Manifest hosting separate git repo (.repo/manifests.git) separate git repo separate git repo (.orbito/manifest-repo/)
Multi-manifest composition <include> imports [[import]] — local, git-URL, or direct-HTTP-URL (§4.3)
Parallel sync yes (-j) yes (-j) yes (-j, round-robin threads, §5.1)
Pinned/local projects repo forall/manual [projects] ... exclude project.pinned
Per-project hooks repo-hooks (separate mechanism) manifest hooks element project.hooks, run once on first clone
Code review upload repo upload (Gerrit) jiri upload not implementedremote.review is parsed & surfaced only
Cross-project shell command repo forall n/a orbito forall
Cross-project branch repo start n/a orbito branch
Runtime dependency Python Go (compiled) none (static Rust binary)

10.2 Design trade-offs made explicitly

  • No cached/serialized resolved manifest (§3.2): simplicity over micro-optimized re-run speed. Revisit if import chains grow deep enough (many git-hosted imports) that resolution itself becomes slow.
  • Round-robin static partitioning over work-stealing for sync (§5.1): a rayon/work-stealing pool would balance uneven project sizes better, at the cost of a dependency and more complex progress reporting. Deferred until real-world manifests show this mattering.
  • Shelling out to git/curl over libgit2/an HTTP crate: slower process-spawn overhead per operation, in exchange for zero behavioral drift from the user's actual installed tools (§2, principle 1) and a much smaller dependency tree to audit and compile.

10.3 Security considerations

orbito executes code and fetches remote content on behalf of a manifest file that, in the general case, arrived from someone else's git repo. Treat a manifest with the same trust level as a Makefile or justfile you'd cd into and run, not as inert configuration data:

  • project.hooks and orbito forall/orbito branch all invoke sh -c "<string from manifest or CLI>" with no sandboxing, seccomp, or restricted environment — full shell, full user permissions.
  • [[import]] url = "..." (both the direct-HTTP and git-hosted forms) means a manifest can pull in further manifests — and therefore further projects, further hooks — from anywhere the author points it, including at sync time on every run (direct-HTTP imports are re-fetched, not cached). There is currently no checksum/signature pinning on imports (§9) — integrity relies entirely on TLS + trusting the URL's owner.
  • orbito init/orbito sync will happily clone from file:// URLs (used throughout this document's examples and the test suite) — fine for local testing, but note file:// manifest/import URLs on a shared machine mean "trust whatever's on disk," not just "trust the network."

Recommendation for consumers: review a manifest (and its import graph) the same way you'd review a build script before running it, especially before running sync for the first time against a manifest you didn't author.


11. Testing approach (current state)

There is no automated test suite yet (cargo test currently runs zero tests — src/main.rs's implicit unit test harness is empty). Verification to date has been manual/integration-style against locally-created git repos and a local HTTP server, covering:

  • initsyncstatusforallbranch end-to-end against multiple local file:// project repos.
  • Re-sync correctly fast-forwards an already-cloned project.
  • All three [[import]] forms (local-relative, git-hosted URL, direct HTTP URL) in combination within one manifest, including a nested local-relative import inside a fetched manifest.
  • remote.reviewProject.remote_review → printed by orbito manifest end-to-end.

This is tracked as a gap: §9-style edge cases (path collisions, tag/SHA first-clone, import cycles) do not have regression tests and are verified only by code inspection. Adding an integration-test harness that spins up throwaway local git repos (as the manual verification above did by hand) is the natural next step — see §12.


12. Roadmap / open questions

Roughly in order of "next":

  1. Fix sync/branch exit-code inconsistency (§9) — propagate per-project failure into the process exit code, matching forall.
  2. Integration test harness (§11) — turn the manual verification flows above into #[test]s using throwaway local git repos.
  3. Import cycle detection (§9) — track the in-progress Source stack in load_into and error clearly instead of stack-overflowing.
  4. Fix tag/SHA first-clone (§6.3) — clone() should attempt --branch <revision> and fall back to clone + checkout_revision on failure, rather than assuming revision is always a branch.
  5. orbito manifest --freeze — emit a manifest with every project's revision rewritten to its exact current commit SHA, the orbito analogue of repo manifest -r — for reproducible "what exactly did CI build" snapshots.
  6. Path-collision detection (§4.4) — error (or at least warn) instead of silently overwriting when two resolved projects share a path.
  7. orbito upload (§1.2, §10.1) — the Gerrit-style review-upload command remote.review is already threaded through for.
  8. Integrity pinning for URL-based imports (§10.3) — an optional sha256 = "..." field on [[import]] to pin direct-HTTP imports, at minimum.

Appendix A — Glossary

  • Manifest — the TOML file describing remotes, defaults, projects, and imports (§4).
  • Manifest repo — the git repository that hosts the manifest file; cloned into .orbito/manifest-repo/ by orbito init.
  • Project — one entry in the resolved project list; corresponds to exactly one local git checkout under the workspace root.
  • Remote — a named base URL ([[remote]]) that project names are joined onto to form a clone URL.
  • Import — a directive ([[import]]) that merges another manifest's projects into the current resolution (§4.3).
  • Workspace — the directory tree rooted at the .orbito/ you get from orbito init; discovered by walking up from cwd (§7).
  • Pinned project — a project orbito sync will clone but never subsequently move (§4.6).

Appendix B — File reference

Path Purpose
Cargo.toml / Cargo.lock crate manifest; lockfile committed for reproducible builds
src/*.rs implementation (§3.1)
examples/manifest.toml a documented example manifest, also bundled into release archives
README.md user-facing quickstart, command table, install/push instructions
DESIGN.md this document
LICENSE-MIT / LICENSE-APACHE dual license, matching Cargo.toml's license field
.github/workflows/ci.yml build/test/lint on every push/PR (§8.1)
.github/workflows/release.yml tag-triggered multi-platform release builds (§8.2)