diff --git a/src/context/local.rs b/src/context/local.rs index 6e161a8..3ed5c56 100644 --- a/src/context/local.rs +++ b/src/context/local.rs @@ -173,6 +173,15 @@ fn copy_dir_recursive(src: &Path, dest: &Path) -> io::Result<()> { for entry in std::fs::read_dir(src)? { let entry = entry?; let path = entry.path(); + // Never ship VCS metadata into the build tree: its presence + // flips autotools 'building from VCS' detection (see + // is_vcs_dir_name) and activates maintainer-only regeneration + // rules requiring undeclared tools (e.g. help2man). + if path.symlink_metadata().map(|m| m.is_dir()).unwrap_or(false) + && super::is_vcs_dir_name(&entry.file_name()) + { + continue; + } let dest_path = dest.join(entry.file_name()); copy_dir_recursive(&path, &dest_path)?; } diff --git a/src/context/mod.rs b/src/context/mod.rs index 862c217..e0bd280 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -20,6 +20,49 @@ pub fn current() -> Arc { manager::MANAGER.current() } +/// Version-control metadata directories that must never leak into a +/// prepared build tree. +/// +/// Their presence flips autotools' "building from VCS" detection (e.g. GNU +/// hello's `BUILD_FROM_GIT`, triggered by a `.git` directory next to +/// `configure.ac`), which activates maintainer-only regeneration rules +/// requiring tools that are deliberately not declared as build-dependencies +/// (e.g. `help2man`). Source packages produced by dpkg-source never contain +/// them, so package builds must not see them either. +pub(crate) fn is_vcs_dir_name(name: &std::ffi::OsStr) -> bool { + matches!( + name.to_str(), + Some(".git") | Some(".hg") | Some(".svn") | Some(".bzr") | Some("CVS") + ) +} + +/// Recursively remove version-control metadata directories below `root`. +/// +/// Used after an overlay mount, where the source tree is exposed verbatim +/// and entries cannot be filtered during the copy. +pub(crate) fn prune_vcs_dirs(root: &std::path::Path) -> std::io::Result<()> { + let mut pending = vec![root.to_path_buf()]; + while let Some(dir) = pending.pop() { + for entry in std::fs::read_dir(&dir)? { + let entry = entry?; + let path = entry.path(); + // symlink_metadata: never follow symlinks while pruning. + let Ok(meta) = std::fs::symlink_metadata(&path) else { + continue; + }; + if meta.is_dir() { + if is_vcs_dir_name(&entry.file_name()) { + log::debug!("Removing VCS metadata from build tree: {}", path.display()); + std::fs::remove_dir_all(&path)?; + } else { + pending.push(path); + } + } + } + } + Ok(()) +} + #[cfg(test)] mod tests { use super::*; @@ -178,4 +221,43 @@ mod tests { std::path::Path::new("/nonexistent/target") ); } + + /// Copying a source tree into a build context must strip version-control + /// metadata directories at any depth: their presence flips autotools + /// "building from git" detection and activates maintainer-only rules + /// needing undeclared tools (e.g. help2man for GNU hello's man page). + #[test] + fn test_ensure_available_strips_vcs_metadata() { + let temp_dir = tempfile::tempdir().unwrap(); + let src_root = temp_dir.path().join("pkg"); + fs::create_dir_all(src_root.join(".git/objects")).unwrap(); + fs::write(src_root.join(".git/HEAD"), "ref: refs/heads/main").unwrap(); + fs::create_dir_all(src_root.join("src/.svn")).unwrap(); + fs::write(src_root.join("src/hello.c"), "int main() {}").unwrap(); + + let ctx = Context::new(ContextConfig::Local); + let dest = ctx.ensure_available(&src_root, "/tmp").unwrap(); + + assert!(dest.join("src/hello.c").exists()); + assert!(!dest.join(".git").exists()); + assert!(!dest.join("src/.svn").exists()); + } + + /// The overlay-mount path exposes the tree verbatim, so pruning happens + /// after the fact: nested VCS metadata must be removed recursively. + #[test] + fn test_prune_vcs_dirs_removes_nested_metadata() { + let temp_dir = tempfile::tempdir().unwrap(); + let root = temp_dir.path().join("tree"); + fs::create_dir_all(root.join("a/.git/objects")).unwrap(); + fs::create_dir_all(root.join("b/c/CVS")).unwrap(); + fs::write(root.join("a/.git/HEAD"), "ref").unwrap(); + fs::write(root.join("b/keep.txt"), "x").unwrap(); + + prune_vcs_dirs(&root).unwrap(); + + assert!(!root.join("a/.git").exists()); + assert!(!root.join("b/c/CVS").exists()); + assert!(root.join("b/keep.txt").exists()); + } } diff --git a/src/context/ssh.rs b/src/context/ssh.rs index b33d671..d5d5b35 100644 --- a/src/context/ssh.rs +++ b/src/context/ssh.rs @@ -315,6 +315,14 @@ impl SshDriver { let entry = entry?; let path = entry.path(); let name = entry.file_name(); + // Skip VCS metadata (see is_vcs_dir_name): shipping it into + // the remote build tree would flip autotools 'building from + // VCS' detection and activate maintainer-only rules. + if path.symlink_metadata().map(|m| m.is_dir()).unwrap_or(false) + && super::is_vcs_dir_name(&name) + { + continue; + } let dest_path = dest.join(name); Self::upload_recursive(sftp, &path, &dest_path)?; } diff --git a/src/context/unshare.rs b/src/context/unshare.rs index 31937cc..6847dae 100644 --- a/src/context/unshare.rs +++ b/src/context/unshare.rs @@ -55,6 +55,14 @@ fn copy_dir_recursive(src: &Path, dest: &Path) -> io::Result<()> { } }; + // Never ship VCS metadata into the build tree: its presence flips + // autotools 'building from VCS' detection (see is_vcs_dir_name) and + // activates maintainer-only regeneration rules requiring tools that + // are not declared build-dependencies (e.g. help2man). + if metadata.is_dir() && super::is_vcs_dir_name(&entry.file_name()) { + continue; + } + if metadata.file_type().is_symlink() { let target = std::fs::read_link(&src_path).map_err(|e| { io::Error::new( @@ -428,6 +436,20 @@ impl UnshareDriver { .lock() .unwrap() .push(dest_path.to_path_buf()); + + // The overlay exposes the source tree verbatim: strip VCS metadata + // (e.g. '.git') whose presence would flip autotools 'building from + // git' detection and activate maintainer-only regeneration rules + // (see is_vcs_dir_name). Removals land as whiteouts in the upper + // layer, leaving the lowerdir untouched. + if let Err(e) = super::prune_vcs_dirs(dest_path) { + log::warn!( + "Failed to prune VCS metadata from '{}': {}", + dest_path.display(), + e + ); + } + Ok(()) }