new: ignore vendored rust artifacts in the generated gitignore
This commit is contained in:
+62
-12
@@ -323,15 +323,25 @@ pub const ROOT_GITIGNORE_ENTRIES: [&str; 6] = [
|
||||
"target/",
|
||||
];
|
||||
|
||||
/// Merge the root `.gitignore` entries into `existing` (the current file
|
||||
/// contents, when there is one): missing entries are appended, an existing
|
||||
/// file is never overwritten just to duplicate entries. Returns the new
|
||||
/// contents, or `None` when nothing has to be written.
|
||||
pub fn merge_root_gitignore(existing: Option<&str>) -> Option<String> {
|
||||
/// Comment heading a root `.gitignore` freshly created by pkh (the skeleton
|
||||
/// build-artifact section).
|
||||
pub const ROOT_GITIGNORE_HEADER: &str = "# pkh build artifacts";
|
||||
|
||||
/// Merge `entries` into the root `.gitignore` contents `existing` (the
|
||||
/// current file contents, when there is one): missing entries are appended,
|
||||
/// an existing file is never overwritten just to duplicate entries. A fresh
|
||||
/// file is headed by the `header` comment when one is given; appending to a
|
||||
/// user file adds bare entries. Returns the new contents, or `None` when
|
||||
/// nothing has to be written.
|
||||
pub fn merge_gitignore_entries(
|
||||
existing: Option<&str>,
|
||||
entries: &[&str],
|
||||
header: Option<&str>,
|
||||
) -> Option<String> {
|
||||
let have: HashSet<&str> = existing
|
||||
.map(|content| content.lines().map(str::trim).collect())
|
||||
.unwrap_or_default();
|
||||
let missing: Vec<&str> = ROOT_GITIGNORE_ENTRIES
|
||||
let missing: Vec<&str> = entries
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|entry| !have.contains(entry))
|
||||
@@ -346,8 +356,11 @@ pub fn merge_root_gitignore(existing: Option<&str>) -> Option<String> {
|
||||
}
|
||||
// Section comment only for a fresh file; appending to a user file adds
|
||||
// bare entries.
|
||||
if existing.is_none() {
|
||||
out.push_str("# pkh build artifacts\n");
|
||||
if existing.is_none()
|
||||
&& let Some(header) = header
|
||||
{
|
||||
out.push_str(header);
|
||||
out.push('\n');
|
||||
}
|
||||
for entry in missing {
|
||||
out.push_str(entry);
|
||||
@@ -746,17 +759,30 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn root_gitignore_merge() {
|
||||
fn gitignore_merge() {
|
||||
// Fresh file: header + all entries.
|
||||
let fresh = merge_root_gitignore(None).unwrap();
|
||||
let fresh =
|
||||
merge_gitignore_entries(None, &ROOT_GITIGNORE_ENTRIES, Some(ROOT_GITIGNORE_HEADER))
|
||||
.unwrap();
|
||||
assert!(fresh.starts_with("# pkh build artifacts\n"));
|
||||
for entry in ROOT_GITIGNORE_ENTRIES {
|
||||
assert!(fresh.contains(entry), "{entry} missing");
|
||||
}
|
||||
|
||||
// A fresh file without a header carries the bare entries.
|
||||
assert_eq!(
|
||||
merge_gitignore_entries(None, &["a/", "b"], None).unwrap(),
|
||||
"a/\nb\n"
|
||||
);
|
||||
|
||||
// Existing file: only the missing entries are appended, nothing lost.
|
||||
let existing = "*.deb\nnode_modules/\n";
|
||||
let merged = merge_root_gitignore(Some(existing)).unwrap();
|
||||
let merged = merge_gitignore_entries(
|
||||
Some(existing),
|
||||
&ROOT_GITIGNORE_ENTRIES,
|
||||
Some(ROOT_GITIGNORE_HEADER),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(merged.starts_with(existing));
|
||||
assert!(merged.contains("*.dsc\n"));
|
||||
assert!(!merged.contains("*.deb\n*.deb"));
|
||||
@@ -766,7 +792,31 @@ mod tests {
|
||||
.iter()
|
||||
.map(|e| format!("{e}\n"))
|
||||
.collect();
|
||||
assert!(merge_root_gitignore(Some(&full)).is_none());
|
||||
assert!(
|
||||
merge_gitignore_entries(
|
||||
Some(&full),
|
||||
&ROOT_GITIGNORE_ENTRIES,
|
||||
Some(ROOT_GITIGNORE_HEADER),
|
||||
)
|
||||
.is_none()
|
||||
);
|
||||
}
|
||||
|
||||
/// The vendoring entries of the rust template merge into an existing
|
||||
/// user `.gitignore` like any other entry set: appended after the
|
||||
/// user's lines, no header comment, idempotent.
|
||||
#[test]
|
||||
fn gitignore_merge_appends_template_entries() {
|
||||
let entries = ["vendor/", ".cargo/config.toml"];
|
||||
let merged =
|
||||
merge_gitignore_entries(Some("# my project\n*.log\n"), &entries, None).unwrap();
|
||||
assert_eq!(merged, "# my project\n*.log\nvendor/\n.cargo/config.toml\n");
|
||||
|
||||
// Already ignored: nothing to write.
|
||||
assert!(
|
||||
merge_gitignore_entries(Some("vendor/\n.cargo/config.toml\n"), &entries, None)
|
||||
.is_none()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+59
-8
@@ -3,7 +3,9 @@
|
||||
//!
|
||||
//! This module orchestrates a scaffold run: target directory checks, project
|
||||
//! detection, in-memory rendering of every file (all-or-nothing write), the
|
||||
//! template post-write hook (e.g. `cargo vendor`), orig tarball creation
|
||||
//! root `.gitignore` merge (skeleton build artifacts plus the template's own
|
||||
//! entries, e.g. the rust vendored layout), the template post-write hook
|
||||
//! (e.g. `cargo vendor`), orig tarball creation
|
||||
//! (from the origin the run decided on — see [`origin`] and [`orig`]), git
|
||||
//! initialization, structural verification and the next-steps message.
|
||||
//! The interactive wizard ([`questions`]) fills a [`options::NewCli`] from
|
||||
@@ -35,8 +37,10 @@ use templates::{OutputFile, ScaffoldOutcome};
|
||||
/// 1. resolve the template from the registry,
|
||||
/// 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),
|
||||
/// 4. write the files all-or-nothing (plus the root `.gitignore` merge:
|
||||
/// the skeleton build-artifact entries in skeleton mode and the
|
||||
/// template's own entries — rust: the vendored layout — in every mode,
|
||||
/// appending to an existing file),
|
||||
/// 5. run the template post-write hook (e.g. `cargo vendor`, so the vendored
|
||||
/// sources land inside the orig tarball created next),
|
||||
/// 6. create the orig tarball (quilt only, refusing overwrites),
|
||||
@@ -171,13 +175,25 @@ fn scaffold_steps(opts: &NewOptions, pb: &ProgressBar) -> Result<ScaffoldOutcome
|
||||
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(
|
||||
// Root .gitignore: the skeleton build-artifact entries in skeleton
|
||||
// mode, plus the template's own entries (rust: the vendored layout) in
|
||||
// every mode — missing ones appended, an existing file never
|
||||
// overwritten.
|
||||
let template_gitignore = template.gitignore_entries(opts);
|
||||
let mut entries: Vec<&str> = Vec::new();
|
||||
let mut header = None;
|
||||
if skeleton {
|
||||
entries.extend(debian::ROOT_GITIGNORE_ENTRIES);
|
||||
header = Some(debian::ROOT_GITIGNORE_HEADER);
|
||||
}
|
||||
entries.extend(template_gitignore.iter().map(String::as_str));
|
||||
if !entries.is_empty()
|
||||
&& let Some(contents) = debian::merge_gitignore_entries(
|
||||
std::fs::read_to_string(target.join(".gitignore"))
|
||||
.ok()
|
||||
.as_deref(),
|
||||
&entries,
|
||||
header,
|
||||
)
|
||||
{
|
||||
std::fs::write(target.join(".gitignore"), contents)?;
|
||||
@@ -502,7 +518,8 @@ mod tests {
|
||||
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
|
||||
// root .gitignore (the shell template contributes none and the
|
||||
// artifact entries are skeleton-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());
|
||||
@@ -662,6 +679,40 @@ mod tests {
|
||||
assert!(config.contains("[source.crates-io]"), "{config}");
|
||||
assert!(config.contains("[net]\noffline = true"), "{config}");
|
||||
}
|
||||
|
||||
// Root .gitignore: the skeleton build-artifact entries plus the
|
||||
// template's vendoring entries (contributed in every mode).
|
||||
let gitignore = std::fs::read_to_string(tree.join(".gitignore")).unwrap();
|
||||
assert!(gitignore.contains("# pkh build artifacts"), "{gitignore}");
|
||||
assert!(gitignore.contains("*.deb"), "{gitignore}");
|
||||
assert!(gitignore.contains("target/"), "{gitignore}");
|
||||
assert!(gitignore.contains("vendor/\n"), "{gitignore}");
|
||||
assert!(gitignore.contains(".cargo/config.toml\n"), "{gitignore}");
|
||||
}
|
||||
|
||||
/// The rust template's vendoring entries land in the root `.gitignore`
|
||||
/// in every mode: a Here-mode tree gets them appended to its existing
|
||||
/// file (custom lines kept, no header comment), while the skeleton-only
|
||||
/// build-artifact entries do not appear.
|
||||
#[test]
|
||||
#[serial]
|
||||
fn scaffold_rust_here_merges_vendoring_gitignore_entries() {
|
||||
let dir = tempdir().unwrap();
|
||||
let tree = dir.path().join("packdir");
|
||||
std::fs::create_dir_all(&tree).unwrap();
|
||||
std::fs::write(tree.join(".gitignore"), "# my project\n*.log\n").unwrap();
|
||||
scaffold_in(&tree, opts(TemplateId::Rust, "mytool", SourceDir::Here)).unwrap();
|
||||
|
||||
let gitignore = std::fs::read_to_string(tree.join(".gitignore")).unwrap();
|
||||
assert!(
|
||||
gitignore.starts_with("# my project\n*.log\n"),
|
||||
"{gitignore}"
|
||||
);
|
||||
assert!(gitignore.contains("vendor/\n"), "{gitignore}");
|
||||
assert!(gitignore.contains(".cargo/config.toml\n"), "{gitignore}");
|
||||
// The build-artifact entries stay skeleton-only.
|
||||
assert!(!gitignore.contains("*.deb"), "{gitignore}");
|
||||
assert!(!gitignore.contains("# pkh build artifacts"), "{gitignore}");
|
||||
}
|
||||
|
||||
/// End-to-end vendored rust quilt package: the vendoring hook creates
|
||||
|
||||
@@ -137,6 +137,15 @@ pub trait Template: Sync {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
/// Root `.gitignore` entries the template contributes in every mode
|
||||
/// (the skeleton-mode build-artifact entries of
|
||||
/// [`crate::new::debian::ROOT_GITIGNORE_ENTRIES`] are separate), merged
|
||||
/// into the root `.gitignore` after the files are written — missing
|
||||
/// ones appended, an existing file never overwritten. Default: none.
|
||||
fn gitignore_entries(&self, _opts: &NewOptions) -> Vec<String> {
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
/// Extra defaults derived from project metadata in `dir` (detect.rs);
|
||||
/// `None` when the project carries nothing this template can read.
|
||||
fn probe(&self, _dir: &Path) -> Option<ProbeResult> {
|
||||
@@ -248,6 +257,48 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
/// Only the rust template contributes root `.gitignore` entries of its
|
||||
/// own — exactly the vendoring pair, and unconditionally (its vendoring
|
||||
/// hook runs in every mode); every other template contributes nothing.
|
||||
#[test]
|
||||
fn gitignore_entries_are_rust_only() {
|
||||
let o = NewOptions {
|
||||
name: "mytool".into(),
|
||||
template: TemplateId::Rust,
|
||||
source_dir: SourceDir::Here,
|
||||
upstream_version: "0.1.0".into(),
|
||||
revision: 1,
|
||||
summary: "A tool".into(),
|
||||
long_description: "A tool".into(),
|
||||
homepage: None,
|
||||
license: crate::new::options::License::Mit,
|
||||
command: "mytool".into(),
|
||||
maintainer: ("Jane".into(), "jane@example.com".into()),
|
||||
dist: "ubuntu".into(),
|
||||
series: "resolute".into(),
|
||||
release: false,
|
||||
depends: Vec::new(),
|
||||
source_format: crate::new::options::SourceFormat::Quilt,
|
||||
orig: None,
|
||||
git: false,
|
||||
autopkgtest: false,
|
||||
pkg_config: false,
|
||||
watch: None,
|
||||
};
|
||||
|
||||
for id in TemplateId::all() {
|
||||
let entries = get(id).unwrap().gitignore_entries(&o);
|
||||
if id == TemplateId::Rust {
|
||||
assert_eq!(
|
||||
entries,
|
||||
vec!["vendor/".to_string(), ".cargo/config.toml".to_string()]
|
||||
);
|
||||
} else {
|
||||
assert!(entries.is_empty(), "{id} contributes {entries:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The final `debian/rules` of every template must be valid-looking
|
||||
/// make: `#!/usr/bin/make -f` shebang, exactly one `%:` target whose
|
||||
/// recipe is the template's dh line, tab-indented recipes only, and no
|
||||
|
||||
@@ -75,6 +75,17 @@ impl Template for Rust {
|
||||
vec!["cargo:native".to_string(), "rustc:native".to_string()]
|
||||
}
|
||||
|
||||
/// Root `.gitignore` entries contributed in every mode: the vendoring
|
||||
/// hook runs unconditionally for this template, so `vendor/` and
|
||||
/// `.cargo/config.toml` exist (or will exist) in every scaffolded rust
|
||||
/// tree and must stay out of the repository. `.cargo/config.toml` is
|
||||
/// the subtle one: it points cargo at `vendor/`, so committing it would
|
||||
/// break plain `cargo build` for anyone cloning the repository without
|
||||
/// the vendored sources.
|
||||
fn gitignore_entries(&self, _opts: &NewOptions) -> Vec<String> {
|
||||
vec!["vendor/".to_string(), ".cargo/config.toml".to_string()]
|
||||
}
|
||||
|
||||
fn architecture(&self, _opts: &NewOptions) -> &'static str {
|
||||
"any"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user