gitai/remote/cache/
fetcher.rs

1use crate::remote::models::repo_config::RepositoryConfiguration;
2
3use super::super::common::{ErrorType, Method};
4use cause::{Cause, cause};
5use git2::Repository;
6use std::process::Command;
7
8pub struct RepositoryFetcher;
9
10impl RepositoryFetcher {
11    /// Fetch a repository to a temporary directory
12    /// This is a wrapper around the existing fetch functionality with caching logic
13    pub fn fetch_repository(
14        &self,
15        config: &RepositoryConfiguration,
16        cache_path: &str,
17    ) -> Result<(), Cause<ErrorType>> {
18        // Check if the repository is already cached and up-to-date
19        if Self::is_cache_valid(config, cache_path) {
20            println!("Using cached repository: {}", config.url);
21            return Ok(());
22        }
23
24        println!("Fetching repository: {} to cache", config.url);
25
26        Self::execute_git_clone(config, cache_path)?;
27        if !matches!(config.mtd, Some(Method::ShallowNoSparse)) {
28            Self::execute_git_checkout(cache_path, &config.branch)?;
29        }
30
31        println!("Repository fetched and cached at: {cache_path}");
32        Ok(())
33    }
34
35    /// Execute the git clone command with error handling
36    fn execute_git_clone(
37        config: &RepositoryConfiguration,
38        cache_path: &str,
39    ) -> Result<(), Cause<ErrorType>> {
40        // Remove the cache directory if it exists
41        if std::path::Path::new(cache_path).exists() {
42            std::fs::remove_dir_all(cache_path)
43                .map_err(|e| cause!(ErrorType::GitCloneCommand).src(e))?;
44        }
45
46        if matches!(config.mtd, Some(Method::ShallowNoSparse)) {
47            // Use git command for shallow clone with branch
48            let output = Command::new("git")
49                .args([
50                    "clone",
51                    "--depth",
52                    "1",
53                    "--branch",
54                    &config.branch,
55                    &config.url,
56                    cache_path,
57                ])
58                .output()
59                .map_err(|e| cause!(ErrorType::GitCloneCommand).src(e))?;
60            if !output.status.success() {
61                return Err(
62                    cause!(ErrorType::GitCloneCommand).msg(String::from_utf8_lossy(&output.stderr))
63                );
64            }
65        } else {
66            Repository::clone(&config.url, cache_path)
67                .map_err(|e| cause!(ErrorType::GitCloneCommand).src(e))?;
68        }
69
70        Ok(())
71    }
72
73    /// Execute the git checkout command with error handling
74    fn execute_git_checkout(cache_path: &str, rev: &str) -> Result<(), Cause<ErrorType>> {
75        let repo = Repository::open(cache_path)
76            .map_err(|e| cause!(ErrorType::GitCheckoutCommand).src(e))?;
77
78        let obj = repo
79            .revparse_single(rev)
80            .map_err(|e| cause!(ErrorType::GitCheckoutCommand).src(e))?
81            .peel(git2::ObjectType::Commit)
82            .map_err(|e| cause!(ErrorType::GitCheckoutCommand).src(e))?;
83
84        repo.checkout_tree(&obj, None)
85            .map_err(|e| cause!(ErrorType::GitCheckoutCommand).src(e))?;
86
87        repo.set_head_detached(obj.id())
88            .map_err(|e| cause!(ErrorType::GitCheckoutCommand).src(e))?;
89
90        Ok(())
91    }
92
93    /// Check if the cached repository is still valid (up-to-date)
94    fn is_cache_valid(_config: &RepositoryConfiguration, cache_path: &str) -> bool {
95        // Check if the directory exists
96        std::path::Path::new(cache_path).exists()
97    }
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103
104    #[test]
105    fn test_fetcher_creation() {
106        let _ = RepositoryFetcher;
107        // Just checking the struct can be instantiated
108    }
109
110    #[test]
111    fn test_is_cache_valid_with_nonexistent_path() {
112        let config = RepositoryConfiguration::new(
113            "https://github.com/example/repo.git".to_string(),
114            "main".to_string(),
115            "./src/module1".to_string(),
116            vec!["src/".to_string()],
117            None,
118            None,
119        );
120
121        // Test with a path that doesn't exist
122        let result = RepositoryFetcher::is_cache_valid(&config, "/definitely/does/not/exist");
123        // It should return false since the path doesn't exist
124        assert!(!result);
125    }
126}