Skip to main content

vibe_workspace/utils/
git.rs

1use anyhow::Result;
2use std::path::Path;
3use std::process::Command;
4
5/// Check if git is available on the system
6pub fn is_git_available() -> bool {
7    Command::new("git")
8        .arg("--version")
9        .output()
10        .map(|output| output.status.success())
11        .unwrap_or(false)
12}
13
14/// Check if GitHub CLI is available on the system
15pub fn is_github_cli_available() -> bool {
16    Command::new("gh")
17        .arg("--version")
18        .output()
19        .map(|output| output.status.success())
20        .unwrap_or(false)
21}
22
23/// Get git version string
24pub fn get_git_version() -> Result<String> {
25    let output = Command::new("git").arg("--version").output()?;
26
27    if !output.status.success() {
28        anyhow::bail!("Failed to get git version");
29    }
30
31    let version = String::from_utf8_lossy(&output.stdout);
32    Ok(version.trim().to_string())
33}
34
35/// Validate git repository path
36pub fn validate_git_repository<P: AsRef<Path>>(path: P) -> Result<()> {
37    let path = path.as_ref();
38
39    if !path.exists() {
40        anyhow::bail!("Path does not exist: {}", path.display());
41    }
42
43    if !path.is_dir() {
44        anyhow::bail!("Path is not a directory: {}", path.display());
45    }
46
47    let git_dir = path.join(".git");
48    if !git_dir.exists() {
49        anyhow::bail!("Not a git repository: {}", path.display());
50    }
51
52    Ok(())
53}
54
55/// Extract repository name from URL
56pub fn extract_repo_name_from_url(url: &str) -> Option<String> {
57    // Handle both SSH and HTTPS URLs
58    let url = url.trim_end_matches(".git");
59
60    if let Some(last_part) = url.split('/').next_back() {
61        if !last_part.is_empty() {
62            return Some(last_part.to_string());
63        }
64    }
65
66    None
67}
68
69/// Normalize git URL to HTTPS format
70pub fn normalize_git_url(url: &str) -> String {
71    if url.starts_with("git@github.com:") {
72        // Convert SSH to HTTPS
73        let repo_path = url.strip_prefix("git@github.com:").unwrap_or(url);
74        let repo_path = repo_path.strip_suffix(".git").unwrap_or(repo_path);
75        format!("https://github.com/{repo_path}")
76    } else if url.starts_with("https://github.com/") {
77        // Already HTTPS, just ensure no .git suffix
78        url.strip_suffix(".git").unwrap_or(url).to_string()
79    } else {
80        // Return as-is for other hosts
81        url.to_string()
82    }
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88
89    #[test]
90    fn test_extract_repo_name_from_url() {
91        assert_eq!(
92            extract_repo_name_from_url("https://github.com/user/repo.git"),
93            Some("repo".to_string())
94        );
95
96        assert_eq!(
97            extract_repo_name_from_url("git@github.com:user/repo.git"),
98            Some("repo".to_string())
99        );
100
101        assert_eq!(
102            extract_repo_name_from_url("https://github.com/user/repo"),
103            Some("repo".to_string())
104        );
105
106        assert_eq!(
107            extract_repo_name_from_url("invalid-url"),
108            Some("invalid-url".to_string())
109        );
110    }
111
112    #[test]
113    fn test_normalize_git_url() {
114        assert_eq!(
115            normalize_git_url("git@github.com:user/repo.git"),
116            "https://github.com/user/repo"
117        );
118
119        assert_eq!(
120            normalize_git_url("https://github.com/user/repo.git"),
121            "https://github.com/user/repo"
122        );
123
124        assert_eq!(
125            normalize_git_url("https://github.com/user/repo"),
126            "https://github.com/user/repo"
127        );
128
129        assert_eq!(
130            normalize_git_url("https://gitlab.com/user/repo.git"),
131            "https://gitlab.com/user/repo.git"
132        );
133    }
134}