replace panics with results
This commit is contained in:
parent
dd77c19124
commit
a85a1ab887
5 changed files with 94 additions and 47 deletions
|
@ -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();
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
16
src/main.rs
16
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 {
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue