git_stk/commands/
status.rs1use anyhow::Result;
2use clap_complete::engine::ArgValueCompleter;
3
4use crate::commands::Run;
5use crate::completions;
6use crate::providers::{BaseGap, CheckStatus, 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 is_base = stack::is_floor(&branch)?;
32 let parent = stack::stacked_parent_of(&branch)?;
33 let children = stack::children_of(&branch)?;
34
35 anstream::println!("branch: {}", style::paint(style::CURRENT, &branch));
36 if let Some(path) = git::worktree_holding(&branch).ok().flatten() {
39 anstream::println!("worktree: {}", git::display_path(&path));
40 }
41 match parent.as_deref() {
42 Some(parent) => anstream::println!("parent: {}", style::paint(style::BRANCH, parent)),
43 None if is_base => anstream::println!("parent: none (this stack's base)"),
46 None => anstream::println!("parent: none"),
47 }
48 if children.is_empty() {
49 anstream::println!("children: none");
50 } else {
51 let children: Vec<String> = children
52 .iter()
53 .map(|child| style::paint(style::BRANCH, child))
54 .collect();
55 anstream::println!("children: {}", children.join(", "));
56 }
57
58 let detected = detect_review_provider().ok();
61 let review = match &detected {
62 Some((provider, review_provider)) => {
63 anstream::println!("provider: {} ({})", provider.kind, provider.source);
64 let review = review_provider.review_for_branch_including_closed(&branch)?;
67 match &review {
68 Some(review) => {
69 let annotation = review_provider.annotate_review(review, false).ok();
75
76 let queued = annotation.as_ref().is_some_and(|found| found.queued);
79 let marker = if queued {
80 crate::providers::QUEUED_MARK
81 } else {
82 annotation
83 .as_ref()
84 .map_or(CheckStatus::None, |found| found.checks)
85 .dot()
86 };
87 anstream::println!(
88 "review: {marker}{} {} {} -> {}",
89 review.id,
90 style::state(&review.state),
91 style::paint(style::BRANCH, &review.branch),
92 style::paint(style::BRANCH, &review.base)
93 );
94 anstream::println!("url: {}", style::paint(style::DIM, &review.url));
95 if let Some(at) = annotation.as_ref().and_then(|found| found.stack) {
100 anstream::println!(
101 "stack: {} stack {} ({} of {})",
102 provider.kind,
103 at.number,
104 at.position,
105 at.size
106 );
107 }
108
109 if let Some(parent) = parent.as_deref()
114 && parent != review.base
115 {
116 match review_provider.base_gap(review, parent).unwrap_or(None) {
120 Some(BaseGap::Platform) => anstream::println!(
121 "{}",
122 style::paint(
123 style::DIM,
124 &format!(
125 "review base is {}, local parent is {parent} - \
126 the platform retargets it as the layer below lands",
127 review.base
128 )
129 )
130 ),
131 Some(BaseGap::Sync) => anstream::println!(
132 "{} review base is {}, local parent is {parent} - the \
133 platform moved it when {parent} landed; run `git stk sync`",
134 style::paint(style::WARN, "warning:"),
135 review.base
136 ),
137 Some(BaseGap::Neither) => anstream::println!(
138 "{} review base is {}, local parent is {parent} - the \
139 platform will not move it there and refuses a change by \
140 hand; run `git stk unstack`, then \
141 `git stk submit`",
142 style::paint(style::WARN, "warning:"),
143 review.base
144 ),
145 None => anstream::println!(
146 "{} review base is {}, local parent is {parent} - run `git stk submit`",
147 style::paint(style::WARN, "warning:"),
148 review.base
149 ),
150 }
151 }
152 }
153 None => anstream::println!("review: none"),
154 }
155 review
156 }
157 None => {
158 anstream::println!("{}", style::dim("provider: not detected (no review info)"));
159 None
160 }
161 };
162
163 let mut hints = Vec::new();
166 if is_base {
167 hints.push(format!(
168 "{branch} is this stack's base, so nothing rebases, submits, or lands it - \
169 `git stk detach {branch}` if it should be"
170 ));
171 }
172 match &review {
173 Some(review)
177 if is_base && matches!(review.state, ReviewState::Merged | ReviewState::Closed) =>
178 {
179 hints.push(format!(
180 "review {} is {} - git-stk leaves a stack's base alone, so this is yours to \
181 finish; `git stk detach {branch}` first if it should be managed",
182 review.id,
183 style::state(&review.state)
184 ));
185 }
186 Some(review) if review.state == ReviewState::Merged => {
187 hints.push(format!(
188 "review {} is merged - run `git stk sync`",
189 review.id
190 ));
191 }
192 Some(review) if review.state == ReviewState::Closed => {
193 hints.push(format!(
194 "review {} was closed without merging - `git stk submit` opens a new review",
195 review.id
196 ));
197 }
198 _ => {}
199 }
200 if let Some(parent) = parent.as_deref() {
201 if let Some((_, review_provider)) = &detected {
202 match review_provider.review_for_branch_including_closed(parent) {
203 Ok(Some(parent_review)) if parent_review.branch == parent => {
204 let parent_is_base = stack::is_floor(parent)?;
208 match parent_review.state {
209 _ if parent_is_base
213 && matches!(
214 parent_review.state,
215 ReviewState::Merged | ReviewState::Closed
216 ) =>
217 {
218 hints.push(format!(
219 "parent review {} is {} - {parent} is this stack's base, so \
220 git-stk does not retarget off it; re-root with \
221 `git stk adopt {branch} --parent <parent>`",
222 parent_review.id,
223 style::state(&parent_review.state)
224 ));
225 }
226 ReviewState::Merged => hints.push(format!(
227 "parent review {} is merged - run `git stk sync`",
228 parent_review.id
229 )),
230 ReviewState::Closed => hints.push(format!(
231 "parent review {} was closed without merging - \
232 retarget with `git stk adopt {branch} --parent <parent>`",
233 parent_review.id
234 )),
235 _ => {}
236 }
237 }
238 _ => {}
239 }
240 }
241
242 if hints.is_empty()
243 && let Some(hint) = stack::behind_parent_hint(&branch, parent)
244 {
245 hints.push(hint);
246 }
247 }
248 for hint in hints {
249 anstream::println!("{} {hint}", style::paint(style::HINT, "hint:"));
250 }
251
252 Ok(())
253}