Skip to content

Commit 08ec44a

Browse files
committed
cargo-rail: updating public facing docs, readme, etc. and fully wiring change-detection (dogfooding)
1 parent 9130377 commit 08ec44a

File tree

6 files changed

+326
-15
lines changed

6 files changed

+326
-15
lines changed

.config/rail.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,14 @@ infrastructure = [
6262
"scripts/**",
6363
"justfile",
6464
"deny.toml",
65+
"rust-toolchain.toml",
66+
"rustfmt.toml",
67+
".config/nextest.toml",
6568
"Cargo.toml",
6669
"Cargo.lock",
6770
]
68-
# custom = {} # Custom path categories (e.g., custom.verify = ["verify/**/*.rs"])
71+
# Custom categories are optional - docs_only is detected automatically
72+
# custom = {} # Example: verify = ["verify/**/*.rs"]
6973

7074

7175
# Split/Sync: Use 'cargo rail split init <crate>' to configure individual crates

.github/workflows/commit.yaml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,36 @@ permissions:
2626
pull-requests: write # Required for PR comments on test results
2727

2828
jobs:
29+
# ==========================================================================
30+
# Job 1: Detect what changed (dogfooding cargo-rail-action)
31+
# ==========================================================================
32+
detect:
33+
name: Detect Changes
34+
runs-on: ubuntu-latest
35+
outputs:
36+
docs-only: ${{ steps.affected.outputs.docs-only }}
37+
rebuild-all: ${{ steps.affected.outputs.rebuild-all }}
38+
count: ${{ steps.affected.outputs.count }}
39+
steps:
40+
- name: Checkout
41+
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
42+
with:
43+
fetch-depth: 0 # Required for change detection
44+
persist-credentials: false
45+
46+
- name: Detect affected crates
47+
id: affected
48+
uses: loadingalias/cargo-rail-action@88618d12427cee84efbe1ef556dd9f8445acad95 # v1.0.2
49+
with:
50+
since: ${{ github.event.before }}
51+
52+
# ==========================================================================
53+
# Job 2: CI (skipped if docs-only)
54+
# ==========================================================================
2955
ci:
3056
name: CI (${{ matrix.target.name }})
57+
needs: detect
58+
if: needs.detect.outputs.docs-only != 'true'
3159
runs-on: ${{ matrix.target.runner }}
3260
timeout-minutes: 30
3361
strategy:

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,6 @@ Thumbs.db
1919
rules.md
2020
**/demo.cast
2121
dx.md
22-
distribution
22+
distribution
23+
issue.md
24+
backlog

README.md

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -160,17 +160,17 @@ cargo rail release run my_crate --bump minor # Execute
160160

161161
Tested on production Rust workspaces:
162162

163-
| Repo | Members | Deps Unified | Notes |
164-
|------|---------|--------------|-------|
165-
| **[tikv](https://github.com/loadingalias/tikv)** | 72 | 61 | Largest stress test |
166-
| **[meilisearch](https://github.com/loadingalias/meilisearch)** | 19 | 46 | Significant unification |
167-
| **[helix-db](https://github.com/loadingalias/helix-db)** | 6 | 18 | Growing project |
168-
| **[helix](https://github.com/loadingalias/helix)** | 12 | 16 | Editor workspace |
169-
| **[tokio](https://github.com/loadingalias/tokio)** | 10 | 10 | Core ecosystem |
170-
| **[ripgrep](https://github.com/loadingalias/ripgrep)** | 10 | 9 | CLI baseline |
171-
| **[polars](https://github.com/loadingalias/polars)** | 8 | 2 | Already clean |
172-
| **[ruff](https://github.com/loadingalias/ruff)** | 43 | 0 | Already unified |
173-
| **[codex](https://github.com/loadingalias/codex)** | 49 | 0 | Already unified |
163+
| Repo | Members | Deps Unified | Dead Features | Notes |
164+
|------|---------|--------------|---------------|-------|
165+
| **[tikv](https://github.com/loadingalias/tikv)** | 72 | 61 | 3 | Largest stress test |
166+
| **[meilisearch](https://github.com/loadingalias/meilisearch)** | 19 | 46 | 1 | Significant unification |
167+
| **[helix-db](https://github.com/loadingalias/helix-db)** | 6 | 18 | 0 | Growing project |
168+
| **[helix](https://github.com/loadingalias/helix)** | 12 | 16 | 1 | Editor workspace |
169+
| **[tokio](https://github.com/loadingalias/tokio)** | 10 | 10 | 0 | Core ecosystem |
170+
| **[ripgrep](https://github.com/loadingalias/ripgrep)** | 10 | 9 | 6 | CLI baseline |
171+
| **[polars](https://github.com/loadingalias/polars)** | 8 | 2 | 9 | Already clean |
172+
| **[ruff](https://github.com/loadingalias/ruff)** | 43 | 0 | 0 | Already unified |
173+
| **[codex](https://github.com/loadingalias/codex)** | 49 | 0 | 0 | Already unified |
174174

175175
Each link above points to a fork with cargo-rail configured. Clone and compare.
176176

docs/architecture.md

Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
# Architecture
2+
3+
## The Big Picture
4+
5+
```
6+
┌─────────────────────────────────────────────────────────────────────────────┐
7+
│ cargo rail <cmd> │
8+
└─────────────────────────────────────────────────────────────────────────────┘
9+
10+
11+
┌─────────────────────────────────────────────────────────────────────────────┐
12+
│ main.rs │
13+
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │
14+
│ │ Parse CLI │ → │ Init output │ → │ Build ctx │ → │ dispatch(cmd) │ │
15+
│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────────┘ │
16+
└─────────────────────────────────────────────────────────────────────────────┘
17+
18+
┌──────────────────────────┼──────────────────────────┐
19+
▼ ▼ ▼
20+
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
21+
│ affected │ │ unify │ │ release │
22+
│ test │ │ split │ │ sync │
23+
└─────────────┘ └─────────────┘ └─────────────┘
24+
│ │ │
25+
└──────────────────────────┼──────────────────────────┘
26+
27+
28+
┌─────────────────────────────────────────────────────────────────────────────┐
29+
│ WorkspaceContext (built once, passed everywhere) │
30+
│ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │
31+
│ │ GitState │ │ CargoState │ │WorkspaceGraph │ │ RailConfig │ │
32+
│ │ (Arc) │ │ (Arc) │ │ (Arc) │ │ (Arc) │ │
33+
│ └───────────────┘ └───────────────┘ └───────────────┘ └───────────────┘ │
34+
└─────────────────────────────────────────────────────────────────────────────┘
35+
│ │ │
36+
▼ ▼ ▼
37+
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
38+
│ src/git/ │ │ src/cargo/ │ │ src/graph/ │
39+
│ system git │ │ metadata │ │ petgraph │
40+
└─────────────┘ │ manifests │ └─────────────┘
41+
│ unify │
42+
└─────────────┘
43+
```
44+
45+
**The one rule that really matters:** WorkspaceContext loads once, passes by reference everywhere else. No command re-loads metadata.
46+
47+
---
48+
49+
## Module Map
50+
51+
| Module | What it does | Key files |
52+
|--------|--------------|-----------|
53+
| `commands/` | CLI handlers (one file = one command) | `cli.rs`, `mod.rs` (dispatch) |
54+
| `workspace/` | WorkspaceContext + state management | `context.rs`, `view.rs`, `change_analyzer.rs` |
55+
| `graph/` | Dependency graph (petgraph) | `core.rs`, `query.rs` |
56+
| `cargo/` | Metadata loading, manifest ops, unify | `multi_target_metadata.rs`, `unify_analyzer.rs` |
57+
| `git/` | System git wrapper | `system.rs`, `ops.rs` |
58+
| `change_detection/` | File classification | `classify.rs`, `presentation.rs` |
59+
| `split/` | Crate extraction with history | `engine.rs` |
60+
| `sync/` | Bidirectional sync | `engine.rs`, `conflict.rs` |
61+
| `release/` | Version, changelog, publish | `planner.rs`, `publisher.rs`, `validator.rs` |
62+
| `config/` | rail.toml parsing | `mod.rs`, per-feature files |
63+
| `backup/` | Undo support | |
64+
| `toml/` | Lossless TOML editing | |
65+
| `output.rs` | Quiet/JSON mode control | |
66+
| `error.rs` | RailError + exit codes | |
67+
68+
---
69+
70+
## Core Type: WorkspaceContext
71+
72+
```rust
73+
pub struct WorkspaceContext {
74+
pub workspace_root: PathBuf,
75+
pub git: Arc<GitState>, // Git pps
76+
pub cargo: Arc<CargoState>, // Cargo metadata (O(1) package lookup)
77+
pub graph: Arc<WorkspaceGraph>, // Dep graph
78+
pub config: Option<Arc<RailConfig>>,
79+
}
80+
```
81+
82+
**Why Arc?** Cheap cloning across threads. Heavy state loaded once, shared freely.
83+
84+
**Access Patterns:**
85+
86+
```rust
87+
// Get changed files
88+
let files = ctx.git.git().changed_files_between(from, to)?;
89+
90+
// Look up a package (O(1))
91+
let pkg = ctx.cargo.get_package("my-crate")?;
92+
93+
// Find transitive dependents
94+
let dependents = ctx.graph.transitive_dependents("my-crate")?;
95+
96+
// Get config (errors if not present)
97+
let config = ctx.require_config()?;
98+
```
99+
100+
---
101+
102+
## Data Flow
103+
104+
### Startup (main.rs)
105+
106+
```
107+
Parse CLI (clap)
108+
109+
Init output mode (quiet/JSON)
110+
111+
Handle early commands (init, undo, sync) ← These don't need full context
112+
113+
Build WorkspaceContext (~100-300ms)
114+
├─ GitState (~5ms)
115+
├─ CargoState (50-200ms, cached)
116+
├─ WorkspaceGraph (10-50ms)
117+
└─ RailConfig (<5ms)
118+
119+
dispatch(cmd, &ctx)
120+
```
121+
122+
### Command Execution
123+
124+
```
125+
dispatch(cmd, &ctx)
126+
127+
Match command → call handler
128+
129+
Handler uses ctx.{git,cargo,graph,config}
130+
131+
Return RailResult<()>
132+
```
133+
134+
Every command follows this pattern:
135+
136+
```rust
137+
pub fn run_whatever(ctx: &WorkspaceContext, args: Args) -> RailResult<()> {
138+
let config = ctx.require_config()?;
139+
// Use ctx.git, ctx.cargo, ctx.graph as needed
140+
// ...
141+
Ok(())
142+
}
143+
```
144+
145+
---
146+
147+
## Change Detection (3 Layers)
148+
149+
```
150+
Layer 1: classify_file(path) → ChangeKind
151+
Pure function. No I/O. Just path inspection.
152+
153+
Layer 2: ChangeImpact::analyze(from, to) → ImpactReport
154+
Uses git + graph + cargo metadata.
155+
156+
Layer 3: ChangeClassifier::classify(files) → ChangeClassification
157+
Applies user config (docs-only, rebuild_all, custom categories).
158+
```
159+
160+
Why layered? Layer 1 is fast and testable. Layer 2 adds graph awareness. Layer 3 adds user preferences.
161+
162+
---
163+
164+
## Unify Pipeline
165+
166+
```
167+
cargo metadata (per target, parallel)
168+
169+
MultiTargetMetadata (merged view)
170+
171+
ManifestAnalyzer (what's used where?)
172+
173+
FeatureScanner (classify features)
174+
175+
UnifyAnalyzer (generate plan)
176+
177+
ManifestWriter (lossless TOML edits)
178+
```
179+
180+
Key insight: operates on **resolved** dependencies (what Cargo chose), not manifest syntax.
181+
182+
---
183+
184+
## Key Design Decisions
185+
186+
| Decision | Why |
187+
|----------|-----|
188+
| System git (no libgit2) | Deterministic SHAs, full git fidelity |
189+
| Resolution-based unification | Accurate to what Cargo actually resolves |
190+
| Lossless TOML (toml_edit) | Preserve comments and formatting |
191+
| Thin main.rs (<100 lines) | All logic testable in library |
192+
| O(1) lookups everywhere | HashMap indexes pre-built at load time |
193+
| Petgraph directly | Own the domain types, no guppy abstraction |
194+
195+
---
196+
197+
## Where to Make Changes
198+
199+
### Adding a new command
200+
201+
1. Define CLI args in `src/commands/cli.rs`
202+
2. Create handler in `src/commands/your_command.rs`
203+
3. Add to dispatch in `src/commands/mod.rs`
204+
4. Handle in `main.rs` if it needs special pre-context handling
205+
206+
### Changing workspace loading
207+
208+
`src/workspace/context.rs`
209+
210+
### Modifying dependency graph logic
211+
212+
`src/graph/core.rs` (structure)
213+
`src/graph/query.rs` (algorithms)
214+
215+
### Adjusting unification
216+
217+
`src/cargo/unify_analyzer.rs` (plan generation)
218+
`src/cargo/manifest_writer.rs` (TOML output)
219+
220+
### Changing change detection
221+
222+
`src/change_detection/classify.rs` (file classification)
223+
`src/workspace/change_analyzer.rs` (impact analysis)
224+
225+
### Modifying split/sync/release
226+
227+
`src/split/engine.rs`
228+
`src/sync/engine.rs`
229+
`src/release/planner.rs`, `publisher.rs`
230+
231+
---
232+
233+
## File → Module Quick Reference
234+
235+
```
236+
"Where do I find..."
237+
238+
affected crates logic → src/commands/affected.rs
239+
→ src/workspace/change_analyzer.rs
240+
241+
test runner → src/commands/test.rs
242+
→ src/test/
243+
244+
dependency unification → src/cargo/unify_*.rs
245+
→ src/commands/unify.rs
246+
247+
split operation → src/split/engine.rs
248+
→ src/commands/split.rs
249+
250+
sync operation → src/sync/engine.rs
251+
→ src/commands/sync.rs
252+
253+
release workflow → src/release/planner.rs
254+
→ src/release/publisher.rs
255+
→ src/commands/release.rs
256+
257+
config loading → src/config/mod.rs
258+
259+
git operations → src/git/system.rs
260+
→ src/git/ops.rs
261+
262+
error handling → src/error.rs
263+
264+
output control → src/output.rs
265+
```
266+
267+
---
268+
269+
## Exit Codes
270+
271+
| Code | Meaning |
272+
|------|---------|
273+
| 0 | Success |
274+
| 1 | Check mode found changes (actionable, not error) |
275+
| 2 | Error |
276+
277+
Exit code 1 lets CI detect "changes needed" vs "something broke". This is honestly not needed and will likely be adjusted in the next major release.

docs/config.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ major_version_conflict = "bump"
137137

138138
**Notes:**
139139

140-
- `major_version_conflict = "bump"` works in ~85% of cases; the remaining ~15% may require code fixes
140+
- In my experience, `major_version_conflict = "bump"` works in most cases; some may require code fixes
141141
- Use `"warn"` for safety, `"bump"` for the leanest build graph
142142

143143
#### Dependency Selection
@@ -718,5 +718,5 @@ None. All configuration is file-based for reproducibility.
718718
## See Also
719719

720720
- [Commands Reference](./commands.md) - All cargo-rail commands
721-
- [Recipes](./recipes.md) - Common workflows and patterns
721+
- [Migration Guide](./migrate-hakari.md) - Migrating from cargo-hakari
722722
- [README](../README.md) - Project overview and quick start

0 commit comments

Comments
 (0)