git_same/commands/
status.rs1use crate::cli::StatusArgs;
4use crate::config::{Config, WorkspaceManager};
5use crate::discovery::DiscoveryOrchestrator;
6use crate::errors::Result;
7use crate::git::{GitOperations, ShellGit};
8use crate::output::{format_count, Output};
9
10pub async fn run(args: &StatusArgs, config: &Config, output: &Output) -> Result<()> {
12 let workspace = WorkspaceManager::resolve(args.workspace.as_deref(), config)?;
13
14 super::ensure_base_path(&workspace, output)?;
16 let base_path = workspace.expanded_base_path();
17
18 let structure = workspace.structure.as_deref().unwrap_or(&config.structure);
19
20 let git = ShellGit::new();
22 let orchestrator = DiscoveryOrchestrator::new(workspace.filters.clone(), structure.to_string());
23 let local_repos = orchestrator.scan_local(&base_path, &git);
24
25 if local_repos.is_empty() {
26 output.warn("No repositories found");
27 return Ok(());
28 }
29
30 output.info(&format_count(local_repos.len(), "repositories found"));
31
32 let mut uncommitted_count = 0;
34 let mut behind_count = 0;
35 let mut error_count = 0;
36
37 for (path, org, name) in &local_repos {
38 let status = git.status(path);
39
40 match status {
41 Ok(s) => {
42 let is_uncommitted = s.is_uncommitted || s.has_untracked;
43 let is_behind = s.behind > 0;
44
45 if args.uncommitted && !is_uncommitted {
47 continue;
48 }
49 if args.behind && !is_behind {
50 continue;
51 }
52 if !args.org.is_empty() && !args.org.contains(org) {
53 continue;
54 }
55
56 if is_uncommitted {
57 uncommitted_count += 1;
58 }
59 if is_behind {
60 behind_count += 1;
61 }
62
63 let full_name = format!("{}/{}", org, name);
65 if args.detailed {
66 println!("{}", full_name);
67 println!(" Branch: {}", s.branch);
68 if s.ahead > 0 || s.behind > 0 {
69 println!(" Ahead: {}, Behind: {}", s.ahead, s.behind);
70 }
71 if s.is_uncommitted {
72 println!(" Status: uncommitted changes");
73 }
74 if s.has_untracked {
75 println!(" Status: has untracked files");
76 }
77 } else {
78 let mut indicators = Vec::new();
79 if is_uncommitted {
80 indicators.push("*".to_string());
81 }
82 if s.ahead > 0 {
83 indicators.push(format!("+{}", s.ahead));
84 }
85 if s.behind > 0 {
86 indicators.push(format!("-{}", s.behind));
87 }
88
89 if indicators.is_empty() {
90 println!(" {} (clean)", full_name);
91 } else {
92 println!(" {} [{}]", full_name, indicators.join(", "));
93 }
94 }
95 }
96 Err(e) => {
97 error_count += 1;
98 output.verbose(&format!(" {}/{} - error: {}", org, name, e));
99 }
100 }
101 }
102
103 println!();
105 if uncommitted_count > 0 {
106 output.warn(&format!(
107 "{} repositories have uncommitted changes",
108 uncommitted_count
109 ));
110 }
111 if behind_count > 0 {
112 output.info(&format!(
113 "{} repositories are behind upstream",
114 behind_count
115 ));
116 }
117 if error_count > 0 {
118 output.warn(&format!(
119 "{} repositories could not be checked",
120 error_count
121 ));
122 } else if uncommitted_count == 0 && behind_count == 0 {
123 output.success("All repositories are clean and up to date");
124 }
125
126 Ok(())
127}
128
129#[cfg(test)]
130#[path = "status_tests.rs"]
131mod tests;