distro_info: add UNRELEASED series handling, use it in deb and chlog

This commit is contained in:
2026-09-16 11:15:09 +02:00
parent 4af8dbddb0
commit ae420989f9
3 changed files with 116 additions and 5 deletions
+74
View File
@@ -169,6 +169,16 @@ pub fn supported_dists() -> Vec<String> {
DATA.dist.keys().cloned().collect()
}
/// Special changelog distribution marking an entry that has not been
/// released to any archive series yet
pub const UNRELEASED: &str = "UNRELEASED";
/// Whether `series` is the special [`UNRELEASED`] distribution rather than
/// a real archive series
pub fn is_unreleased(series: &str) -> bool {
series == UNRELEASED
}
/// Get time-ordered list of series information for a distribution, development series first
pub async fn get_ordered_series(dist: &str) -> Result<Vec<SeriesInformation>, Box<dyn Error>> {
let dist_data = DATA.dist.get(dist).ok_or_else(|| {
@@ -206,6 +216,24 @@ pub async fn get_ordered_series_name(dist: &str) -> Result<Vec<String>, Box<dyn
Ok(series.iter().map(|info| info.series.clone()).collect())
}
/// The series to actually target when the changelog says [`UNRELEASED`]:
/// the development series of `dist`, i.e. the first entry of
/// [`get_ordered_series_name`] (which is documented "development series
/// first"). UNRELEASED work conventionally targets the next release, not
/// the last stable one. Any other `series` is returned unchanged. Errors
/// when `dist` is unknown or has no series list.
pub async fn effective_series(series: &str, dist: &str) -> Result<String, Box<dyn Error>> {
if !is_unreleased(series) {
return Ok(series.to_string());
}
get_ordered_series_name(dist)
.await?
.into_iter()
.next()
.ok_or_else(|| format!("Distribution '{}' has no series to target", dist).into())
}
/// Get the latest released series for a dist (excluding future releases and special cases like sid)
pub async fn get_latest_released_series(dist: &str) -> Result<String, Box<dyn Error>> {
let latest = get_n_latest_released_series(dist, 1).await?;
@@ -523,6 +551,52 @@ mod tests {
);
}
#[test]
fn test_is_unreleased() {
// Matching is exact: UNRELEASED is uppercase by Debian convention
assert!(is_unreleased("UNRELEASED"));
assert!(!is_unreleased("unreleased"));
assert!(!is_unreleased("noble"));
assert!(!is_unreleased(""));
}
#[tokio::test]
async fn test_effective_series_passthrough() {
// A real series is returned unchanged, and the dist is not even
// looked up (an unknown dist only matters for UNRELEASED)
assert_eq!(effective_series("noble", "ubuntu").await.unwrap(), "noble");
assert_eq!(effective_series("sid", "debian").await.unwrap(), "sid");
assert_eq!(
effective_series("noble", "unknown-distro").await.unwrap(),
"noble"
);
}
#[tokio::test]
async fn test_effective_series_unreleased() {
// UNRELEASED resolves to the development series of the dist, i.e.
// the first entry of the time-ordered list. On current distro-info
// data this is the next Ubuntu release, while Debian's list starts
// with 'experimental' (sid comes second), so assert against the
// data itself rather than a hardcoded name.
for dist in ["ubuntu", "debian"] {
let ordered = get_ordered_series_name(dist).await.unwrap();
let resolved = effective_series(UNRELEASED, dist).await.unwrap();
assert_eq!(resolved, ordered[0]);
assert_ne!(resolved, UNRELEASED);
}
}
#[tokio::test]
async fn test_effective_series_unknown_dist() {
// UNRELEASED on an unknown distribution cannot be resolved
assert!(
effective_series(UNRELEASED, "unknown-distro")
.await
.is_err()
);
}
#[tokio::test]
async fn test_get_debian_series() {
let series = get_ordered_series_name("debian").await.unwrap();