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);
22
23 println!();
24
25 match &repo_type {
27 RepoType::MainRepo => {
28 output::info("Repository: main repo");
29 }
30 RepoType::Worktree { home_branch } => {
31 output::info(&format!(
32 "Repository: worktree (home: {})",
33 output::bold(home_branch)
34 ));
35 }
36 }
37
38 if current == home_branch {
40 output::success(&format!("Branch: {} (home)", output::bold(¤t)));
41 } else {
42 output::info(&format!(
43 "Branch: {} (home: {})",
44 output::bold(¤t),
45 home_branch
46 ));
47 }
48
49 match working_dir {
51 WorkingDirState::Clean => {
52 output::success("Working directory: clean");
53 }
54 _ => {
55 output::warn(&format!("Working directory: {}", working_dir.description()));
56 }
57 }
58
59 match &sync_state {
61 SyncState::NoUpstream => {
62 output::info("Upstream: no tracking branch");
63 }
64 SyncState::Synced => {
65 output::success("Upstream: synced");
66 }
67 SyncState::HasUnpushedCommits { count } => {
68 output::warn(&format!("Upstream: {} unpushed commit(s)", count));
69 }
70 SyncState::Behind { count } => {
71 output::warn(&format!("Upstream: {} commit(s) behind", count));
72 }
73 SyncState::Diverged { ahead, behind } => {
74 output::warn(&format!(
75 "Upstream: diverged ({} ahead, {} behind)",
76 ahead, behind
77 ));
78 }
79 }
80
81 let (pr_info, base_pr_merged) = if current != home_branch {
83 get_and_show_pr_info(¤t)
84 } else {
85 (None, None)
86 };
87
88 if current != home_branch {
90 if has_remote {
91 output::info(&format!("Remote: origin/{} exists", current));
92 } else {
93 output::info("Remote: not pushed");
94 }
95 }
96
97 let stash_count = git::stash_count();
99 if stash_count > 0 {
100 output::info(&format!("Stashes: {}", stash_count));
101 }
102
103 let next_action = NextAction::detect(
105 ¤t,
106 home_branch,
107 &working_dir,
108 &sync_state,
109 pr_info.as_ref(),
110 has_remote,
111 base_pr_merged.as_deref(),
112 );
113 next_action.display(¤t);
114
115 Ok(())
116}
117
118fn get_and_show_pr_info(branch: &str) -> (Option<PrInfo>, Option<String>) {
125 if !github::is_gh_available() {
126 return (None, None);
127 }
128
129 match github::get_pr_for_branch(branch) {
130 Ok(Some(pr)) => {
131 let state_str = match &pr.state {
132 PrState::Open => "OPEN",
133 PrState::Merged { .. } => "MERGED",
134 PrState::Closed => "CLOSED",
135 };
136
137 let method_str = match &pr.state {
138 PrState::Merged { method, .. } => format!(" ({})", method),
139 _ => String::new(),
140 };
141
142 match &pr.state {
143 PrState::Open => {
144 output::info(&format!("PR: #{} {} [{}]", pr.number, pr.title, state_str));
145 }
146 PrState::Merged { .. } => {
147 output::success(&format!(
148 "PR: #{} {} [{}{}]",
149 pr.number, pr.title, state_str, method_str
150 ));
151 }
152 PrState::Closed => {
153 output::warn(&format!("PR: #{} {} [{}]", pr.number, pr.title, state_str));
154 }
155 }
156
157 if pr.base_branch != "main" {
159 output::info(&format!("Base: {} (not main)", pr.base_branch));
160 }
161
162 let base_pr_merged = if pr.base_branch != "main" && pr.state.is_open() {
164 check_base_pr_merged(&pr.base_branch)
165 } else {
166 None
167 };
168
169 (Some(pr), base_pr_merged)
170 }
171 Ok(None) => {
172 output::info("PR: none");
173 (None, None)
174 }
175 Err(_) => {
176 (None, None)
178 }
179 }
180}
181
182fn check_base_pr_merged(base_branch: &str) -> Option<String> {
184 match github::get_pr_for_branch(base_branch) {
185 Ok(Some(base_pr)) => {
186 if base_pr.state.is_merged() {
187 output::success(&format!("Base PR: #{} [MERGED] ✓", base_pr.number));
188 Some(base_branch.to_string())
189 } else {
190 let state_str = if base_pr.state.is_open() {
191 "OPEN"
192 } else {
193 "CLOSED"
194 };
195 output::info(&format!("Base PR: #{} [{}]", base_pr.number, state_str));
196 None
197 }
198 }
199 Ok(None) => {
200 output::info(&format!("Base PR: none (for {})", base_branch));
201 None
202 }
203 Err(_) => None,
204 }
205}