gitai/remote/cache/
fetcher.rs1use 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 pub fn fetch_repository(
14 &self,
15 config: &RepositoryConfiguration,
16 cache_path: &str,
17 ) -> Result<(), Cause<ErrorType>> {
18 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 fn execute_git_clone(
37 config: &RepositoryConfiguration,
38 cache_path: &str,
39 ) -> Result<(), Cause<ErrorType>> {
40 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 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 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 fn is_cache_valid(_config: &RepositoryConfiguration, cache_path: &str) -> bool {
95 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 }
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 let result = RepositoryFetcher::is_cache_valid(&config, "/definitely/does/not/exist");
123 assert!(!result);
125 }
126}