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