use std::fs::File; use std::path::PathBuf; use std::io::{self, Lines, BufReader}; use std::collections::{HashSet, HashMap}; use crypto::digest::Digest; use crypto::sha1::Sha1; use colored::Colorize; use crate::utils::path; use crate::utils::read::{read_folder, read_lines}; use crate::store::object::tree; use crate::store::index; #[derive(PartialEq)] enum RemoveSide { Left, Both, Right, } #[derive(PartialEq, Debug, Clone)] pub enum State { Default, New, Renamed, Modified, Deleted, } // todo: relative path, filename, get modified // todo: not catch added empty folder pub fn status() { let (mut new_objs_hashes, mut del_objs_hashes) = get_diff(); // get copy, modified let mut staged_objs = get_staged(&mut new_objs_hashes, &mut del_objs_hashes); let mut objs: Vec = del_objs_hashes.iter().map(|x| { x.1.clone() }).collect(); for (_, elt) in new_objs_hashes { objs.push(elt.clone()); } dbg!(objs.clone()); dbg!(staged_objs.clone()); print_status(staged_objs, objs); } #[derive(Debug, Clone)] pub struct LocalObj { pub otype: String, pub name: String, pub path: PathBuf, pub state: State, } pub fn get_all_staged() -> Vec { let (mut new_objs_hashes, mut del_objs_hashes) = get_diff(); // get copy, modified let mut staged_objs = get_staged(&mut new_objs_hashes, &mut del_objs_hashes); staged_objs.clone() // todo opti getting staged and then finding differences ? } fn get_staged(new_objs_h: &mut HashMap, del_objs_h: &mut HashMap) -> Vec { let mut lines: Vec = vec![]; if let Ok(entries) = index::read_line() { for entry in entries { lines.push(entry.unwrap()); } } let mut hasher = Sha1::new(); let mut staged_objs: Vec = vec![]; let ref_p = path::repo_root(); for obj in lines { // hash the object hasher.input_str(&obj); let hash = hasher.result_str(); hasher.reset(); // find it on the list of hashes if new_objs_h.contains_key(&hash) { staged_objs.push(new_objs_h.get(&hash).unwrap().clone()); new_objs_h.remove(&hash); } else if del_objs_h.contains_key(&hash) { staged_objs.push(del_objs_h.get(&hash).unwrap().clone()); del_objs_h.remove(&hash); }else { let mut t_path = ref_p.clone(); t_path.push(PathBuf::from(obj.clone())); staged_objs.push(LocalObj { otype: get_otype(t_path.clone()), name: obj.to_string(), path: t_path.clone(), state: { if t_path.exists() { State::New } else { State::Deleted } }, }); } } staged_objs } fn get_diff() -> (HashMap, HashMap) { let mut hashes = HashMap::new(); let mut objs: Vec = vec![]; let root = path::repo_root(); let nextsync_path = path::nextsync(); let current_p = path::current().unwrap(); let dist_path = current_p.strip_prefix(root.clone()).unwrap().to_path_buf(); if let Ok(lines) = read_head(nextsync_path.clone()) { add_to_hashmap(lines, &mut hashes, dist_path.clone()); } if let Ok(entries) = read_folder(root.clone()) { add_to_vec(entries, &mut objs, root.clone()); } let mut obj_to_analyse = remove_duplicate(&mut hashes, &mut objs, RemoveSide::Both); while obj_to_analyse.len() > 0 { let cur_obj = obj_to_analyse.pop().unwrap(); let cur_path = PathBuf::from(&cur_obj); let obj_path = root.clone().join(cur_path.clone()); if obj_path.is_dir() { if let Some((_, lines)) = tree::read(cur_obj.clone()) { add_to_hashmap(lines, &mut hashes, cur_path.clone()); } if let Ok(entries) = read_folder(obj_path.clone()) { add_to_vec(entries, &mut objs, root.clone()); } let diff = remove_duplicate(&mut hashes, &mut objs, RemoveSide::Both); obj_to_analyse.append(&mut diff.clone()); } else { // todo look for change } } for (_, elt) in &mut hashes { elt.state = State::Deleted; } let mut new_objs_hashes = HashMap::new(); let mut hasher = Sha1::new(); for obj in objs { // hash the object hasher.input_str(&obj); let hash = hasher.result_str(); hasher.reset(); let p = PathBuf::from(obj.to_string()); // todo name new_objs_hashes.insert(String::from(hash), LocalObj { otype: get_otype(p.clone()), name: obj.to_string(), path: p, state: State::New }); } (new_objs_hashes, hashes) } fn get_otype(p: PathBuf) -> String { if p.is_dir() { String::from("tree") } else { String::from("blob") } } fn add_to_hashmap(lines: Lines>, hashes: &mut HashMap, path: PathBuf) { for line in lines { if let Ok(ip) = line { if ip.clone().len() > 5 { let (ftype, hash, name) = tree::parse_line(ip); let mut p = path.clone(); p.push(name.clone()); hashes.insert(String::from(hash), LocalObj{ otype: String::from(ftype), name: String::from(name), path: p, state: State::Default, }); } } } } fn add_to_vec(entries: Vec, objects: &mut Vec, root: PathBuf) { for entry in entries { if !is_nextsync_config(entry.clone()) { let object_path = entry.strip_prefix(root.clone()).unwrap(); objects.push(String::from(object_path.to_str().unwrap())); } } } fn print_status(staged_objs: Vec, objs: Vec) { if staged_objs.len() == 0 && objs.len() == 0 { println!("Nothing to push, working tree clean"); return; } // staged file if staged_objs.len() != 0 { println!("Changes to be pushed:"); println!(" (Use \"nextsync reset\" to unstage)"); for object in staged_objs { print_staged_object(object); } } // not staged files if objs.len() != 0 { println!("Changes not staged for push:"); println!(" (Use\"nextsync add ...\" to update what will be pushed)"); for object in objs { print_object(object); } } } fn print_object(obj: LocalObj) { if obj.state == State::Deleted { println!(" {} {}", String::from("deleted:").red(), obj.name.red()); } else if obj.state == State::Renamed { println!(" {} {}", String::from("renamed:").red(), obj.name.red()); } else if obj.state == State::New { println!(" {} {}", String::from("new:").red(), obj.name.red()); } else if obj.state == State::Modified { println!(" {} {}", String::from("modified:").red(), obj.name.red()); } } fn print_staged_object(obj: LocalObj) { if obj.state == State::Deleted { println!(" {} {}", String::from("deleted:").green(), obj.name.green()); } else if obj.state == State::Renamed { println!(" {} {}", String::from("renamed:").green(), obj.name.green()); } else if obj.state == State::New { println!(" {} {}", String::from("new:").green(), obj.name.green()); } else if obj.state == State::Modified { println!(" {} {}", String::from("modified:").green(), obj.name.green()); } } fn remove_duplicate(hashes: &mut HashMap, objects: &mut Vec, remove_option: RemoveSide) -> Vec { let mut hasher = Sha1::new(); let mut duplicate = vec![]; objects.retain(|obj| { // hash the object hasher.input_str(obj); let hash = hasher.result_str(); hasher.reset(); // find it on the list of hashes if hashes.contains_key(&hash) { duplicate.push(obj.clone()); // remove from hashes if remove_option == RemoveSide::Left || remove_option == RemoveSide::Both { hashes.remove(&hash); } // remove from objects remove_option != RemoveSide::Right && remove_option != RemoveSide::Both } else { true } }); duplicate } fn is_nextsync_config(path: PathBuf) -> bool { path.ends_with(".nextsync") } fn read_head(mut path: PathBuf) -> io::Result>> { path.push("HEAD"); read_lines(path) } #[cfg(test)] mod tests { use super::*; #[test] fn test_remove_duplicate() { let mut hasher = Sha1::new(); hasher.input_str("file1"); let hash1 = hasher.result_str(); hasher.reset(); let mut hasher = Sha1::new(); hasher.input_str("file2"); let hash2 = hasher.result_str(); hasher.reset(); let mut hasher = Sha1::new(); hasher.input_str("file4"); let hash4 = hasher.result_str(); hasher.reset(); let mut hashes = HashMap::new(); let default_obj = LocalObj { otype: String::from("tree"), name: String::from("test"), path: PathBuf::from(""), state: State::Default, }; hashes.insert(hash1.clone(), default_obj.clone()); hashes.insert(hash2.clone(), default_obj.clone()); hashes.insert(hash4.clone(), default_obj.clone()); let mut objects: Vec = vec![]; objects.push(String::from("file1")); objects.push(String::from("file2")); objects.push(String::from("file3")); remove_duplicate(&mut hashes, &mut objects, RemoveSide::Both); assert_eq!(hashes.contains_key(&hash4), true); assert_eq!(hashes.len(), 1); assert_eq!(objects, vec!["file3"]); } }