use git2::{Cred, PushOptions, Remote, RemoteCallbacks, Repository}; use sha2::{Sha256, Digest}; use crate::common::{self, Refractr}; use crate::config::ConfigFile; use std::fs; use std::env; use std::path::{Path, PathBuf}; use git2::{Error, ErrorCode}; use hex; 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) } work_dir.to_string_lossy().to_string() } fn get_branches(repo: &Repository, branches: &Option>) -> Vec { match branches { Some(repo_branches) => 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() { strings.push(name.to_string()); } } } strings } } } fn fast_forward(repo_dir: &str, branches: &Option>) -> Result<(), Error> { let repo = Repository::open(repo_dir)?; let branch_list: Vec = get_branches(&repo, branches); repo.find_remote("origin")?.fetch(&branch_list, None, None)?; Ok(()) } pub fn start(refractr: Refractr, cfgs: Vec) -> std::io::Result<()> { common::verbose(refractr.verbose, 3, format!("Starting main refractr loop")); 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") }; // 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(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) } } }; // create remotes for each "to" repo let mut remote_list: Vec> = 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(r) => remote_list.push(r), Err(e) => { if e.code() == ErrorCode::Exists { eprintln!("refractr: warning: remote {} already exists, skipping", remote_id) } else { panic!("refractr: failed to create remote: {}", e); } } } } for mut remote in remote_list { let mut callbacks = RemoteCallbacks::new(); callbacks.credentials(|_,_,_| Cred::ssh_key("git", None, &Path::new(&cfg.config.git.ssh_identity_file), None)); let mut push_options = PushOptions::new(); push_options.remote_callbacks(callbacks); match remote.push::<&str>(&["refs/heads/master"], Some(&mut push_options)) { Ok(_) => (), Err(e) => { eprintln!("refractr: failed to push to remote: {}: {}", remote.url().unwrap(), e) } } } } Ok(()) }