From 8c6f6f4028835e4f11e43e2df6a7d3d386343ed0 Mon Sep 17 00:00:00 2001 From: Valentin Haudiquet Date: Mon, 21 Sep 2026 11:52:39 +0200 Subject: [PATCH] distro_info: match changelog suite names with their series Debian packages conventionally target 'unstable' in their debian/changelog distribution field, but the series data (the distro-info CSVs) only knows codenames: the suite is the alias 'unstable' of the series 'sid', a mapping the debian-distro-info tool resolves internally without exposing it in its data. Add a per-dist suite_aliases reference-data key (debian: unstable -> sid), with two helpers on top: resolve_suite_alias, identifying a changelog suite name with its series codename and the dist that codename belongs to, and series_suite_alias, the inverse direction. The two names identify the same series. --- data/distro_info.yml | 10 +++++++ src/distro_info.rs | 64 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/data/distro_info.yml b/data/distro_info.yml index 45a90c3..a3ea840 100644 --- a/data/distro_info.yml +++ b/data/distro_info.yml @@ -28,6 +28,12 @@ ## series (`-updates`, ...). Deliberately not the ## `pockets` key: that one is the *search order* of pull, ## where backports must not fold in. +## suite_aliases: the changelog suite names that alias a series +## codename: Debian packages conventionally target +## 'unstable' where the series data carries 'sid'. +## Mapped suite name -> series codename; the two +## names identify the same series, and the selector +## offers the aliased entry as ' ()'. ## build_profiles: the vendor's default DEB_BUILD_PROFILES (Ubuntu ## activates derivative.ubuntu noudeb, Debian none), ## mirroring what Dpkg::BuildProfiles resolves when the @@ -47,6 +53,10 @@ dist: - updates - security - proposed-updates + # Debian changelogs conventionally target 'unstable'; the series data + # knows the same series as 'sid'. + suite_aliases: + unstable: sid sections: # Valid Section values for debian/control: the Debian policy section # list unioned with the sections observed in the live Ubuntu archive. diff --git a/src/distro_info.rs b/src/distro_info.rs index 11b15ac..9e7fb6c 100644 --- a/src/distro_info.rs +++ b/src/distro_info.rs @@ -87,6 +87,11 @@ struct DistData { cross_pockets: Vec, #[serde(default)] build_profiles: Vec, + /// Changelog suite names aliasing a distro-info series codename + /// ('unstable' for Debian's 'sid'): the two names identify the same + /// series ([`series_suite_alias`], [`resolve_suite_alias`]) + #[serde(default)] + suite_aliases: HashMap, series: SeriesInfo, } @@ -413,6 +418,30 @@ pub async fn get_dist_from_series(series: &str) -> Result Err(format!("Unknown series: {}", series).into()) } +/// The changelog suite name that aliases the series codename of `dist` +/// (Debian's 'unstable' for 'sid'): the two names identify the same +/// series. `None` when the series carries no suite alias. +pub fn series_suite_alias(dist: &str, series: &str) -> Option { + dist_data(dist) + .ok()? + .suite_aliases + .iter() + .find(|(_suite, codename)| codename.as_str() == series) + .map(|(suite, _)| suite.clone()) +} + +/// Identify a changelog suite name with the distro-info series codename +/// it aliases (Debian's 'unstable' is 'sid'), and the dist that codename +/// belongs to. `None` when `suite` is not a known alias of any dist. +pub fn resolve_suite_alias(suite: &str) -> Option<(String, String)> { + for (dist, data) in DATA.dist.iter() { + if let Some(codename) = data.suite_aliases.get(suite) { + return Some((dist.clone(), codename.clone())); + } + } + None +} + /// Get the package pockets available for a given distribution, in search order /// /// The main archive ('') comes first so that a search without an explicit @@ -1035,6 +1064,41 @@ mod tests { assert!(series.contains(&"jammy".to_string())); } + /// Suite aliases identify a changelog suite name with the series + /// codename of the same series: Debian's 'unstable' is 'sid' + #[test] + fn test_suite_aliases() { + assert_eq!( + resolve_suite_alias("unstable"), + Some(("debian".to_string(), "sid".to_string())) + ); + // A series codename or unknown suite is not an alias + assert_eq!(resolve_suite_alias("sid"), None); + assert_eq!(resolve_suite_alias("noble"), None); + assert_eq!( + series_suite_alias("debian", "sid"), + Some("unstable".to_string()) + ); + assert_eq!(series_suite_alias("debian", "trixie"), None); + assert_eq!(series_suite_alias("ubuntu", "noble"), None); + } + + /// Every suite alias must map to a real series of its dist, or the + /// selector would offer a phantom entry + #[tokio::test] + async fn test_suite_aliases_target_real_series() { + for (dist, data) in DATA.dist.iter() { + for (suite, codename) in &data.suite_aliases { + let series = get_ordered_series_name(dist).await.unwrap_or_default(); + assert!( + series.contains(codename), + "suite alias '{suite}' of {dist} maps to '{codename}', \ + which is not a known series" + ); + } + } + } + #[tokio::test] async fn test_get_dist_from_series() { assert_eq!(get_dist_from_series("sid").await.unwrap(), "debian");