diff --git a/src/obikmerge/src/merge.rs b/src/obikmerge/src/merge.rs index b204759b..62ff8bef 100644 --- a/src/obikmerge/src/merge.rs +++ b/src/obikmerge/src/merge.rs @@ -514,15 +514,35 @@ fn choose_base(sources: &[&KmerIndex], src_genomes: &[Vec], mode: Me .unwrap() } +/// Copies `src` (the base source's whole `partitions/` tree) into `dst` at +/// bootstrap, before any actual merging happens. `merge_partition` (right +/// after) widens the pre-existing layers' `presence`/`counts` matrix files +/// in place — appending the new sources' genome columns — so those must +/// stay real, independent copies; everything else in a layer directory +/// (`mphf.bin`/`unitigs.bin`/`evidence.bin`/`unitigs.bin.idx`/ +/// `fingerprint.bin`/`layer_meta.json`) is never touched again once copied +/// here, exactly the same "kmer identity vs. data matrix" split +/// `obikselect::select_layer::copy_layer_files` makes — so those are safe +/// to hard-link instead, avoiding a second full copy of a layer's largest +/// files on a big index. Falls back to a real copy per file when linking +/// itself fails (e.g. `src`/`dst` on different filesystems). fn copy_dir_all(src: &Path, dst: &Path) -> io::Result<()> { + copy_dir_all_inner(src, dst, false) +} + +fn copy_dir_all_inner(src: &Path, dst: &Path, force_copy: bool) -> io::Result<()> { fs::create_dir_all(dst)?; for entry in fs::read_dir(src)? { let entry = entry?; let src_path = entry.path(); let dst_path = dst.join(entry.file_name()); if src_path.is_dir() { - copy_dir_all(&src_path, &dst_path)?; - } else { + // Once inside a `presence`/`counts` matrix subdirectory, every + // file below it — however deep — must be a real copy too. + let name = entry.file_name(); + let is_matrix_dir = name == "presence" || name == "counts"; + copy_dir_all_inner(&src_path, &dst_path, force_copy || is_matrix_dir)?; + } else if force_copy || fs::hard_link(&src_path, &dst_path).is_err() { fs::copy(&src_path, &dst_path)?; } }