diff --git a/libips/src/repository/file_backend.rs b/libips/src/repository/file_backend.rs index 2fe2598..21fd6fd 100644 --- a/libips/src/repository/file_backend.rs +++ b/libips/src/repository/file_backend.rs @@ -120,7 +120,7 @@ fn parse_query(query: &str) -> SearchQuery { } } -fn glob_to_regex(pattern: &str) -> String { +pub fn glob_to_regex(pattern: &str) -> String { let mut regex = String::from("^"); for c in pattern.chars() { match c { @@ -1000,7 +1000,7 @@ impl ReadableRepository for FileBackend { self.find_manifests_recursive( &publisher_pkg_dir, &pub_name, - pattern, + pattern.map(glob_to_regex).as_deref(), &mut packages, )?; } diff --git a/libips/src/repository/rest_backend.rs b/libips/src/repository/rest_backend.rs index 50d515e..7b8532e 100644 --- a/libips/src/repository/rest_backend.rs +++ b/libips/src/repository/rest_backend.rs @@ -1218,7 +1218,7 @@ impl RestBackend { .join(&pub_name) }; - let catalog_manager = self.get_catalog_manager(&pub_name)?; + let mut catalog_manager = self.get_catalog_manager(&pub_name)?; let attrs_path = cache_path.join("catalog.attrs"); let attrs_content = fs::read_to_string(&attrs_path).map_err(|e| { @@ -1238,6 +1238,11 @@ impl RestBackend { let mut seen_fmris = HashSet::new(); for part_name in parts.keys() { + // Load part explicitly because CatalogManager doesn't load them automatically + catalog_manager.load_part(part_name).map_err(|e| { + RepositoryError::Other(format!("Failed to load catalog part {}: {}", part_name, e)) + })?; + if let Some(part) = catalog_manager.get_part(part_name) { // Match stems against pattern for (publisher_in_catalog, stems) in &part.packages { @@ -1248,16 +1253,13 @@ impl RestBackend { for (stem, versions) in stems { let matches = if pattern == "*" { true - } else if pattern.contains('*') { - // Basic glob matching (stem matching pattern) - let re_pattern = pattern.replace('*', ".*"); - if let Ok(re) = regex::Regex::new(&format!("^{}$", re_pattern)) { + } else { + let re_str = super::file_backend::glob_to_regex(pattern); + if let Ok(re) = regex::Regex::new(&re_str) { re.is_match(stem) } else { stem == pattern } - } else { - stem == pattern }; if matches { diff --git a/pkg6recv/src/main.rs b/pkg6recv/src/main.rs index 6109423..f18e6e3 100644 --- a/pkg6recv/src/main.rs +++ b/pkg6recv/src/main.rs @@ -6,7 +6,7 @@ use libips::repository::{ }; use miette::{IntoDiagnostic, Result}; use std::path::PathBuf; -use tracing::info; +use tracing::{info, warn}; use tracing_subscriber::{EnvFilter, fmt}; struct ConsoleProgressReporter; @@ -55,22 +55,15 @@ fn main() -> Result<()> { let cli = Cli::parse(); - // Open destination repository - // We'll open it inside each branch to avoid borrow checker issues with moves - - let fmris: Vec = cli - .packages - .iter() - .map(|s| Fmri::parse(s)) - .collect::, _>>() - .into_diagnostic()?; - let progress = ConsoleProgressReporter; // Determine if source is a URL or a path and receive packages if cli.source.starts_with("http://") || cli.source.starts_with("https://") { let source_repo = RestBackend::open(&cli.source).into_diagnostic()?; let dest_repo = FileBackend::open(&cli.dest).into_diagnostic()?; + + let fmris = resolve_packages(&source_repo, cli.publisher.as_deref(), &cli.packages)?; + let mut receiver = PackageReceiver::new(&source_repo, dest_repo); receiver = receiver.with_progress(&progress); receiver @@ -79,6 +72,9 @@ fn main() -> Result<()> { } else { let source_repo = FileBackend::open(&cli.source).into_diagnostic()?; let dest_repo = FileBackend::open(&cli.dest).into_diagnostic()?; + + let fmris = resolve_packages(&source_repo, cli.publisher.as_deref(), &cli.packages)?; + let mut receiver = PackageReceiver::new(&source_repo, dest_repo); receiver = receiver.with_progress(&progress); receiver @@ -90,3 +86,59 @@ fn main() -> Result<()> { Ok(()) } + +fn resolve_packages( + repo: &R, + default_publisher: Option<&str>, + packages: &[String], +) -> Result> { + let mut resolved_fmris = Vec::new(); + + for pkg_str in packages { + if pkg_str.contains('*') || pkg_str.contains('?') { + // It's a pattern, resolve it + info!("Resolving wildcard pattern: {}", pkg_str); + let matched = repo.list_packages(default_publisher, Some(pkg_str)).into_diagnostic()?; + + if matched.is_empty() { + warn!("No packages matched pattern: {}", pkg_str); + } + + // For each matched stem, we probably want the newest version if not specified. + // list_packages returns all versions. PackageReceiver::receive also handles + // FMRIs without versions by picking the newest. + // But list_packages returns full FMRIs. If the pattern matched multiple packages, + // we get all versions of all of them. + + // To be consistent with IPS, if someone says "text/*", they usually want + // the latest version of everything that matches. + + let mut latest_versions: std::collections::HashMap = std::collections::HashMap::new(); + + for pi in matched { + let entry = latest_versions.entry(pi.fmri.name.clone()); + match entry { + std::collections::hash_map::Entry::Occupied(mut oe) => { + if pi.fmri.version() > oe.get().version() { + oe.insert(pi.fmri); + } + } + std::collections::hash_map::Entry::Vacant(ve) => { + ve.insert(pi.fmri); + } + } + } + + for (_, fmri) in latest_versions { + info!("Found package: {}", fmri); + resolved_fmris.push(fmri); + } + } else { + // It's a regular FMRI or package name + let fmri = Fmri::parse(pkg_str).into_diagnostic()?; + resolved_fmris.push(fmri); + } + } + + Ok(resolved_fmris) +}