Skip to content

Commit 477c867

Browse files
committed
cargo-rail: fix(unify): don’t fail --check when only optional features are reported (exit code fuckery)
1 parent 3956e35 commit 477c867

File tree

4 files changed

+55
-11
lines changed

4 files changed

+55
-11
lines changed

.config/rail.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ detect_undeclared_features = true # Detect features borrowed via Cargo unificat
4949
fix_undeclared_features = true # Auto-fix borrowed features (default: true)
5050
skip_undeclared_patterns = ["default", "std", "alloc", "*_backend", "*_impl"] # Features to skip in undeclared detection (glob patterns)
5151
sort_dependencies = true # Sort deps alphabetically (default: true)
52+
enforce_msrv_inheritance = false # Ensure members inherit workspace rust-version
5253

5354

5455
[release]

src/cargo/unify_analyzer.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,9 @@ impl UnifyAnalyzer {
556556
// Run validation
557557
let validation_results = self.validate_targets()?;
558558

559+
// Defensive cleanup: avoid representing "no-op members" as pending changes.
560+
member_edits.retain(|_, edits| !edits.is_empty());
561+
559562
Ok(UnificationPlan {
560563
workspace_deps,
561564
member_edits,

src/cargo/unify_types.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,24 @@ impl UnificationPlan {
260260
self.issues.iter().any(|i| i.severity == IssueSeverity::Error)
261261
}
262262

263+
/// Total number of concrete member edits planned across the workspace.
264+
///
265+
/// Note: `member_edits` may contain empty vectors in edge cases; this returns the
266+
/// actual edit count and is the right basis for "would this mutate files?" checks.
267+
pub fn member_edit_count(&self) -> usize {
268+
self.member_edits.values().map(|v| v.len()).sum()
269+
}
270+
271+
/// Returns true if the plan would mutate manifests or workspace MSRV.
272+
///
273+
/// Intentionally ignores informational-only fields like `optional_features`.
274+
pub fn has_planned_changes(&self, msrv_write_needed: bool) -> bool {
275+
!self.workspace_deps.is_empty()
276+
|| self.member_edit_count() > 0
277+
|| !self.transitive_pins.is_empty()
278+
|| msrv_write_needed
279+
}
280+
263281
/// Generates a human-readable summary of the plan
264282
pub fn summary(&self) -> String {
265283
let mut s = String::new();
@@ -947,4 +965,33 @@ mod tests {
947965
// Should NOT contain "borrowed from" when empty
948966
assert!(!summary.contains("borrowed from"));
949967
}
968+
969+
#[test]
970+
fn test_member_edit_count_ignores_empty_entries() {
971+
let mut member_edits = std::collections::HashMap::new();
972+
member_edits.insert("crate-a".to_string(), vec![]);
973+
974+
let plan = UnificationPlan {
975+
workspace_deps: vec![],
976+
member_edits,
977+
member_paths: std::collections::HashMap::new(),
978+
transitive_pins: vec![],
979+
validation_results: vec![],
980+
issues: vec![],
981+
computed_msrv: None,
982+
duplicates_cleaned: vec![],
983+
pruned_features: vec![],
984+
optional_features: vec![OptionalFeature {
985+
crate_name: "crate-a".to_string(),
986+
feature_name: "serde".to_string(),
987+
enables: vec!["serde/derive".to_string()],
988+
}],
989+
version_mismatches: vec![],
990+
unused_deps: vec![],
991+
undeclared_features: vec![],
992+
};
993+
994+
assert_eq!(plan.member_edit_count(), 0);
995+
assert!(!plan.has_planned_changes(false));
996+
}
950997
}

src/commands/unify.rs

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ pub fn run_unify_analyze(
4848
})).collect::<Vec<_>>(),
4949
"summary": {
5050
"workspace_deps_count": plan.workspace_deps.len(),
51-
"member_edits_count": plan.member_edits.values().map(|v| v.len()).sum::<usize>(),
51+
"member_edits_count": plan.member_edit_count(),
5252
"members_affected": plan.member_edits.len(),
5353
"transitive_pins_count": plan.transitive_pins.len(),
5454
"duplicates_unified": plan.duplicates_cleaned.len(),
@@ -94,10 +94,7 @@ pub fn run_unify_analyze(
9494
}
9595

9696
// Show diff if requested
97-
let has_changes = !plan.member_edits.is_empty()
98-
|| !plan.workspace_deps.is_empty()
99-
|| !plan.transitive_pins.is_empty()
100-
|| msrv_write_needed;
97+
let has_changes = plan.has_planned_changes(msrv_write_needed);
10198
if show_diff && has_changes {
10299
println!("\nplanned changes:\n");
103100

@@ -275,7 +272,7 @@ pub fn run_unify_analyze(
275272
crate::error!("blocking issues prevent unification");
276273
return Err(RailError::message("blocking issues prevent unification"));
277274
} else if has_changes {
278-
let total_edits: usize = plan.member_edits.values().map(|v| v.len()).sum();
275+
let total_edits = plan.member_edit_count();
279276
if !plan.workspace_deps.is_empty() || !plan.transitive_pins.is_empty() || msrv_write_needed {
280277
println!(
281278
"\nready: {} dependencies, {} member edits",
@@ -325,11 +322,7 @@ pub fn run_unify_apply(
325322
return Err(crate::error::RailError::message("blocking issues prevent unification"));
326323
}
327324

328-
if plan.workspace_deps.is_empty()
329-
&& plan.member_edits.is_empty()
330-
&& plan.transitive_pins.is_empty()
331-
&& !msrv_write_needed
332-
{
325+
if !plan.has_planned_changes(msrv_write_needed) {
333326
println!("nothing to unify");
334327
return Ok(());
335328
}

0 commit comments

Comments
 (0)