1use console::Style;
2use std::process::Command;
3
4pub fn run() {
5 let bold = Style::new().bold();
6 let green = Style::new().green().bold();
7 let yellow = Style::new().yellow().bold();
8 let red = Style::new().red().bold();
9
10 println!("{}", bold.apply_to("Repository Health Check"));
11 println!("{}", bold.apply_to("========================="));
12 println!();
13
14 if !is_git_repo(&std::env::current_dir().unwrap_or_else(|_| ".".into())) {
16 println!("{} Not in a Git repository", red.apply_to("✗"));
17 return;
18 }
19
20 check_repo_status(&green, &yellow, &red);
22
23 check_untracked_files(&green, &yellow, &red);
25
26 check_stale_branches(&green, &yellow, &red);
28
29 check_repo_size(&green, &yellow, &red);
31
32 check_uncommitted_changes(&green, &yellow, &red);
34
35 println!();
36 println!("{}", bold.apply_to("Health check complete!"));
37}
38
39pub fn is_git_repo(path: &std::path::Path) -> bool {
40 Command::new("git")
41 .args(["rev-parse", "--git-dir"])
42 .current_dir(path)
43 .output()
44 .map(|output| output.status.success())
45 .unwrap_or(false)
46}
47
48fn check_repo_status(green: &Style, _yellow: &Style, red: &Style) {
49 let output = Command::new("git")
50 .args(["status", "--porcelain"])
51 .output()
52 .expect("Failed to run git status");
53
54 let status_output = String::from_utf8_lossy(&output.stdout);
55
56 if status_output.trim().is_empty() {
57 println!("{} Working directory is clean", green.apply_to("✓"));
58 } else {
59 println!("{} Working directory has changes", red.apply_to("✗"));
60 }
61}
62
63fn check_untracked_files(green: &Style, yellow: &Style, _red: &Style) {
64 let output = Command::new("git")
65 .args(["ls-files", "--others", "--exclude-standard"])
66 .output()
67 .expect("Failed to list untracked files");
68
69 let untracked = String::from_utf8_lossy(&output.stdout);
70 let untracked_files: Vec<&str> = untracked.lines().collect();
71
72 if untracked_files.is_empty() {
73 println!("{} No untracked files", green.apply_to("✓"));
74 } else {
75 println!(
76 "{} {} untracked files found",
77 yellow.apply_to("!"),
78 untracked_files.len()
79 );
80 }
81}
82
83fn check_stale_branches(green: &Style, yellow: &Style, _red: &Style) {
84 let output = Command::new("git")
85 .args([
86 "for-each-ref",
87 "--format=%(refname:short) %(committerdate:relative)",
88 "refs/heads/",
89 ])
90 .output()
91 .expect("Failed to list branches");
92
93 let branches = String::from_utf8_lossy(&output.stdout);
94 let mut stale_count = 0;
95
96 for line in branches.lines() {
97 if line.contains("months ago") || line.contains("year") {
98 stale_count += 1;
99 }
100 }
101
102 if stale_count == 0 {
103 println!(
104 "{} No stale branches (older than 1 month)",
105 green.apply_to("✓")
106 );
107 } else {
108 println!(
109 "{} {} potentially stale branches found",
110 yellow.apply_to("!"),
111 stale_count
112 );
113 }
114}
115
116fn check_repo_size(green: &Style, yellow: &Style, red: &Style) {
117 let output = Command::new("du")
118 .args(["-sh", ".git"])
119 .output()
120 .expect("Failed to check repository size");
121
122 let size_output = String::from_utf8_lossy(&output.stdout);
123 let size = size_output.split_whitespace().next().unwrap_or("unknown");
124
125 if size.ends_with('K')
127 || (size.ends_with('M') && size.chars().next().unwrap_or('0').to_digit(10).unwrap_or(0) < 5)
128 {
129 println!(
130 "{} Repository size: {} (healthy)",
131 green.apply_to("✓"),
132 size
133 );
134 } else if size.ends_with('M')
135 || (size.ends_with('G') && size.chars().next().unwrap_or('0').to_digit(10).unwrap_or(0) < 1)
136 {
137 println!(
138 "{} Repository size: {} (moderate)",
139 yellow.apply_to("!"),
140 size
141 );
142 } else {
143 println!(
144 "{} Repository size: {} (large - consider cleanup)",
145 red.apply_to("✗"),
146 size
147 );
148 }
149}
150
151fn check_uncommitted_changes(green: &Style, yellow: &Style, _red: &Style) {
152 let output = Command::new("git")
153 .args(["diff", "--cached", "--name-only"])
154 .output()
155 .expect("Failed to check staged changes");
156
157 let staged = String::from_utf8_lossy(&output.stdout);
158 let staged_files: Vec<&str> = staged.lines().filter(|line| !line.is_empty()).collect();
159
160 if staged_files.is_empty() {
161 println!("{} No staged changes", green.apply_to("✓"));
162 } else {
163 println!(
164 "{} {} files staged for commit",
165 yellow.apply_to("!"),
166 staged_files.len()
167 );
168 }
169}