diff --git a/src/common.rs b/src/common.rs index 85b6f0a..aef15f4 100644 --- a/src/common.rs +++ b/src/common.rs @@ -5,7 +5,9 @@ pub enum ExitCode { FilesystemError = 3, RepositoryError = 4, RemoteError = 5, - PushError = 6 + PushError = 6, + FetchError = 7, + ConfigError = 8 } pub struct ReturnData { diff --git a/src/config.rs b/src/config.rs index 87bc1a1..3108557 100644 --- a/src/config.rs +++ b/src/config.rs @@ -49,6 +49,11 @@ impl fmt::Display for ConfigFile { Some(path) => format!("{}", path) }; + let ssh_key = match &self.config.git.ssh_identity_file { + Some(k) => k, + None => &format!("Not defined") + }; + let schedule_interval = match self.config.schedule.interval { None => { if !self.config.schedule.enabled { @@ -72,7 +77,7 @@ impl fmt::Display for ConfigFile { Schedule enabled: {}\ {}" , self.path, self.file.is_file(), self.file.permissions().readonly(), self.config.from - , to_list, branches_list, work_dir_path, self.config.git.ssh_identity_file, self.config.schedule.enabled + , to_list, branches_list, work_dir_path, ssh_key, self.config.schedule.enabled , schedule_interval) } } @@ -91,7 +96,7 @@ pub struct Config { #[derive(PartialEq)] #[derive(Deserialize)] pub struct Git { - pub ssh_identity_file: String, + pub ssh_identity_file: Option, } #[derive(PartialEq)] @@ -184,6 +189,10 @@ fn verify_config(config: &Config) -> Result<(), String> { && !&config.from.starts_with("https://") && !&config.from.starts_with("http://") { return Err(format!("'from' value does not use a supported protocol")) + } else if !&config.from.starts_with("ssh://") { + if let None = &config.git.ssh_identity_file { + return Err(format!("'from' value using ssh, but no ssh identity file was defined")) + } } Ok(()) diff --git a/src/main.rs b/src/main.rs index 99f9b5c..d141f2c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -90,10 +90,12 @@ fn main() -> Result<(), String> { return Ok(()) } - let cfgs = match config::read_config(args.config, &refractr) { - Ok(cfgs) => cfgs, - Err(e) => return Err(e) + let mut cfgs = vec![]; + match config::read_config(args.config, &refractr) { + Ok(c) => cfgs = c, + Err(e) => common::error(format!("{}", e), common::ExitCode::ConfigError) }; + if refractr.verbose >= 2 { // no need to loop over configs if verbose is not at the correct level for i in &cfgs { @@ -108,5 +110,4 @@ fn main() -> Result<(), String> { }; Ok(()) - } diff --git a/src/refractr.rs b/src/refractr.rs index 3266580..5b92c4e 100644 --- a/src/refractr.rs +++ b/src/refractr.rs @@ -25,6 +25,7 @@ pub struct Refractr { struct OpenedRepository { repo: Repository, path: String, + ssh: bool, remotes: Vec, cfg: Config, } @@ -60,35 +61,33 @@ impl Refractr { Ok(()) } - fn fetch(&self, repo: &Repository, branches: &Vec, ssh: Option<&String>) -> Result<(), Error> { - match ssh { - Some(key) => { - let mut cb = RemoteCallbacks::new(); - let mut fo = FetchOptions::new(); - cb.credentials(move |_,_,_| Cred::ssh_key( - "git", - None, - Path::new(&key), - None)); - cb.certificate_check(|cert, url| { - let mut sha256 = String::new(); - for i in cert.as_hostkey().unwrap().hash_sha256().unwrap().to_vec() { - sha256.push_str(&hex::encode(i.to_string())); - } - common::warning( - format!("implicitly trusting unknown host {} with sha256 host key {}", - url, - hex::encode(cert.as_hostkey().unwrap().hash_sha256().unwrap().to_vec()))); - common::warning( - format!("to ignore this error in the future, add this host to your known_hosts file")); - Ok(CertificateCheckStatus::CertificateOk) - }); - fo.download_tags(git2::AutotagOption::All); - fo.remote_callbacks(cb); - repo.find_remote("origin")?.fetch(&branches, Some(&mut fo), None)?; - }, - None => repo.find_remote("origin")?.fetch(&branches, None, None)? - }; + fn fetch(&self, repo: &Repository, branches: &Vec, ssh: bool, ssh_key: &Option) -> Result<(), Error> { + let mut cb = RemoteCallbacks::new(); + let mut fo = FetchOptions::new(); + if ssh { + let key_string: String = ssh_key.clone().unwrap(); + cb.credentials(move |_,_,_| Cred::ssh_key( + "git", + None, + Path::new(&key_string), + None)); + cb.certificate_check(|cert, url| { + let mut sha256 = String::new(); + for i in cert.as_hostkey().unwrap().hash_sha256().unwrap().to_vec() { + sha256.push_str(&hex::encode(i.to_string())); + } + common::warning( + format!("implicitly trusting unknown host {} with sha256 host key {}", + url, + hex::encode(cert.as_hostkey().unwrap().hash_sha256().unwrap().to_vec()))); + common::warning( + format!("to ignore this error in the future, add this host to your known_hosts file")); + Ok(CertificateCheckStatus::CertificateOk) + }); + } + fo.download_tags(git2::AutotagOption::All); + fo.remote_callbacks(cb); + repo.find_remote("origin")?.fetch(&branches, Some(&mut fo), None)?; Ok(()) } @@ -129,7 +128,7 @@ impl Refractr { Ok(remote_list) } - fn push_remotes(&self, cfg: &Config, repo: &Repository, remote_list: &Vec) -> Result<(), String> { + fn push_remotes(&self, cfg: &Config, ssh: bool, repo: &Repository, remote_list: &Vec) -> Result<(), String> { for id in remote_list { let mut remote = repo.find_remote(&id).unwrap(); common::verbose( @@ -137,24 +136,27 @@ impl Refractr { 1, format!("Pushing to remote: {}", remote.url().unwrap())); let mut callbacks = RemoteCallbacks::new(); - callbacks.credentials(|_,_,_| Cred::ssh_key( - "git", - None, - &Path::new(&cfg.git.ssh_identity_file), - None)); - callbacks.certificate_check(|cert, url| { - let mut sha256 = String::new(); - for i in cert.as_hostkey().unwrap().hash_sha256().unwrap().to_vec() { - sha256.push_str(&hex::encode(i.to_string())); - } - common::warning( - format!("implicitly trusting unknown host {} with sha256 host key {}", - url, - hex::encode(cert.as_hostkey().unwrap().hash_sha256().unwrap().to_vec()))); - common::warning( - format!("to ignore this error in the future, add this host to your known_hosts file")); - Ok(CertificateCheckStatus::CertificateOk) - }); + if ssh { + let key_string: String = cfg.git.ssh_identity_file.clone().unwrap(); + callbacks.credentials(move |_,_,_| Cred::ssh_key( + "git", + None, + &Path::new(&key_string), + None)); + callbacks.certificate_check(|cert, url| { + let mut sha256 = String::new(); + for i in cert.as_hostkey().unwrap().hash_sha256().unwrap().to_vec() { + sha256.push_str(&hex::encode(i.to_string())); + } + common::warning( + format!("implicitly trusting unknown host {} with sha256 host key {}", + url, + hex::encode(cert.as_hostkey().unwrap().hash_sha256().unwrap().to_vec()))); + common::warning( + format!("to ignore this error in the future, add this host to your known_hosts file")); + Ok(CertificateCheckStatus::CertificateOk) + }); + } let mut push_options = PushOptions::new(); push_options.remote_callbacks(callbacks); @@ -221,6 +223,7 @@ impl Refractr { let _ = self.fast_forward(&repos[i].path, &repos[i].cfg.branches); if let Err(e) = self.push_remotes( &repos[i].cfg, + repos[i].ssh, &repos[i].repo, &repos[i].remotes) { common::error(e, ExitCode::PushError) @@ -273,13 +276,14 @@ impl Refractr { }) }; + let ssh = cfg.config.from.starts_with("ssh://"); let mut builder = RepoBuilder::new(); let mut cb = RemoteCallbacks::new(); let mut fo = FetchOptions::new(); // make initial clone - if cfg.config.from.starts_with("ssh://") { - let key_string = cfg.config.git.ssh_identity_file.clone(); + if ssh { + let key_string = cfg.config.git.ssh_identity_file.clone().unwrap(); cb.credentials(move |_,_,_| Cred::ssh_key( "git", None, @@ -311,7 +315,9 @@ impl Refractr { let repo = match builder.clone(&cfg.config.from, Path::new(&repo_dir)) { Ok(repo) => repo, Err(e) => { - println!("{}", e); + if e.code() != ErrorCode::Exists { + common::error(format!("failed to clone repo to {}: {}", repo_dir, e), ExitCode::FilesystemError); + } common::warning(format!("found existing repo at {}, attempting to use", repo_dir)); match self.fast_forward(&repo_dir, &cfg.config.branches) { Ok(_) => if let Ok(repo) = Repository::open(Path::new(&repo_dir)) { @@ -331,7 +337,12 @@ impl Refractr { }; self.set_up_refs(&repo, &cfg.config.branches).unwrap(); - self.fetch(&repo, &cfg.config.branches, Some(&cfg.config.git.ssh_identity_file)).unwrap(); + if let Err(e) = self.fetch(&repo, + &cfg.config.branches, + ssh, + &cfg.config.git.ssh_identity_file) { + common::error(format!("failed to fetch repo {}: {}", cfg.config.from, e), ExitCode::FetchError); + } let remotes = match self.make_remotes(&repo, &cfg) { Ok(v) => v, @@ -340,7 +351,7 @@ impl Refractr { msg: e }) }; - if let Err(e) = self.push_remotes(&cfg.config, &repo, &remotes) { + if let Err(e) = self.push_remotes(&cfg.config, ssh, &repo, &remotes) { common::error(e, ExitCode::PushError); } @@ -348,6 +359,7 @@ impl Refractr { loop_repos.push(OpenedRepository { repo, path: repo_dir, + ssh: cfg.config.from.starts_with("ssh://"), remotes, cfg: cfg.config });