use crate::config::SubmoduleConfig;
use git2::Repository;
use std::path::Path;
use super::WorktreeEntry;
use super::change_summary::{ChangeSummary, collect_change_summary};
pub(super) fn collect_worktree_info(
repo: &Repository,
sub_cfg: &SubmoduleConfig,
) -> Vec<WorktreeEntry> {
let wt_names = match repo.worktrees() {
Ok(names) => names,
Err(_) => return Vec::new(),
};
let mut entries = Vec::new();
for i in 0..wt_names.len() {
let name = match wt_names.get(i) {
Ok(Some(n)) => n,
_ => continue,
};
let wt = match repo.find_worktree(name) {
Ok(wt) => wt,
Err(_) => continue,
};
let wt_path = wt.path().to_path_buf();
let wt_repo = match Repository::open(&wt_path) {
Ok(wt_repo) => wt_repo,
Err(_) => continue,
};
let branch = match wt_repo.head() {
Ok(head) => head.shorthand().unwrap_or("HEAD").to_string(),
Err(_) => "(no branch)".to_string(),
};
let (ahead, behind) = compute_ahead_behind(&wt_repo);
let summary =
collect_change_summary(&wt_repo, &wt_path, false, sub_cfg).unwrap_or(ChangeSummary {
files: Vec::new(),
is_dirty: false,
has_submodules: false,
submodules: Vec::new(),
has_dirty_submodules: false,
has_unpushed_submodules: false,
});
entries.push(WorktreeEntry {
name: name.to_string(),
path: wt_path,
branch,
ahead,
behind,
is_dirty: summary.is_dirty,
file_count: summary.files.len(),
has_dirty_submodules: summary.has_dirty_submodules,
has_unpushed_submodules: summary.has_unpushed_submodules,
});
}
entries
}
pub(super) fn fetch_remote_silent(path: &Path) -> bool {
use wait_timeout::ChildExt;
let child = fetch_child(path);
match child {
Ok(mut c) => {
match c.wait_timeout(std::time::Duration::from_secs(30)) {
Ok(Some(status)) => status.success(),
Ok(None) => {
kill_process_group(&mut c);
let _ = c.wait();
false
}
Err(_) => false,
}
}
Err(_) => false,
}
}
fn fetch_child(path: &Path) -> std::io::Result<std::process::Child> {
let mut cmd = std::process::Command::new("git");
cmd.arg("-C")
.arg(path)
.arg("fetch")
.arg("--quiet")
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null());
#[cfg(unix)]
{
use std::os::unix::process::CommandExt;
cmd.process_group(0);
}
cmd.spawn()
}
#[cfg(unix)]
pub(super) fn kill_process_group(child: &mut std::process::Child) {
let pgid = child.id() as i32;
if unsafe { libc::killpg(pgid, libc::SIGKILL) } != 0
&& std::io::Error::last_os_error().raw_os_error() != Some(libc::ESRCH)
{
let _ = child.kill();
}
}
#[cfg(not(unix))]
pub(super) fn kill_process_group(child: &mut std::process::Child) {
let _ = child.kill();
}
pub(super) fn compute_ahead_behind(repo: &Repository) -> (usize, usize) {
let head = match repo.head() {
Ok(h) => h,
Err(_) => return (0, 0),
};
let local_oid = match head.target() {
Some(oid) => oid,
None => return (0, 0),
};
let branch_name = match head.shorthand() {
Ok(name) => name.to_string(),
Err(_) => return (0, 0),
};
let branch = match repo.find_branch(&branch_name, git2::BranchType::Local) {
Ok(b) => b,
Err(_) => return (0, 0),
};
if let Ok(upstream) = branch.upstream()
&& let Some(upstream_oid) = upstream.get().target()
{
return repo
.graph_ahead_behind(local_oid, upstream_oid)
.unwrap_or((0, 0));
}
ahead_against_remote_tips(repo, local_oid)
}
fn ahead_against_remote_tips(repo: &Repository, local_oid: git2::Oid) -> (usize, usize) {
let mut walk = match repo.revwalk() {
Ok(w) => w,
Err(_) => return (0, 0),
};
if walk.push(local_oid).is_err() {
return (0, 0);
}
let mut any_remote = false;
if let Ok(branches) = repo.branches(Some(git2::BranchType::Remote)) {
for (branch, _) in branches.flatten() {
if let Some(tip) = branch.get().target() {
any_remote = true;
let _ = walk.hide(tip);
}
}
}
if !any_remote {
return (0, 0);
}
let count = walk.filter_map(Result::ok).count();
(count, 0)
}