git_workflow/commands/
status.rs1use crate::error::{GwError, Result};
4use crate::git;
5use crate::github::{self, PrInfo, PrState};
6use crate::output;
7use crate::state::{NextAction, RepoType, SyncState, WorkingDirState};
8
9pub fn run() -> Result<()> {
11 if !git::is_git_repo() {
13 return Err(GwError::NotAGitRepository);
14 }
15
16 let repo_type = RepoType::detect()?;
17 let home_branch = repo_type.home_branch();
18 let current = git::current_branch()?;
19 let working_dir = WorkingDirState::detect();
20 let sync_state = SyncState::detect(¤t).unwrap_or(SyncState::NoUpstream);
21 let has_remote = git::remote_branch_exists(¤t).unwrap_or(false);
23
24 println!();
25
26 match &repo_type {
28 RepoType::MainRepo { .. } => {
29 output::info("Repository: main repo");
30 }
31 RepoType::Worktree { home_branch } => {
32 output::info(&format!(
33 "Repository: worktree (home: {})",
34 output::bold(home_branch)
35 ));
36 }
37 }
38
39 if current == home_branch {
41 output::success(&format!("Branch: {} (home)", output::bold(¤t)));
42 } else {
43 output::info(&format!(
44 "Branch: {} (home: {})",
45 output::bold(¤t),
46 home_branch
47 ));
48 }
49
50 match working_dir {
52 WorkingDirState::Clean => {
53 output::success("Working directory: clean");
54 }
55 _ => {
56 output::warn(&format!("Working directory: {}", working_dir.description()));
57 }
58 }
59
60 match &sync_state {
62 SyncState::NoUpstream => {
63 output::info("Upstream: no tracking branch");
64 }
65 SyncState::Synced => {
66 output::success("Upstream: synced");
67 }
68 SyncState::HasUnpushedCommits { count } => {
69 output::warn(&format!("Upstream: {} unpushed commit(s)", count));
70 }
71 SyncState::Behind { count } => {
72 output::warn(&format!("Upstream: {} commit(s) behind", count));
73 }
74 SyncState::Diverged { ahead, behind } => {
75 output::warn(&format!(
76 "Upstream: diverged ({} ahead, {} behind)",
77 ahead, behind
78 ));
79 }
80 }
81
82 let default_branch = git::default_branch_name()?;
86 let (pr_info, base_pr_merged) = if current != home_branch {
87 get_and_show_pr_info(¤t, &default_branch)
88 } else {
89 (None, None)
90 };
91
92 if current != home_branch {
94 if has_remote {
95 output::info(&format!("Remote: origin/{} exists", current));
96 } else {
97 output::info("Remote: not pushed");
98 }
99 }
100
101 let stash_count = git::stash_count();
103 if stash_count > 0 {
104 output::info(&format!("Stashes: {}", stash_count));
105 }
106
107 let next_action = NextAction::detect(
109 ¤t,
110 home_branch,
111 &working_dir,
112 &sync_state,
113 pr_info.as_ref(),
114 has_remote,
115 base_pr_merged.as_deref(),
116 );
117 next_action.display(¤t);
118
119 Ok(())
120}
121
122fn get_and_show_pr_info(branch: &str, default_branch: &str) -> (Option<PrInfo>, Option<String>) {
129 if !github::is_gh_available() {
130 return (None, None);
131 }
132
133 match github::get_pr_for_branch(branch) {
134 Ok(Some(pr)) => {
135 let state_str = match &pr.state {
136 PrState::Open => "OPEN",
137 PrState::Merged { .. } => "MERGED",
138 PrState::Closed => "CLOSED",
139 };
140
141 let method_str = match &pr.state {
142 PrState::Merged { method, .. } => format!(" ({})", method),
143 _ => String::new(),
144 };
145
146 match &pr.state {
147 PrState::Open => {
148 output::info(&format!("PR: #{} {} [{}]", pr.number, pr.title, state_str));
149 }
150 PrState::Merged { .. } => {
151 output::success(&format!(
152 "PR: #{} {} [{}{}]",
153 pr.number, pr.title, state_str, method_str
154 ));
155 }
156 PrState::Closed => {
157 output::warn(&format!("PR: #{} {} [{}]", pr.number, pr.title, state_str));
158 }
159 }
160
161 if pr.base_branch != default_branch {
164 output::info(&format!(
165 "Base: {} (not {})",
166 pr.base_branch, default_branch
167 ));
168 }
169
170 let base_pr_merged = if pr.base_branch != default_branch && pr.state.is_open() {
172 check_base_pr_merged(&pr.base_branch)
173 } else {
174 None
175 };
176
177 (Some(pr), base_pr_merged)
178 }
179 Ok(None) => {
180 output::info("PR: none");
181 (None, None)
182 }
183 Err(e) => {
184 output::warn(&format!("Could not fetch PR info: {}", e));
185 (None, None)
186 }
187 }
188}
189
190fn check_base_pr_merged(base_branch: &str) -> Option<String> {
192 match github::get_pr_for_branch(base_branch) {
193 Ok(Some(base_pr)) => {
194 if base_pr.state.is_merged() {
195 output::success(&format!("Base PR: #{} [MERGED] ✓", base_pr.number));
196 Some(base_branch.to_string())
197 } else {
198 let state_str = if base_pr.state.is_open() {
199 "OPEN"
200 } else {
201 "CLOSED"
202 };
203 output::info(&format!("Base PR: #{} [{}]", base_pr.number, state_str));
204 None
205 }
206 }
207 Ok(None) => {
208 output::info(&format!("Base PR: none (for {})", base_branch));
209 None
210 }
211 Err(_) => None,
212 }
213}