pub mod rel;
pub mod config;
pub mod history;
pub mod hook;
pub mod meta;
use crate::checker::Checker;
use crate::models::{LoopBranch, ListType};
use crate::utils;
use std::collections::HashSet;
use config::Config;
use rel::Relations;
use history::History;
use meta::Meta;
use crate::RifError;
use std::path::{Path, PathBuf};
use crate::consts::*;
pub struct Rif {
config: Config,
history: History,
relation: Relations,
meta: Meta,
black_list: HashSet<PathBuf>,
root_path: Option<PathBuf>,
}
impl Rif {
pub fn new(path: Option<impl AsRef<Path>>) -> Result<Self, RifError> {
let config = Config::read_from_file(path.as_ref())?;
let black_list = utils::get_black_list(config.git_ignore)?;
Ok(Self {
config,
history: History::read_from_file(path.as_ref())?,
relation: Relations::read_from_file(path.as_ref())?,
meta: Meta::read_from_file(path.as_ref())?,
black_list,
root_path: path.map(|p| p.as_ref().clone().to_owned()),
})
}
pub fn init(path: Option<impl AsRef<Path>>,create_rif_ignore: bool) -> Result<(), RifError> {
let path = if let Some(path) = path {
path.as_ref().to_owned()
} else { std::env::current_dir()? };
if path.join(RIF_DIECTORY).exists() {
return Err(RifError::RifIoError(String::from("Directory is already initiated")));
}
std::fs::create_dir(path.join(RIF_DIECTORY))?;
let new_relations = Relations::new();
new_relations.save_to_file(Some(&path))?;
let new_rif_history = History::new();
new_rif_history.save_to_file(Some(&path))?;
let new_config = Config::new();
new_config.save_to_file(Some(&path))?;
let new_meta = Meta::new();
new_meta.save_to_file(Some(&path))?;
println!("Initiated a rif directory \"{}\"", std::env::current_dir()?.display());
if create_rif_ignore {
std::fs::write(".rifignore",".git")?;
}
Ok(())
}
pub fn add(&mut self, files: &Vec<impl AsRef<Path>>, force: bool) -> Result<(), RifError> {
for file in files {
let mut path = file.as_ref().to_owned();
if !path.exists() {
continue;
}
if path.to_str().unwrap() == "." {
path = std::env::current_dir()?;
self.add_directory(&path)?;
continue;
} else if path.is_dir() {
self.add_directory(&path)?;
continue;
}
if self.is_in_black_list(&path) {
continue;
}
if self.relation.files.contains_key(&path) {
self.add_old_file(&path, force)?;
} else {
self.add_new_file(&path)?;
}
}
self.relation.save_to_file(self.root_path.as_ref())?;
self.meta.save_to_file(self.root_path.as_ref())?;
Ok(())
}
pub fn revert(&mut self, files: Option<&Vec<impl AsRef<Path>>>) -> Result<(), RifError> {
if let Some(files) = files {
for file in files {
let path = file.as_ref();
self.meta.remove_add_queue(&path);
} } else {
self.meta.clear();
}
self.meta.save_to_file(self.root_path.as_ref())?;
Ok(())
}
pub fn commit(&mut self, message: Option<&str>) -> Result<(), RifError> {
if self.relation.get_deleted_files().len() != self.meta.to_be_deleted.len() {
return Err(RifError::CommitFail("Commit without deleted files are illegal. Rejected".to_owned()))
}
for file in self.meta.to_be_deleted.clone().iter() {
self.remove_file(file)?;
}
for file in self.meta.to_be_registerd.clone().into_iter() {
self.register_new_file(&file, message)?;
}
for file in self.meta.to_be_forced.iter() {
self.relation.update_filestamp_force(&file)?;
}
for file in self.meta.to_be_added.iter() {
self.relation.update_filestamp(&file)?;
if let Some(msg) = message {
self.history.add_history(&file, msg)?;
self.history.save_to_file(self.root_path.as_ref())?;
}
}
if self.meta.to_be_added_later().count() != 0 {
self.check_exec()?;
}
self.meta.clear();
self.meta.save_to_file(self.root_path.as_ref())?;
self.relation.save_to_file(self.root_path.as_ref())?;
self.history.save_to_file(self.root_path.as_ref())?;
Ok(())
}
pub fn discard(&mut self, file: impl AsRef<Path>) -> Result<(), RifError> {
self.relation.discard_change(file.as_ref())?;
self.relation.save_to_file(self.root_path.as_ref())?;
Ok(())
}
pub fn rename(&mut self, source_name: &str, new_name: &str) -> Result<(), RifError> {
let source_name = Path::new(source_name);
let new_name = Path::new(new_name);
if let Some(_) = self.relation.files.get(new_name) {
return Err(RifError::RenameFail(format!("Rename target: \"{}\" already exists", new_name.display())));
}
if source_name.exists() && self.relation.files.contains_key(source_name) {
if !new_name.exists() {
std::fs::rename(source_name, new_name)?;
} else {
return Err(RifError::RenameFail("New name already exists".to_owned()));
}
}
self.relation.rename_file(source_name, new_name)?;
self.relation.save_to_file(self.root_path.as_ref())?;
Ok(())
}
pub fn remove(&mut self, files: &Vec<impl AsRef<Path>>) -> Result<(), RifError> {
for file in files {
self.remove_file(file.as_ref())?;
}
self.relation.save_to_file(self.root_path.as_ref())?;
self.history.save_to_file(self.root_path.as_ref())?;
Ok(())
}
pub fn set(&mut self, file: &Path, refs : &Vec<impl AsRef<Path>>) -> Result<(), RifError> {
let refs: HashSet<PathBuf> = refs.iter().map(|a| a.as_ref().to_owned()).collect();
self.relation.add_reference(file, &refs)?;
self.relation.save_to_file(self.root_path.as_ref())?;
Ok(())
}
pub fn unset(&mut self, file: &Path, refs : &Vec<impl AsRef<Path>>) -> Result<(), RifError> {
let refs: HashSet<PathBuf> = refs.iter().map(|a| a.as_ref().to_owned()).collect();
self.relation.remove_reference(file, &refs)?;
self.relation.save_to_file(self.root_path.as_ref())?;
Ok(())
}
pub fn status(&mut self, ignore: bool, verbose: bool) -> Result<(), RifError> {
self.meta.remove_non_exsitent();
let mut to_be_added_later = self.meta.to_be_added_later().peekable();
if let Some(_) = to_be_added_later.peek() {
println!("# Changes to be commited :");
for item in &self.meta.to_be_registerd {
let format = format!(" new file : {}", &item.to_str().unwrap());
println!("{}", utils::green(&format));
}
for item in &self.meta.to_be_added {
let format = format!(" modified : {}", &item.to_str().unwrap());
println!("{}", utils::green(&format));
}
for item in &self.meta.to_be_forced {
let format = format!(" forced : {}", &item.to_str().unwrap());
println!("{}", utils::green(&format));
}
for item in &self.meta.to_be_deleted {
let format = format!(" deleted : {}", &item.to_str().unwrap());
println!("{}", utils::green(&format));
}
println!("");
}
println!("# Changed files :");
self.relation.track_modified_files(self.meta.to_be_added_later())?;
if !ignore {
println!("\n# Untracked files :");
self.relation.track_unregistered_files(&self.black_list, &self.meta.to_be_registerd)?;
}
if verbose {
println!("\n# Current rif status:\n---");
print!("{}", self.relation);
}
self.meta.save_to_file(self.root_path.as_ref())?;
Ok(())
}
pub fn list(&self, file : Option<impl AsRef<Path>>, list_type: ListType, depth: Option<usize>) -> Result<(), RifError> {
if let Some(file) = file {
self.relation.display_file_depth(file.as_ref(), 0)?;
println!("\n# History : ");
self.history.print_history(file.as_ref())?;
} else { match list_type {
ListType::All => {
self.relation.display_depth(depth.unwrap_or(0))?;
}
ListType::Stale => {
self.relation.display_stale_files(depth.unwrap_or(0))?;
}
_ => (),
}
}
Ok(())
}
pub fn data(&self, data_type: Option<&str>, compact: bool) -> Result<(), RifError> {
if let Some(data_type) = data_type {
match data_type {
"meta" => {
println!("{:#?}", self.meta);
}
"history" => {
println!("{:#?}", self.history);
}
_ => () }
} else {
if compact {
println!("{:?}", self.relation);
} else {
println!("{:#?}", self.relation);
}
}
Ok(())
}
pub fn depend(&self, file: &Path) -> Result<(), RifError> {
let dependes = self.relation.find_depends(file)?;
println!("Files that depends on \"{}\"", file.display());
println!("=====");
for item in dependes {
println!("{}", utils::green(&item.display().to_string()));
}
Ok(())
}
pub fn check(&mut self) -> Result<(), RifError> {
if self.relation.get_deleted_files().len() != 0 {
return Err(RifError::CheckerError("Check with deleted files are illegal. Rejected".to_owned()));
}
self.check_exec()?;
Ok(())
}
pub fn sanity(&mut self, fix: bool) -> Result<(), RifError> {
if fix {
self.relation.sanity_fix()?;
self.relation.save_to_file(self.root_path.as_ref())?;
println!("Sucessfully fixed the rif file");
} else {
self.relation.sanity_check()?;
println!("Sucessfully checked the rif file");
}
Ok(())
}
fn check_exec(&mut self) -> Result<(), RifError> {
let mut checker = Checker::with_relations(&self.relation)?;
let changed_files = checker.check(&mut self.relation)?;
if changed_files.len() != 0 && self.config.hook.trigger {
println!("\nHook Output");
self.config.hook.execute(changed_files)?;
}
Ok(())
}
fn is_in_black_list(&self, path: &Path) -> bool {
if let Some(_) = self.black_list.get(path) {
if BLACK_LIST.to_vec().contains(&path.to_str().unwrap()) {
eprintln!("File : \"{}\" is not allowed", path.display());
} else {
println!("\"{}\" is in rifignore file, which is ignored.", path.display());
}
return true;
}
false
}
fn add_new_file(&mut self, file: &Path) -> Result<(), RifError> {
self.meta.to_be_registerd.insert(file.to_owned());
Ok(())
}
fn remove_file(&mut self, file: &Path) -> Result<(), RifError> {
self.relation.remove_file(file)?;
self.history.remove_file(file)?;
Ok(())
}
fn register_new_file(&mut self, file: &Path, message: Option<&str>) -> Result<(), RifError> {
let mut closure = |entry_path : PathBuf| -> Result<LoopBranch, RifError> {
let striped_path = utils::relativize_path(&entry_path)?;
if let Some(_) = self.black_list.get(&striped_path) {
if striped_path.is_dir() {
return Ok(LoopBranch::Exit);
}
else {
return Ok(LoopBranch::Continue);
}
}
if !self.relation.add_file(&striped_path)? { return Ok(LoopBranch::Continue); }
Ok(LoopBranch::Continue)
};
if file.is_dir() {
utils::walk_directory_recursive(file, &mut closure)?;
} else {
let file = utils::relativize_path(file)?;
self.relation.add_file(&file)?;
self.history.add_history(&file, message.unwrap_or(""))?;
}
Ok(())
}
fn add_directory(&mut self, dir: &Path) -> Result<(), RifError> {
let tracked = self.relation.files.keys().cloned().collect::<Vec<PathBuf>>();
let modified = self.relation.get_modified_files()?.clone();
let mut deleted = self.relation.get_deleted_files().clone();
let mut to_be_deleted = HashSet::new();
let mut to_be_added = HashSet::new();
let mut to_be_registerd = HashSet::new();
let mut closure = |entry_path : PathBuf| -> Result<LoopBranch, RifError> {
let striped_path = utils::relativize_path(&entry_path)?;
if let Some(_) = self.black_list.get(&striped_path) {
if striped_path.is_dir() {
return Ok(LoopBranch::Exit);
}
else {
return Ok(LoopBranch::Continue);
}
}
if striped_path.is_dir() {
deleted.retain(|path| {
let is_inside = path.starts_with(&entry_path);
if is_inside {
to_be_deleted.insert(path.to_owned());
}
!is_inside
});
return Ok(LoopBranch::Continue);
}
if modified.contains(&striped_path) {
to_be_added.insert(striped_path);
} else if !tracked.contains(&striped_path) {
to_be_registerd.insert(striped_path);
}
Ok(LoopBranch::Continue)
};
utils::walk_directory_recursive(dir, &mut closure)?;
self.meta.to_be_registerd.extend(to_be_registerd);
self.meta.to_be_added.extend(to_be_added);
self.meta.to_be_deleted.extend(to_be_deleted);
Ok(())
}
fn add_old_file(&mut self, file: &Path, force: bool) -> Result<(), RifError> {
if file.exists() {
self.meta.queue_added(file, force);
} else {
self.meta.queue_deleted(file);
}
Ok(())
}
}