Skip to content
This repository was archived by the owner on Feb 7, 2026. It is now read-only.

Commit e4f49cd

Browse files
committed
Refactor: Introduce resolve_packages for wildcard handling and enhance repository package resolution
- Added `resolve_packages` to centralize wildcard pattern processing and match the latest package versions consistently. - Updated both `FileBackend` and `RestBackend` to support wildcard patterns via `glob_to_regex` logic. - Made `glob_to_regex` public to facilitate regex conversion for glob patterns. - Improved `catalog_manager` handling in `RestBackend` by explicitly loading catalog parts to ensure accurate package matching. - Replaced redundant FMRI parsing logic with `resolve_packages` for cleaner and more maintainable code.
1 parent f806836 commit e4f49cd

3 files changed

Lines changed: 74 additions & 20 deletions

File tree

libips/src/repository/file_backend.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ fn parse_query(query: &str) -> SearchQuery {
120120
}
121121
}
122122

123-
fn glob_to_regex(pattern: &str) -> String {
123+
pub fn glob_to_regex(pattern: &str) -> String {
124124
let mut regex = String::from("^");
125125
for c in pattern.chars() {
126126
match c {
@@ -1000,7 +1000,7 @@ impl ReadableRepository for FileBackend {
10001000
self.find_manifests_recursive(
10011001
&publisher_pkg_dir,
10021002
&pub_name,
1003-
pattern,
1003+
pattern.map(glob_to_regex).as_deref(),
10041004
&mut packages,
10051005
)?;
10061006
}

libips/src/repository/rest_backend.rs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1218,7 +1218,7 @@ impl RestBackend {
12181218
.join(&pub_name)
12191219
};
12201220

1221-
let catalog_manager = self.get_catalog_manager(&pub_name)?;
1221+
let mut catalog_manager = self.get_catalog_manager(&pub_name)?;
12221222

12231223
let attrs_path = cache_path.join("catalog.attrs");
12241224
let attrs_content = fs::read_to_string(&attrs_path).map_err(|e| {
@@ -1238,6 +1238,11 @@ impl RestBackend {
12381238
let mut seen_fmris = HashSet::new();
12391239

12401240
for part_name in parts.keys() {
1241+
// Load part explicitly because CatalogManager doesn't load them automatically
1242+
catalog_manager.load_part(part_name).map_err(|e| {
1243+
RepositoryError::Other(format!("Failed to load catalog part {}: {}", part_name, e))
1244+
})?;
1245+
12411246
if let Some(part) = catalog_manager.get_part(part_name) {
12421247
// Match stems against pattern
12431248
for (publisher_in_catalog, stems) in &part.packages {
@@ -1248,16 +1253,13 @@ impl RestBackend {
12481253
for (stem, versions) in stems {
12491254
let matches = if pattern == "*" {
12501255
true
1251-
} else if pattern.contains('*') {
1252-
// Basic glob matching (stem matching pattern)
1253-
let re_pattern = pattern.replace('*', ".*");
1254-
if let Ok(re) = regex::Regex::new(&format!("^{}$", re_pattern)) {
1256+
} else {
1257+
let re_str = super::file_backend::glob_to_regex(pattern);
1258+
if let Ok(re) = regex::Regex::new(&re_str) {
12551259
re.is_match(stem)
12561260
} else {
12571261
stem == pattern
12581262
}
1259-
} else {
1260-
stem == pattern
12611263
};
12621264

12631265
if matches {

pkg6recv/src/main.rs

Lines changed: 63 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use libips::repository::{
66
};
77
use miette::{IntoDiagnostic, Result};
88
use std::path::PathBuf;
9-
use tracing::info;
9+
use tracing::{info, warn};
1010
use tracing_subscriber::{EnvFilter, fmt};
1111

1212
struct ConsoleProgressReporter;
@@ -55,22 +55,15 @@ fn main() -> Result<()> {
5555

5656
let cli = Cli::parse();
5757

58-
// Open destination repository
59-
// We'll open it inside each branch to avoid borrow checker issues with moves
60-
61-
let fmris: Vec<Fmri> = cli
62-
.packages
63-
.iter()
64-
.map(|s| Fmri::parse(s))
65-
.collect::<std::result::Result<Vec<_>, _>>()
66-
.into_diagnostic()?;
67-
6858
let progress = ConsoleProgressReporter;
6959

7060
// Determine if source is a URL or a path and receive packages
7161
if cli.source.starts_with("http://") || cli.source.starts_with("https://") {
7262
let source_repo = RestBackend::open(&cli.source).into_diagnostic()?;
7363
let dest_repo = FileBackend::open(&cli.dest).into_diagnostic()?;
64+
65+
let fmris = resolve_packages(&source_repo, cli.publisher.as_deref(), &cli.packages)?;
66+
7467
let mut receiver = PackageReceiver::new(&source_repo, dest_repo);
7568
receiver = receiver.with_progress(&progress);
7669
receiver
@@ -79,6 +72,9 @@ fn main() -> Result<()> {
7972
} else {
8073
let source_repo = FileBackend::open(&cli.source).into_diagnostic()?;
8174
let dest_repo = FileBackend::open(&cli.dest).into_diagnostic()?;
75+
76+
let fmris = resolve_packages(&source_repo, cli.publisher.as_deref(), &cli.packages)?;
77+
8278
let mut receiver = PackageReceiver::new(&source_repo, dest_repo);
8379
receiver = receiver.with_progress(&progress);
8480
receiver
@@ -90,3 +86,59 @@ fn main() -> Result<()> {
9086

9187
Ok(())
9288
}
89+
90+
fn resolve_packages<R: ReadableRepository>(
91+
repo: &R,
92+
default_publisher: Option<&str>,
93+
packages: &[String],
94+
) -> Result<Vec<Fmri>> {
95+
let mut resolved_fmris = Vec::new();
96+
97+
for pkg_str in packages {
98+
if pkg_str.contains('*') || pkg_str.contains('?') {
99+
// It's a pattern, resolve it
100+
info!("Resolving wildcard pattern: {}", pkg_str);
101+
let matched = repo.list_packages(default_publisher, Some(pkg_str)).into_diagnostic()?;
102+
103+
if matched.is_empty() {
104+
warn!("No packages matched pattern: {}", pkg_str);
105+
}
106+
107+
// For each matched stem, we probably want the newest version if not specified.
108+
// list_packages returns all versions. PackageReceiver::receive also handles
109+
// FMRIs without versions by picking the newest.
110+
// But list_packages returns full FMRIs. If the pattern matched multiple packages,
111+
// we get all versions of all of them.
112+
113+
// To be consistent with IPS, if someone says "text/*", they usually want
114+
// the latest version of everything that matches.
115+
116+
let mut latest_versions: std::collections::HashMap<String, Fmri> = std::collections::HashMap::new();
117+
118+
for pi in matched {
119+
let entry = latest_versions.entry(pi.fmri.name.clone());
120+
match entry {
121+
std::collections::hash_map::Entry::Occupied(mut oe) => {
122+
if pi.fmri.version() > oe.get().version() {
123+
oe.insert(pi.fmri);
124+
}
125+
}
126+
std::collections::hash_map::Entry::Vacant(ve) => {
127+
ve.insert(pi.fmri);
128+
}
129+
}
130+
}
131+
132+
for (_, fmri) in latest_versions {
133+
info!("Found package: {}", fmri);
134+
resolved_fmris.push(fmri);
135+
}
136+
} else {
137+
// It's a regular FMRI or package name
138+
let fmri = Fmri::parse(pkg_str).into_diagnostic()?;
139+
resolved_fmris.push(fmri);
140+
}
141+
}
142+
143+
Ok(resolved_fmris)
144+
}

0 commit comments

Comments
 (0)