use anyhow::Result;
use clap::ValueEnum;
use crate::commands::Run;
use crate::providers::{ReviewRequest, ReviewState, detect_provider, review_provider};
use crate::{git, stack};
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
pub enum Format {
Markdown,
Plain,
}
#[derive(Debug, clap::Args)]
pub struct List {
#[arg(long, value_enum)]
format: Option<Format>,
}
impl Run for List {
fn run(self) -> Result<()> {
match self.format {
Some(format) => list_formatted(format),
None => crate::stack::print_stack(),
}
}
}
pub fn list_formatted(format: Format) -> Result<()> {
let current = git::current_branch()?;
let root = stack::stack_root(¤t)?;
let branches: Vec<String> = stack::branch_and_descendants(&root)?
.into_iter()
.skip(1) .collect();
if branches.is_empty() {
println!("no stacked branches");
return Ok(());
}
let review_provider = detect_provider().ok().map(|p| review_provider(p.kind));
let entries: Vec<(String, Option<ReviewRequest>)> = branches
.iter()
.map(|branch| {
let review = review_provider
.as_ref()
.and_then(|rp| rp.review_for_branch(branch).ok().flatten())
.filter(|review| review.branch == *branch);
(branch.clone(), review)
})
.collect();
println!("{}", summary(&entries, &root, format));
println!();
for (index, (branch, review)) in entries.iter().enumerate() {
let number = index + 1;
match (format, review) {
(Format::Markdown, Some(review)) => {
println!(
"{number}. [{}]({}) - {}",
review.label(),
review.url,
review.state
);
}
(Format::Markdown, None) => println!("{number}. `{branch}` (no review)"),
(Format::Plain, Some(review)) => {
println!("{number}. {} - {}", review.label(), review.state);
println!(" {}", review.url);
}
(Format::Plain, None) => println!("{number}. {branch} (no review)"),
}
}
Ok(())
}
fn summary(entries: &[(String, Option<ReviewRequest>)], base: &str, format: Format) -> String {
let total = entries.len();
let reviews: Vec<&ReviewRequest> = entries.iter().filter_map(|(_, r)| r.as_ref()).collect();
let base = match format {
Format::Markdown => format!("`{base}`"),
Format::Plain => base.to_owned(),
};
let mut summary = if reviews.is_empty() {
format!(
"{total} branch{}, base {base}",
if total == 1 { "" } else { "es" }
)
} else if reviews.len() == total {
format!(
"{total} PR{}, base {base}",
if total == 1 { "" } else { "s" }
)
} else {
format!(
"{total} branches ({} with reviews), base {base}",
reviews.len()
)
};
if !reviews.is_empty() {
let mut counts = Vec::new();
for (state, label) in [
(ReviewState::Open, "open"),
(ReviewState::Merged, "merged"),
(ReviewState::Closed, "closed"),
] {
let count = reviews
.iter()
.filter(|review| review.state == state)
.count();
if count > 0 {
counts.push(format!("{count} {label}"));
}
}
if !counts.is_empty() {
summary.push_str(&format!(", {}", counts.join(" / ")));
}
}
summary
}