use std::process::Command;
use anyhow::{Result, bail};
use clap::ArgAction;
use crate::stack;
use crate::style;
#[derive(Debug, clap::Args)]
pub struct Run {
#[arg(long, action = ArgAction::SetTrue)]
fail_fast: bool,
#[arg(
trailing_var_arg = true,
allow_hyphen_values = true,
required = true,
num_args = 1..,
value_name = "CMD"
)]
command: Vec<String>,
}
impl crate::commands::Run for Run {
fn run(self) -> Result<()> {
if !crate::git::worktree_is_clean()? {
bail!("working tree has uncommitted changes; commit or stash before `git stk run`");
}
let original = crate::git::current_branch()?;
let root = stack::stack_root(&original)?;
let trunk = stack::trunk_branch(&crate::git::local_branches()?);
let branches: Vec<String> = stack::branch_and_descendants(&root)?
.into_iter()
.filter(|branch| Some(branch) != trunk.as_ref())
.collect();
if branches.is_empty() {
bail!("no stacked branches to run on");
}
let (program, args) = self
.command
.split_first()
.expect("clap requires at least one command word");
let result = run_each(&branches, program, args, self.fail_fast);
let _ = crate::git::checkout(&original);
let results = result?;
print_summary(&results);
if results.iter().any(|(_, passed)| !passed) {
bail!("`{program}` failed on one or more branches");
}
Ok(())
}
}
fn run_each(
branches: &[String],
program: &str,
args: &[String],
fail_fast: bool,
) -> Result<Vec<(String, bool)>> {
let mut results = Vec::new();
for branch in branches {
crate::git::checkout(branch)?;
anstream::println!("{}", style::branch(branch));
let passed = Command::new(program)
.args(args)
.status()
.is_ok_and(|status| status.success());
results.push((branch.clone(), passed));
if !passed && fail_fast {
break;
}
}
Ok(results)
}
fn print_summary(results: &[(String, bool)]) {
let width = results.iter().map(|(b, _)| b.len()).max().unwrap_or(0);
anstream::println!();
for (branch, passed) in results {
let pad = " ".repeat(width - branch.len());
let marker = if *passed {
style::success("ok")
} else {
style::paint(style::CLOSED, "FAIL")
};
anstream::println!(" {}{pad} {marker}", style::branch(branch));
}
let passed = results.iter().filter(|(_, passed)| *passed).count();
let total = results.len();
anstream::println!(
"{}",
style::dim(&format!(
"ran on {total} branch{}, {passed} passed",
if total == 1 { "" } else { "es" }
))
);
}