chlog: number Ubuntu backports with the per-release SRU scheme
--backport was Debian-only: ~bpo is backports.debian.org's scheme and its number the Debian release, so Ubuntu targets were rejected outright. Ubuntu backports have their own documented scheme (Ubuntu version- strings): the development release's version with a per-release ~YY.MM.1 appended, sorting before it (3.1-1ubuntu2 backported to 22.04 becomes 3.1-1ubuntu2~22.04.1; native 3.1 becomes 3.1~22.04.1) and independent of the version the target release carries. The .N increments for subsequent per-release SRU uploads. backport_series_number becomes backport_suffix_for_series: the release number comes from the new generic get_series_release_number (version column of the target series' own distro-info data, leading token kept — "12" for bookworm, "26.04" out of resolute's "26.04 LTS"; empty column as on sid/experimental means None), and the suffix is picked per vendor: ~bpoNN+ for Debian (plain integer releases only), ~YY.MM. for Ubuntu. Unnumbered series still error before anything is written. Also serialize the changelog tests that mutate the process-global DEBFULLNAME/DEBEMAIL variables behind a tokio Mutex: run in parallel they raced each other's identity reads, which started failing intermittently as generate_entry tests accumulated.
This commit is contained in:
+127
-39
@@ -44,9 +44,12 @@ pub enum EntryKind {
|
||||
/// `1.0-1ubuntu1`. Library-only: the CLI has no flag selecting it,
|
||||
/// and targeting an Ubuntu series already picks this numbering.
|
||||
Ubuntu,
|
||||
/// A backport: `1.0-1` becomes `1.0-1~bpo12+1`, where 12 is the Debian
|
||||
/// release number of the target series (derived from it: the series
|
||||
/// must therefore be a numbered Debian release, e.g. not sid)
|
||||
/// A backport: numbered after the vendor of the target series —
|
||||
/// Debian's backports scheme (`1.0-1` becomes `1.0-1~bpo12+1`) or
|
||||
/// Ubuntu's per-release SRU scheme (`3.1-1ubuntu2` becomes
|
||||
/// `3.1-1ubuntu2~24.04.1`). The release number is derived from the
|
||||
/// target series, which must therefore be a numbered release (e.g.
|
||||
/// not sid).
|
||||
Backport,
|
||||
}
|
||||
|
||||
@@ -97,8 +100,8 @@ pub async fn generate_entry(
|
||||
EntryKind::Rebuild => compute_new_version(&old_version, Bump::Rebuild)?,
|
||||
EntryKind::Ubuntu => compute_new_version(&old_version, Bump::Ubuntu)?,
|
||||
EntryKind::Backport => {
|
||||
let number = backport_series_number(&series).await?;
|
||||
compute_new_version(&old_version, Bump::Backport(number))?
|
||||
let suffix = backport_suffix_for_series(&series).await?;
|
||||
compute_new_version(&old_version, Bump::Backport(suffix))?
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -138,8 +141,10 @@ enum Bump {
|
||||
Nmu,
|
||||
/// No-change rebuild: `1.0-1` becomes `1.0-1build1`
|
||||
Rebuild,
|
||||
/// Backport to the Debian release whose series number is carried
|
||||
/// (`"12"` for bookworm): `1.0-1` becomes `1.0-1~bpo12+1`
|
||||
/// Backport to the release whose version suffix is carried
|
||||
/// (`"~bpo12+"` for Debian bookworm, `"~24.04."` for Ubuntu noble):
|
||||
/// `1.0-1` becomes `1.0-1~bpo12+1`, `3.1-1ubuntu2` becomes
|
||||
/// `3.1-1ubuntu2~24.04.1`
|
||||
Backport(String),
|
||||
}
|
||||
|
||||
@@ -159,11 +164,12 @@ fn compute_new_version(
|
||||
increment_suffix(old_version, "+nmu")
|
||||
}
|
||||
}
|
||||
// A re-backport of the same release reuses its `~bpoNN+M` counter,
|
||||
// incrementing M: increment_suffix appends a fresh `~bpoNN+1` when
|
||||
// the version carries no such suffix yet (including when it is a
|
||||
// backport of another release, whose counter is left untouched)
|
||||
Bump::Backport(number) => increment_suffix(old_version, &format!("~bpo{}+", number)),
|
||||
// A re-backport of the same release reuses its suffix counter,
|
||||
// incrementing the trailing number: increment_suffix appends the
|
||||
// suffix with a fresh 1 when the version carries no such suffix
|
||||
// yet (including when it is a backport of another release, whose
|
||||
// counter is left untouched)
|
||||
Bump::Backport(suffix) => increment_suffix(old_version, &suffix),
|
||||
Bump::Normal => increment_suffix(old_version, ""),
|
||||
}
|
||||
}
|
||||
@@ -180,28 +186,47 @@ async fn normal_bump_for_series(series: &str) -> Bump {
|
||||
}
|
||||
}
|
||||
|
||||
/// The Debian release number (e.g. `"12"` for bookworm) that backport
|
||||
/// versions are numbered after (`~bpo12+1`), derived from the target series;
|
||||
/// a backport suite name (`bookworm-backports`) is accepted too. Errors when
|
||||
/// the series has no numeric Debian release number (sid, an Ubuntu series,
|
||||
/// UNRELEASED, unknown): the numbering cannot be derived for it.
|
||||
async fn backport_series_number(series: &str) -> Result<String, Box<dyn std::error::Error>> {
|
||||
/// The version suffix that backport versions are numbered with, derived
|
||||
/// from the vendor of the target series: Debian backports follow the
|
||||
/// backports.debian.org scheme (`~bpo12+1`), Ubuntu backports the
|
||||
/// per-release SRU scheme of the Ubuntu version-strings documentation
|
||||
/// (`3.1-1ubuntu2~22.04.1` for a development version backported to 22.04).
|
||||
/// A backport suite name (`bookworm-backports`) is accepted too. Errors
|
||||
/// when the series has no usable release number (Debian sid or
|
||||
/// experimental, unknown series): the numbering cannot be derived for it.
|
||||
async fn backport_suffix_for_series(series: &str) -> Result<String, Box<dyn std::error::Error>> {
|
||||
let base = series
|
||||
.strip_suffix("-backports-sloppy")
|
||||
.or_else(|| series.strip_suffix("-backports"))
|
||||
.unwrap_or(series);
|
||||
crate::distro_info::get_debian_series_number(base)
|
||||
.await?
|
||||
.filter(|number| !number.is_empty() && number.chars().all(|c| c.is_ascii_digit()))
|
||||
.ok_or_else(|| {
|
||||
format!(
|
||||
"Could not determine the Debian release number of series '{series}', \
|
||||
needed to number the backport version (as in 1.0-1~bpo12+1). \
|
||||
Target a numbered Debian release (e.g. --series bookworm) \
|
||||
or pass the version explicitly with --version."
|
||||
)
|
||||
.into()
|
||||
})
|
||||
let unnumbered = || {
|
||||
format!(
|
||||
"Could not determine the release number of series '{series}', \
|
||||
needed to number the backport version (Debian ~bpo12+1, \
|
||||
Ubuntu ~24.04.1). Target a released Debian or Ubuntu series \
|
||||
or pass the version explicitly with --version."
|
||||
)
|
||||
};
|
||||
let Some((dist, number)) = crate::distro_info::get_series_release_number(base).await? else {
|
||||
return Err(unnumbered().into());
|
||||
};
|
||||
match dist.as_str() {
|
||||
// backports.debian.org numbers with the plain integer release number
|
||||
"debian" if !number.is_empty() && number.chars().all(|c| c.is_ascii_digit()) => {
|
||||
Ok(format!("~bpo{}+", number))
|
||||
}
|
||||
// Ubuntu numbers per release as YY.MM; the trailing `.N` counts
|
||||
// the per-release SRU uploads
|
||||
"ubuntu"
|
||||
if number.split('.').count() == 2
|
||||
&& number
|
||||
.split('.')
|
||||
.all(|part| !part.is_empty() && part.chars().all(|c| c.is_ascii_digit())) =>
|
||||
{
|
||||
Ok(format!("~{}.", number))
|
||||
}
|
||||
_ => Err(unnumbered().into()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Increment a version number by 1, for a given suffix
|
||||
@@ -572,6 +597,12 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
/// Serializes the tests that mutate the process-global DEBFULLNAME /
|
||||
/// DEBEMAIL variables: run in parallel, they otherwise race each
|
||||
/// other's identity reads and assertions.
|
||||
static IDENTITY_LOCK: std::sync::LazyLock<tokio::sync::Mutex<()>> =
|
||||
std::sync::LazyLock::new(|| tokio::sync::Mutex::new(()));
|
||||
|
||||
fn setup_repo(dir: &Path) {
|
||||
Command::new("git")
|
||||
.arg("init")
|
||||
@@ -641,6 +672,7 @@ mod tests {
|
||||
commit(repo_dir, "Add feature B");
|
||||
|
||||
// Generate entry
|
||||
let _identity = IDENTITY_LOCK.lock().await;
|
||||
unsafe {
|
||||
std::env::set_var("DEBFULLNAME", "Maintainer Maintainer");
|
||||
std::env::set_var("DEBEMAIL", "maintainer@maintainer.com");
|
||||
@@ -730,27 +762,47 @@ mod tests {
|
||||
"1.0-1.3"
|
||||
);
|
||||
|
||||
// Backport
|
||||
// Backport, Debian scheme
|
||||
assert_eq!(
|
||||
compute_new_version("1.0-1", Bump::Backport("12".to_string())).unwrap(),
|
||||
compute_new_version("1.0-1", Bump::Backport("~bpo12+".to_string())).unwrap(),
|
||||
"1.0-1~bpo12+1"
|
||||
);
|
||||
// Re-backporting the same source reuses the counter
|
||||
assert_eq!(
|
||||
compute_new_version("1.0-1~bpo12+1", Bump::Backport("12".to_string())).unwrap(),
|
||||
compute_new_version("1.0-1~bpo12+1", Bump::Backport("~bpo12+".to_string())).unwrap(),
|
||||
"1.0-1~bpo12+2"
|
||||
);
|
||||
// Native packages backport too
|
||||
assert_eq!(
|
||||
compute_new_version("1.0", Bump::Backport("12".to_string())).unwrap(),
|
||||
compute_new_version("1.0", Bump::Backport("~bpo12+".to_string())).unwrap(),
|
||||
"1.0~bpo12+1"
|
||||
);
|
||||
// A version carrying another release's counter gains a fresh one
|
||||
assert_eq!(
|
||||
compute_new_version("1.0-1~bpo11+1", Bump::Backport("12".to_string())).unwrap(),
|
||||
compute_new_version("1.0-1~bpo11+1", Bump::Backport("~bpo12+".to_string())).unwrap(),
|
||||
"1.0-1~bpo11+1~bpo12+1"
|
||||
);
|
||||
|
||||
// Backport, Ubuntu scheme (Ubuntu version-strings documentation)
|
||||
assert_eq!(
|
||||
compute_new_version("3.1-1ubuntu2", Bump::Backport("~22.04.".to_string())).unwrap(),
|
||||
"3.1-1ubuntu2~22.04.1"
|
||||
);
|
||||
// Subsequent per-release SRU uploads increment the counter
|
||||
assert_eq!(
|
||||
compute_new_version(
|
||||
"3.1-1ubuntu2~22.04.1",
|
||||
Bump::Backport("~22.04.".to_string())
|
||||
)
|
||||
.unwrap(),
|
||||
"3.1-1ubuntu2~22.04.2"
|
||||
);
|
||||
// Native packages
|
||||
assert_eq!(
|
||||
compute_new_version("3.1", Bump::Backport("~22.04.".to_string())).unwrap(),
|
||||
"3.1~22.04.1"
|
||||
);
|
||||
|
||||
// Native package uploads
|
||||
assert_eq!(compute_new_version("1.0", Bump::Normal).unwrap(), "1.1");
|
||||
assert_eq!(compute_new_version("1.0.5", Bump::Normal).unwrap(), "1.0.6");
|
||||
@@ -794,6 +846,7 @@ mod tests {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let _identity = IDENTITY_LOCK.lock().await;
|
||||
unsafe {
|
||||
std::env::set_var("DEBFULLNAME", "Maintainer Maintainer");
|
||||
std::env::set_var("DEBEMAIL", "maintainer@maintainer.com");
|
||||
@@ -818,9 +871,9 @@ mod tests {
|
||||
assert!(content.contains("mypackage (1.0-1~bpo12+1) bookworm; urgency=medium"));
|
||||
}
|
||||
|
||||
/// Backport numbering needs a numeric Debian release number: series
|
||||
/// without one (Ubuntu series, sid, ...) are rejected before anything
|
||||
/// is written, pointing at --series/--version instead.
|
||||
/// Backport numbering needs a usable release number: Debian's rolling
|
||||
/// series (sid) carry none and are rejected before anything is written,
|
||||
/// pointing at --series/--version instead.
|
||||
#[tokio::test]
|
||||
async fn test_generate_entry_backport_requires_numbered_series() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
@@ -837,7 +890,7 @@ mod tests {
|
||||
"debian/changelog",
|
||||
Some(repo_dir),
|
||||
None,
|
||||
Some("noble"),
|
||||
Some("sid"),
|
||||
EntryKind::Backport,
|
||||
)
|
||||
.await;
|
||||
@@ -848,6 +901,40 @@ mod tests {
|
||||
assert!(content.starts_with("mypackage (1.0-1) unstable"));
|
||||
}
|
||||
|
||||
/// Ubuntu backports follow the per-release SRU scheme of the Ubuntu
|
||||
/// version-strings documentation: the development release's version
|
||||
/// with ~YY.MM.1 appended, independently of what the target release
|
||||
/// carries. Relies on the host distro-info data mapping noble to
|
||||
/// 24.04 (see the distro_info tests).
|
||||
#[tokio::test]
|
||||
async fn test_generate_entry_backport_ubuntu_numbering() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let repo_dir = temp_dir.path();
|
||||
setup_repo(repo_dir);
|
||||
let changelog_path = repo_dir.join("debian/changelog");
|
||||
std::fs::create_dir_all(repo_dir.join("debian")).unwrap();
|
||||
std::fs::write(
|
||||
&changelog_path,
|
||||
"mypackage (3.1-1ubuntu2) questing; urgency=medium\n\n * Initial release\n\n -- Maintainer <m@e.com> Wed, 01 Jan 2020 00:00:00 +0000\n",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let entry = generate_entry(
|
||||
"debian/changelog",
|
||||
Some(repo_dir),
|
||||
None,
|
||||
Some("noble"),
|
||||
EntryKind::Backport,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(entry.new_version, "3.1-1ubuntu2~24.04.1");
|
||||
assert_eq!(entry.series, "noble");
|
||||
let content = std::fs::read_to_string(&changelog_path).unwrap();
|
||||
assert!(content.contains("mypackage (3.1-1ubuntu2~24.04.1) noble; urgency=medium"));
|
||||
}
|
||||
|
||||
/// A regular upload is numbered after the vendor of its target series:
|
||||
/// a Debian-style version uploaded to an Ubuntu series gains the
|
||||
/// ubuntu1 suffix, and re-bumping the now-Ubuntu changelog increments
|
||||
@@ -895,6 +982,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_get_maintainer_info() {
|
||||
let _identity = IDENTITY_LOCK.blocking_lock();
|
||||
// Test with env vars
|
||||
unsafe {
|
||||
std::env::set_var("DEBFULLNAME", "Env Name");
|
||||
|
||||
@@ -712,6 +712,30 @@ pub async fn get_debian_series_number(series: &str) -> Result<Option<String>, Bo
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// The release number of a distribution series, paired with the dist it
|
||||
/// belongs to: the version column of the series data, stripped to its
|
||||
/// leading token ("12" for Debian bookworm, "26.04" out of Ubuntu
|
||||
/// resolute's "26.04 LTS"). `None` when the series carries no version at
|
||||
/// all (Debian's rolling sid/experimental have an empty column;
|
||||
/// pseudo-versions like "unstable" pass through, callers validate per
|
||||
/// vendor). Errors when no known distribution carries the series.
|
||||
pub async fn get_series_release_number(
|
||||
series: &str,
|
||||
) -> Result<Option<(String, String)>, Box<dyn Error>> {
|
||||
let dist = get_dist_from_series(series).await?;
|
||||
for info in get_ordered_series(&dist).await? {
|
||||
if info.series == series {
|
||||
let number = info
|
||||
.version
|
||||
.as_deref()
|
||||
.and_then(|version| version.split_whitespace().next())
|
||||
.map(str::to_string);
|
||||
return Ok(number.map(|number| (dist, number)));
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -1033,6 +1057,25 @@ mod tests {
|
||||
assert!(unknown_number.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_series_release_number() {
|
||||
let (dist, bookworm) = get_series_release_number("bookworm")
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(dist, "debian");
|
||||
assert_eq!(bookworm, "12");
|
||||
|
||||
// Ubuntu LTS rows carry a " LTS" decoration: only the leading
|
||||
// YY.MM token is the release number
|
||||
let (dist, noble) = get_series_release_number("noble").await.unwrap().unwrap();
|
||||
assert_eq!(dist, "ubuntu");
|
||||
assert_eq!(noble, "24.04");
|
||||
|
||||
// No known dist carries the series
|
||||
assert!(get_series_release_number("not-a-series").await.is_err());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_keyring_urls_sid() {
|
||||
// Test that 'sid' returns keyrings from the 3 latest released versions
|
||||
|
||||
+1
-1
@@ -176,7 +176,7 @@ fn main() {
|
||||
Command::new("chlog")
|
||||
.about("Auto-generate changelog entry, editing it, committing it afterwards")
|
||||
.arg(arg!(-s --series <series> "Target distribution series").required(false))
|
||||
.arg(arg!(--backport "Number the entry as a backport of the target series (1.0-1 becomes 1.0-1~bpo12+1)").required(false)
|
||||
.arg(arg!(--backport "Number the entry as a backport of the target series (Debian: 1.0-1 becomes 1.0-1~bpo12+1; Ubuntu: 3.1-1ubuntu2 becomes 3.1-1ubuntu2~24.04.1)").required(false)
|
||||
.conflicts_with_all(["nmu", "rebuild"]))
|
||||
.arg(arg!(--nmu "Number the entry as a non-maintainer upload (1.0-1 becomes 1.0-1.1, native 1.0 becomes 1.0+nmu1)").required(false)
|
||||
.conflicts_with("rebuild"))
|
||||
|
||||
Reference in New Issue
Block a user