git_x/domain/
git_repository.rs1use crate::core::{git::*, validation::Validate};
2use crate::{GitXError, Result};
3
4pub struct GitRepository {
6 root_path: String,
7}
8
9impl GitRepository {
10 pub fn open() -> Result<Self> {
12 let root_path = GitOperations::repo_root()?;
13 Ok(Self { root_path })
14 }
15
16 pub fn info(&self) -> Result<RepositoryInfo> {
18 let name = std::path::Path::new(&self.root_path)
19 .file_name()
20 .map(|s| s.to_string_lossy().to_string())
21 .unwrap_or_else(|| "Unknown".to_string());
22
23 let (current_branch, upstream, ahead, behind) = GitOperations::branch_info_optimized()?;
24 let is_clean = GitOperations::is_working_directory_clean()?;
25 let staged_files = GitOperations::staged_files()?;
26
27 Ok(RepositoryInfo {
28 name,
29 root_path: self.root_path.clone(),
30 current_branch,
31 upstream_branch: upstream,
32 ahead_count: ahead,
33 behind_count: behind,
34 is_clean,
35 staged_files_count: staged_files.len(),
36 })
37 }
38
39 pub fn health(&self) -> Result<HealthStatus> {
41 let mut issues = Vec::new();
42 let mut warnings = Vec::new();
43
44 if GitOperations::run(&["config", "user.name"]).is_err() {
46 issues.push("Git user.name not configured".to_string());
47 }
48 if GitOperations::run(&["config", "user.email"]).is_err() {
49 issues.push("Git user.email not configured".to_string());
50 }
51
52 match RemoteOperations::list() {
54 Ok(remotes) => {
55 if remotes.is_empty() {
56 warnings.push("No remotes configured".to_string());
57 }
58 }
59 Err(_) => {
60 issues.push("Could not check remotes".to_string());
61 }
62 }
63
64 match GitOperations::local_branches() {
66 Ok(branches) => {
67 if branches.len() > 20 {
68 warnings.push(format!(
69 "Many local branches ({}) - consider cleaning up",
70 branches.len()
71 ));
72 }
73 }
74 Err(_) => {
75 issues.push("Could not check branches".to_string());
76 }
77 }
78
79 let status = if issues.is_empty() && warnings.is_empty() {
80 HealthLevel::Healthy
81 } else if issues.is_empty() {
82 HealthLevel::Warning
83 } else {
84 HealthLevel::Unhealthy
85 };
86
87 Ok(HealthStatus {
88 level: status,
89 issues,
90 warnings,
91 })
92 }
93
94 pub fn validate_for_operation(&self, operation: &str) -> Result<()> {
96 Validate::in_git_repo()?;
97
98 match operation {
99 "destructive" => {
100 if !GitOperations::is_working_directory_clean()? {
101 return Err(GitXError::GitCommand(
102 "Working directory must be clean for destructive operations".to_string(),
103 ));
104 }
105 }
106 "commit" => {
107 let staged = GitOperations::staged_files()?;
108 if staged.is_empty() {
109 return Err(GitXError::GitCommand("No staged changes found".to_string()));
110 }
111 }
112 _ => {} }
114
115 Ok(())
116 }
117
118 pub fn root_path(&self) -> &str {
120 &self.root_path
121 }
122}
123
124#[derive(Debug, Clone)]
126pub struct RepositoryInfo {
127 pub name: String,
128 pub root_path: String,
129 pub current_branch: String,
130 pub upstream_branch: Option<String>,
131 pub ahead_count: u32,
132 pub behind_count: u32,
133 pub is_clean: bool,
134 pub staged_files_count: usize,
135}
136
137impl RepositoryInfo {
138 pub fn is_in_sync(&self) -> bool {
140 self.ahead_count == 0 && self.behind_count == 0
141 }
142
143 pub fn has_local_changes(&self) -> bool {
145 !self.is_clean || self.staged_files_count > 0
146 }
147
148 pub fn status_description(&self) -> String {
150 let mut parts = Vec::new();
151
152 if let Some(ref upstream) = self.upstream_branch {
153 if self.is_in_sync() {
154 parts.push(format!("up to date with {upstream}"));
155 } else {
156 if self.ahead_count > 0 {
157 parts.push(format!("{} ahead", self.ahead_count));
158 }
159 if self.behind_count > 0 {
160 parts.push(format!("{} behind", self.behind_count));
161 }
162 }
163 } else {
164 parts.push("no upstream configured".to_string());
165 }
166
167 if self.has_local_changes() {
168 parts.push("has local changes".to_string());
169 } else {
170 parts.push("clean".to_string());
171 }
172
173 parts.join(", ")
174 }
175}
176
177#[derive(Debug, Clone)]
179pub struct HealthStatus {
180 pub level: HealthLevel,
181 pub issues: Vec<String>,
182 pub warnings: Vec<String>,
183}
184
185impl HealthStatus {
186 pub fn is_healthy(&self) -> bool {
188 matches!(self.level, HealthLevel::Healthy)
189 }
190
191 pub fn summary(&self) -> String {
193 match self.level {
194 HealthLevel::Healthy => "Repository is healthy".to_string(),
195 HealthLevel::Warning => format!("Repository has {} warning(s)", self.warnings.len()),
196 HealthLevel::Unhealthy => format!("Repository has {} issue(s)", self.issues.len()),
197 }
198 }
199
200 pub fn all_problems(&self) -> Vec<String> {
202 let mut problems = self.issues.clone();
203 problems.extend(self.warnings.clone());
204 problems
205 }
206}
207
208#[derive(Debug, Clone, PartialEq)]
210pub enum HealthLevel {
211 Healthy,
212 Warning,
213 Unhealthy,
214}