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#[derive(Debug, Clone)]
13pub struct Driver {
14 config: Config,
15}
16
17impl Driver {
18 pub fn with_config(config: Config) -> Driver {
20 Driver { config }
21 }
22
23 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 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 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#[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 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}