build: only redistribute the orig tarball on new upstream (-si)
dpkg-genchanges includes the upstream tarballs in the .changes only when the upload brings a new upstream: no previous changelog entry, a changed upstream version or a renamed source. On a plain revision bump the tarball already sits in the archive, and dpkg strips it (and its .asc) from the distribution set. pkh's native source pipeline listed every .dsc-referenced tarball unconditionally, making every upload re-ship the orig. Implement the dpkg source styles as --orig auto|always|never (auto being the -si default; always/never are -sa/-sd), stripping the tarballs out of the changes, buildinfo-free checksum set and artifact list like dpkg, with the explicit 'never' ignored for native packages. Comparison uses the epoch-less upstream version, exactly like dpkg's version(). Differential tests against real dpkg cover revision bumps, new upstream versions and both forced styles.
This commit is contained in:
@@ -4,10 +4,58 @@
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use super::OrigSourceMode;
|
||||
use crate::debian::changelog::ChangelogEntry;
|
||||
use crate::debian::checksums::FileChecksums;
|
||||
use crate::debian::control::{Paragraph, write_paragraph};
|
||||
use crate::debian::files::FilesList;
|
||||
|
||||
/// Compression suffixes dpkg recognizes on source tarballs.
|
||||
const TARBALL_COMPRESSIONS: &[&str] = &[".gz", ".bz2", ".xz", ".lzma", ".zst"];
|
||||
|
||||
/// Whether this `.dsc`-listed file is an upstream orig tarball
|
||||
/// (`*.orig.tar.<ext>` or a component tarball `*.orig-<c>.tar.<ext>`),
|
||||
/// mirroring dpkg-genchanges' strip pattern `\.orig(-.+)?\.tar\.$ext`.
|
||||
pub fn is_orig_tarball(name: &str) -> bool {
|
||||
TARBALL_COMPRESSIONS.iter().any(|ext| {
|
||||
name.strip_suffix(ext)
|
||||
.and_then(|s| s.strip_suffix(".tar"))
|
||||
.is_some_and(|stem| stem.ends_with(".orig") || stem.contains(".orig-"))
|
||||
})
|
||||
}
|
||||
|
||||
/// Whether this `.dsc`-listed file is the Debian part of the source package
|
||||
/// (`*.debian.tar.<ext>` for the 3.0 formats, `*.diff.<ext>` for 1.0).
|
||||
pub fn is_debian_tarball_or_diff(name: &str) -> bool {
|
||||
TARBALL_COMPRESSIONS.iter().any(|ext| {
|
||||
name.ends_with(&format!(".debian.tar{ext}")) || name.ends_with(&format!(".diff{ext}"))
|
||||
})
|
||||
}
|
||||
|
||||
/// Whether the upload redistributes the upstream tarballs, mirroring the
|
||||
/// dpkg-genchanges source styles: `Always`/`Never` are the forced
|
||||
/// `-sa`/`-sd`, while `Auto` is the default `-si` — include them only when
|
||||
/// there is no previous changelog entry (first upload) or the source name or
|
||||
/// upstream version changed since it. Like dpkg, the comparison uses the
|
||||
/// epoch-less upstream version: a plain revision bump reuses the tarball
|
||||
/// already in the archive.
|
||||
pub fn include_orig_tarball(
|
||||
mode: OrigSourceMode,
|
||||
current: &ChangelogEntry,
|
||||
previous: Option<&ChangelogEntry>,
|
||||
) -> bool {
|
||||
match mode {
|
||||
OrigSourceMode::Always => true,
|
||||
OrigSourceMode::Never => false,
|
||||
OrigSourceMode::Auto => match previous {
|
||||
None => true,
|
||||
Some(prev) => {
|
||||
prev.source != current.source || prev.version.upstream != current.version.upstream
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Everything needed to render a `.changes` file.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ChangesInput {
|
||||
@@ -159,6 +207,91 @@ pub fn save_changes(path: &Path, paragraph: &Paragraph) -> Result<(), Box<dyn st
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn orig_tarball_detection() {
|
||||
assert!(is_orig_tarball("pkg_1.0.orig.tar.gz"));
|
||||
assert!(is_orig_tarball("pkg_1.0.orig.tar.xz"));
|
||||
assert!(is_orig_tarball("pkg_1.0.orig.tar.zst"));
|
||||
assert!(is_orig_tarball("pkg_1.0~rc1.orig.tar.bz2"));
|
||||
// Component tarballs.
|
||||
assert!(is_orig_tarball("pkg_1.0.orig-docs.tar.xz"));
|
||||
assert!(is_orig_tarball("pkg_1.0.orig-vendor.tar.gz"));
|
||||
// Not orig tarballs.
|
||||
assert!(!is_orig_tarball("pkg_1.0.debian.tar.xz"));
|
||||
assert!(!is_debian_tarball_or_diff("pkg_1.0.orig.tar.xz"));
|
||||
assert!(!is_orig_tarball("pkg_1.0.tar.xz")); // native tarball
|
||||
assert!(!is_orig_tarball("pkg_1.0.dsc"));
|
||||
assert!(!is_orig_tarball("pkg_1.0.orig.tar")); // no compression suffix
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn debian_tarball_detection() {
|
||||
assert!(is_debian_tarball_or_diff("pkg_1.0.debian.tar.xz"));
|
||||
assert!(is_debian_tarball_or_diff("pkg_1.0.diff.gz"));
|
||||
assert!(!is_debian_tarball_or_diff("pkg_1.0.orig.tar.xz"));
|
||||
assert!(!is_debian_tarball_or_diff("pkg_1.0.tar.xz"));
|
||||
}
|
||||
|
||||
/// Build a minimal changelog entry for one source/version pair.
|
||||
fn entry(src: &str, ver: &str) -> ChangelogEntry {
|
||||
crate::debian::changelog::parse_changelog_entries_from_str(
|
||||
&format!(
|
||||
"{src} ({ver}) unstable; urgency=medium\n\n * x\n\n \
|
||||
-- A B <a@b.c> Thu, 01 Jan 2026 00:00:00 +0000\n"
|
||||
),
|
||||
Some(1),
|
||||
)
|
||||
.unwrap()
|
||||
.remove(0)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn orig_inclusion_matrix() {
|
||||
let cur = entry("pkg", "1.4-2");
|
||||
let prev_same_upstream = entry("pkg", "1.4-1");
|
||||
let prev_new_upstream = entry("pkg", "2.0-1");
|
||||
let prev_renamed = entry("renamed", "1.4-1");
|
||||
|
||||
// -sa / -sd force the outcome.
|
||||
assert!(include_orig_tarball(
|
||||
OrigSourceMode::Always,
|
||||
&cur,
|
||||
Some(&prev_same_upstream)
|
||||
));
|
||||
assert!(!include_orig_tarball(
|
||||
OrigSourceMode::Never,
|
||||
&cur,
|
||||
Some(&prev_new_upstream)
|
||||
));
|
||||
|
||||
// -si: first upload includes; a revision bump excludes; a new
|
||||
// upstream version or a renamed source includes.
|
||||
assert!(include_orig_tarball(OrigSourceMode::Auto, &cur, None));
|
||||
assert!(!include_orig_tarball(
|
||||
OrigSourceMode::Auto,
|
||||
&cur,
|
||||
Some(&prev_same_upstream)
|
||||
));
|
||||
assert!(include_orig_tarball(
|
||||
OrigSourceMode::Auto,
|
||||
&cur,
|
||||
Some(&prev_new_upstream)
|
||||
));
|
||||
assert!(include_orig_tarball(
|
||||
OrigSourceMode::Auto,
|
||||
&cur,
|
||||
Some(&prev_renamed)
|
||||
));
|
||||
|
||||
// The epoch is not part of the comparison, like dpkg's version().
|
||||
let cur_epoch = entry("pkg", "2:1.4-2");
|
||||
assert!(!include_orig_tarball(
|
||||
OrigSourceMode::Auto,
|
||||
&cur_epoch,
|
||||
Some(&prev_same_upstream)
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn description_formatting() {
|
||||
assert_eq!(
|
||||
|
||||
Reference in New Issue
Block a user