use failure::{Error, ResultExt};
use std::io::Write;
use std::path::Path;
use config::Config;
use github::GitHub;
use gitlab_provider::GitLab;
use {Provider, Repo};
#[derive(Debug, Clone)]
pub struct Driver {
config: Config,
}
impl Driver {
pub fn with_config(config: Config) -> Driver {
Driver { config }
}
pub fn run(&self) -> Result<(), Error> {
info!("Starting repository backup");
let providers = get_providers(&self.config)?;
let repos = self.get_repos_from_providers(&providers)?;
self.update_repos(&repos)?;
info!("Finished repository backup");
Ok(())
}
pub fn update_repos(&self, repos: &[Repo]) -> Result<(), UpdateFailure> {
info!("Updating repositories");
let mut errors = Vec::new();
for repo in repos {
if let Err(e) = self.update_repo(repo) {
warn!("Updating {} failed, {}", repo.name, e);
errors.push((repo.clone(), e));
}
if errors.len() >= 10 {
error!("Too many errors, bailing...");
break;
}
}
if errors.is_empty() {
Ok(())
} else {
Err(UpdateFailure { errors })
}
}
fn update_repo(&self, repo: &Repo) -> Result<(), Error> {
let dest_dir = self.config.general.dest_dir.join(repo.full_name());
if dest_dir.exists() {
debug!("Fetching updates for {}", repo.full_name());
fetch_updates(&dest_dir)?;
} else {
debug!("Cloning into {}", dest_dir.display());
clone_repo(&dest_dir, repo)?;
}
Ok(())
}
pub fn get_repos_from_providers(
&self,
providers: &[Box<Provider>],
) -> Result<Vec<Repo>, Error> {
let mut repos = Vec::new();
for provider in providers {
info!("Fetching repositories from {}", provider.name());
let found = provider
.repositories()
.context("Unable to fetch repositories")?;
info!("Found {} repos from {}", found.len(), provider.name());
repos.extend(found);
}
Ok(repos)
}
}
fn clone_repo(dest_dir: &Path, repo: &Repo) -> Result<(), Error> {
cmd!("git clone --quiet {} {}", &repo.url, dest_dir.display())
}
fn fetch_updates(dest_dir: &Path) -> Result<(), Error> {
cmd!(in dest_dir; "git pull --ff-only --prune --quiet --recurse-submodules")
}
#[derive(Debug, Fail)]
#[fail(display = "One or more errors ecountered while updating repos")]
pub struct UpdateFailure {
errors: Vec<(Repo, Error)>,
}
impl UpdateFailure {
pub fn display<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
writeln!(
writer,
"There were {} errors updating repositories",
self.errors.len()
)?;
for &(ref repo, ref err) in &self.errors {
writeln!(
writer,
"Error: {} failed with {}",
repo.full_name(),
err
)?;
for cause in err.iter_causes() {
writeln!(writer, "\tCaused By: {}", cause)?;
}
}
Ok(())
}
}
fn get_providers(cfg: &Config) -> Result<Vec<Box<Provider>>, Error> {
let mut providers: Vec<Box<Provider>> = Vec::new();
if let Some(gh_config) = cfg.github.as_ref() {
let gh = GitHub::with_config(gh_config.clone());
providers.push(Box::new(gh));
}
if let Some(gl_config) = cfg.gitlab.as_ref() {
let gl = GitLab::with_config(gl_config.clone())?;
providers.push(Box::new(gl));
}
if providers.is_empty() {
warn!("No providers found");
}
Ok(providers)
}