hg_git_fast_import/
single.rs1use std::{path::Path, time::Instant};
2
3use indicatif::{HumanDuration, ProgressBar, ProgressStyle};
4use tracing::{debug, info};
5
6use crate::error::ErrorKind;
7
8use super::{config, env, MercurialRepo, RepositorySavedState, TargetRepository};
9
10pub fn hg2git<P: AsRef<Path>>(
11 repourl: P,
12 verify: bool,
13 git_active_branches: Option<usize>,
14 ignore_unknown_requirements: bool,
15 target: &mut dyn TargetRepository,
16 env: &env::Environment,
17 repository_config: &config::RepositoryConfig,
18) -> Result<(), ErrorKind> {
19 debug!("Config: {:?}", repository_config);
20 debug!("Environment: {:?}", env);
21
22 let repo = MercurialRepo::open_with_pull(
23 repourl.as_ref(),
24 repository_config,
25 ignore_unknown_requirements,
26 env,
27 )?;
28
29 if !repo.verify_heads(repository_config.allow_unnamed_heads)? {
30 return Err(ErrorKind::VerifyFailure("Verify heads failed".into()));
31 };
32
33 let tip = repo.changelog_len()?;
34
35 let to = if let Some(limit_high) = repository_config.limit_high {
36 tip.min(limit_high)
37 } else {
38 tip
39 };
40
41 debug!("Checking saved state...");
42 let mut brmap = repository_config.branches.clone().unwrap_or_default();
43 let mut counter: usize = 0;
44 let offset = repository_config.offset.unwrap_or(0);
45
46 let mut errors = None;
47 let from_tag = {
48 let (output, saved_state, default_branch) =
49 target.start_import(git_active_branches, repository_config.default_branch())?;
50
51 let (from, from_tag) = if let Some(saved_state) = saved_state.as_ref() {
52 match saved_state {
53 RepositorySavedState::OffsetedRevision(rev, from_tag) => {
54 (rev - offset, from_tag - offset)
55 }
56 }
57 } else {
58 (0, 0)
59 };
60
61 info!("Exporting commits from {}", from);
62
63 let show_progress_bar = !env.cron;
64
65 let start = Instant::now();
66 let progress_bar = ProgressBar::new((to - from) as u64);
67 if show_progress_bar {
68 progress_bar.set_style(ProgressStyle::default_bar().template(
69 "{spinner:.green}[{elapsed_precise}] [{wide_bar:.cyan/blue}] {msg} ({eta})",
70 )?);
71 }
72 for mut changeset in repo.range(from..to) {
73 if show_progress_bar {
74 progress_bar.inc(1);
75 progress_bar.set_message(format!("{:6}/{}", changeset.revision.0, to));
76 }
77
78 match repo.export_commit(&mut changeset, counter, &mut brmap, output, &default_branch) {
79 Ok(progress) => counter = progress,
80 x => {
81 errors = Some((x, changeset.revision.0));
82 break;
83 }
84 }
85 }
86
87 if errors.is_none() {
88 if show_progress_bar {
89 progress_bar.finish_with_message(format!(
90 "Repository {} [{from};{to}). Elapsed: {}",
91 repourl.as_ref().to_str().unwrap(),
92 HumanDuration(start.elapsed())
93 ));
94 }
95
96 counter = repo.export_tags(from_tag..to, counter, output)?;
97 }
98
99 from_tag
100 };
101
102 if let Some((error, at)) = errors {
103 if at > 0 {
104 let at = at as usize;
105 eprintln!("Import failed at {}", at);
106 info!("Saving last success state at {}...", at);
107 target.save_state(RepositorySavedState::OffsetedRevision(
108 at + offset,
109 from_tag + offset,
110 ))?;
111 }
112 error?;
113 }
114
115 info!("Issued {} commands", counter);
116 info!("Saving state...");
117 target.save_state(RepositorySavedState::OffsetedRevision(
118 to + offset,
119 to + offset,
120 ))?;
121
122 target.finish()?;
123
124 if verify {
125 target.verify(
126 repourl.as_ref().to_str().unwrap(),
127 repository_config.path_prefix.as_ref().map(|x| &x[..]),
128 )?;
129 }
130
131 Ok(())
132}