diff --git a/src/staged_pkg.rs b/src/staged_pkg.rs index 561ddbdd80..8e2e63abdc 100644 --- a/src/staged_pkg.rs +++ b/src/staged_pkg.rs @@ -13,39 +13,52 @@ use pkg::{Package, PackageError, PackageName}; static RECIPE_PATHS: LazyLock> = LazyLock::new(|| { let mut recipe_paths = HashMap::::new(); - let mut walker = ignore::WalkBuilder::new("recipes"); - walker.follow_links(true); - for entry_res in walker.build() { - let Ok(entry) = entry_res else { - continue; - }; - if entry.file_name() == OsStr::new("recipe.toml") { - let recipe_file = entry.path(); - let Some(recipe_dir) = recipe_file.parent() else { + // Walk local/recipes/ first so Red Bear forks take precedence over + // upstream recipes. This is the canonical discovery order for a + // full fork: the local overlay is the source of truth. + for root in ["local/recipes", "recipes"] { + let mut walker = ignore::WalkBuilder::new(root); + walker.follow_links(true); + for entry_res in walker.build() { + let Ok(entry) = entry_res else { continue; }; - let Some(recipe_name): Option = recipe_dir - .file_name() - .and_then(|x| x.to_str()?.try_into().ok()) - else { - continue; - }; - let existing = recipe_paths.get(&recipe_name); - match existing { - Some(other_dir) => { - let other_dir = other_dir.clone(); - let current_is_deeper = recipe_dir.starts_with(&other_dir); - let existing_is_deeper = other_dir.starts_with(&recipe_dir); - if current_is_deeper { - recipe_paths.insert(recipe_name.clone(), other_dir); - } else if existing_is_deeper { - recipe_paths.insert(recipe_name, recipe_dir.to_path_buf()); - } else { - recipe_paths.insert(recipe_name.clone(), recipe_dir.to_path_buf()); + if entry.file_name() == OsStr::new("recipe.toml") { + let recipe_file = entry.path(); + let Some(recipe_dir) = recipe_file.parent() else { + continue; + }; + let Some(recipe_name): Option = recipe_dir + .file_name() + .and_then(|x| x.to_str()?.try_into().ok()) + else { + continue; + }; + let existing = recipe_paths.get(&recipe_name); + match existing { + Some(other_dir) => { + let other_dir = other_dir.clone(); + let current_is_deeper = recipe_dir.starts_with(&other_dir); + let existing_is_deeper = other_dir.starts_with(&recipe_dir); + // Red Bear full-fork rule: local/recipes/ ALWAYS wins. + // If either side is a local/recipes/ path, keep it. + let current_is_local = recipe_dir.starts_with("local/recipes"); + let existing_is_local = other_dir.starts_with("local/recipes"); + if current_is_local && !existing_is_local { + recipe_paths.insert(recipe_name, recipe_dir.to_path_buf()); + } else if existing_is_local && !current_is_local { + // keep existing + } else if current_is_deeper { + recipe_paths.insert(recipe_name.clone(), other_dir); + } else if existing_is_deeper { + recipe_paths.insert(recipe_name, recipe_dir.to_path_buf()); + } else { + recipe_paths.insert(recipe_name.clone(), recipe_dir.to_path_buf()); + } + } + None => { + recipe_paths.insert(recipe_name, recipe_dir.to_path_buf()); } - } - None => { - recipe_paths.insert(recipe_name, recipe_dir.to_path_buf()); } } } @@ -53,6 +66,10 @@ static RECIPE_PATHS: LazyLock> = LazyLock::new(|| recipe_paths }); + + + + pub fn find(recipe: &str) -> Option<&'static Path> { RECIPE_PATHS.get(recipe).map(PathBuf::as_path) }