refractr/src/refractr.rs

233 lines
7.7 KiB
Rust
Raw Normal View History

use git2::{Cred, PushOptions, RemoteCallbacks, Repository};
2025-03-03 21:10:31 -07:00
use sha2::{Sha256, Digest};
2025-03-02 18:33:33 -07:00
use crate::common::{self, Refractr};
use crate::config::{Config, ConfigFile};
2025-03-02 18:33:33 -07:00
use std::fs;
use std::env;
use std::thread;
use std::time;
2025-03-02 18:33:33 -07:00
use std::path::{Path, PathBuf};
2025-03-03 21:10:31 -07:00
use git2::{Error, ErrorCode};
use hex;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
struct OpenedRepository {
repo: Repository,
remotes: Vec<String>,
cfg: Config,
}
2025-03-02 18:33:33 -07:00
fn set_up_work_dir(work_dir: PathBuf) -> String {
if let Err(e) = fs::create_dir_all(&work_dir) {
2025-03-03 21:10:31 -07:00
panic!("refractr: could not create working directory: {}: {}", work_dir.to_string_lossy(), e)
2025-03-02 18:33:33 -07:00
}
work_dir.to_string_lossy().to_string()
}
2025-03-03 23:15:35 -07:00
fn get_branches(repo: &Repository, branches: &Option<Vec<String>>, refs: bool) -> Vec<String> {
2025-03-03 22:54:58 -07:00
match branches {
2025-03-03 23:15:35 -07:00
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()
}
},
2025-03-02 18:33:33 -07:00
None => {
let mut strings = Vec::new();
2025-03-03 22:54:58 -07:00
let remote_branches = match repo.branches(Some(git2::BranchType::Remote)) {
Ok(b) => b,
Err(e) => panic!("refractr: failed to get branches: {}", e)
};
2025-03-02 18:33:33 -07:00
for branch in remote_branches {
if let Ok((b, _)) = branch {
if let Ok(Some(name)) = b.name() {
2025-03-03 23:15:35 -07:00
if refs {
strings.push(format!("refs/heads/{}", name.to_string()))
} else {
strings.push(name.to_string());
}
2025-03-02 18:33:33 -07:00
}
}
}
strings
}
2025-03-03 22:54:58 -07:00
}
}
fn fast_forward(repo_dir: &str, branches: &Option<Vec<String>>) -> Result<(), Error> {
let repo = Repository::open(repo_dir)?;
2025-03-03 23:15:35 -07:00
let branch_list: Vec<String> = get_branches(&repo, branches, false);
2025-03-02 18:33:33 -07:00
repo.find_remote("origin")?.fetch(&branch_list, None, None)?;
Ok(())
}
fn make_remotes<'a> (refractr: &Refractr, repo: &'a Repository, cfg: &ConfigFile) -> Vec<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(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);
}
}
}
}
remote_list
}
fn push_remotes(refractr: &Refractr, cfg: &Config, repo: &Repository, remote_list: &Vec<String>) {
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);
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)
}
}
}
}
fn looper(refractr: Refractr, repos: Vec<OpenedRepository>) {
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(&repos[i].repo.path().to_string_lossy(), &repos[i].cfg.branches);
push_remotes(&refractr, &repos[i].cfg, &repos[i].repo, &repos[i].remotes);
}
}
do_break = false;
break
}
}
}
common::verbose(refractr.verbose, 1, format!("Exited looper due to ^C"));
}
pub fn run(refractr: Refractr, cfgs: Vec<ConfigFile>) -> std::io::Result<()> {
2025-03-02 18:33:33 -07:00
common::verbose(refractr.verbose, 3, format!("Starting main refractr loop"));
let mut loop_repos = Vec::new();
for cfg in cfgs {
2025-03-02 18:33:33 -07:00
// set up the working directory
2025-03-03 21:10:31 -07:00
common::verbose(refractr.verbose, 3, format!("Loaded config: {}", cfg.path));
let path_str = set_up_work_dir(match &cfg.config.work_dir {
2025-03-02 18:33:33 -07:00
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));
2025-03-03 21:10:31 -07:00
let repo_name = match &cfg.config.from.split("/").last() {
2025-03-02 18:33:33 -07:00
Some(split) => split.to_string(),
None => panic!("refractr: failed to parse repository name")
};
// make initial clone
2025-03-03 21:10:31 -07:00
common::verbose(refractr.verbose, 1, format!("Cloning repository: {}", &cfg.config.from));
2025-03-02 18:33:33 -07:00
let repo_dir = &format!("{}/{}", &path_str, repo_name);
2025-03-03 21:10:31 -07:00
let repo = match Repository::clone(&cfg.config.from, Path::new(&repo_dir)) {
Ok(repo) => repo,
Err(_) => {
2025-03-02 18:33:33 -07:00
eprintln!("refractr: warning: found existing repo at {}, attempting to use", repo_dir);
2025-03-03 21:10:31 -07:00
match 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 = make_remotes(&refractr, &repo_fresh, &cfg);
push_remotes(&refractr, &cfg.config, &repo, &remotes);
if cfg.config.schedule.enabled {
loop_repos.push(OpenedRepository {
repo,
remotes,
cfg: cfg.config
});
2025-03-02 18:33:33 -07:00
}
}
2025-03-03 22:54:58 -07:00
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"));
2025-03-02 18:33:33 -07:00
}
Ok(())
}