Select Git revision
checkout.rs
checkout.rs 11.37 KiB
use crate::commit;
use std::env;
use std::{
path::PathBuf,
process::{exit, Command, Stdio},
};
/// Create a commit of the results folder named as the current git commit id
pub fn silent_commit(compression: String, mut commit_id: String, update: bool) {
let (borg_folder, results_folder) = commit::check_path();
let (has_ignore, borgignore) = commit::check_if_borgignore_exists(&borg_folder);
if commit_id == String::from("") {
commit_id = commit::get_current_commit();
}
if update {
commit::delete_commit(&commit_id, &borg_folder);
}
let mut margs: Vec<&str> = vec!["create", "--compression", &compression];
if has_ignore {
margs.push("--exclude-from");
margs.push(borgignore.to_str().unwrap());
}
let cmd_part = format!("{}::{}", borg_folder.to_str().unwrap(), commit_id);
margs.push(&cmd_part);
margs.push(results_folder.to_str().unwrap());
let output = Command::new("borg").args(margs).output().unwrap();
match output.status.code().unwrap() {
0 => (),
num => {
eprintln!("{}", String::from_utf8(output.stderr).unwrap());
exit(num);
}
}
}
/// Check if two commits are differents
pub fn is_diff(commit1: &str, commit2: &str, borg_folder: &PathBuf,
prepare_checkout: bool) -> bool {
let archive1 = format!("{}::{}", borg_folder.to_str().unwrap(), commit1);
let (has_ignore, borgignore) = commit::check_if_borgignore_exists(&borg_folder);
let cmd = match has_ignore {
false => format!("borg diff {} {}", archive1, commit2),
true => format!(
"borg diff --exclude-from {} {} {}",
borgignore.to_str().unwrap(),
archive1,
commit2
),
};
let output = Command::new("sh").arg("-c").arg(cmd).output().unwrap();
match output.status.code().unwrap() {
0 => (),
num => {
eprintln!("{}", String::from_utf8(output.stderr.clone()).unwrap());
exit(num);
}
}
let mut diff_files = String::from_utf8(output.stdout.clone()).unwrap();
if diff_files.ends_with('\n') {
diff_files.pop();
}
let real_diff = diff_files
.split("\n")
.filter(|x| x.len() > 1)
.collect::<Vec<&str>>();
let res = if prepare_checkout {
let file_diff = real_diff.iter().map(|x| *x).filter(|x| !x.starts_with("added directory")).collect::<Vec<&str>>();
let res = file_diff.len();
if res > 0 { println!("{}\n", file_diff.join("\n")); }
res
} else {
let res = real_diff.len();
if res > 0 { println!("{}\n", real_diff.join("\n")); }
res
};
res > 0
}
/// Function that checks if the archive with the name of the current commit id
/// exits in borg repository
pub fn check_if_current_commit_exits(borg_path: &PathBuf, commit_id: &str) {
// Trying to create an archive with the current git commit id
let output = Command::new("borg")
.arg("list")
.arg(format!("{}::{}", borg_path.to_str().unwrap(), commit_id))
.output()
.unwrap();
match output.status.code().unwrap() {
0 => (),
num => {
let msg = format!("Archive {} does not exist", commit_id);
let mut output_msg = String::from_utf8(output.stderr).unwrap();
if output_msg.ends_with("\n") {
output_msg.pop();
}
if output_msg == msg {
eprintln!("{}\n{}\n{}",
"No archive with the current commit id found !",
"You should commit it first !",
"If you're using hooks and you want to force the chekout, run git conh [TARGET-BRANCH] && gblk checkout --mode hard"
);
exit(3);
} else {
eprintln!("{}", output_msg);
exit(num);
}
}
}
}
/// Function that prepare checkout. **This function should be used before a
/// git checkout !
///
/// # Description:
/// This function will check if the archive named as the current commit exits.
/// If it doesn't exists, it will create one. If it exists, it will create a
/// temporary archive of the current result respository and compare it with
/// the archive with the current git commit id.
/// If it is the same as the temporary achive then you can make a
/// checkout without losing data. If not, then you might.
pub fn prepare_checkout() {
let (borg_path, _) = commit::check_path();
let commit_id = commit::get_current_commit();
check_if_current_commit_exits(&borg_path, &commit_id);
// Create an archive with the content of the current commit
let tmp_name = format!("{}-tmp", commit_id);
silent_commit(String::from("none"), tmp_name.clone(), false);
let res = is_diff(&commit_id, &tmp_name, &borg_path, true);
commit::delete_commit(&tmp_name, &borg_path);
if res {
eprintln!(
"{}\n{}\n{}",
"Your results folder contains unsaved changes!",
"Please update your current commit with: gblk commit --update",
"Or revert it back to it's previous state with gblk commit --revert"
);
exit(4);
}
}
/// Function that perform a checkout on your results
pub fn checkout(mode: &str) {
let (borg_path, results) = commit::check_path();
if mode == "hard" {
remove_results::remove_results(&borg_path, &results)
}
let commit_id = commit::get_current_commit();
let project_dir = borg_path
.canonicalize()
.unwrap()
.parent()
.unwrap()
.to_owned();
env::set_current_dir(&project_dir).unwrap();
let (borg_path, _) = commit::check_path();
let (has_ignore, borgignore) = commit::check_if_borgignore_exists(&borg_path);
let temp = Command::new("pwd").output().unwrap();
match temp.status.code().unwrap() {
0 => (),
_ => {
eprintln!("{}", String::from_utf8(temp.stderr).unwrap());
exit(23);
}
};
let progress_part = match has_ignore {
false => String::from("--progress"),
true => format!("--progress --exclude-from {}", borgignore.to_str().unwrap()),
};
let cmd = format!(
"borg extract {} {}::{}",
progress_part,
borg_path.to_str().unwrap(),
commit_id
);
let mut output = Command::new("sh")
.arg("-c")
.arg(cmd)
.stdout(Stdio::piped())
.spawn()
.unwrap();
let ecode = output.wait().expect("error");
match ecode.code().unwrap() {
0 => (),
num => {
//eprintln!("{}", ecode.to_string());
exit(num);
}
}
}
mod remove_results {
// import everything from the parent module
use super::*;
// Usage of globwalk for this module
extern crate globwalk;
// other import dedicated to this module
use std::fs;
/// Get the list of files to remove
/// # Arguments
/// * `results` The results folder
/// * `borgignore` The borgignore files
/// * `project_dir` the current project directory
/// # Return
/// The list of paths to keep
fn get_entries_to_remove(results: &PathBuf, borgignore: &PathBuf, project_dir: &PathBuf) -> Vec<PathBuf> {
let content = fs::read_to_string(borgignore).unwrap();
let mut content = content.lines().map(|x| {
format!("!{}", x)
}).collect::<Vec<_>>();
let mut ctmp = vec![format!("{}/**", results.to_str().unwrap())];
ctmp.append(&mut content);
let ncontent = ctmp.iter().map(|x| {x.as_str()}).collect::<Vec<_>>();
let walker = globwalk::GlobWalkerBuilder::from_patterns(
project_dir,
&ncontent,
)
.max_depth(100)
.follow_links(false)
.build()
.unwrap()
.into_iter()
.filter_map(Result::ok)
.map(|x| x.path().to_path_buf())
.collect::<Vec<_>>();
walker
}
/// Separate file and directory from a vector of paths
/// # Arguments
/// * `entries` : A vector containing path and directory
/// # Return
/// A tuble containing the files and the direcotries separately
fn separate_files_n_directory(entries: Vec<PathBuf>) -> (Vec<PathBuf>, Vec<PathBuf>){
let mut files: Vec<PathBuf> = Vec::new();
let mut dir: Vec<PathBuf> = Vec::new();
for x in entries.into_iter() {
if x.is_dir() {
dir.push(x);
} else if x.is_file() {
files.push(x);
}
}
let dir = dir.into_iter().rev().collect();
(files, dir)
}
/// Remove files given in input
/// # Arguments
/// * `list_files` A vector containing a list of files to remove
/// * `project_dir` A path corresponding to the current project directory
fn remove_files(list_files: Vec<PathBuf>, project_dir: &PathBuf) -> () {
for my_file in list_files.iter() {
let dir_file = my_file
.canonicalize()
.unwrap();
let dir_file = dir_file
.parent()
.unwrap();
if dir_file.starts_with(project_dir) {
fs::remove_file(my_file).unwrap();
} else {
eprintln!("Error: file {:?} is not in the project folder", {dir_file});
exit(99);
}
}
}
/// Remove the directory given by an input vector if the directory is empty
/// # Arguments
/// * `list_dirs` A vector containing directories to remove only if the directory is empty
///
fn remove_directories(list_dirs: Vec<PathBuf>) -> () {
for dir in list_dirs.iter() {
let is_empty = dir.read_dir().unwrap().next().is_none();
if is_empty {
fs::remove_dir(dir).unwrap();
}
}
}
/// remove files that are not ignored if a borgignore file exists
/// # Arguments
/// * `results` The results folder
/// * `borgignore` The borgignore files
fn remove_with_borgignore(results: &PathBuf, borgignore: &PathBuf) {
let project_dir = results
.canonicalize()
.unwrap()
.parent()
.unwrap()
.to_owned();
let entries = get_entries_to_remove(results, borgignore, &project_dir);
let (files, dir) = separate_files_n_directory(entries);
remove_files(files, &project_dir);
remove_directories(dir);
}
/// remove results folder is no .borgignore file exits
/// # Arguments
/// * `results` The results folder
fn remove_no_borgignore(results: &PathBuf) -> () {
let cmd = format!("rm -r {}/*", results.to_str().unwrap());
let output = Command::new("sh").arg("-c").arg(cmd).output().unwrap();
match output.status.code().unwrap() {
0 => (),
123 => (),
1 => (),
num => {
eprintln!("{}", String::from_utf8(output.stderr).unwrap());
exit(num);
}
}
}
/// Remove content of the results folder that is not ignored
pub fn remove_results(borg_path: &PathBuf, results: &PathBuf) {
let (has_ignore, borgignore) = commit::check_if_borgignore_exists(&borg_path);
if !has_ignore {
remove_no_borgignore(results);
} else {
remove_with_borgignore(results, &borgignore);
};
}
}