1#![warn(missing_docs)]
2
3use nargo_types::{Error, Result, Span};
4use std::{path::{Path, PathBuf}, process::Command};
5
6use crate::types::VcsType;
7
8pub struct VcsIntegration {
10 pub repo_dir: PathBuf,
12 pub vcs_type: VcsType,
14}
15
16impl VcsIntegration {
17 pub fn new(repo_dir: &Path) -> Self {
19 let vcs_type = Self::detect_vcs(repo_dir);
20 Self { repo_dir: repo_dir.to_path_buf(), vcs_type }
21 }
22
23 pub fn detect_vcs(repo_dir: &Path) -> VcsType {
25 if repo_dir.join(".git").exists() {
26 VcsType::Git
27 }
28 else if repo_dir.join(".svn").exists() {
29 VcsType::Svn
30 }
31 else if repo_dir.join(".hg").exists() {
32 VcsType::Mercurial
33 }
34 else {
35 VcsType::None
36 }
37 }
38
39 pub fn get_vcs_type(&self) -> VcsType {
41 self.vcs_type.clone()
42 }
43
44 pub fn is_git_repo(&self) -> bool {
46 self.repo_dir.join(".git").exists()
47 }
48
49 pub fn is_svn_repo(&self) -> bool {
51 self.repo_dir.join(".svn").exists()
52 }
53
54 pub fn is_hg_repo(&self) -> bool {
56 self.repo_dir.join(".hg").exists()
57 }
58
59 pub fn get_current_branch(&self) -> Result<String> {
61 match self.vcs_type {
62 VcsType::Git => self.get_git_branch(),
63 VcsType::Svn => self.get_svn_branch(),
64 VcsType::Mercurial => self.get_hg_branch(),
65 VcsType::None => Err(Error::external_error("vcs".to_string(), "No version control system detected".to_string(), Span::unknown())),
66 }
67 }
68
69 pub fn get_git_branch(&self) -> Result<String> {
71 let output = Command::new("git").arg("branch").arg("--show-current").current_dir(&self.repo_dir).output()?;
72
73 if !output.status.success() {
74 return Err(Error::external_error("vcs".to_string(), format!("Failed to get current branch: {}", String::from_utf8_lossy(&output.stderr)), Span::unknown()));
75 }
76
77 Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
78 }
79
80 pub fn get_svn_branch(&self) -> Result<String> {
82 let output = Command::new("svn").arg("info").current_dir(&self.repo_dir).output()?;
83
84 if !output.status.success() {
85 return Err(Error::external_error("vcs".to_string(), format!("Failed to get SVN info: {}", String::from_utf8_lossy(&output.stderr)), Span::unknown()));
86 }
87
88 let output_str = String::from_utf8_lossy(&output.stdout).to_string();
89 for line in output_str.lines() {
90 if line.starts_with("URL:") {
91 let url = line.split(": ").nth(1).unwrap_or("");
92 if let Some(branch_part) = url.split("/branches/").nth(1) {
94 return Ok(branch_part.split("/").next().unwrap_or("").to_string());
95 }
96 else if url.contains("/trunk/") {
97 return Ok("trunk".to_string());
98 }
99 else if url.contains("/tags/") {
100 return Ok("tags".to_string());
101 }
102 }
103 }
104
105 Ok("unknown".to_string())
106 }
107
108 pub fn get_hg_branch(&self) -> Result<String> {
110 let output = Command::new("hg").arg("branch").current_dir(&self.repo_dir).output()?;
111
112 if !output.status.success() {
113 return Err(Error::external_error("vcs".to_string(), format!("Failed to get Mercurial branch: {}", String::from_utf8_lossy(&output.stderr)), Span::unknown()));
114 }
115
116 Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
117 }
118
119 pub fn get_latest_commit(&self) -> Result<String> {
121 match self.vcs_type {
122 VcsType::Git => self.get_git_commit(),
123 VcsType::Svn => self.get_svn_commit(),
124 VcsType::Mercurial => self.get_hg_commit(),
125 VcsType::None => Err(Error::external_error("vcs".to_string(), "No version control system detected".to_string(), Span::unknown())),
126 }
127 }
128
129 pub fn get_git_commit(&self) -> Result<String> {
131 let output = Command::new("git").arg("rev-parse").arg("HEAD").current_dir(&self.repo_dir).output()?;
132
133 if !output.status.success() {
134 return Err(Error::external_error("vcs".to_string(), format!("Failed to get latest commit: {}", String::from_utf8_lossy(&output.stderr)), Span::unknown()));
135 }
136
137 Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
138 }
139
140 pub fn get_svn_commit(&self) -> Result<String> {
142 let output = Command::new("svn").arg("info").current_dir(&self.repo_dir).output()?;
143
144 if !output.status.success() {
145 return Err(Error::external_error("vcs".to_string(), format!("Failed to get SVN info: {}", String::from_utf8_lossy(&output.stderr)), Span::unknown()));
146 }
147
148 let output_str = String::from_utf8_lossy(&output.stdout).to_string();
149 for line in output_str.lines() {
150 if line.starts_with("Revision:") {
151 return Ok(line.split(": ").nth(1).unwrap_or("").to_string());
152 }
153 }
154
155 Err(Error::external_error("vcs".to_string(), "Failed to extract SVN revision".to_string(), Span::unknown()))
156 }
157
158 pub fn get_hg_commit(&self) -> Result<String> {
160 let output = Command::new("hg").arg("identify").arg("--id").current_dir(&self.repo_dir).output()?;
161
162 if !output.status.success() {
163 return Err(Error::external_error("vcs".to_string(), format!("Failed to get Mercurial commit: {}", String::from_utf8_lossy(&output.stderr)), Span::unknown()));
164 }
165
166 Ok(String::from_utf8_lossy(&output.stdout).trim().to_string())
167 }
168
169 pub fn get_status(&self) -> Result<String> {
171 match self.vcs_type {
172 VcsType::Git => self.get_git_status(),
173 VcsType::Svn => self.get_svn_status(),
174 VcsType::Mercurial => self.get_hg_status(),
175 VcsType::None => Err(Error::external_error("vcs".to_string(), "No version control system detected".to_string(), Span::unknown())),
176 }
177 }
178
179 pub fn get_git_status(&self) -> Result<String> {
181 let output = Command::new("git").arg("status").arg("--porcelain").current_dir(&self.repo_dir).output()?;
182
183 if !output.status.success() {
184 return Err(Error::external_error("vcs".to_string(), format!("Failed to get git status: {}", String::from_utf8_lossy(&output.stderr)), Span::unknown()));
185 }
186
187 Ok(String::from_utf8_lossy(&output.stdout).to_string())
188 }
189
190 pub fn get_svn_status(&self) -> Result<String> {
192 let output = Command::new("svn").arg("status").current_dir(&self.repo_dir).output()?;
193
194 if !output.status.success() {
195 return Err(Error::external_error("vcs".to_string(), format!("Failed to get SVN status: {}", String::from_utf8_lossy(&output.stderr)), Span::unknown()));
196 }
197
198 Ok(String::from_utf8_lossy(&output.stdout).to_string())
199 }
200
201 pub fn get_hg_status(&self) -> Result<String> {
203 let output = Command::new("hg").arg("status").current_dir(&self.repo_dir).output()?;
204
205 if !output.status.success() {
206 return Err(Error::external_error("vcs".to_string(), format!("Failed to get Mercurial status: {}", String::from_utf8_lossy(&output.stderr)), Span::unknown()));
207 }
208
209 Ok(String::from_utf8_lossy(&output.stdout).to_string())
210 }
211
212 pub fn generate_change_summary(&self) -> Result<String> {
214 match self.vcs_type {
215 VcsType::Git => self.generate_git_change_summary(),
216 VcsType::Svn => self.generate_svn_change_summary(),
217 VcsType::Mercurial => self.generate_hg_change_summary(),
218 VcsType::None => Err(Error::external_error("vcs".to_string(), "No version control system detected".to_string(), Span::unknown())),
219 }
220 }
221
222 pub fn generate_git_change_summary(&self) -> Result<String> {
224 let status = self.get_git_status()?;
225 let mut summary = String::new();
226 let mut added = 0;
227 let mut modified = 0;
228 let mut deleted = 0;
229 let mut renamed = 0;
230
231 for line in status.lines() {
232 if line.len() < 3 {
233 continue;
234 }
235
236 let status_code = &line[0..2];
237 match status_code {
238 "A " => added += 1,
239 "M " => modified += 1,
240 "D " => deleted += 1,
241 "R " => renamed += 1,
242 _ => {}
243 }
244 }
245
246 summary.push_str(&format!("Git change summary:\n"));
247 summary.push_str(&format!("- Added: {}\n", added));
248 summary.push_str(&format!("- Modified: {}\n", modified));
249 summary.push_str(&format!("- Deleted: {}\n", deleted));
250 summary.push_str(&format!("- Renamed: {}\n", renamed));
251
252 if !status.is_empty() {
253 summary.push_str("\nDetailed changes:\n");
254 summary.push_str(&status);
255 }
256
257 Ok(summary)
258 }
259
260 pub fn generate_svn_change_summary(&self) -> Result<String> {
262 let status = self.get_svn_status()?;
263 let mut summary = String::new();
264 let mut added = 0;
265 let mut modified = 0;
266 let mut deleted = 0;
267 let mut other = 0;
268
269 for line in status.lines() {
270 if line.is_empty() {
271 continue;
272 }
273
274 let status_char = line.chars().next().unwrap_or(' ');
275 match status_char {
276 'A' => added += 1,
277 'M' => modified += 1,
278 'D' => deleted += 1,
279 _ => other += 1,
280 }
281 }
282
283 summary.push_str(&format!("SVN change summary:\n"));
284 summary.push_str(&format!("- Added: {}\n", added));
285 summary.push_str(&format!("- Modified: {}\n", modified));
286 summary.push_str(&format!("- Deleted: {}\n", deleted));
287 if other > 0 {
288 summary.push_str(&format!("- Other: {}\n", other));
289 }
290
291 if !status.is_empty() {
292 summary.push_str("\nDetailed changes:\n");
293 summary.push_str(&status);
294 }
295
296 Ok(summary)
297 }
298
299 pub fn generate_hg_change_summary(&self) -> Result<String> {
301 let status = self.get_hg_status()?;
302 let mut summary = String::new();
303 let mut added = 0;
304 let mut modified = 0;
305 let mut deleted = 0;
306 let mut renamed = 0;
307 let mut other = 0;
308
309 for line in status.lines() {
310 if line.is_empty() {
311 continue;
312 }
313
314 let status_char = line.chars().next().unwrap_or(' ');
315 match status_char {
316 'A' => added += 1,
317 'M' => modified += 1,
318 'D' => deleted += 1,
319 'R' => renamed += 1,
320 _ => other += 1,
321 }
322 }
323
324 summary.push_str(&format!("Mercurial change summary:\n"));
325 summary.push_str(&format!("- Added: {}\n", added));
326 summary.push_str(&format!("- Modified: {}\n", modified));
327 summary.push_str(&format!("- Deleted: {}\n", deleted));
328 if renamed > 0 {
329 summary.push_str(&format!("- Renamed: {}\n", renamed));
330 }
331 if other > 0 {
332 summary.push_str(&format!("- Other: {}\n", other));
333 }
334
335 if !status.is_empty() {
336 summary.push_str("\nDetailed changes:\n");
337 summary.push_str(&status);
338 }
339
340 Ok(summary)
341 }
342}