new: scaffold new Debian source packages (non-interactive core)
This commit is contained in:
+589
@@ -0,0 +1,589 @@
|
||||
//! `pkh new`: interactive-first package scaffolding (see
|
||||
//! `plans/pkh-new.md`).
|
||||
//!
|
||||
//! This module orchestrates a scaffold run: target directory checks, project
|
||||
//! detection, in-memory rendering of every file (all-or-nothing write), orig
|
||||
//! tarball creation, git initialization, structural verification and the
|
||||
//! next-steps message. The interactive wizard and the remaining templates
|
||||
//! (python, meson, cmake, autotools, go, rust, makefile) are follow-up work
|
||||
//! built on top of the same [`options::NewOptions`] and
|
||||
//! [`templates::Template`] surface.
|
||||
|
||||
pub mod debian;
|
||||
pub mod detect;
|
||||
pub mod git;
|
||||
pub mod options;
|
||||
pub mod templates;
|
||||
pub mod verify;
|
||||
|
||||
use std::error::Error;
|
||||
use std::time::Duration;
|
||||
|
||||
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
|
||||
|
||||
use options::NewOptions;
|
||||
use templates::OutputFile;
|
||||
|
||||
/// Scaffold a full Debian source tree from `opts`.
|
||||
///
|
||||
/// Steps, aborting early with a pointed error message:
|
||||
/// 1. resolve the template (unimplemented ids fail here, not at parse time),
|
||||
/// 2. check the target directory (refuse an existing `debian/control`),
|
||||
/// 3. render every file in memory and check for collisions,
|
||||
/// 4. write the files all-or-nothing (plus the root `.gitignore` in skeleton
|
||||
/// mode, appending to an existing one),
|
||||
/// 5. create the orig tarball (quilt only, refusing overwrites),
|
||||
/// 6. `git init` unless `--no-git` or already inside a repository,
|
||||
/// 7. run the structural verification,
|
||||
/// 8. print the success message with the next steps.
|
||||
pub fn scaffold(opts: NewOptions, multi: &MultiProgress) -> Result<(), Box<dyn Error>> {
|
||||
let pb = multi.add(ProgressBar::new_spinner());
|
||||
pb.enable_steady_tick(Duration::from_millis(50));
|
||||
pb.set_style(
|
||||
ProgressStyle::default_bar()
|
||||
.template("> {spinner:.blue} {prefix}")
|
||||
.unwrap(),
|
||||
);
|
||||
pb.set_prefix("Scaffolding");
|
||||
|
||||
let result = scaffold_steps(&opts, &pb);
|
||||
|
||||
// Clear the spinner whatever the outcome; errors are reported by the
|
||||
// caller as plain log lines.
|
||||
pb.finish_and_clear();
|
||||
multi.remove(&pb);
|
||||
|
||||
if result.is_ok() {
|
||||
print_success(&opts);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// The scaffold steps proper, reporting progress through `pb`. Nothing is
|
||||
/// written to the filesystem before every file rendered successfully.
|
||||
fn scaffold_steps(opts: &NewOptions, pb: &ProgressBar) -> Result<(), Box<dyn Error>> {
|
||||
// 1. Template resolution: unknown-but-valid ids fail here with the
|
||||
// friendly message instead of a parse error.
|
||||
let template = templates::get(opts.template).ok_or_else(|| {
|
||||
format!(
|
||||
"The '{}' template is not implemented yet. Implemented templates: {}.",
|
||||
opts.template,
|
||||
templates::all()
|
||||
.iter()
|
||||
.map(|t| t.id().as_str())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
)
|
||||
})?;
|
||||
|
||||
// 2. Target directory checks.
|
||||
let cwd = std::env::current_dir()?;
|
||||
let target = opts.target_dir(&cwd);
|
||||
if target.join("debian/control").exists() {
|
||||
return Err(format!(
|
||||
"'{}' already contains a debian/control file: pkh new refuses to \
|
||||
touch an existing Debian packaging tree",
|
||||
target.display()
|
||||
)
|
||||
.into());
|
||||
}
|
||||
match &opts.source_dir {
|
||||
options::SourceDir::Skeleton => {
|
||||
if target.exists() {
|
||||
if !target.is_dir() {
|
||||
return Err(
|
||||
format!("'{}' exists and is not a directory", target.display()).into(),
|
||||
);
|
||||
}
|
||||
if std::fs::read_dir(&target)?.next().is_some() {
|
||||
return Err(format!(
|
||||
"directory '{}' already exists and is not empty: \
|
||||
pkh new refuses to scaffold into it",
|
||||
target.display()
|
||||
)
|
||||
.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
options::SourceDir::Here => {
|
||||
// The cwd always exists.
|
||||
}
|
||||
options::SourceDir::Path(path) => {
|
||||
if !path.is_dir() {
|
||||
return Err(format!(
|
||||
"source directory '{}' does not exist or is not a directory",
|
||||
path.display()
|
||||
)
|
||||
.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fail before writing anything when the orig tarball already exists.
|
||||
if !opts.native
|
||||
&& let Some(tarball) =
|
||||
debian::orig_tarball_path(&target, &opts.name, &opts.upstream_version_no_epoch())
|
||||
&& tarball.exists()
|
||||
{
|
||||
return Err(format!(
|
||||
"'{}' already exists: pkh new refuses to overwrite it. \
|
||||
Remove it first, or pass --native to skip the orig tarball.",
|
||||
tarball.display()
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
// 3. Render everything in memory, then check for collisions (within the
|
||||
// generated set and against existing files).
|
||||
pb.set_message("Rendering files");
|
||||
let skeleton = matches!(opts.source_dir, options::SourceDir::Skeleton);
|
||||
let mut files: Vec<OutputFile> = debian::files(opts, template);
|
||||
if skeleton {
|
||||
files.extend(template.skeleton(opts));
|
||||
}
|
||||
files.extend(template.debian(opts));
|
||||
|
||||
let paths: Vec<String> = files.iter().map(|f| f.path.clone()).collect();
|
||||
options::check_file_collisions(&paths)?;
|
||||
for path in &paths {
|
||||
let existing = target.join(path);
|
||||
if existing.exists() {
|
||||
return Err(format!(
|
||||
"refusing to overwrite existing file '{}'",
|
||||
existing.display()
|
||||
)
|
||||
.into());
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Write the files (all-or-nothing: nothing was written on any error
|
||||
// above).
|
||||
pb.set_message("Writing files");
|
||||
debian::write_files(&target, &files)?;
|
||||
|
||||
// Root .gitignore: skeleton mode only, never overwriting an existing
|
||||
// file (append the missing entries instead).
|
||||
if skeleton
|
||||
&& let Some(contents) = debian::merge_root_gitignore(
|
||||
std::fs::read_to_string(target.join(".gitignore"))
|
||||
.ok()
|
||||
.as_deref(),
|
||||
)
|
||||
{
|
||||
std::fs::write(target.join(".gitignore"), contents)?;
|
||||
}
|
||||
|
||||
// 5. Orig tarball (quilt only).
|
||||
if !opts.native {
|
||||
pb.set_message("Creating orig tarball");
|
||||
debian::create_orig_tarball(&target, &opts.name, &opts.upstream_version_no_epoch())?;
|
||||
}
|
||||
|
||||
// 6. Git.
|
||||
pb.set_message("Initializing git");
|
||||
git::ensure_repository(&target, opts.git)?;
|
||||
|
||||
// 7. Structural verification.
|
||||
pb.set_message("Verifying");
|
||||
verify::verify(&target)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The success message: what was created and the next steps.
|
||||
fn print_success(opts: &NewOptions) {
|
||||
let target =
|
||||
opts.target_dir(&std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from(".")));
|
||||
// `display_path` yields an empty string when the target is the cwd
|
||||
// itself (Here mode): show it as `.`.
|
||||
let display = match crate::ui::display_path(&target) {
|
||||
display if display.is_empty() => ".".to_string(),
|
||||
display => display,
|
||||
};
|
||||
log::info!(
|
||||
"Created {display} — {} ({}-{}) for {}/{}, template '{}'",
|
||||
opts.name,
|
||||
opts.upstream_version,
|
||||
opts.revision,
|
||||
opts.dist,
|
||||
opts.series,
|
||||
opts.template
|
||||
);
|
||||
log::info!("Next steps:");
|
||||
log::info!(" cd {display}");
|
||||
if opts.release {
|
||||
log::info!(
|
||||
" pkh chlog # for later changes; the entry already targets {}",
|
||||
opts.series
|
||||
);
|
||||
} else {
|
||||
log::info!(
|
||||
" pkh chlog # releases the UNRELEASED entry to '{}' when ready",
|
||||
opts.series
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::new::options::{License, SourceDir, TemplateId};
|
||||
use serial_test::serial;
|
||||
use tempfile::tempdir;
|
||||
|
||||
fn opts(template: TemplateId, name: &str, source_dir: SourceDir) -> NewOptions {
|
||||
NewOptions {
|
||||
name: name.to_string(),
|
||||
template,
|
||||
source_dir,
|
||||
upstream_version: "0.1.0".into(),
|
||||
revision: 1,
|
||||
summary: "A tool that does one thing well".into(),
|
||||
long_description: "A tool that does one thing well".into(),
|
||||
homepage: None,
|
||||
license: License::Mit,
|
||||
command: name.to_string(),
|
||||
maintainer: ("Jane Doe".into(), "jane@example.com".into()),
|
||||
dist: "ubuntu".into(),
|
||||
series: "resolute".into(),
|
||||
release: false,
|
||||
depends: Vec::new(),
|
||||
native: false,
|
||||
git: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Run `scaffold` with the cwd changed to `dir` (restored afterwards);
|
||||
/// must run under `#[serial]` because the cwd is process-global.
|
||||
fn scaffold_in(dir: &std::path::Path, opts: NewOptions) -> Result<(), Box<dyn Error>> {
|
||||
let previous = std::env::current_dir()?;
|
||||
std::env::set_current_dir(dir)?;
|
||||
let result = scaffold(opts, &MultiProgress::new());
|
||||
std::env::set_current_dir(previous)?;
|
||||
result
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn scaffold_shell_skeleton_tree() {
|
||||
let dir = tempdir().unwrap();
|
||||
scaffold_in(
|
||||
dir.path(),
|
||||
opts(TemplateId::Shell, "mytool", SourceDir::Skeleton),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let tree = dir.path().join("mytool");
|
||||
|
||||
// Every expected file exists.
|
||||
for path in [
|
||||
"debian/control",
|
||||
"debian/changelog",
|
||||
"debian/rules",
|
||||
"debian/copyright",
|
||||
"debian/source/format",
|
||||
"debian/source/local-options",
|
||||
"debian/.gitignore",
|
||||
"debian/install",
|
||||
"mytool.sh",
|
||||
".gitignore",
|
||||
] {
|
||||
assert!(tree.join(path).exists(), "{path} missing");
|
||||
}
|
||||
|
||||
// rules and the script carry the exec bit.
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
for executable in ["debian/rules", "mytool.sh"] {
|
||||
let mode = std::fs::metadata(tree.join(executable))
|
||||
.unwrap()
|
||||
.permissions()
|
||||
.mode();
|
||||
assert_eq!(mode & 0o777, 0o755, "{executable}");
|
||||
}
|
||||
|
||||
// control re-parses.
|
||||
let control = crate::debian::ControlInfo::parse(&tree.join("debian/control")).unwrap();
|
||||
assert_eq!(control.source_name(), "mytool");
|
||||
assert_eq!(control.binaries[0].get("Architecture"), Some("all"));
|
||||
assert_eq!(
|
||||
control.source.get("Build-Depends"),
|
||||
Some("debhelper-compat (= 13)")
|
||||
);
|
||||
assert!(control.binaries[0].get("Depends").is_none());
|
||||
|
||||
// changelog re-parses: UNRELEASED by default.
|
||||
let (_, version, distribution) =
|
||||
crate::changelog::parse_changelog_header(&tree.join("debian/changelog")).unwrap();
|
||||
assert_eq!(version, "0.1.0-1");
|
||||
assert_eq!(distribution, "UNRELEASED");
|
||||
|
||||
// source/format + local-options.
|
||||
assert_eq!(
|
||||
std::fs::read_to_string(tree.join("debian/source/format")).unwrap(),
|
||||
"3.0 (quilt)\n"
|
||||
);
|
||||
assert_eq!(
|
||||
std::fs::read_to_string(tree.join("debian/source/local-options")).unwrap(),
|
||||
"single-debian-patch\n"
|
||||
);
|
||||
|
||||
// install mapping.
|
||||
assert_eq!(
|
||||
std::fs::read_to_string(tree.join("debian/install")).unwrap(),
|
||||
"mytool.sh usr/bin/mytool\n"
|
||||
);
|
||||
|
||||
// Root .gitignore.
|
||||
let gitignore = std::fs::read_to_string(tree.join(".gitignore")).unwrap();
|
||||
assert!(gitignore.contains("*.deb"));
|
||||
assert!(gitignore.contains("target/"));
|
||||
|
||||
// Orig tarball: contains the skeleton file, excludes debian/.
|
||||
let tarball = dir.path().join("mytool_0.1.0.orig.tar.xz");
|
||||
assert!(tarball.exists());
|
||||
let mut archive = tar::Archive::new(xz2::read::XzDecoder::new(
|
||||
std::fs::File::open(&tarball).unwrap(),
|
||||
));
|
||||
let names: Vec<String> = archive
|
||||
.entries()
|
||||
.unwrap()
|
||||
.map(|entry| {
|
||||
entry
|
||||
.unwrap()
|
||||
.path()
|
||||
.unwrap()
|
||||
.to_string_lossy()
|
||||
.into_owned()
|
||||
})
|
||||
.collect();
|
||||
assert!(
|
||||
names.iter().any(|n| n == "mytool-0.1.0/mytool.sh"),
|
||||
"{names:?}"
|
||||
);
|
||||
assert!(!names.iter().any(|n| n.contains("debian")), "{names:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn scaffold_empty_base_and_metapackage_flavors() {
|
||||
let dir = tempdir().unwrap();
|
||||
// Metapackage flavor: non-empty depends.
|
||||
let mut o = opts(TemplateId::Empty, "metapkg", SourceDir::Skeleton);
|
||||
o.depends = vec!["hello".into(), "hello-data (>= 1.0)".into()];
|
||||
scaffold_in(dir.path(), o).unwrap();
|
||||
|
||||
let tree = dir.path().join("metapkg");
|
||||
let control = crate::debian::ControlInfo::parse(&tree.join("debian/control")).unwrap();
|
||||
assert_eq!(
|
||||
control.binaries[0].get("Depends"),
|
||||
Some("hello,\nhello-data (>= 1.0)")
|
||||
);
|
||||
assert_eq!(control.binaries[0].get("Architecture"), Some("all"));
|
||||
// No install file, no build-system skeleton: the README stub only.
|
||||
assert!(!tree.join("debian/install").exists());
|
||||
assert!(tree.join("README").exists());
|
||||
// The tarball excludes debian/ but carries the README.
|
||||
let tarball = dir.path().join("metapkg_0.1.0.orig.tar.xz");
|
||||
let mut archive = tar::Archive::new(xz2::read::XzDecoder::new(
|
||||
std::fs::File::open(&tarball).unwrap(),
|
||||
));
|
||||
let names: Vec<String> = archive
|
||||
.entries()
|
||||
.unwrap()
|
||||
.map(|entry| {
|
||||
entry
|
||||
.unwrap()
|
||||
.path()
|
||||
.unwrap()
|
||||
.to_string_lossy()
|
||||
.into_owned()
|
||||
})
|
||||
.collect();
|
||||
assert!(
|
||||
names.iter().any(|n| n == "metapkg-0.1.0/README"),
|
||||
"{names:?}"
|
||||
);
|
||||
|
||||
// Empty base flavor: no depends, no Depends field.
|
||||
let dir = tempdir().unwrap();
|
||||
scaffold_in(
|
||||
dir.path(),
|
||||
opts(TemplateId::Empty, "basepkg", SourceDir::Skeleton),
|
||||
)
|
||||
.unwrap();
|
||||
let control =
|
||||
crate::debian::ControlInfo::parse(&dir.path().join("basepkg/debian/control")).unwrap();
|
||||
assert!(control.binaries[0].get("Depends").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn scaffold_release_targets_series() {
|
||||
let dir = tempdir().unwrap();
|
||||
let mut o = opts(TemplateId::Empty, "released", SourceDir::Skeleton);
|
||||
o.release = true;
|
||||
scaffold_in(dir.path(), o).unwrap();
|
||||
|
||||
let (_, _, distribution) =
|
||||
crate::changelog::parse_changelog_header(&dir.path().join("released/debian/changelog"))
|
||||
.unwrap();
|
||||
assert_eq!(distribution, "resolute");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn scaffold_here_mode_packages_existing_dir() {
|
||||
let dir = tempdir().unwrap();
|
||||
// Here mode packages the cwd itself, so the orig tarball lands one
|
||||
// level up (dpkg convention): package a subdirectory of the tempdir
|
||||
// to keep the artifacts inside it.
|
||||
let tree = dir.path().join("packdir");
|
||||
std::fs::create_dir_all(&tree).unwrap();
|
||||
std::fs::write(tree.join("run.sh"), "#!/bin/sh\necho hi\n").unwrap();
|
||||
scaffold_in(&tree, opts(TemplateId::Shell, "runtool", SourceDir::Here)).unwrap();
|
||||
|
||||
// debian/ lands directly in the directory; no skeleton file, no
|
||||
// root .gitignore (skeleton mode only), no debian/install (the
|
||||
// generated one would reference the non-existent skeleton script),
|
||||
// and the existing script is left alone.
|
||||
assert!(tree.join("debian/control").exists());
|
||||
assert!(!tree.join("runtool.sh").exists());
|
||||
assert!(!tree.join(".gitignore").exists());
|
||||
assert!(!tree.join("debian/install").exists());
|
||||
assert_eq!(
|
||||
std::fs::read_to_string(tree.join("run.sh")).unwrap(),
|
||||
"#!/bin/sh\necho hi\n"
|
||||
);
|
||||
// The orig tarball carries the pre-existing script, next to the tree.
|
||||
let tarball = dir.path().join("runtool_0.1.0.orig.tar.xz");
|
||||
let mut archive = tar::Archive::new(xz2::read::XzDecoder::new(
|
||||
std::fs::File::open(&tarball).unwrap(),
|
||||
));
|
||||
let names: Vec<String> = archive
|
||||
.entries()
|
||||
.unwrap()
|
||||
.map(|entry| {
|
||||
entry
|
||||
.unwrap()
|
||||
.path()
|
||||
.unwrap()
|
||||
.to_string_lossy()
|
||||
.into_owned()
|
||||
})
|
||||
.collect();
|
||||
assert!(
|
||||
names.iter().any(|n| n == "runtool-0.1.0/run.sh"),
|
||||
"{names:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn scaffold_refuses_existing_trees_and_artifacts() {
|
||||
let dir = tempdir().unwrap();
|
||||
|
||||
// Existing debian/control.
|
||||
let tree = dir.path().join("mytool");
|
||||
std::fs::create_dir_all(tree.join("debian")).unwrap();
|
||||
std::fs::write(tree.join("debian/control"), "Source: mytool\n").unwrap();
|
||||
let err = scaffold_in(
|
||||
dir.path(),
|
||||
opts(TemplateId::Shell, "mytool", SourceDir::Skeleton),
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(err.to_string().contains("debian/control"), "{err}");
|
||||
|
||||
// Non-empty skeleton target.
|
||||
let dir = tempdir().unwrap();
|
||||
std::fs::create_dir_all(dir.path().join("mytool")).unwrap();
|
||||
std::fs::write(dir.path().join("mytool/junk"), "x").unwrap();
|
||||
let err = scaffold_in(
|
||||
dir.path(),
|
||||
opts(TemplateId::Shell, "mytool", SourceDir::Skeleton),
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(err.to_string().contains("not empty"), "{err}");
|
||||
|
||||
// Existing orig tarball: nothing gets written.
|
||||
let dir = tempdir().unwrap();
|
||||
std::fs::write(dir.path().join("mytool_0.1.0.orig.tar.xz"), b"old").unwrap();
|
||||
let err = scaffold_in(
|
||||
dir.path(),
|
||||
opts(TemplateId::Shell, "mytool", SourceDir::Skeleton),
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(err.to_string().contains("already exists"), "{err}");
|
||||
assert!(!dir.path().join("mytool/debian/control").exists());
|
||||
|
||||
// Missing --source directory.
|
||||
let dir = tempdir().unwrap();
|
||||
let err = scaffold_in(
|
||||
dir.path(),
|
||||
opts(
|
||||
TemplateId::Shell,
|
||||
"mytool",
|
||||
SourceDir::Path(dir.path().join("missing")),
|
||||
),
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(err.to_string().contains("does not exist"), "{err}");
|
||||
|
||||
// Unimplemented template.
|
||||
let dir = tempdir().unwrap();
|
||||
let err = scaffold_in(
|
||||
dir.path(),
|
||||
opts(TemplateId::Rust, "mytool", SourceDir::Skeleton),
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(err.to_string().contains("not implemented yet"), "{err}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn scaffold_native_skips_tarball_and_local_options() {
|
||||
let dir = tempdir().unwrap();
|
||||
let mut o = opts(TemplateId::Shell, "nativepkg", SourceDir::Skeleton);
|
||||
o.native = true;
|
||||
scaffold_in(dir.path(), o).unwrap();
|
||||
|
||||
let tree = dir.path().join("nativepkg");
|
||||
assert!(!dir.path().join("nativepkg_0.1.0.orig.tar.xz").exists());
|
||||
assert!(!tree.join("debian/source/local-options").exists());
|
||||
assert_eq!(
|
||||
std::fs::read_to_string(tree.join("debian/source/format")).unwrap(),
|
||||
"3.0 (native)\n"
|
||||
);
|
||||
}
|
||||
|
||||
/// End-to-end: the scaffolded shell tree passes the real source build
|
||||
/// (`dpkg-source` and friends, same prerequisites as the differential
|
||||
/// tests).
|
||||
#[test]
|
||||
#[serial]
|
||||
fn scaffold_then_source_build_produces_artifacts() {
|
||||
let dir = tempdir().unwrap();
|
||||
scaffold_in(
|
||||
dir.path(),
|
||||
opts(TemplateId::Shell, "mytool", SourceDir::Skeleton),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let output = crate::build::run_source_build(
|
||||
&dir.path().join("mytool"),
|
||||
&crate::build::SourceBuildOptions::default(),
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(output.dsc.exists(), "{:?} missing", output.dsc);
|
||||
assert!(output.buildinfo.exists(), "{:?} missing", output.buildinfo);
|
||||
assert!(output.changes.exists(), "{:?} missing", output.changes);
|
||||
// 3.0 (quilt): the orig tarball plus the debian diff tarball that
|
||||
// dpkg-source generates for the debian/ directory.
|
||||
assert_eq!(output.tarballs.len(), 2, "{:?}", output.tarballs);
|
||||
assert!(output.tarballs[0].exists());
|
||||
assert!(output.tarballs[1].exists());
|
||||
// UNRELEASED: nothing is signed.
|
||||
assert!(!output.signed);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user