repo_backup/
driver.rs

1use failure::{Error, ResultExt};
2use std::io::Write;
3use std::path::Path;
4
5use config::Config;
6use github::GitHub;
7use gitlab_provider::GitLab;
8use {Provider, Repo};
9
10/// A driver for orchestrating the process of fetching a list of repositories
11/// and then downloading each of them.
12#[derive(Debug, Clone)]
13pub struct Driver {
14    config: Config,
15}
16
17impl Driver {
18    /// Create a new `Driver` with the provided config.
19    pub fn with_config(config: Config) -> Driver {
20        Driver { config }
21    }
22
23    /// Download a list of all repositories from the `Provider`s found in the
24    /// configuration file, then fetch any recent changes (running `git clone`
25    /// if necessary).
26    pub fn run(&self) -> Result<(), Error> {
27        info!("Starting repository backup");
28
29        let providers = get_providers(&self.config)?;
30        let repos = self.get_repos_from_providers(&providers)?;
31        self.update_repos(&repos)?;
32
33        info!("Finished repository backup");
34        Ok(())
35    }
36
37    /// Update the provided repositories.
38    pub fn update_repos(&self, repos: &[Repo]) -> Result<(), UpdateFailure> {
39        info!("Updating repositories");
40        let mut errors = Vec::new();
41
42        for repo in repos {
43            if let Err(e) = self.update_repo(repo) {
44                warn!("Updating {} failed, {}", repo.name, e);
45                errors.push((repo.clone(), e));
46            }
47
48            if errors.len() >= 10 {
49                error!("Too many errors, bailing...");
50                break;
51            }
52        }
53
54        if errors.is_empty() {
55            Ok(())
56        } else {
57            Err(UpdateFailure { errors })
58        }
59    }
60
61    fn update_repo(&self, repo: &Repo) -> Result<(), Error> {
62        let dest_dir = self.config.general.dest_dir.join(repo.full_name());
63
64        if dest_dir.exists() {
65            debug!("Fetching updates for {}", repo.full_name());
66            fetch_updates(&dest_dir)?;
67        } else {
68            debug!("Cloning into {}", dest_dir.display());
69            clone_repo(&dest_dir, repo)?;
70        }
71
72        Ok(())
73    }
74
75    /// Iterate over the `Provider`s and collect all the repositories they've
76    /// found into one big list.
77    pub fn get_repos_from_providers(
78        &self,
79        providers: &[Box<Provider>],
80    ) -> Result<Vec<Repo>, Error> {
81        let mut repos = Vec::new();
82
83        for provider in providers {
84            info!("Fetching repositories from {}", provider.name());
85            let found = provider
86                .repositories()
87                .context("Unable to fetch repositories")?;
88
89            info!("Found {} repos from {}", found.len(), provider.name());
90            repos.extend(found);
91        }
92
93        Ok(repos)
94    }
95}
96
97fn clone_repo(dest_dir: &Path, repo: &Repo) -> Result<(), Error> {
98    cmd!("git clone --quiet {} {}", &repo.url, dest_dir.display())
99}
100
101fn fetch_updates(dest_dir: &Path) -> Result<(), Error> {
102    cmd!(in dest_dir; "git pull --ff-only --prune --quiet --recurse-submodules")
103}
104
105/// A wrapper around one or more failures during the updating process.
106#[derive(Debug, Fail)]
107#[fail(display = "One or more errors ecountered while updating repos")]
108pub struct UpdateFailure {
109    errors: Vec<(Repo, Error)>,
110}
111
112impl UpdateFailure {
113    /// Print a "backtrace" for each error encountered.
114    pub fn display<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
115        writeln!(
116            writer,
117            "There were {} errors updating repositories",
118            self.errors.len()
119        )?;
120
121        for &(ref repo, ref err) in &self.errors {
122            writeln!(
123                writer,
124                "Error: {} failed with {}",
125                repo.full_name(),
126                err
127            )?;
128            for cause in err.iter_causes() {
129                writeln!(writer, "\tCaused By: {}", cause)?;
130            }
131        }
132
133        Ok(())
134    }
135}
136
137fn get_providers(cfg: &Config) -> Result<Vec<Box<Provider>>, Error> {
138    let mut providers: Vec<Box<Provider>> = Vec::new();
139
140    if let Some(gh_config) = cfg.github.as_ref() {
141        let gh = GitHub::with_config(gh_config.clone());
142        providers.push(Box::new(gh));
143    }
144
145    if let Some(gl_config) = cfg.gitlab.as_ref() {
146        let gl = GitLab::with_config(gl_config.clone())?;
147        providers.push(Box::new(gl));
148    }
149
150    if providers.is_empty() {
151        warn!("No providers found");
152    }
153
154    Ok(providers)
155}