From 4791e1dd8467f9238877d5cf0effa021a0ce93f8 Mon Sep 17 00:00:00 2001 From: Bryson Steck Date: Wed, 5 Mar 2025 20:45:35 -0700 Subject: [PATCH] make functions part of Refractr object --- src/common.rs | 6 - src/config.rs | 3 +- src/main.rs | 5 +- src/refractr.rs | 407 +++++++++++++++++++++++++----------------------- 4 files changed, 213 insertions(+), 208 deletions(-) diff --git a/src/common.rs b/src/common.rs index 490651f..749c580 100644 --- a/src/common.rs +++ b/src/common.rs @@ -1,10 +1,4 @@ -pub struct Refractr { - pub verbose: u8, - pub pid: u32, - pub unix: bool -} - 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 5e3260a..f31b5e8 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,4 +1,5 @@ use crate::common; +use crate::refractr::Refractr; use core::fmt; use std::io::Read; use std::path::PathBuf; @@ -106,7 +107,7 @@ pub struct Schedule { pub interval: Option, } -pub fn read_config(paths: Vec, refractr: &common::Refractr) -> Vec { +pub fn read_config(paths: Vec, refractr: &Refractr) -> Vec { 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()))); diff --git a/src/main.rs b/src/main.rs index beba68e..b10f91b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,7 @@ use clap::Parser; use std::path::PathBuf; use std::process; use users; +use crate::refractr::Refractr; #[derive(Parser)] #[command(name = "refractor")] @@ -33,7 +34,7 @@ fn get_config_default() -> &'static str { fn main() -> std::io::Result<()> { let args = Args::parse(); - let refractr = common::Refractr { + let refractr = Refractr { verbose: args.verbose, pid: process::id(), unix: cfg!(unix) @@ -65,7 +66,7 @@ fn main() -> std::io::Result<()> { } common::verbose(refractr.verbose, 1, format!("Config file(s) read successfully")); - refractr::run(refractr, cfgs) + refractr.run(cfgs) } } diff --git a/src/refractr.rs b/src/refractr.rs index 7799c15..cf46d7b 100644 --- a/src/refractr.rs +++ b/src/refractr.rs @@ -1,8 +1,8 @@ use git2::build::CheckoutBuilder; -use git2::{BranchType, Cred, PushOptions, RemoteCallbacks, Repository}; +use git2::{Cred, PushOptions, RemoteCallbacks, Repository}; use sha2::{Sha256, Digest}; -use crate::common::{self, Refractr}; +use crate::common; use crate::config::{Config, ConfigFile}; use std::fs; use std::env; @@ -14,6 +14,12 @@ use hex; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; +pub struct Refractr { + pub verbose: u8, + pub pid: u32, + pub unix: bool +} + struct OpenedRepository { repo: Repository, path: String, @@ -21,231 +27,234 @@ struct OpenedRepository { cfg: Config, } -fn set_up_work_dir(work_dir: PathBuf) -> String { - if let Err(e) = fs::create_dir_all(&work_dir) { - panic!("refractr: could not create working directory: {}: {}", work_dir.to_string_lossy(), e) +impl Refractr { + fn set_up_work_dir(&self, work_dir: PathBuf) -> String { + if let Err(e) = fs::create_dir_all(&work_dir) { + panic!("refractr: could not create working directory: {}: {}", work_dir.to_string_lossy(), e) + } + work_dir.to_string_lossy().to_string() } - work_dir.to_string_lossy().to_string() -} -fn get_branches(repo: &Repository, branches: &Option>, refs: bool) -> Vec { - match branches { - Some(repo_branches) => { - if refs { - let mut refs_branches = Vec::new(); - for branch in repo_branches { - refs_branches.push(format!("refs/heads/{}", branch)); + fn get_branches(&self, repo: &Repository, branches: &Option>, refs: bool) -> Vec { + match branches { + Some(repo_branches) => { + if refs { + let mut refs_branches = Vec::new(); + for branch in repo_branches { + refs_branches.push(format!("refs/heads/{}", branch)); + } + refs_branches + } else { + repo_branches.to_vec() } - refs_branches - } else { - repo_branches.to_vec() - } - }, - None => { - let mut strings = Vec::new(); - let remote_branches = match repo.branches(Some(git2::BranchType::Remote)) { - Ok(b) => b, - Err(e) => panic!("refractr: failed to get branches: {}", e) - }; - for branch in remote_branches { - if let Ok((b, _)) = branch { - if let Ok(Some(name)) = b.name() { - if refs { - strings.push(format!("refs/heads/{}", name.to_string())) - } else { - strings.push(name.to_string()); + }, + None => { + let mut strings = Vec::new(); + let remote_branches = match repo.branches(Some(git2::BranchType::Remote)) { + Ok(b) => b, + Err(e) => panic!("refractr: failed to get branches: {}", e) + }; + for branch in remote_branches { + if let Ok((b, _)) = branch { + if let Ok(Some(name)) = b.name() { + if refs { + strings.push(format!("refs/heads/{}", name.to_string())) + } else { + strings.push(name.to_string()); + } } } } - } - strings - } - } -} - -fn fast_forward(refractr: &Refractr, repo_dir: &str, branches: &Option>) -> Result<(), Error> { - let repo = Repository::open(repo_dir)?; - let branch_list: Vec = get_branches(&repo, branches, false); - - common::verbose(refractr.verbose, 2, format!("Pulling origin")); - repo.find_remote("origin")?.fetch(&branch_list, None, None)?; - - let fetch_head = repo.find_reference("FETCH_HEAD")?; - let fetch_commit = repo.reference_to_annotated_commit(&fetch_head)?; - let analysis = repo.merge_analysis(&[&fetch_commit])?; - if analysis.0.is_fast_forward() { - for branch in branch_list { - let refname = format!("refs/heads/{}", branch); - let mut reference = repo.find_reference(&refname)?; - reference.set_target(fetch_commit.id(), "Fast-forward")?; - repo.set_head(&refname)?; - let _ = repo.checkout_head(Some(CheckoutBuilder::default().force())); - } - } - - Ok(()) - -} - -fn make_remotes<'a> (refractr: &Refractr, repo: &'a Repository, cfg: &ConfigFile) -> Vec { - // 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(refractr.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) => { - if e.code() == ErrorCode::Exists { - eprintln!("refractr: warning: remote {} already exists, skipping", remote_id); - remote_list.push(remote_id) - } else { - panic!("refractr: failed to create remote: {}", e); - } + strings } } } - remote_list -} + fn fast_forward(&self, repo_dir: &str, branches: &Option>) -> Result<(), Error> { + let repo = Repository::open(repo_dir)?; + let branch_list: Vec = self.get_branches(&repo, branches, false); -fn push_remotes(refractr: &Refractr, cfg: &Config, repo: &Repository, remote_list: &Vec) { - for id in remote_list { - let mut remote = repo.find_remote(&id).unwrap(); - common::verbose(refractr.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)); - let mut push_options = PushOptions::new(); - push_options.remote_callbacks(callbacks); + common::verbose(self.verbose, 2, format!("Pulling origin")); + repo.find_remote("origin")?.fetch(&branch_list, None, None)?; - let mut refs = Vec::new(); - let strings = get_branches(&repo, &cfg.branches, true); - for branch in &strings { - refs.push(branch.as_str()); - } - - match remote.push::<&str>(&refs, Some(&mut push_options)) { - Ok(_) => (), - Err(e) => { - eprintln!("refractr: failed to push to remote: {}: {}", remote.url().unwrap(), e) + let fetch_head = repo.find_reference("FETCH_HEAD")?; + let fetch_commit = repo.reference_to_annotated_commit(&fetch_head)?; + let analysis = repo.merge_analysis(&[&fetch_commit])?; + if analysis.0.is_fast_forward() { + for branch in branch_list { + let refname = format!("refs/heads/{}", branch); + let mut reference = repo.find_reference(&refname)?; + reference.set_target(fetch_commit.id(), "Fast-forward")?; + repo.set_head(&refname)?; + let _ = repo.checkout_head(Some(CheckoutBuilder::default().force())); } } + + Ok(()) + } -} -fn looper(refractr: Refractr, repos: Vec) { - let mut current_ints = Vec::new(); - let running = Arc::new(AtomicBool::new(true)); - let r = running.clone(); - let count = repos.len(); - for i in 0..repos.len() { - current_ints.push(u64::from(repos[i].cfg.schedule.interval.unwrap().unsigned_abs())); - }; - let original_ints = current_ints.clone(); - - ctrlc::set_handler(move || { - r.store(false, Ordering::SeqCst); - }).expect("Failed to set ^C handler"); - - common::verbose(refractr.verbose, 1, format!("Starting scheduled loop")); - let min = *current_ints.iter().min().unwrap(); - let mut do_break = false; - while !do_break { - do_break = true; - let sleep_int = time::Duration::from_secs(min); - let now = time::Instant::now(); - - common::verbose(refractr.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() { - common::verbose(refractr.verbose, 3, format!("Thread has awoken!")); - for i in 0..count { - current_ints[i] -= now.elapsed().as_secs(); - if i <= 0 { - current_ints[i] = original_ints[i].clone(); - common::verbose(refractr.verbose, 2, format!("Interval for {} has arrived, pulling", repos[i].cfg.from)); - let _ = fast_forward(&refractr, &repos[i].path, &repos[i].cfg.branches); - push_remotes(&refractr, &repos[i].cfg, &repos[i].repo, &repos[i].remotes); + fn make_remotes<'a> (&self, repo: &'a Repository, cfg: &ConfigFile) -> Vec { + // 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)); + match repo.remote(remote_id.as_str(), to) { + Ok(_) => remote_list.push(remote_id), + Err(e) => { + if e.code() == ErrorCode::Exists { + eprintln!("refractr: warning: remote {} already exists, skipping", remote_id); + remote_list.push(remote_id) + } else { + panic!("refractr: failed to create remote: {}", e); } } - do_break = false; - break + } + } + + 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())); + let mut callbacks = RemoteCallbacks::new(); + callbacks.credentials(|_,_,_| Cred::ssh_key("git", None, &Path::new(&cfg.git.ssh_identity_file), None)); + let mut push_options = PushOptions::new(); + push_options.remote_callbacks(callbacks); + + let mut refs = Vec::new(); + let strings = self.get_branches(&repo, &cfg.branches, true); + for branch in &strings { + refs.push(branch.as_str()); + } + + match remote.push::<&str>(&refs, Some(&mut push_options)) { + Ok(_) => (), + Err(e) => { + eprintln!("refractr: failed to push to remote: {}: {}", remote.url().unwrap(), e) + } } } } - common::verbose(refractr.verbose, 1, format!("Exited looper due to ^C")); -} - -pub fn run(refractr: Refractr, cfgs: Vec) -> std::io::Result<()> { - common::verbose(refractr.verbose, 3, format!("Starting main refractr loop")); - let mut loop_repos = Vec::new(); - - for cfg in cfgs { - // set up the working directory - common::verbose(refractr.verbose, 3, format!("Loaded config: {}", cfg.path)); - let path_str = 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!") - })) - } else { - PathBuf::from("/tmp/refractr") - } - }, - Some(path) => PathBuf::from(path) - }); - - common::verbose(refractr.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") + fn looper(&self, repos: Vec) { + let mut current_ints = Vec::new(); + let running = Arc::new(AtomicBool::new(true)); + let r = running.clone(); + let count = repos.len(); + for i in 0..repos.len() { + current_ints.push(u64::from(repos[i].cfg.schedule.interval.unwrap().unsigned_abs())); }; + let original_ints = current_ints.clone(); - // make initial clone - common::verbose(refractr.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, - Err(_) => { - eprintln!("refractr: warning: found existing repo at {}, attempting to use", repo_dir); - match fast_forward(&refractr, &repo_dir, &cfg.config.branches) { - Ok(_) => if let Ok(repo) = Repository::open(Path::new(&repo_dir)) { - repo + ctrlc::set_handler(move || { + r.store(false, Ordering::SeqCst); + }).expect("Failed to set ^C handler"); + + common::verbose(self.verbose, 1, format!("Starting scheduled loop")); + let min = *current_ints.iter().min().unwrap(); + let mut do_break = false; + while !do_break { + do_break = true; + 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())); + while running.load(Ordering::SeqCst) { + thread::sleep(time::Duration::from_secs(1)); + if now.elapsed().as_secs() >= sleep_int.as_secs() { + common::verbose(self.verbose, 3, format!("Thread has awoken!")); + for i in 0..count { + 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)); + let _ = self.fast_forward(&repos[i].path, &repos[i].cfg.branches); + self.push_remotes(&repos[i].cfg, &repos[i].repo, &repos[i].remotes); + } + } + do_break = false; + break + } + } + } + common::verbose(self.verbose, 1, format!("Exited looper due to ^C")); + + } + + pub fn run(&self, cfgs: Vec) -> std::io::Result<()> { + 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 { + None => { + if cfg!(windows) { + PathBuf::from(format!("\"{}\\refractr\"", match env::var("TEMP") { + Ok(val) => val, + Err(_) => format!("This shouldn't happen!") + })) } else { - panic!("refractr: failed to obtain existing repo") - }, - Err(e) => panic!("refractr: failed to obtain existing repo: {}", e) - } - } - }; - - - let repo_fresh = Repository::open(Path::new(&repo_dir)).unwrap(); - let remotes = make_remotes(&refractr, &repo_fresh, &cfg); - push_remotes(&refractr, &cfg.config, &repo, &remotes); - if cfg.config.schedule.enabled { - loop_repos.push(OpenedRepository { - repo, - path: repo_dir, - remotes, - cfg: cfg.config + PathBuf::from("/tmp/refractr") + } + }, + Some(path) => PathBuf::from(path) }); + + 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") + }; + + // make initial clone + 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, + Err(_) => { + eprintln!("refractr: warning: 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)) { + repo + } else { + panic!("refractr: failed to obtain existing repo") + }, + Err(e) => panic!("refractr: failed to obtain existing repo: {}", e) + } + } + }; + + + let repo_fresh = Repository::open(Path::new(&repo_dir)).unwrap(); + let remotes = self.make_remotes(&repo_fresh, &cfg); + self.push_remotes(&cfg.config, &repo, &remotes); + if cfg.config.schedule.enabled { + loop_repos.push(OpenedRepository { + repo, + path: repo_dir, + remotes, + cfg: cfg.config + }); + } } - } - if loop_repos.len() >= 1 { - common::verbose(refractr.verbose, 2, format!("{} configs have schedules enabled, setting up looper", loop_repos.len())); - looper(refractr, loop_repos); - } else { - common::verbose(refractr.verbose, 2, format!("No scheduled configs found, exiting refractr")); - } + if loop_repos.len() >= 1 { + 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")); + } - Ok(()) + Ok(()) + } } +