From a85a1ab88705144a8142118eddd9b7c1ca3f4981 Mon Sep 17 00:00:00 2001 From: Bryson Steck Date: Sun, 9 Mar 2025 12:16:51 -0600 Subject: [PATCH] replace panics with results --- src/common.rs | 11 +++++ src/config.rs | 15 +++---- src/example/config.toml | 2 +- src/main.rs | 16 ++++--- src/refractr.rs | 97 +++++++++++++++++++++++++++-------------- 5 files changed, 94 insertions(+), 47 deletions(-) diff --git a/src/common.rs b/src/common.rs index 749c580..4296567 100644 --- a/src/common.rs +++ b/src/common.rs @@ -1,4 +1,15 @@ +#[macro_export] +macro_rules! freak_out { + ($msg:expr) => { + return Err($msg) + }; +} + +pub fn warning(msg: String) { + eprintln!("warning: {}", msg) +} + pub fn verbose(level: u8, msg_lvl: u8, msg: String) { if level < msg_lvl { return }; let mut prefix = String::new(); diff --git a/src/config.rs b/src/config.rs index 4605cb9..e06fbcd 100644 --- a/src/config.rs +++ b/src/config.rs @@ -3,9 +3,8 @@ use crate::refractr::Refractr; use core::fmt; use std::io::Read; use std::path::PathBuf; -use std::fs; use std::env; -use std::fs::{File, Metadata}; +use std::fs::{self, File, Metadata}; use toml; use serde_derive::Deserialize; @@ -101,27 +100,27 @@ pub struct Schedule { pub interval: Option, } -pub fn read_config(paths: Vec, refractr: &Refractr) -> Vec { +pub fn read_config(paths: Vec, refractr: &Refractr) -> Result, String> { let mut config_files: Vec = vec![]; for path in paths { common::verbose(refractr.verbose, 1, format!("Reading config file: \"{}\"", String::from(path.to_string_lossy()))); let mut data = String::new(); let mut file = match File::open(path.as_path()) { - Err(e) => panic!("refractr: unable to open {}: {}", path.as_path().display(), e), + Err(e) => return Err(format!("refractr: unable to open {}: {}", path.as_path().display(), e)), Ok(file) => file }; if let Err(e) = file.read_to_string(&mut data) { - panic!("refractr: unable to read {}: {}", path.as_path().display(), e) + return Err(format!("refractr: unable to read {}: {}", path.as_path().display(), e)) } let config_file = ConfigFile { path: match fs::canonicalize(&path) { - Err(_) => panic!("refractr: cannot get absolute path of config file: {}", path.as_path().display()), + Err(_) => return Err(format!("refractr: cannot get absolute path of config file: {}", path.as_path().display())), Ok(abs) => abs.to_string_lossy().to_string() }, file: match fs::metadata(&path) { - Err(_) => panic!("refractr: cannot obtain metadata for config file: {}", path.as_path().display()), + Err(_) => return Err(format!("refractr: cannot obtain metadata for config file: {}", path.as_path().display())), Ok(metadata) => metadata }, config: verify_config(toml::from_str(&data).unwrap()) @@ -143,7 +142,7 @@ pub fn read_config(paths: Vec, refractr: &Refractr) -> Vec } } - return config_files; + return Ok(config_files); } fn verify_config(config: Config) -> Config { diff --git a/src/example/config.toml b/src/example/config.toml index 0c7fcc8..4c30042 100644 --- a/src/example/config.toml +++ b/src/example/config.toml @@ -15,7 +15,7 @@ # The "branches" field is a list of branches you want to mirror from the original # repository. -# This field is OPTIONAL, will default to ["master"] if omitted +# This field is REQUIRED #branches = ["master"] # The "work_dir" field is where refractr will write the clone to diff --git a/src/main.rs b/src/main.rs index b10f91b..8650820 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,9 +9,9 @@ use users; use crate::refractr::Refractr; #[derive(Parser)] -#[command(name = "refractor")] -#[command(version = "0.1.0")] -#[command(about = "An automated push/pull/clone utility for mirroring Git repositories")] +#[command(name = "refractr")] +#[command(version = "0.2.0")] +#[command(about = "An automated pull/push utility for mirroring Git repositories")] #[command(long_about = None)] struct Args { #[arg(short, long, help = "Specify a config file", default_value = get_config_default())] @@ -32,7 +32,7 @@ fn get_config_default() -> &'static str { } } -fn main() -> std::io::Result<()> { +fn main() -> Result<(), String> { let args = Args::parse(); let refractr = Refractr { verbose: args.verbose, @@ -41,6 +41,7 @@ fn main() -> std::io::Result<()> { }; common::verbose(refractr.verbose, 1, format!("Level {} verbosity enabled", refractr.verbose.to_string())); + common::verbose(refractr.verbose, 3, format!("Process ID: {}", refractr.pid)); common::verbose(refractr.verbose, 2, format!("Checking for create flag")); if args.create { common::verbose(refractr.verbose, 3, format!("Printing sample config")); @@ -51,13 +52,16 @@ fn main() -> std::io::Result<()> { // warn to avoid root/admin if refractr.unix { if users::get_current_uid() == 0 { - eprintln!("refractr: warning: this program should not ran as root") + common::warning(format!("this program should not ran as root")); } } else { // TODO: print message for Windows } - let cfgs = config::read_config(args.config, &refractr); + let cfgs = match config::read_config(args.config, &refractr) { + Ok(cfgs) => cfgs, + Err(e) => freak_out!(e) + }; if refractr.verbose >= 2 { // no need to loop over configs if verbose is not at the correct level for i in &cfgs { diff --git a/src/refractr.rs b/src/refractr.rs index 152011c..f5fcba0 100644 --- a/src/refractr.rs +++ b/src/refractr.rs @@ -2,6 +2,7 @@ use git2::build::CheckoutBuilder; use git2::{CertificateCheckStatus, Cred, PushOptions, RemoteCallbacks, Repository}; use sha2::{Sha256, Digest}; +use crate::freak_out; use crate::common; use crate::config::{Config, ConfigFile}; use std::fs; @@ -28,11 +29,11 @@ struct OpenedRepository { } impl Refractr { - fn set_up_work_dir(&self, work_dir: PathBuf) -> String { + fn set_up_work_dir(&self, work_dir: PathBuf) -> Result { if let Err(e) = fs::create_dir_all(&work_dir) { - panic!("refractr: could not create working directory: {}: {}", work_dir.to_string_lossy(), e) + freak_out!(format!("could not create working directory: {}: {}", work_dir.to_string_lossy().to_string(), e)) } - work_dir.to_string_lossy().to_string() + Ok(work_dir.to_string_lossy().to_string()) } fn get_refs(&self, branches: &Vec) -> Vec { @@ -66,14 +67,17 @@ impl Refractr { } - fn make_remotes<'a> (&self, repo: &'a Repository, cfg: &ConfigFile) -> Vec { + fn make_remotes<'a> (&self, repo: &'a Repository, cfg: &ConfigFile) -> Result, String> { // create remotes for each "to" repo let mut remote_list = Vec::new(); for to in &cfg.config.to { let mut hasher = Sha256::new(); hasher.update(to); let remote_id = format!("refractr-{}", &hex::encode(hasher.finalize())[..8]); - common::verbose(self.verbose, 2, format!("Attempting to create remote {} for url {}", remote_id, to)); + common::verbose( + self.verbose, + 2, + format!("Attempting to create remote {} for url {}", remote_id, to)); match repo.remote(remote_id.as_str(), to) { Ok(_) => remote_list.push(remote_id), Err(e) => { @@ -81,28 +85,39 @@ impl Refractr { eprintln!("refractr: warning: remote {} already exists, skipping", remote_id); remote_list.push(remote_id) } else { - panic!("refractr: failed to create remote: {}", e); + freak_out!(format!("failed to create remote: {}", e)); } } } } - remote_list + Ok(remote_list) } fn push_remotes(&self, cfg: &Config, repo: &Repository, remote_list: &Vec) { for id in remote_list { let mut remote = repo.find_remote(&id).unwrap(); - common::verbose(self.verbose, 1, format!("Pushing to remote: {}", remote.url().unwrap())); + common::verbose( + self.verbose, + 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.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())); } - eprintln!("refractr: warning: implicitly trusting unknown host {} with sha256 host key {}", url, hex::encode(cert.as_hostkey().unwrap().hash_sha256().unwrap().to_vec())); - eprintln!("refractr: warning: to ignore this error in the future, add this host to your known_hosts file"); + 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(); @@ -145,7 +160,10 @@ impl Refractr { let sleep_int = time::Duration::from_secs(min); let now = time::Instant::now(); - common::verbose(self.verbose, 2, format!("Sleeping for {} seconds", sleep_int.as_secs())); + common::verbose( + self.verbose, + 2, + format!("Sleeping for {} seconds", sleep_int.as_secs())); while running.load(Ordering::SeqCst) { thread::sleep(time::Duration::from_secs(1)); if now.elapsed().as_secs() >= sleep_int.as_secs() { @@ -154,7 +172,10 @@ impl Refractr { current_ints[i] -= now.elapsed().as_secs(); if i <= 0 { current_ints[i] = original_ints[i].clone(); - common::verbose(self.verbose, 2, format!("Interval for {} has arrived, pulling", repos[i].cfg.from)); + common::verbose( + self.verbose, + 2, + format!("Interval for {} has arrived, pulling", repos[i].cfg.from)); let _ = self.fast_forward(&repos[i].path, &repos[i].cfg.branches); self.push_remotes(&repos[i].cfg, &repos[i].repo, &repos[i].remotes); } @@ -168,35 +189,41 @@ impl Refractr { } - pub fn run(&self, cfgs: Vec) -> std::io::Result<()> { + pub fn run(&self, cfgs: Vec) -> Result<(), String> { common::verbose(self.verbose, 3, format!("Starting main refractr loop")); let mut loop_repos = Vec::new(); for cfg in cfgs { // set up the working directory - common::verbose(self.verbose, 3, format!("Loaded config: {}", cfg.path)); - let path_str = self.set_up_work_dir(match &cfg.config.work_dir { + common::verbose(self.verbose, 3, format!("Loading config: {}", cfg.path)); + let work_dir = self.set_up_work_dir(match &cfg.config.work_dir { None => { if cfg!(windows) { - PathBuf::from(format!("\"{}\\refractr\"", match env::var("TEMP") { - Ok(val) => val, - Err(_) => format!("This shouldn't happen!") - })) + PathBuf::from(format!("\"{}\\refractr\"", env::var("TEMP").unwrap())) } else { PathBuf::from("/tmp/refractr") } }, Some(path) => PathBuf::from(path) }); - - common::verbose(self.verbose, 2, format!("Created working directory: {}", &path_str)); + let path_str = match work_dir { + Ok(p) => p, + Err(e) => return Err(e) + }; + common::verbose( + self.verbose, + 2, + format!("Created working directory: {}", &path_str)); let repo_name = match &cfg.config.from.split("/").last() { Some(split) => split.to_string(), - None => panic!("refractr: failed to parse repository name") + None => freak_out!(format!("failed to parse repository name")) }; // make initial clone - common::verbose(self.verbose, 1, format!("Cloning repository: {}", &cfg.config.from)); + common::verbose( + self.verbose, + 1, + format!("Cloning repository: {}", &cfg.config.from)); let repo_dir = format!("{}/{}", &path_str, repo_name); let repo = match Repository::clone(&cfg.config.from, Path::new(&repo_dir)) { Ok(repo) => repo, @@ -206,16 +233,18 @@ impl Refractr { Ok(_) => if let Ok(repo) = Repository::open(Path::new(&repo_dir)) { repo } else { - panic!("refractr: failed to obtain existing repo") + freak_out!(format!("failed to obtain existing repo")) }, - Err(e) => panic!("refractr: failed to obtain existing repo: {}", e) + Err(e) => freak_out!(format!("failed to obtain existing repo: {}", e)) } } }; - let repo_fresh = Repository::open(Path::new(&repo_dir)).unwrap(); - let remotes = self.make_remotes(&repo_fresh, &cfg); + let remotes = match self.make_remotes(&repo_fresh, &cfg) { + Ok(v) => v, + Err(e) => return Err(e) + }; self.push_remotes(&cfg.config, &repo, &remotes); if cfg.config.schedule.enabled { loop_repos.push(OpenedRepository { @@ -228,14 +257,18 @@ impl Refractr { } if loop_repos.len() >= 1 { - common::verbose(self.verbose, 2, format!("{} configs have schedules enabled, setting up looper", loop_repos.len())); + common::verbose( + self.verbose, + 2, + format!("{} configs have schedules enabled, setting up looper", loop_repos.len())); self.looper(loop_repos); } else { - common::verbose(self.verbose, 2, format!("No scheduled configs found, exiting refractr")); + common::verbose( + self.verbose, + 2, + format!("No scheduled configs found, exiting refractr")); } Ok(()) } } - -