git_x/domain/
git_repository.rs

1use crate::core::{git::*, validation::Validate};
2use crate::{GitXError, Result};
3
4/// High-level git repository abstraction
5pub struct GitRepository {
6    root_path: String,
7}
8
9impl GitRepository {
10    /// Create a new repository instance
11    pub fn open() -> Result<Self> {
12        let root_path = GitOperations::repo_root()?;
13        Ok(Self { root_path })
14    }
15
16    /// Get repository information
17    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    /// Get repository health status
40    pub fn health(&self) -> Result<HealthStatus> {
41        let mut issues = Vec::new();
42        let mut warnings = Vec::new();
43
44        // Check git configuration
45        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        // Check remotes
53        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        // Check for too many branches
65        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    /// Validate repository state for operations
95    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            _ => {} // No specific validation needed
113        }
114
115        Ok(())
116    }
117
118    /// Get the repository root path
119    pub fn root_path(&self) -> &str {
120        &self.root_path
121    }
122}
123
124/// Repository information structure
125#[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    /// Check if the repository is in sync with upstream
139    pub fn is_in_sync(&self) -> bool {
140        self.ahead_count == 0 && self.behind_count == 0
141    }
142
143    /// Check if there are local changes
144    pub fn has_local_changes(&self) -> bool {
145        !self.is_clean || self.staged_files_count > 0
146    }
147
148    /// Get a human-readable status description
149    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/// Repository health status
178#[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    /// Check if the repository is healthy
187    pub fn is_healthy(&self) -> bool {
188        matches!(self.level, HealthLevel::Healthy)
189    }
190
191    /// Get a summary message
192    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    /// Get all problems (issues + warnings)
201    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/// Health level enumeration
209#[derive(Debug, Clone, PartialEq)]
210pub enum HealthLevel {
211    Healthy,
212    Warning,
213    Unhealthy,
214}