use git2::Repository;
use super::worktree::compute_ahead_behind;
use super::{SubmoduleHead, SubmoduleWarn};
impl SubmoduleWarn {
pub fn is_clean(&self) -> bool {
self.unpushed_commits == 0 && !self.pointer_unreachable && !self.needs_merge_to_default
}
}
pub(super) fn compute_submodule_head_and_warn(
sub: &git2::Submodule,
warn_enabled: bool,
) -> (Option<SubmoduleHead>, SubmoduleWarn) {
let inner = match sub.open() {
Ok(r) => r,
Err(_) => return (None, SubmoduleWarn::default()),
};
let head = Some(read_submodule_head(&inner));
let warn = if warn_enabled {
compute_submodule_warn(sub, &inner)
} else {
SubmoduleWarn::default()
};
(head, warn)
}
fn read_submodule_head(inner: &Repository) -> SubmoduleHead {
if inner.head_detached().unwrap_or(true) {
return SubmoduleHead::Detached;
}
match inner.head() {
Ok(head) => SubmoduleHead::Branch(head.shorthand().unwrap_or("HEAD").to_string()),
Err(_) => SubmoduleHead::Detached,
}
}
fn compute_submodule_warn(sub: &git2::Submodule, inner: &Repository) -> SubmoduleWarn {
let recorded = match sub.index_id() {
Some(o) => o,
None => return SubmoduleWarn::default(),
};
let unpushed_commits = compute_ahead_behind(inner).0;
if inner.find_object(recorded, None).is_err() {
return SubmoduleWarn {
unpushed_commits,
pointer_unreachable: true,
needs_merge_to_default: false,
};
}
if !remote_reaches(inner, recorded) {
return SubmoduleWarn {
unpushed_commits,
pointer_unreachable: true,
needs_merge_to_default: false,
};
}
let needs_merge_to_default = match default_branch_tip(inner) {
Some(tip) => !reaches(inner, tip, recorded),
None => false,
};
SubmoduleWarn {
unpushed_commits,
pointer_unreachable: false,
needs_merge_to_default,
}
}
fn reaches(inner: &Repository, tip: git2::Oid, recorded: git2::Oid) -> bool {
tip == recorded || inner.graph_descendant_of(tip, recorded).unwrap_or(false)
}
fn remote_reaches(inner: &Repository, recorded: git2::Oid) -> bool {
let Ok(branches) = inner.branches(Some(git2::BranchType::Remote)) else {
return false;
};
for (b, _) in branches.flatten() {
if let Some(tip) = b.get().target()
&& reaches(inner, tip, recorded)
{
return true;
}
}
false
}
fn default_branch_tip(inner: &Repository) -> Option<git2::Oid> {
if let Ok(head_ref) = inner.find_reference("refs/remotes/origin/HEAD")
&& let Ok(Some(target_name)) = head_ref.symbolic_target()
&& let Ok(target_ref) = inner.find_reference(target_name)
&& let Some(oid) = target_ref.target()
{
return Some(oid);
}
for name in ["refs/remotes/origin/main", "refs/remotes/origin/master"] {
if let Ok(r) = inner.find_reference(name)
&& let Some(oid) = r.target()
{
return Some(oid);
}
}
None
}