benchmark-repository-rs/src/lib.rs

447 lines
12 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

extern crate git2;
extern crate pretty_env_logger;
#[macro_use]
extern crate log;
extern crate indicatif;
extern crate tempfile;
extern crate walkdir;
use git2::{Cred, Error, FetchOptions, Index, IndexEntry, IndexTime, Oid, Progress, PushOptions, RemoteCallbacks, Repository, Signature};
use std::path::{Path, PathBuf};
use std::string::String;
use std::str;
use std::sync::Arc;
use indicatif::{ProgressBar, ProgressStyle};
use walkdir::WalkDir;
pub struct BenchmarkRepositoryInner
{
repository: Repository,
ssh_user: String,
user_name: String,
user_email: String,
}
pub struct BenchmarkRepository
{
inner: Arc<BenchmarkRepositoryInner>
}
pub struct TargetPath
{
pub source: PathBuf,
pub destination: PathBuf,
}
impl BenchmarkRepository
{
fn progress_bar_style() -> ProgressStyle
{
ProgressStyle::default_bar()
.template("{bar:74.on_black} {percent:>3} %")
.progress_chars("█▉▊▋▌▍▎▏ ")
}
fn transfer_progress_callback(progress: Progress, progress_bar: &mut ProgressBar) -> bool
{
progress_bar.set_length(progress.total_objects() as u64);
progress_bar.set_position(progress.received_objects() as u64);
// continue the transfer
true
}
fn push_update_reference_callback(reference: &str, status: Option<&str>) -> Result<(), Error>
{
match status
{
None => trace!("Reference “{}” pushed successfully to remote “origin”", reference),
Some(error) => panic!("Couldnt push reference “{}” to remote “origin”: {}", reference, error),
};
Ok(())
}
fn reset_origin(&self, remote_url: &str) -> &Self
{
let inner = self.inner.clone();
if let Err(error) = inner.repository.find_remote("origin")
.or_else(|_| inner.repository.remote("origin", remote_url))
{
panic!("Could not reset remote “origin”: {}", error);
}
info!("Reset origin to “{}”", remote_url);
self
}
fn fetch_branch(&self, branch_name: &str) -> &Self
{
let inner = self.inner.clone();
let mut progress_bar = ProgressBar::new(0);
progress_bar.set_style(BenchmarkRepository::progress_bar_style());
{
let mut remote_callbacks = RemoteCallbacks::new();
remote_callbacks.credentials(
|_, _, _|
{
match Cred::ssh_key_from_agent(&inner.ssh_user)
{
Ok(credentials) => Ok(credentials),
Err(error) => panic!("could not retrieve key pair for SSH authentication as user “{}”: {}", inner.ssh_user, error),
}
});
remote_callbacks.transfer_progress(
|progress| BenchmarkRepository::transfer_progress_callback(progress, &mut progress_bar));
let mut fetch_options = FetchOptions::new();
fetch_options.remote_callbacks(remote_callbacks);
let mut origin = inner.repository.find_remote("origin").expect("could not find remote “origin”");
info!("Updating branch “{}”", branch_name);
if let Err(error) = origin.fetch(&[branch_name], Some(&mut fetch_options), None)
{
panic!("failed to fetch branch “{}” from remote “origin”: {}", branch_name, error);
}
}
progress_bar.finish_and_clear();
trace!("Branch “{}” is up-to-date", branch_name);
self
}
fn init(base_path: &Path) -> Repository
{
let repository = match Repository::init_bare(base_path)
{
Ok(repository) => repository,
Err(error) => panic!("failed to initialize Git repository in “{}”: {}", base_path.display(), error),
};
info!("Initialized Git repository in “{}”", base_path.display());
repository
}
pub fn new(remote_url: &str, base_path: &Path, ssh_user: &str, user_name: &str, user_email: &str) -> BenchmarkRepository
{
let repository = match Repository::open(base_path)
{
Ok(repository) =>
{
info!("Using existing Git repository");
repository
},
Err(_) => BenchmarkRepository::init(base_path),
};
let benchmark_repository_inner =
BenchmarkRepositoryInner
{
repository: repository,
ssh_user: ssh_user.to_string(),
user_name: user_name.to_string(),
user_email: user_email.to_string(),
};
let benchmark_repository =
BenchmarkRepository
{
inner: Arc::new(benchmark_repository_inner),
};
benchmark_repository
.reset_origin(remote_url)
.fetch_branch("config")
.fetch_branch("results")
.fetch_branch("status");
benchmark_repository
}
pub fn read_file_as_index_entry(inner: &BenchmarkRepositoryInner, file_path: &Path, result_file_path: &Path) -> IndexEntry
{
// create a new blob with the file contents
let object_id = match inner.repository.blob_path(file_path)
{
Ok(object_id) => object_id,
Err(error) => panic!("Could not write blob for “{}”: {}", file_path.display(), error),
};
info!("Created object “{}” from “{}”", object_id, file_path.display());
IndexEntry
{
ctime: IndexTime::new(0, 0),
mtime: IndexTime::new(0, 0),
dev: 0,
ino: 0,
mode: 0o100644,
uid: 0,
gid: 0,
file_size: 0,
id: object_id,
flags: 0,
flags_extended: 0,
path: result_file_path.to_string_lossy().as_bytes().to_owned(),
}
}
pub fn commit_directory(inner: &BenchmarkRepositoryInner, directory_path: &TargetPath, branch_name: &str)
{
let mut file_paths = vec![];
for entry in WalkDir::new(&directory_path.source)
{
let entry = entry.unwrap();
if entry.path().file_name().unwrap() == ".lock" || entry.file_type().is_dir()
{
continue;
}
let relative_path = entry.path().strip_prefix(&directory_path.source).unwrap();
trace!("Adding “{}” (from “{}”) to autocommit list", relative_path.display(), entry.path().display());
file_paths.push(TargetPath{source: entry.path().to_owned(), destination: directory_path.destination.join(&relative_path)});
}
BenchmarkRepository::commit_files(&inner, &file_paths, branch_name);
}
pub fn commit_files(inner: &BenchmarkRepositoryInner, file_paths: &[TargetPath], branch_name: &str)
{
let tip_reference_name = format!("refs/remotes/origin/{}", branch_name);
let tip_reference = match inner.repository.find_reference(&tip_reference_name)
{
Ok(value) => value,
Err(error) => panic!("Could not find reference “{}”: {}", tip_reference_name, error),
};
let parent_tree = match tip_reference.peel_to_tree()
{
Ok(value) => value,
Err(error) => panic!("Could not peel reference to tree: {}", error),
};
let mut index = match Index::new()
{
Ok(value) => value,
Err(error) => panic!("Could not create index: {}", error),
};
match index.read_tree(&parent_tree)
{
Ok(value) => value,
Err(error) => panic!("Could not read parent tree into index: {}", error),
};
for target_path in file_paths
{
let index_entry = BenchmarkRepository::read_file_as_index_entry(inner, &target_path.source, &target_path.destination);
if let Err(error) = index.add(&index_entry)
{
panic!("Could not add index entry for “{}”: {}", target_path.destination.display(), error);
}
}
let tree_object_id = match index.write_tree_to(&inner.repository)
{
Ok(value) => value,
Err(error) => panic!("Could not write index to tree: {}", error),
};
let tree = match inner.repository.find_tree(tree_object_id)
{
Ok(value) => value,
Err(error) => panic!("Could obtain tree: {}", error),
};
info!("Created tree object “{}”", tree_object_id);
let signature = Signature::now(&inner.user_name, &inner.user_email).expect("Could not create signature");
let message = format!("Add files");
let parent = match tip_reference.peel_to_commit()
{
Ok(value) => value,
Err(error) => panic!("Could not peel reference: {}", error),
};
let commit_id = match inner.repository.commit(Some(&tip_reference_name), &signature, &signature, &message, &tree, &[&parent])
{
Ok(value) => value,
Err(error) => panic!("Could not write commit: {}", error),
};
let push_refspec = format!("refs/remotes/origin/{}:refs/heads/{}", branch_name, branch_name);
trace!("Created commit “{}”, using refspec “{}”", commit_id, push_refspec);
let mut remote_callbacks = RemoteCallbacks::new();
remote_callbacks.credentials(
|_, _, _|
{
match Cred::ssh_key_from_agent(&inner.ssh_user)
{
Ok(value) => Ok(value),
Err(error) => panic!("could not retrieve key pair for SSH authentication as user “{}”: {}", inner.ssh_user, error),
}
});
remote_callbacks.push_update_reference(
|reference, status| BenchmarkRepository::push_update_reference_callback(reference, status));
let mut push_options = PushOptions::new();
push_options.remote_callbacks(remote_callbacks);
let mut remote = inner.repository.find_remote("origin").expect("");
remote.push(&[&push_refspec], Some(&mut push_options)).expect("couldnt push");
}
pub fn file_exists(&self, file_path: &Path, branch_name: &str) -> bool
{
let inner = self.inner.clone();
let tip_reference_name = format!("refs/remotes/origin/{}", branch_name);
let tip_reference = match inner.repository.find_reference(&tip_reference_name)
{
Ok(value) => value,
Err(error) => panic!("Could not find reference “{}”: {}", tip_reference_name, error),
};
let tree = match tip_reference.peel_to_tree()
{
Ok(value) => value,
Err(error) => panic!("Could not peel reference to tree: {}", error),
};
let object_id = match tree.get_path(file_path)
{
Ok(tree_entry) => tree_entry.id(),
Err(_) => return false,
};
if let Err(_) = inner.repository.find_blob(object_id)
{
return false;
}
true
}
pub fn read_file(&self, file_path: &Path, branch_name: &str) -> Option<String>
{
let inner = self.inner.clone();
let tip_reference_name = format!("refs/remotes/origin/{}", branch_name);
let tip_reference = match inner.repository.find_reference(&tip_reference_name)
{
Ok(value) => value,
Err(error) => panic!("Could not find reference “{}”: {}", tip_reference_name, error),
};
let tree = match tip_reference.peel_to_tree()
{
Ok(value) => value,
Err(error) => panic!("Could not peel reference to tree: {}", error),
};
let object_id = match tree.get_path(file_path)
{
Ok(tree_entry) => tree_entry.id(),
Err(_) => return None,
};
let blob = match inner.repository.find_blob(object_id)
{
Ok(blob) => blob,
Err(_) => return None,
};
let content = match std::str::from_utf8(blob.content())
{
Ok(content) => content,
Err(error) => panic!("Could not interpret file “{}” as UTF-8: {}", file_path.display(), error),
};
Some(content.to_owned())
}
pub fn create_branch_for_job(&self, job_id: u32) -> String
{
let inner = self.inner.clone();
let empty_tree_hash = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
let empty_tree_object_id = Oid::from_str(&empty_tree_hash).unwrap();
let empty_tree = match inner.repository.find_tree(empty_tree_object_id)
{
Ok(value) => value,
Err(error) => panic!("Could not find empty tree object: {}", error),
};
let signature = Signature::now(&inner.user_name, &inner.user_email).expect("Could not create signature");
let message = format!("Initial commit for job {}", job_id);
let commit_id = match inner.repository.commit(None, &signature, &signature, &message, &empty_tree, &[])
{
Ok(value) => value,
Err(error) => panic!("Could not write commit: {}", error),
};
let commit = match inner.repository.find_commit(commit_id)
{
Ok(value) => value,
Err(error) => panic!("Could not find newly written commit: {}", error),
};
let branch_name = format!("__job_{}", job_id);
match inner.repository.branch(&branch_name, &commit, true)
{
Ok(_) => println!("Created new branch “{}", branch_name),
Err(error) => panic!("Could not create job branch “{}”: {}", branch_name, error),
};
branch_name
}
pub fn join(&self)
{
loop
{
//let origin = self.inner.repository.find_remote("origin").expect("could not find remote “origin”");
for reference in self.inner.repository.references().unwrap().names()
{
println!("{}", reference.unwrap());
}
println!("================");
use std::{thread, time};
let time = time::Duration::from_secs(2);
thread::sleep(time);
}
}
}
#[cfg(test)]
mod tests
{
}