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
63pub 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 let cache_manager = CacheManager::new();
76 let fetcher = RepositoryFetcher;
77
78 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 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 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 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 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 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}