replace panics with results

This commit is contained in:
Bryson Steck 2025-03-09 12:16:51 -06:00
parent dd77c19124
commit a85a1ab887
Signed by: brysonsteck
SSH key fingerprint: SHA256:XpKABw/nP4z8UVaH+weLaBnEOD86+cVwif+QjuYLGT4
5 changed files with 94 additions and 47 deletions

View file

@ -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();

View file

@ -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<i32>,
}
pub fn read_config(paths: Vec<PathBuf>, refractr: &Refractr) -> Vec<ConfigFile> {
pub fn read_config(paths: Vec<PathBuf>, refractr: &Refractr) -> Result<Vec<ConfigFile>, String> {
let mut config_files: Vec<ConfigFile> = 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<PathBuf>, refractr: &Refractr) -> Vec<ConfigFile>
}
}
return config_files;
return Ok(config_files);
}
fn verify_config(config: Config) -> Config {

View file

@ -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

View file

@ -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 {

View file

@ -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<String, String> {
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<String>) -> Vec<String> {
@ -66,14 +67,17 @@ impl Refractr {
}
fn make_remotes<'a> (&self, repo: &'a Repository, cfg: &ConfigFile) -> Vec<String> {
fn make_remotes<'a> (&self, repo: &'a Repository, cfg: &ConfigFile) -> Result<Vec<String>, 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<String>) {
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<ConfigFile>) -> std::io::Result<()> {
pub fn run(&self, cfgs: Vec<ConfigFile>) -> 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(())
}
}