1use std::{
2 collections::{HashMap, HashSet},
3 path::{Path, PathBuf},
4 time::Instant,
5};
6
7use indicatif::{HumanDuration, ProgressBar, ProgressStyle};
8use tracing::{debug, info};
9
10use super::{config, env, MercurialRepo, RepositorySavedState, TargetRepository};
11use crate::error::ErrorKind;
12use crate::git::GitTargetRepository;
13
14fn construct_path<P: AsRef<Path>>(config_path: &Option<P>, target: P) -> PathBuf {
15 let target = target.as_ref();
16 if target.is_absolute() {
17 target.into()
18 } else {
19 config_path
20 .as_ref()
21 .map(|c| c.as_ref().join(target))
22 .unwrap_or_else(|| target.into())
23 }
24}
25
26pub fn multi2git<P: AsRef<Path>>(
27 verify: bool,
28 git_active_branches: Option<usize>,
29 ignore_unknown_requirements: bool,
30 env: &env::Environment,
31 config_filename: P,
32 multi_config: &config::MultiConfig,
33) -> Result<(), ErrorKind> {
34 debug!("Config: {:?}", multi_config);
35 debug!("Environment: {:?}", env);
36
37 let config_path = config_filename.as_ref().parent();
38
39 for repo in &multi_config.repositories {
40 export_repository(
41 &config_path,
42 repo,
43 env,
44 verify,
45 git_active_branches,
46 ignore_unknown_requirements,
47 )?;
48 }
49
50 let path_git = construct_path(&config_path, &multi_config.path_git);
51
52 let git_repo = GitTargetRepository::open(&path_git);
53
54 let new_repository = !path_git.exists();
55
56 let default_branch = git_repo.git_config_default_branch()?;
57 let remotes = if new_repository {
58 git_repo.create_repo(&default_branch)?;
59 HashSet::new()
60 } else {
61 git_repo.remote_list()?
62 };
63
64 let mut merge = HashMap::new();
65 for repo in &multi_config.repositories {
66 let alias = repo
67 .alias
68 .as_ref()
69 .unwrap_or_else(|| repo.config.path_prefix.as_ref().unwrap());
70 if !remotes.contains(alias) {
71 git_repo.remote_add(
72 alias,
73 construct_path(&config_path, &repo.path_git)
74 .canonicalize()?
75 .to_str()
76 .unwrap(),
77 )?;
78 }
79 if let Some(merged_branches) = &repo.merged_branches {
80 for (branch_to, branch_from) in merged_branches {
81 merge
82 .entry(branch_to)
83 .or_insert_with(Vec::new)
84 .push(format!("{alias}/{branch_from}"));
85 }
86 }
87 }
88
89 git_repo.fetch_all()?;
90
91 for (branch_to, branches_from) in merge {
92 git_repo.checkout(branch_to)?;
93
94 if new_repository {
95 for branch_from in branches_from {
96 git_repo.merge_unrelated(&[branch_from.as_ref()])?;
97 }
98 } else {
99 let branches_from_str: Vec<_> = branches_from.iter().map(AsRef::as_ref).collect();
100 git_repo.merge_unrelated(&branches_from_str)?;
101 }
102 }
103
104 Ok(())
105}
106
107fn export_repository(
108 config_path: &Option<&Path>,
109 repo: &config::PathRepositoryConfig,
110 env: &env::Environment,
111 verify: bool,
112 git_active_branches: Option<usize>,
113 ignore_unknown_requirements: bool,
114) -> Result<(), ErrorKind> {
115 let path_hg = construct_path(config_path, &repo.path_hg);
116
117 info!("Reading repo: {:?}", repo.path_hg);
118 let mercurial_repo = match MercurialRepo::open_with_pull(
119 &path_hg,
120 &repo.config,
121 ignore_unknown_requirements,
122 env,
123 ) {
124 Ok(repo) => repo,
125 Err(ErrorKind::HgParserFailure(fail)) => panic!("Cannot open {:?}: {:?}", path_hg, fail),
126 Err(other) => panic!("Cannot open {:?}: {:?}", path_hg, other),
127 };
128
129 info!("Verifying heads in repository {:?}", repo.path_hg);
130 if !mercurial_repo.verify_heads(repo.config.allow_unnamed_heads)? {
131 return Err(ErrorKind::VerifyFailure("Verify heads failed".into()));
132 }
133
134 let tip = mercurial_repo.changelog_len()?;
135
136 let to = if let Some(limit_high) = repo.config.limit_high {
137 tip.min(limit_high)
138 } else {
139 tip
140 };
141
142 let offset = repo.config.offset.unwrap_or(0);
143
144 let path_git = construct_path(config_path, &repo.path_git);
145
146 let mut git_repo = GitTargetRepository::open(path_git);
147
148 git_repo.set_env(env);
149
150 let mut errors = None;
151 let mut counter: usize = 0;
152 let from_tag = {
153 let (output, saved_state, default_branch) =
154 git_repo.start_import(git_active_branches, repo.config.default_branch())?;
155
156 let (from, from_tag) = if let Some(saved_state) = saved_state.as_ref() {
157 match saved_state {
158 RepositorySavedState::OffsetedRevision(rev, from_tag) => {
159 (rev - offset, from_tag - offset)
160 }
161 }
162 } else {
163 (0, 0)
164 };
165
166 let mut brmap = repo.config.branches.clone().unwrap_or_default();
167
168 info!(
169 "Exporting commits from repo: {:?} from {} to {} offset {:?}",
170 repo.path_hg, from, to, repo.config.offset
171 );
172
173 let show_progress_bar = !env.cron;
174
175 let start = Instant::now();
176 let progress_bar = ProgressBar::new((to - from) as u64);
177 if show_progress_bar {
178 progress_bar.set_style(ProgressStyle::default_bar().template(
179 "{spinner:.green}[{elapsed_precise}] [{wide_bar:.cyan/blue}] {msg} ({eta})",
180 )?);
181 }
182 for mut changeset in mercurial_repo.range(from..to) {
183 if show_progress_bar {
184 progress_bar.inc(1);
185 progress_bar.set_message(format!("{:6}/{}", changeset.revision.0, to));
186 }
187
188 match mercurial_repo.export_commit(
189 &mut changeset,
190 counter,
191 &mut brmap,
192 output,
193 &default_branch,
194 ) {
195 Ok(progress) => counter = progress,
196 x => {
197 errors = Some((x, changeset.revision.0));
198 break;
199 }
200 }
201 }
202
203 if errors.is_none() {
204 if show_progress_bar {
205 progress_bar.finish_with_message(format!(
206 "Repository {} [{};{}). Elapsed: {}",
207 repo.path_git.to_str().unwrap(),
208 from,
209 to,
210 HumanDuration(start.elapsed())
211 ));
212 }
213
214 counter = mercurial_repo.export_tags(from_tag..to, counter, output)?;
215 }
216 from_tag
217 };
218
219 if let Some((error, at)) = errors {
220 if at > 0 {
221 let at = at as usize;
222 eprintln!("Import failed at {}", at);
223 info!("Saving last success state at {}...", at);
224 git_repo.save_state(RepositorySavedState::OffsetedRevision(
225 at + offset,
226 from_tag + offset,
227 ))?;
228 }
229 error?;
230 }
231
232 info!("Issued {} commands", counter);
233
234 info!("Saving state...");
235 git_repo.save_state(RepositorySavedState::OffsetedRevision(
236 to + offset,
237 to + offset,
238 ))?;
239
240 git_repo.finish()?;
241
242 if verify {
243 git_repo.verify(
244 mercurial_repo.path().to_str().unwrap(),
245 repo.config.path_prefix.as_ref().map(|x| &x[..]),
246 )?;
247 }
248
249 Ok(())
250}