git_stk/commands/
status.rs1use anyhow::Result;
2use clap_complete::engine::ArgValueCompleter;
3
4use crate::commands::Run;
5use crate::completions;
6use crate::providers::{ReviewState, detect_review_provider};
7use crate::style;
8use crate::{git, stack};
9
10#[derive(Debug, clap::Args)]
12pub struct Status {
13 #[arg(add = ArgValueCompleter::new(completions::branch_candidates))]
15 branch: Option<String>,
16}
17
18impl Run for Status {
19 fn run(self) -> Result<()> {
20 print_status(self.branch.as_deref())
21 }
22}
23
24pub fn print_status(branch: Option<&str>) -> Result<()> {
25 let branch = branch
26 .map(str::to_owned)
27 .map_or_else(git::current_branch, Ok)?;
28 let parent = stack::parent_of(&branch)?;
29 let children = stack::children_of(&branch)?;
30
31 anstream::println!("branch: {}", style::paint(style::CURRENT, &branch));
32 match parent.as_deref() {
33 Some(parent) => anstream::println!("parent: {}", style::paint(style::BRANCH, parent)),
34 None => anstream::println!("parent: none"),
35 }
36 if children.is_empty() {
37 anstream::println!("children: none");
38 } else {
39 let children: Vec<String> = children
40 .iter()
41 .map(|child| style::paint(style::BRANCH, child))
42 .collect();
43 anstream::println!("children: {}", children.join(", "));
44 }
45
46 let detected = detect_review_provider().ok();
49 let review = match &detected {
50 Some((provider, review_provider)) => {
51 anstream::println!("provider: {} ({})", provider.kind, provider.source);
52 let review = review_provider.review_for_branch_including_closed(&branch)?;
55 match &review {
56 Some(review) => {
57 anstream::println!(
58 "review: {} {} {} -> {}",
59 review.id,
60 style::state(&review.state),
61 style::paint(style::BRANCH, &review.branch),
62 style::paint(style::BRANCH, &review.base)
63 );
64 anstream::println!("url: {}", style::paint(style::DIM, &review.url));
65
66 if let Some(parent) = parent.as_deref()
67 && parent != review.base
68 {
69 anstream::println!(
70 "{} review base is {}, local parent is {parent} - run `git stk submit`",
71 style::paint(style::WARN, "warning:"),
72 review.base
73 );
74 }
75 }
76 None => anstream::println!("review: none"),
77 }
78 review
79 }
80 None => {
81 anstream::println!("{}", style::dim("provider: not detected (no review info)"));
82 None
83 }
84 };
85
86 let mut hints = Vec::new();
89 match &review {
90 Some(review) if review.state == ReviewState::Merged => {
91 hints.push(format!(
92 "review {} is merged - run `git stk sync`",
93 review.id
94 ));
95 }
96 Some(review) if review.state == ReviewState::Closed => {
97 hints.push(format!(
98 "review {} was closed without merging - `git stk submit` opens a new review",
99 review.id
100 ));
101 }
102 _ => {}
103 }
104 if let Some(parent) = parent.as_deref() {
105 if let Some((_, review_provider)) = &detected {
106 match review_provider.review_for_branch_including_closed(parent) {
107 Ok(Some(parent_review)) if parent_review.branch == parent => {
108 match parent_review.state {
109 ReviewState::Merged => hints.push(format!(
110 "parent review {} is merged - run `git stk sync`",
111 parent_review.id
112 )),
113 ReviewState::Closed => hints.push(format!(
114 "parent review {} was closed without merging - \
115 retarget {branch} with `git stk adopt`",
116 parent_review.id
117 )),
118 _ => {}
119 }
120 }
121 _ => {}
122 }
123 }
124
125 if hints.is_empty()
126 && let Some(hint) = stack::behind_parent_hint(&branch, parent)
127 {
128 hints.push(hint);
129 }
130 }
131 for hint in hints {
132 anstream::println!("{} {hint}", style::paint(style::HINT, "hint:"));
133 }
134
135 Ok(())
136}