11# cargo-rail
22
3- ** Graph-aware monorepo orchestration for Rust workspaces.**
3+ ** Monorepo orchestration for Rust workspaces.**
44
55[ ![ Crates.io] ( https://img.shields.io/crates/v/cargo-rail.svg )] ( https://crates.io/crates/cargo-rail )
66[ ![ License: MIT] ( https://img.shields.io/badge/license-MIT-blue.svg )] ( LICENSE )
@@ -14,174 +14,50 @@ cargo install cargo-rail
1414
1515---
1616
17- ## The Problem
18-
19- Rust monorepos are powerful but painful:
20-
21- - ** CI tests everything** on every PR. 50 crates, 12 minutes, every time.
22- - ** Dependency fragmentation** causes duplicate compilations. You're maintaining a workspace-hack crate or living with the overhead.
23- - ** Extracting crates** means git-filter-repo scripts, lost commit mappings, and manual coordination.
24- - ** Releasing** requires juggling cargo-release, git-cliff, and custom scripts.
25-
26- ** cargo-rail** solves all of these with one tool and 12 dependencies.
27-
28- ---
29-
3017## What It Does
3118
32- ### Test only what changed
19+ ** Test only what changed** — Graph-aware change detection. Auto-detects nextest. 70-90% CI reduction.
3320
3421``` bash
35- $ cargo rail test
36-
37- Changed: crates/core/src/lib.rs
38-
39- Affected crates:
40- core (direct change)
41- api (depends on core)
42- cli (depends on api)
43-
44- Skipped (no dependency path):
45- examples
46- benchmarks
47-
48- Running: cargo nextest run -p core -p api -p cli
22+ cargo rail test
4923```
5024
51- Graph-aware change detection. Auto-detects nextest. ** 70-90% reduction in CI time.**
52-
53- ### Unify dependencies without workspace-hack
25+ ** Unify dependencies** — Resolution-based ` [workspace.dependencies] ` without workspace-hack crates.
5426
5527``` bash
56- $ cargo rail unify
57-
58- Analyzed 3 target triples
59- Unified 23 dependencies to [workspace.dependencies]
60- Pinned 8 transitive-only deps
61- Computed MSRV: 1.75.0
62-
63- Features: minimal set for full functionality
64- Versions: resolved from Cargo' s actual resolver
65- TOML: comments and formatting preserved
28+ cargo rail unify --check # Preview
29+ cargo rail unify # Apply
6630```
6731
68- - **Multi-target resolution** — Analyzes all your target triples, produces one correct dependency graph
69- - **Minimum features** — Intersection of required features, not union. Leaner builds.
70- - **Prune dead features** — Removes features declared but never used (`prune_dead_features = true`)
71- - **Detect & remove unused deps** — Finds deps declared but not in the resolved graph (`detect_unused = true`, `remove_unused = true`)
72- - **Automatic MSRV** — Computes workspace MSRV from dependency requirements (`msrv = true`)
73- - **No workspace-hack crate** — Native `[workspace.dependencies]`, nothing extra
74-
75- ### Split crates with full git history
32+ ** Split crates with history** — Extract crates to standalone repos. Deterministic SHAs, git-notes mapping.
7633
7734``` bash
78- $ cargo rail split run my-crate
79-
80- Extracted my-crate to standalone repo
81- 347 commits preserved
82- Same commit SHAs (deterministic)
83- Commit mapping stored in git-notes
35+ cargo rail split run my-crate
8436```
8537
86- Three modes:
87-
88- | Mode | Use Case |
89- |------|----------|
90- | `single` | One crate → one repo |
91- | `multi` | Multiple crates → one repo (separate directories) |
92- | `workspace` | Multiple crates → one monorepo (with workspace Cargo.toml) |
93-
94- ### Sync with safe defaults
38+ ** Sync bidirectionally** — Monorepo ↔ split repo. Changes from external repos create PR branches.
9539
9640``` bash
97- $ cargo rail sync my-crate
98-
99- Monorepo → split: pushes to main
100- Split → monorepo: creates PR branch (never direct to main)
41+ cargo rail sync my-crate
10142```
10243
103- Bidirectional sync with safety guardrails. Changes from external contributors come in as PRs for review.
104-
105- ### Release with dependency ordering
44+ ** Release with ordering** — Dependency-ordered publishing. Native changelog generation.
10645
10746``` bash
108- $ cargo rail release run --all --bump minor --check
109-
110- Planning release:
111- 1. core 0.4.2 → 0.5.0
112- 2. api 0.3.1 → 0.4.0 (depends on core)
113- 3. cli 0.2.0 → 0.3.0 (depends on api)
114-
115- Changelog generated from conventional commits
116- 117-
118- Run without --check to apply.
47+ cargo rail release run --all --bump minor --check
11948```
12049
121- Native changelog generation. Configurable publish delays. GitHub releases via `gh`.
122-
123- ---
124-
125- ## Real-World Impact
126-
127- `cargo rail affected` on recent commits (HEAD~10):
128-
129- | Repository | Crates | Affected | Skipped | Reduction |
130- |------------|--------|----------|---------|-----------|
131- | [helix](https://github.com/helix-editor/helix) | 13 | 5 | 8 | 62% |
132- | [ripgrep](https://github.com/BurntSushi/ripgrep) | 10 | 4 | 6 | 60% |
133- | [vello](https://github.com/linebender/vello) | 26 | 13 | 13 | 50% |
134- | [tikv](https://github.com/tikv/tikv) | 83 | 42 | 41 | 49% |
135- | [ruff](https://github.com/astral-sh/ruff) | 43 | 23 | 20 | 47% |
136- | [meilisearch](https://github.com/meilisearch/meilisearch) | 19 | 17 | 2 | 11% |
137- | [polars](https://github.com/pola-rs/polars) | 33 | 32 | 1 | 3% |
138-
139- *Results vary by commit range. Monorepos with focused PRs see larger reductions.*
140-
14150---
14251
14352## Why cargo-rail
14453
145- ### 12 dependencies, 98 resolved
146-
147- Compare: cargo-hakari pulls ~40 direct via guppy. release-plz pulls 600+ resolved.
148-
149- Minimal supply chain. Easy to audit. Fast to compile.
150-
151- ### Correct by construction
152-
153- cargo-rail queries Cargo' s actual resolver across all your target triples:
154-
155- ` ` ` toml
156- # crate-a: tokio = "1.0" (needs rt)
157- # crate-b: tokio = "^1.5" (needs net)
158- # crate-c: tokio = "1" (needs sync, linux-only)
159-
160- # cargo-rail resolves across x86_64-linux, aarch64-darwin, x86_64-windows:
161- [workspace.dependencies]
162- tokio = { version = " 1.5" , features = [" rt" , " net" , " sync" ] }
163- ` ` `
164-
165- - ** Versions** : Always the resolved version, not the requested range
166- - ** Features** : Minimum set required across all targets (intersection, not union)
167- - ** MSRV** : Auto-computed from dependency requirements when ` msrv = true`
168-
169- # ## System git, no libgit2
170-
171- Split and sync use the system ` git` binary directly. No libgit2 or gitoxide dependencies:
54+ ** 11 dependencies, ~ 100 resolved.** Compare: cargo-hakari pulls ~ 40 direct via guppy. release-plz pulls 600+ resolved.
17255
173- - Maximum fidelity with your existing git workflows
174- - Deterministic: same input → same commit SHAs
175- - git-notes for rebase-safe commit mapping
56+ ** Multi-target resolution.** Queries Cargo's actual resolver across all your target triples. Computes intersection of required features (not union).
17657
177- # ## Lossless TOML editing
58+ ** System git. ** Uses ` git ` binary directly. No libgit2/gitoxide. Deterministic SHAs. git-notes for rebase-safe commit mapping.
17859
179- Uses ` toml_edit` to preserve comments, formatting, and structure:
180-
181- ` ` ` toml
182- # Your carefully written comments
183- tokio = { version = " 1.5" } # ← stays exactly here
184- ` ` `
60+ ** Lossless TOML.** Uses ` toml_edit ` to preserve comments and formatting.
18561
18662---
18763
@@ -195,110 +71,77 @@ tokio = { version = "1.5" } # ← stays exactly here
19571| ` split ` | Extract crate to standalone repo with history |
19672| ` sync ` | Bidirectional monorepo ↔ split repo sync |
19773| ` release ` | Version bump, changelog, tag, publish |
198- | ` init` | Generate ` rail.toml` configuration |
74+ | ` init ` | Generate ` rail.toml ` |
19975| ` check ` | Validate release readiness |
20076| ` clean ` | Remove generated artifacts |
201- | ` config` | Validate configuration file |
77+ | ` config ` | Validate configuration |
20278
203- Run ` cargo rail < command> --help` for details, or see [docs/commands.md](docs/commands.md)
79+ Run ` cargo rail <command> --help ` for details, or see [ docs/commands.md] ( docs/commands.md ) .
20480
20581---
20682
20783## Configuration
20884
20985``` bash
210- cargo rail init # Auto-detects targets, generates .config/rail.toml
86+ cargo rail init # Generates .config/rail.toml
21187```
21288
213- Generated config with all options and defaults:
89+ Searched in order: ` rail.toml ` , ` .rail.toml ` , ` .cargo/rail.toml ` , ` .config/rail.toml `
21490
21591``` toml
216- # .config/rail.toml (or rail.toml, .rail.toml, .cargo/rail.toml)
217-
218- # Platform targets for multi-target validation (auto-detected from rust-toolchain.toml, .cargo/config.toml, etc.)
219- targets = [" x86_64-unknown-linux-gnu" , " aarch64-apple-darwin" , " x86_64-pc-windows-msvc" ]
92+ targets = [" x86_64-unknown-linux-gnu" , " aarch64-apple-darwin" ]
22093
22194[unify ]
222- include_paths = true # Handle path dependencies (default: true)
223- include_renamed = false # Handle renamed deps with package = "..." (default: false)
224- pin_transitives = false # Pin transitive-only deps - enable for hakari users (default: false)
225- transitive_host = " root" # Where to put pinned transitive dev-deps: "root" or "crates/foo"
226- exclude = [] # Dependencies to skip unification
227- include = [] # Dependencies to force-include
228- msrv = true # Compute workspace MSRV from deps (default: true)
229- strict_version_compat = true # Treat version mismatches as errors (default: true)
230- exact_pin_handling = " warn" # How to handle =x.y.z pins: skip, preserve, warn (default: warn)
231- detect_unused = true # Detect unused dependencies (default: true)
232- remove_unused = true # Auto-remove unused deps (default: true)
233- max_backups = 3 # Number of backup files to keep (default: 3)
234- prune_dead_features = true # Remove features never enabled in graph (default: true)
95+ pin_transitives = false # Enable for hakari/workspace-hack replacement
96+ prune_dead_features = true # Remove features never enabled in graph
97+ detect_unused = true # Find deps not in resolved graph
98+ remove_unused = true # Auto-remove unused deps
99+ msrv = true # Compute workspace MSRV from deps
235100
236101[release ]
237- tag_prefix = " v" # Prefix for tags
238- tag_format = " {crate}-{prefix}{version}" # Tag template: {crate}, {prefix}, {version}
239- require_clean = true # Require clean working directory
240- publish_delay = 5 # Seconds between crates.io publishes
241- create_github_release = false # Create GitHub releases via gh CLI
242- sign_tags = false # Sign tags with GPG/SSH
243- changelog_path = " CHANGELOG.md" # Path to changelog file
244- changelog_relative_to = " crate" # "crate" or "workspace"
245- skip_changelog_for = [] # Crates to skip changelog generation
246- require_changelog_entries = false # Error if no changelog entries
102+ tag_format = " {crate}-{prefix}{version}"
103+ create_github_release = false
104+ publish_delay = 5
247105
248106[change-detection ]
249- # Files that trigger full workspace rebuild
250- infrastructure = [" .github/**" , " scripts/**" , " justfile" , " Makefile" , " *.sh" ]
251- # custom.verify = ["verify/**/*.rs"] # Custom path categories
107+ infrastructure = [" .github/**" , " justfile" ]
252108
253- # Per-crate configuration
254109[crates .my-crate .split ]
255110remote =
" [email protected] :org/my-crate.git" 256111branch = " main"
257- mode = " single" # single, combined, or monorepo
258-
259- [crates.my-crate.release]
260- publish = true
112+ mode = " single"
261113```
262114
115+ Full reference: [ docs/config.md] ( docs/config.md )
116+
117+ ---
118+
119+ ## Real-World Impact
120+
121+ ` cargo rail unify ` on tested monorepos:
122+
123+ | Repository | Crates | Deps Unified | Edits Saved |
124+ | ------------| --------| --------------| -------------|
125+ | [ tikv] ( https://github.com/tikv/tikv ) | 83 | 57 | 516 |
126+ | [ meilisearch] ( https://github.com/meilisearch/meilisearch ) | 19 | 46 | 209 |
127+ | [ helix] ( https://github.com/helix-editor/helix ) | 13 | 16 | 66 |
128+ | [ tokio] ( https://github.com/tokio-rs/tokio ) | 10 | 10 | 35 |
129+ | [ ripgrep] ( https://github.com/BurntSushi/ripgrep ) | 10 | 9 | 35 |
130+
131+ See [ examples/] ( examples/ ) for full results across 12 repositories.
132+
263133---
264134
265135## Replaces
266136
267- | Tool | What cargo-rail does instead |
268- | ------| ------------------------------|
269- | cargo-hakari | Resolution-based unification, no workspace-hack crate |
270- | cargo-hackerman | Same — workspace-hack replacement via ` pin_transitives` |
271- | cargo-udeps | Unused dep detection via ` detect_unused` , removal via ` remove_unused` |
272- | cargo-machete | Same — detects deps not in resolved graph |
273- | cargo-shear | Same — unused dep detection and removal |
274- | cargo-msrv | Auto-compute workspace MSRV via ` msrv = true` |
275- | cargo-release | Dependency-ordered publishing, native changelog |
276- | release-plz | Same, with 600 fewer dependencies |
277- | git-cliff | Built-in conventional commit parsing |
278- | Copybara | Deterministic split/sync with git-notes |
279- | CI shell scripts | Graph-aware ` affected` with nextest integration |
280-
281- # ## cargo-rail vs cargo-hakari
282-
283- | Feature | cargo-hakari | cargo-rail |
284- | ---------| --------------| ------------|
285- | Requires extra crate | Yes (workspace-hack) | No |
286- | Preserves TOML comments | No | Yes |
287- | Multi-target aware | No | Yes |
288- | Feature computation | Union (all features) | Intersection (minimal) |
289- | Dependencies | ~ 40 via guppy | 12 |
290-
291- # ## cargo-rail vs release-plz
292-
293- | Feature | release-plz | cargo-rail |
294- | ---------| -------------| ------------|
295- | Version bumping | Yes | Yes |
296- | Changelog generation | Yes | Yes |
297- | Dependency ordering | Yes | Yes |
298- | GitHub releases | Yes | Yes |
299- | Dependencies | ~ 600 resolved | 12 |
300- | Crate splitting | No | Yes |
301- | Bidirectional sync | No | Yes |
137+ | Tool | cargo-rail equivalent |
138+ | ------| ----------------------|
139+ | cargo-hakari | ` unify ` with ` pin_transitives = true ` |
140+ | cargo-udeps / cargo-machete | ` detect_unused = true ` , ` remove_unused = true ` |
141+ | cargo-msrv | ` msrv = true ` |
142+ | cargo-release / release-plz | ` release ` command |
143+ | git-cliff | Built-in changelog generation |
144+ | Copybara | ` split ` + ` sync ` commands |
302145
303146---
304147
0 commit comments