use std::path::Path;
use crate::config::{ConfigFile, ConfigScope, ConfigSet};
use crate::index::Index;
use crate::odb::Odb;
use crate::pathspec::pathspec_matches;
#[derive(Debug, Clone)]
pub struct SubmoduleRegistration {
pub name: String,
pub path: String,
}
pub fn load_submodule_registrations(
work_tree: &Path,
index: Option<&Index>,
odb: Option<&Odb>,
) -> Vec<SubmoduleRegistration> {
let gitmodules_path = work_tree.join(".gitmodules");
let content = if gitmodules_path.exists() {
let Some(s) = std::fs::read_to_string(&gitmodules_path).ok() else {
return Vec::new();
};
s
} else if let (Some(ix), Some(db)) = (index, odb) {
let Some(ie) = ix.get(b".gitmodules", 0) else {
return Vec::new();
};
let Ok(obj) = db.read(&ie.oid) else {
return Vec::new();
};
if obj.kind != crate::objects::ObjectKind::Blob {
return Vec::new();
}
let Some(s) = String::from_utf8(obj.data).ok() else {
return Vec::new();
};
s
} else {
return Vec::new();
};
let Ok(config) = ConfigFile::parse(&gitmodules_path, &content, ConfigScope::Local) else {
return Vec::new();
};
#[derive(Default)]
struct Fields {
path: Option<String>,
url: Option<String>,
}
let mut by_name: std::collections::BTreeMap<String, Fields> = std::collections::BTreeMap::new();
for entry in &config.entries {
let key = &entry.key;
if !key.starts_with("submodule.") {
continue;
}
let rest = &key["submodule.".len()..];
let Some(dot) = rest.rfind('.') else {
continue;
};
let name = &rest[..dot];
let var = &rest[dot + 1..];
let slot = by_name.entry(name.to_string()).or_default();
match var {
"path" => slot.path = entry.value.clone(),
"url" => slot.url = entry.value.clone(),
_ => {}
}
}
let mut out = Vec::new();
for (name, f) in by_name {
if let (Some(path), Some(_url)) = (f.path, f.url) {
let path = path.replace('\\', "/");
let path = path.trim_end_matches('/').to_string();
if !path.is_empty() {
out.push(SubmoduleRegistration { name, path });
}
}
}
out
}
pub fn submodule_name_for_path<'a>(
registrations: &'a [SubmoduleRegistration],
path: &str,
) -> Option<&'a str> {
let path = path.replace('\\', "/");
registrations
.iter()
.find(|r| r.path == path)
.map(|r| r.name.as_str())
}
#[must_use]
pub fn is_submodule_active(
config: &ConfigSet,
module_name: Option<&str>,
submodule_path: &str,
) -> bool {
let Some(name) = module_name else {
return false;
};
let active_key = format!("submodule.{name}.active");
if let Some(res) = config.get_bool(&active_key) {
return res.unwrap_or(false);
}
let patterns = config.get_all("submodule.active");
if !patterns.is_empty() {
return patterns.iter().any(|p| pathspec_matches(p, submodule_path));
}
let url_key = format!("submodule.{name}.url");
config.get(&url_key).is_some()
}