gitai/remote/
sync.rs

1use std::env;
2use std::fs;
3use std::path::Path;
4
5use cause::{Cause, cause};
6use fs_extra::{copy_items, dir::CopyOptions, remove_items};
7use log::{debug, info};
8
9use super::cache::{
10    fetcher::RepositoryFetcher, key_generator::CacheKeyGenerator, manager::CacheManager,
11};
12use super::common::{ErrorType, Target, parse};
13use super::models::repo_config::RepositoryConfiguration;
14
15fn get_repo_configs(
16    target: &Target,
17) -> Result<(String, Vec<RepositoryConfiguration>), Cause<ErrorType>> {
18    match target {
19        Target::Declared(opt_name) => {
20            let (root, mut parsed_items) = parse::parse_gitwire()?;
21            if let Some(name) = opt_name {
22                parsed_items.retain(|p| p.name.as_ref() == Some(name));
23                if parsed_items.is_empty() {
24                    return Err(cause!(
25                        ErrorType::NoItemToOperate,
26                        "No item with specified name"
27                    ));
28                }
29            }
30            let repo_configs = parsed_items
31                .into_iter()
32                .map(|parsed| {
33                    RepositoryConfiguration::new(
34                        parsed.url,
35                        parsed.rev,
36                        parsed.dst,
37                        vec![parsed.src],
38                        None,
39                        parsed.mtd,
40                    )
41                })
42                .collect();
43            Ok((root, repo_configs))
44        }
45        Target::Direct(parsed) => {
46            let root = env::current_dir()
47                .or(Err(cause!(ErrorType::CurrentDirRetrieve)))?
48                .to_string_lossy()
49                .to_string();
50            let repo_configs = vec![RepositoryConfiguration::new(
51                parsed.url.clone(),
52                parsed.rev.clone(),
53                parsed.dst.clone(),
54                vec![parsed.src.clone()],
55                None,
56                parsed.mtd.clone(),
57            )];
58            Ok((root, repo_configs))
59        }
60    }
61}
62
63// Enhanced sync functionality that integrates caching
64pub fn sync_with_caching(
65    target: &Target,
66    _mode: super::common::sequence::Mode,
67) -> Result<bool, Cause<ErrorType>> {
68    info!("git-wire sync with caching started");
69
70    let (root_dir, repo_configs) = get_repo_configs(target)?;
71
72    info!("Found {} repository configurations", repo_configs.len());
73
74    // Create components needed for caching
75    let cache_manager = CacheManager::new();
76    let fetcher = RepositoryFetcher;
77
78    // Plan fetch operations to identify unique repositories
79    let (unique_configs, mut wire_operations) = cache_manager
80        .plan_fetch_operations(&repo_configs)
81        .map_err(|e| cause!(ErrorType::NoItemToOperate).msg(e))?;
82
83    info!(
84        "Identified {} unique repositories to fetch ({} redundant fetches avoided)",
85        unique_configs.len(),
86        repo_configs.len().saturating_sub(unique_configs.len())
87    );
88
89    // Fetch each unique repository to its cache location
90    for config in &unique_configs {
91        let cache_key = CacheKeyGenerator::generate_key(config);
92        let cache_dir = env::temp_dir().join("git-wire-cache").join(cache_key);
93        fs::create_dir_all(&cache_dir).map_err(|e| cause!(ErrorType::TempDirCreation).src(e))?;
94        let cache_path = cache_dir.to_string_lossy().to_string();
95
96        debug!(
97            "Fetching repository {} to cache path {}",
98            config.url, cache_path
99        );
100        fetcher.fetch_repository(config, &cache_path)?;
101        debug!("Repository {} successfully cached", config.url);
102
103        // Update the wire operations to use the actual cache path
104        for op in &mut wire_operations {
105            if op.source_config.url == config.url && op.source_config.branch == config.branch {
106                op.cached_repo_path.clone_from(&cache_path);
107            }
108        }
109    }
110
111    // Execute wire operations using cached repositories
112    for wire_op in &wire_operations {
113        if wire_op.source_config.filters.is_empty() {
114            debug!(
115                "Skipping wire operation with no filters: {}",
116                wire_op.operation_id
117            );
118            continue;
119        }
120
121        let source_subdir = &wire_op.source_config.filters[0];
122        let source_content = Path::new(&wire_op.cached_repo_path).join(source_subdir);
123        if !source_content.exists() {
124            info!(
125                "Source path {} does not exist in cached repo {}",
126                source_subdir, wire_op.source_config.url
127            );
128            continue;
129        }
130
131        let dest_dir = Path::new(&root_dir).join(&wire_op.source_config.target_path);
132
133        // Remove destination if it exists
134        if dest_dir.exists() {
135            remove_items(&[dest_dir.as_path()]).map_err(|e| {
136                cause!(ErrorType::MoveFromTempToDest)
137                    .src(e)
138                    .msg(format!("Could not remove {}", dest_dir.display()))
139            })?;
140        }
141
142        // Create destination directory
143        fs::create_dir_all(&dest_dir).map_err(|e| cause!(ErrorType::MoveFromTempToDest).src(e))?;
144
145        let mut opt = CopyOptions::new();
146        opt.overwrite = true;
147        opt.copy_inside = true;
148
149        copy_items(&[source_content.as_path()], &dest_dir, &opt).map_err(|e| {
150            cause!(ErrorType::MoveFromTempToDest).src(e).msg(format!(
151                "Could not copy {} to {}",
152                source_content.display(),
153                dest_dir.display()
154            ))
155        })?;
156
157        debug!(
158            "Copied contents of {source_subdir} to {}",
159            wire_op.source_config.target_path
160        );
161    }
162
163    info!("git-wire sync with caching completed");
164    Ok(true)
165}