hg-git-fast-import 1.3.6

A utility to import single and multiple Mercurial repositories to Git.
#[cfg(feature = "jemalloc")]
#[global_allocator]
static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;

use std::collections::HashMap;
use std::fs::File;
use std::io;
use std::path::Path;
use std::time::Instant;

use exitfailure::ExitFailure;
use failure::{self, ResultExt};
use indicatif::HumanDuration;
use log::info;
use simplelog::{Config, LevelFilter, WriteLogger};

use structopt::StructOpt;

use hg_git_fast_import::config::RepositoryConfig;
use hg_git_fast_import::env::Environment;
use hg_git_fast_import::git::{GitTargetRepository, StdoutTargetRepository};
use hg_git_fast_import::{multi::multi2git, read_file, single::hg2git, tools::build_marks};

mod cli;

use self::cli::{
    Cli::{self, *},
    Common,
};

fn main() -> Result<(), ExitFailure> {
    let start_time = Instant::now();

    let cli = Cli::from_args();
    match cli {
        Completions { shell } => {
            cli::Cli::clap().gen_completions_to(env!("CARGO_PKG_NAME"), shell, &mut io::stdout());
        }
        Single {
            hg_repo,
            git_repo,
            config,
            limit_high,
            common,
        } => {
            if let Some(log) = common.log.as_ref() {
                setup_logger(log)
            }

            let env = load_environment(&common)?;

            let repository_config: Result<RepositoryConfig, failure::Error> = config.map_or_else(
                || Ok(RepositoryConfig::default()),
                |x| {
                    info!("Loading config");
                    let config_str =
                        read_file(&x).with_context(|_| format!("Cannot read config {:?}", x))?;
                    let mut config: RepositoryConfig = toml::from_str(&config_str)
                        .with_context(|_| format!("Cannot parse config {:?}", x))?;
                    info!("Config loaded");
                    if limit_high.is_some() {
                        config.limit_high = limit_high;
                    }
                    Ok(config)
                },
            );
            let repository_config = repository_config?;
            if let Some(git_repo) = git_repo {
                let mut git_target_repository = GitTargetRepository::open(git_repo);

                git_target_repository.set_env(&env);

                hg2git(
                    hg_repo,
                    common.verify,
                    common.git_active_branches,
                    &mut git_target_repository,
                    &env,
                    &repository_config,
                )?;
            } else {
                let stdout = std::io::stdout();
                let stdoutlock = stdout.lock();
                let mut stdout_target = StdoutTargetRepository::from(stdoutlock);
                hg2git(
                    hg_repo,
                    common.verify,
                    common.git_active_branches,
                    &mut stdout_target,
                    &env,
                    &repository_config,
                )?;
            }
            info!("Import done");
            if !common.cron {
                eprintln!(
                    "Finished. Time elapsed: {}",
                    HumanDuration(start_time.elapsed())
                );
            }
        }
        Multi { config, common } => {
            if let Some(log) = common.log.as_ref() {
                setup_logger(log)
            }

            let env = load_environment(&common)?;

            info!("Loading config");
            let config_str =
                read_file(&config).with_context(|_| format!("Cannot read config {:?}", config))?;
            let multi_config = toml::from_str(&config_str)
                .with_context(|_| format!("Cannot parse config from toml {:?}", config))?;
            info!("Config loaded");
            multi2git(
                common.verify,
                common.git_active_branches,
                &env,
                &config,
                &multi_config,
            )?;
            info!("Import done");
            if !common.cron {
                eprintln!(
                    "Finished. Time elapsed: {}",
                    HumanDuration(start_time.elapsed())
                );
            }
        }
        BuildMarks { args } => {
            build_marks(
                args.authors.as_ref().map(load_authors).transpose()?,
                args.hg_repo,
                args.git_repo,
                args.offset,
                !args.no_backup,
            )?;
        }
    }

    Ok(())
}

fn setup_logger(log: impl AsRef<Path>) {
    WriteLogger::init(
        LevelFilter::Info,
        Config::default(),
        File::create(log).unwrap(),
    )
    .unwrap();
}

fn load_environment(common: &Common) -> Result<Environment, failure::Error> {
    Ok(Environment {
        no_clean_closed_branches: common.no_clean_closed_branches,
        authors: common.authors.as_ref().map(load_authors).transpose()?,
        clean: common.clean,
        cron: common.cron,
        target_push: common.target_push,
        target_pull: common.target_pull,
        source_pull: common.source_pull,
        fix_wrong_branchname: common.fix_wrong_branchname,
    })
}

fn load_authors(p: impl AsRef<Path>) -> Result<HashMap<String, String>, failure::Error> {
    info!("Loading authors");
    let authors_str =
        read_file(&p).with_context(|_| format!("Cannot load authors {:?}", p.as_ref()))?;
    let authors = toml::from_str(&authors_str)
        .with_context(|_| format!("Cannot parse authors from toml {:?}", p.as_ref()))?;
    info!("Authors list loaded");
    Ok(authors)
}