2025-03-03 22:54:58 -07:00
|
|
|
use git2::{Cred, PushOptions, Remote, RemoteCallbacks, Repository};
|
2025-03-03 21:10:31 -07:00
|
|
|
use sha2::{Sha256, Digest};
|
2025-03-02 16:36:04 -07:00
|
|
|
|
2025-03-02 18:33:33 -07:00
|
|
|
use crate::common::{self, Refractr};
|
2025-03-03 21:10:31 -07:00
|
|
|
use crate::config::ConfigFile;
|
2025-03-02 18:33:33 -07:00
|
|
|
use std::fs;
|
|
|
|
use std::env;
|
|
|
|
use std::path::{Path, PathBuf};
|
2025-03-03 21:10:31 -07:00
|
|
|
use git2::{Error, ErrorCode};
|
|
|
|
use hex;
|
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 22:54:58 -07:00
|
|
|
fn get_branches(repo: &Repository, branches: &Option<Vec<String>>) -> Vec<String> {
|
|
|
|
match branches {
|
2025-03-02 18:33:33 -07:00
|
|
|
Some(repo_branches) => repo_branches.to_vec(),
|
|
|
|
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() {
|
|
|
|
strings.push(name.to_string());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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)?;
|
|
|
|
let branch_list: Vec<String> = get_branches(&repo, branches);
|
2025-03-02 18:33:33 -07:00
|
|
|
|
|
|
|
repo.find_remote("origin")?.fetch(&branch_list, None, None)?;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn start(refractr: Refractr, cfgs: Vec<ConfigFile>) -> std::io::Result<()> {
|
|
|
|
common::verbose(refractr.verbose, 3, format!("Starting main refractr loop"));
|
2025-03-03 21:10:31 -07:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// create remotes for each "to" repo
|
2025-03-03 22:54:58 -07:00
|
|
|
let mut remote_list: Vec<Remote<'_>> = Vec::new();
|
2025-03-03 21:10:31 -07:00
|
|
|
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);
|
|
|
|
}
|
2025-03-02 18:33:33 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2025-03-03 22:54:58 -07:00
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2025-03-02 18:33:33 -07:00
|
|
|
}
|
2025-03-02 16:36:04 -07:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|