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) {
|
pub fn verbose(level: u8, msg_lvl: u8, msg: String) {
|
||||||
if level < msg_lvl { return };
|
if level < msg_lvl { return };
|
||||||
let mut prefix = String::new();
|
let mut prefix = String::new();
|
||||||
|
|
|
@ -3,9 +3,8 @@ use crate::refractr::Refractr;
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::fs;
|
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fs::{File, Metadata};
|
use std::fs::{self, File, Metadata};
|
||||||
use toml;
|
use toml;
|
||||||
use serde_derive::Deserialize;
|
use serde_derive::Deserialize;
|
||||||
|
|
||||||
|
@ -101,27 +100,27 @@ pub struct Schedule {
|
||||||
pub interval: Option<i32>,
|
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![];
|
let mut config_files: Vec<ConfigFile> = vec![];
|
||||||
for path in paths {
|
for path in paths {
|
||||||
common::verbose(refractr.verbose, 1, format!("Reading config file: \"{}\"", String::from(path.to_string_lossy())));
|
common::verbose(refractr.verbose, 1, format!("Reading config file: \"{}\"", String::from(path.to_string_lossy())));
|
||||||
let mut data = String::new();
|
let mut data = String::new();
|
||||||
let mut file = match File::open(path.as_path()) {
|
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
|
Ok(file) => file
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(e) = file.read_to_string(&mut data) {
|
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 {
|
let config_file = ConfigFile {
|
||||||
path: match fs::canonicalize(&path) {
|
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()
|
Ok(abs) => abs.to_string_lossy().to_string()
|
||||||
},
|
},
|
||||||
file: match fs::metadata(&path) {
|
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
|
Ok(metadata) => metadata
|
||||||
},
|
},
|
||||||
config: verify_config(toml::from_str(&data).unwrap())
|
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 {
|
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
|
# The "branches" field is a list of branches you want to mirror from the original
|
||||||
# repository.
|
# repository.
|
||||||
# This field is OPTIONAL, will default to ["master"] if omitted
|
# This field is REQUIRED
|
||||||
#branches = ["master"]
|
#branches = ["master"]
|
||||||
|
|
||||||
# The "work_dir" field is where refractr will write the clone to
|
# 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;
|
use crate::refractr::Refractr;
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[command(name = "refractor")]
|
#[command(name = "refractr")]
|
||||||
#[command(version = "0.1.0")]
|
#[command(version = "0.2.0")]
|
||||||
#[command(about = "An automated push/pull/clone utility for mirroring Git repositories")]
|
#[command(about = "An automated pull/push utility for mirroring Git repositories")]
|
||||||
#[command(long_about = None)]
|
#[command(long_about = None)]
|
||||||
struct Args {
|
struct Args {
|
||||||
#[arg(short, long, help = "Specify a config file", default_value = get_config_default())]
|
#[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 args = Args::parse();
|
||||||
let refractr = Refractr {
|
let refractr = Refractr {
|
||||||
verbose: args.verbose,
|
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, 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"));
|
common::verbose(refractr.verbose, 2, format!("Checking for create flag"));
|
||||||
if args.create {
|
if args.create {
|
||||||
common::verbose(refractr.verbose, 3, format!("Printing sample config"));
|
common::verbose(refractr.verbose, 3, format!("Printing sample config"));
|
||||||
|
@ -51,13 +52,16 @@ fn main() -> std::io::Result<()> {
|
||||||
// warn to avoid root/admin
|
// warn to avoid root/admin
|
||||||
if refractr.unix {
|
if refractr.unix {
|
||||||
if users::get_current_uid() == 0 {
|
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 {
|
} else {
|
||||||
// TODO: print message for Windows
|
// 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 {
|
if refractr.verbose >= 2 {
|
||||||
// no need to loop over configs if verbose is not at the correct level
|
// no need to loop over configs if verbose is not at the correct level
|
||||||
for i in &cfgs {
|
for i in &cfgs {
|
||||||
|
|
|
@ -2,6 +2,7 @@ use git2::build::CheckoutBuilder;
|
||||||
use git2::{CertificateCheckStatus, Cred, PushOptions, RemoteCallbacks, Repository};
|
use git2::{CertificateCheckStatus, Cred, PushOptions, RemoteCallbacks, Repository};
|
||||||
use sha2::{Sha256, Digest};
|
use sha2::{Sha256, Digest};
|
||||||
|
|
||||||
|
use crate::freak_out;
|
||||||
use crate::common;
|
use crate::common;
|
||||||
use crate::config::{Config, ConfigFile};
|
use crate::config::{Config, ConfigFile};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
@ -28,11 +29,11 @@ struct OpenedRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Refractr {
|
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) {
|
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> {
|
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
|
// create remotes for each "to" repo
|
||||||
let mut remote_list = Vec::new();
|
let mut remote_list = Vec::new();
|
||||||
for to in &cfg.config.to {
|
for to in &cfg.config.to {
|
||||||
let mut hasher = Sha256::new();
|
let mut hasher = Sha256::new();
|
||||||
hasher.update(to);
|
hasher.update(to);
|
||||||
let remote_id = format!("refractr-{}", &hex::encode(hasher.finalize())[..8]);
|
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) {
|
match repo.remote(remote_id.as_str(), to) {
|
||||||
Ok(_) => remote_list.push(remote_id),
|
Ok(_) => remote_list.push(remote_id),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
@ -81,28 +85,39 @@ impl Refractr {
|
||||||
eprintln!("refractr: warning: remote {} already exists, skipping", remote_id);
|
eprintln!("refractr: warning: remote {} already exists, skipping", remote_id);
|
||||||
remote_list.push(remote_id)
|
remote_list.push(remote_id)
|
||||||
} else {
|
} 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>) {
|
fn push_remotes(&self, cfg: &Config, repo: &Repository, remote_list: &Vec<String>) {
|
||||||
for id in remote_list {
|
for id in remote_list {
|
||||||
let mut remote = repo.find_remote(&id).unwrap();
|
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();
|
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| {
|
callbacks.certificate_check(|cert, url| {
|
||||||
let mut sha256 = String::new();
|
let mut sha256 = String::new();
|
||||||
for i in cert.as_hostkey().unwrap().hash_sha256().unwrap().to_vec() {
|
for i in cert.as_hostkey().unwrap().hash_sha256().unwrap().to_vec() {
|
||||||
sha256.push_str(&hex::encode(i.to_string()));
|
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()));
|
common::warning(
|
||||||
eprintln!("refractr: warning: to ignore this error in the future, add this host to your known_hosts file");
|
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)
|
Ok(CertificateCheckStatus::CertificateOk)
|
||||||
});
|
});
|
||||||
let mut push_options = PushOptions::new();
|
let mut push_options = PushOptions::new();
|
||||||
|
@ -145,7 +160,10 @@ impl Refractr {
|
||||||
let sleep_int = time::Duration::from_secs(min);
|
let sleep_int = time::Duration::from_secs(min);
|
||||||
let now = time::Instant::now();
|
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) {
|
while running.load(Ordering::SeqCst) {
|
||||||
thread::sleep(time::Duration::from_secs(1));
|
thread::sleep(time::Duration::from_secs(1));
|
||||||
if now.elapsed().as_secs() >= sleep_int.as_secs() {
|
if now.elapsed().as_secs() >= sleep_int.as_secs() {
|
||||||
|
@ -154,7 +172,10 @@ impl Refractr {
|
||||||
current_ints[i] -= now.elapsed().as_secs();
|
current_ints[i] -= now.elapsed().as_secs();
|
||||||
if i <= 0 {
|
if i <= 0 {
|
||||||
current_ints[i] = original_ints[i].clone();
|
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);
|
let _ = self.fast_forward(&repos[i].path, &repos[i].cfg.branches);
|
||||||
self.push_remotes(&repos[i].cfg, &repos[i].repo, &repos[i].remotes);
|
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"));
|
common::verbose(self.verbose, 3, format!("Starting main refractr loop"));
|
||||||
let mut loop_repos = Vec::new();
|
let mut loop_repos = Vec::new();
|
||||||
|
|
||||||
for cfg in cfgs {
|
for cfg in cfgs {
|
||||||
// set up the working directory
|
// set up the working directory
|
||||||
common::verbose(self.verbose, 3, format!("Loaded config: {}", cfg.path));
|
common::verbose(self.verbose, 3, format!("Loading config: {}", cfg.path));
|
||||||
let path_str = self.set_up_work_dir(match &cfg.config.work_dir {
|
let work_dir = self.set_up_work_dir(match &cfg.config.work_dir {
|
||||||
None => {
|
None => {
|
||||||
if cfg!(windows) {
|
if cfg!(windows) {
|
||||||
PathBuf::from(format!("\"{}\\refractr\"", match env::var("TEMP") {
|
PathBuf::from(format!("\"{}\\refractr\"", env::var("TEMP").unwrap()))
|
||||||
Ok(val) => val,
|
|
||||||
Err(_) => format!("This shouldn't happen!")
|
|
||||||
}))
|
|
||||||
} else {
|
} else {
|
||||||
PathBuf::from("/tmp/refractr")
|
PathBuf::from("/tmp/refractr")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Some(path) => PathBuf::from(path)
|
Some(path) => PathBuf::from(path)
|
||||||
});
|
});
|
||||||
|
let path_str = match work_dir {
|
||||||
common::verbose(self.verbose, 2, format!("Created working directory: {}", &path_str));
|
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() {
|
let repo_name = match &cfg.config.from.split("/").last() {
|
||||||
Some(split) => split.to_string(),
|
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
|
// 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_dir = format!("{}/{}", &path_str, repo_name);
|
||||||
let repo = match Repository::clone(&cfg.config.from, Path::new(&repo_dir)) {
|
let repo = match Repository::clone(&cfg.config.from, Path::new(&repo_dir)) {
|
||||||
Ok(repo) => repo,
|
Ok(repo) => repo,
|
||||||
|
@ -206,16 +233,18 @@ impl Refractr {
|
||||||
Ok(_) => if let Ok(repo) = Repository::open(Path::new(&repo_dir)) {
|
Ok(_) => if let Ok(repo) = Repository::open(Path::new(&repo_dir)) {
|
||||||
repo
|
repo
|
||||||
} else {
|
} 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 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);
|
self.push_remotes(&cfg.config, &repo, &remotes);
|
||||||
if cfg.config.schedule.enabled {
|
if cfg.config.schedule.enabled {
|
||||||
loop_repos.push(OpenedRepository {
|
loop_repos.push(OpenedRepository {
|
||||||
|
@ -228,14 +257,18 @@ impl Refractr {
|
||||||
}
|
}
|
||||||
|
|
||||||
if loop_repos.len() >= 1 {
|
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);
|
self.looper(loop_repos);
|
||||||
} else {
|
} 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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue