use std::collections::{HashMap, HashSet};
use anyhow::Result;
use crate::core::{agent_mode, graph, msg, repo, shortid, status_json};
pub fn run(
file_filter: Option<Vec<String>>,
context: Option<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, resolve_context(&repo, context))?;
let ids = shortid::IdAllocator::new(info.collect_entities());
let stacks = agent_mode::enabled().then(|| stack_edges(&repo, &info, show_all));
if !show_all {
apply_hidden_branches(&repo, &mut info);
}
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 branch_name = info.branch_name.clone();
let sections = graph::build_sections(info);
if let Some(stacks) = stacks {
agent_mode::set_graph(status_json::build(
§ions,
&branch_name,
&stacks,
&ids,
&opts.cwd_prefix,
));
}
msg::human(graph::render_sections(§ions, &ids, &opts));
Ok(())
}
fn stack_edges(
repo: &git2::Repository,
info: &repo::RepoInfo,
show_all: bool,
) -> HashMap<String, status_json::StackEdge> {
let pattern = hide_pattern(repo);
graph::stack_parents(info)
.into_iter()
.map(|(branch, below)| {
let below_hidden = is_hidden(&below, &pattern);
let edge = status_json::StackEdge {
below: (show_all || !below_hidden).then_some(below),
below_hidden,
};
(branch, edge)
})
.collect()
}
pub fn resolve_context(repo: &git2::Repository, arg: Option<usize>) -> usize {
arg.or_else(|| repo::status_context(repo)).unwrap_or(1)
}
pub fn apply_hidden_branches(repo: &git2::Repository, info: &mut repo::RepoInfo) {
let pattern = hide_pattern(repo);
if !pattern.is_empty() {
hide_branches(info, &pattern);
}
}
pub fn hide_pattern(repo: &git2::Repository) -> String {
repo::hide_branch_pattern(repo).unwrap_or_else(|| repo::DEFAULT_HIDE_PATTERN.to_string())
}
pub fn is_hidden(name: &str, pattern: &str) -> bool {
!pattern.is_empty() && name.starts_with(pattern)
}
pub fn loose_commits(repo: &git2::Repository) -> Result<Vec<git2::Oid>> {
let mut info = repo::gather_commit_graph(repo)?;
apply_hidden_branches(repo, &mut info);
Ok(graph::loose_commits(&info))
}
pub fn top_commit(repo: &git2::Repository) -> Result<Option<git2::Oid>> {
Ok(loose_commits(repo)?.first().copied())
}
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;
}
if let [oid] = allocator.find_persistent(id).as_slice() {
filter_oids.insert(*oid);
}
}
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;