use std::collections::{HashMap, HashSet};
use anyhow::Result;
use crate::core::{graph, repo, shortid};
pub fn run(
file_filter: Option<Vec<String>>,
context: usize,
show_all: bool,
theme: graph::Theme,
) -> Result<()> {
let repo = repo::open_repo()?;
let _ = repo::require_workdir(&repo, "display status")?;
let cwd_prefix = repo::cwd_relative_to_repo(&repo).unwrap_or_default();
let opts = graph::default_render_opts(theme, cwd_prefix);
let show_files = file_filter.is_some();
let mut info = repo::gather_repo_info(&repo, show_files, context)?;
let ids = shortid::IdAllocator::new(info.collect_entities());
if !show_all {
let pattern = repo::hide_branch_pattern(&repo)
.unwrap_or_else(|| repo::DEFAULT_HIDE_PATTERN.to_string());
if !pattern.is_empty() {
hide_branches(&mut info, &pattern);
}
}
if let Some(filter_ids) = &file_filter
&& !filter_ids.is_empty()
{
let filter_oids = resolve_commit_filter(&repo, filter_ids, &info, &ids);
for commit in &mut info.commits {
if !filter_oids.contains(&commit.oid) {
commit.files.clear();
}
}
}
let output = graph::render(info, &ids, &opts);
print!("{}", output);
Ok(())
}
fn resolve_commit_filter(
repo: &git2::Repository,
ids: &[String],
info: &repo::RepoInfo,
allocator: &shortid::IdAllocator,
) -> HashSet<git2::Oid> {
let mut filter_oids = HashSet::new();
for id in ids {
if let Ok(obj) = repo.revparse_single(id)
&& let Ok(commit) = obj.peel_to_commit()
{
filter_oids.insert(commit.id());
continue;
}
if let Some(commit) = info
.commits
.iter()
.find(|c| allocator.get_commit(c.oid) == id.as_str())
{
filter_oids.insert(commit.oid);
continue;
}
}
filter_oids
}
fn hide_branches(info: &mut repo::RepoInfo, pattern: &str) {
let hidden_tips: HashSet<git2::Oid> = info
.branches
.iter()
.filter(|b| b.name.starts_with(pattern))
.map(|b| b.tip_oid)
.collect();
if hidden_tips.is_empty() {
return;
}
let all_tips: HashSet<git2::Oid> = info.branches.iter().map(|b| b.tip_oid).collect();
let visible_tips: HashSet<git2::Oid> = info
.branches
.iter()
.filter(|b| !b.name.starts_with(pattern))
.map(|b| b.tip_oid)
.collect();
let commit_map: HashMap<git2::Oid, Option<git2::Oid>> =
info.commits.iter().map(|c| (c.oid, c.parent_oid)).collect();
let mut hidden_commits: HashSet<git2::Oid> = HashSet::new();
for &tip in &hidden_tips {
let mut current = Some(tip);
let mut is_tip = true;
while let Some(oid) = current {
if !commit_map.contains_key(&oid) {
break; }
if visible_tips.contains(&oid) {
break;
}
if !is_tip && all_tips.contains(&oid) {
break; }
is_tip = false;
hidden_commits.insert(oid);
current = commit_map.get(&oid).and_then(|p| *p);
}
}
info.branches.retain(|b| !b.name.starts_with(pattern));
info.commits.retain(|c| !hidden_commits.contains(&c.oid));
}
#[cfg(test)]
#[path = "status_test.rs"]
mod tests;