use anyhow::Result;
use endringer_core::types::{CommitId, SubmoduleState, SubmoduleSummary};
use gix::Repository;
pub(crate) fn submodule_summaries(repo: &Repository) -> Result<Vec<SubmoduleSummary>> {
let modules = crate::submodule::submodules(repo)?;
let workdir = repo.workdir();
let mut results: Vec<SubmoduleSummary> = modules
.into_iter()
.map(|info| {
let path = info.path.clone();
let expected = read_gitlink(repo, &path);
let (state, checked_out) = inspect_submodule(workdir, &path);
SubmoduleSummary {
name: info.name,
path,
url: info.url,
expected_commit_id: expected,
checked_out_commit_id: checked_out,
state,
is_dirty: None, }
})
.collect();
results.sort_by(|a, b| a.path.cmp(&b.path));
Ok(results)
}
fn read_gitlink(repo: &Repository, submodule_path: &std::path::Path) -> Option<CommitId> {
let index = repo.try_index().ok()??;
let path_str = submodule_path.to_str()?;
for entry in index.entries() {
let entry_path = std::str::from_utf8(entry.path(&index)).unwrap_or("");
if entry_path == path_str && entry.mode == gix::index::entry::Mode::COMMIT {
if let Ok(id) = CommitId::from_hex(&entry.id.to_hex().to_string()) {
return Some(id);
}
}
}
None
}
fn inspect_submodule(
workdir: Option<&std::path::Path>,
submodule_path: &std::path::Path,
) -> (SubmoduleState, Option<CommitId>) {
let base = match workdir {
Some(d) => d,
None => return (SubmoduleState::Unknown, None),
};
let full_path = base.join(submodule_path);
if !full_path.exists() {
return (SubmoduleState::MissingWorktree, None);
}
let git_marker = full_path.join(".git");
if !git_marker.exists() {
return (SubmoduleState::MissingGitDir, None);
}
match gix::discover(&full_path) {
Ok(sub_repo) => {
let head_commit = sub_repo.head().ok()
.and_then(|mut h| h.peel_to_commit().ok())
.map(|c| {
CommitId::from_hex(&c.id.to_hex().to_string()).ok()
})
.flatten();
let state = if sub_repo.head().ok().map_or(false, |h| h.is_detached()) {
SubmoduleState::Detached
} else {
SubmoduleState::Initialized
};
(state, head_commit)
}
Err(_) => (SubmoduleState::MissingGitDir, None),
}
}