nextsync-rust/src/commands/status.rs

492 lines
15 KiB
Rust

use std::path::PathBuf;
use std::collections::HashMap;
use crypto::digest::Digest;
use crypto::sha1::Sha1;
use colored::Colorize;
use crate::utils::path::{self, path_buf_to_string};
use crate::store::object::blob::Blob;
use crate::store::object::object::Obj;
use crate::store::object::tree::Tree;
use crate::utils::read::read_folder;
use crate::store::index;
use crate::store::object::object::ObjMethods;
pub struct StatusArgs {
pub nostyle: bool,
}
#[derive(PartialEq)]
enum RemoveSide {
Left,
Both,
Right,
}
#[derive(PartialEq, Debug, Clone)]
pub enum State {
Default,
New,
Moved,
Copied,
Modified,
Deleted,
}
// todo: relative path, filename
// todo: not catch added empty folder
pub fn status(args: StatusArgs) {
let mut all_hashes = get_all_objs_hashes();
let staged_objs = get_staged(&mut all_hashes);
let objs: Vec<LocalObj> = all_hashes.iter().map(|x| {
x.1.clone()
}).collect();
if args.nostyle
{
print_status_nostyle(staged_objs, objs);
}
else
{
print_status(staged_objs, objs);
}
}
pub fn get_all_objs() -> Vec<LocalObj> {
let all_hashes = get_all_objs_hashes();
all_hashes.iter().map(|x| {
x.1.clone()
}).collect()
}
fn get_all_objs_hashes() -> HashMap<String, LocalObj> {
let (mut new_objs_hashes, mut del_objs_hashes, objs_modified) = get_diff();
let move_copy_hashes = get_move_copy_objs(&mut new_objs_hashes, &mut del_objs_hashes);
let mut hasher = Sha1::new();
let mut modified_objs_hashes = HashMap::new();
for obj in objs_modified {
hasher.input_str(&obj);
let hash = hasher.result_str();
hasher.reset();
modified_objs_hashes.insert(hash, LocalObj {
// todo otype
otype: get_otype(PathBuf::from(obj.clone())),
name: obj.clone().to_string(),
path: PathBuf::from(obj),
path_from: None,
state: State::Modified
});
}
let mut all_hashes = HashMap::new();
all_hashes.extend(move_copy_hashes);
all_hashes.extend(del_objs_hashes);
all_hashes.extend(new_objs_hashes);
all_hashes.extend(modified_objs_hashes);
all_hashes
}
fn should_retain(hasher: &mut Sha1, key: String, obj: LocalObj, move_copy_hashes: &mut HashMap<String, LocalObj>, del_objs_h: &mut HashMap<String, LocalObj>) -> bool {
// todo prevent copied or moved if file empty
// todo deal with directories
if obj.path.is_dir()
{
return true;
}
let mut blob = Blob::from_path(obj.path.clone());
let mut flag = true;
let identical_blobs = blob.get_all_identical_blobs();
// try to find an identical blob among the deleted files (=moved)
for obj_s in identical_blobs.clone() {
if !flag { break; }
hasher.input_str(&obj_s);
let hash = hasher.result_str();
hasher.reset();
if del_objs_h.contains_key(&hash) {
let mut new_move = obj.clone();
let deleted = del_objs_h.get(&hash).unwrap().clone();
del_objs_h.remove(&hash);
new_move.path_from = Some(deleted.path);
new_move.state = State::Moved;
move_copy_hashes.insert(key.clone(), new_move.clone());
flag = false;
}
}
// if did not find anything before try to find a file with the same content (=copy)
if flag {
if let Some(rel_s) = identical_blobs.first() {
let root = path::repo_root();
let rel_p = PathBuf::from(rel_s.clone());
let abs_p = root.join(rel_p.clone());
if abs_p.exists() {
let mut new_copy = obj.clone();
new_copy.path_from = Some(rel_p);
new_copy.state = State::Copied;
move_copy_hashes.insert(key, new_copy.clone());
flag = false;
}
}
}
flag
}
fn get_move_copy_objs(new_objs_h: &mut HashMap<String, LocalObj>, del_objs_h: &mut HashMap<String, LocalObj>) -> HashMap<String, LocalObj> {
let mut hasher = Sha1::new();
let mut move_copy_hashes = HashMap::new();
new_objs_h.retain(|key, obj| {
should_retain(&mut hasher, key.to_owned(), obj.clone(), &mut move_copy_hashes, del_objs_h)
});
move_copy_hashes
}
#[derive(Debug, Clone)]
pub struct LocalObj {
pub otype: String,
pub name: String,
pub path: PathBuf,
pub path_from: Option<PathBuf>, // origin path when state is move or copy
pub state: State,
}
pub fn get_all_staged() -> Vec<LocalObj> {
let mut staged_objs = vec![];
if let Ok(entries) = index::read_line() {
for line in entries {
let obj = Obj::from_path(line.unwrap()).get_local_obj();
if obj.state != State::Default {
staged_objs.push(obj);
}
}
}
staged_objs
}
fn get_staged(hashes: &mut HashMap<String, LocalObj>) -> Vec<LocalObj> {
let mut lines: Vec<String> = 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<LocalObj> = 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 hashes.contains_key(&hash) {
staged_objs.push(hashes.get(&hash).unwrap().clone());
hashes.remove(&hash);
}else {
let mut t_path = ref_p.clone();
let relative_p = PathBuf::from(obj.clone());
t_path.push(relative_p.clone());
staged_objs.push(LocalObj {
otype: get_otype(t_path.clone()),
name: obj.to_string(),
path: relative_p.clone(),
path_from: None,
state: {
if t_path.exists() {
State::New
} else {
State::Deleted
}
},
});
}
}
staged_objs
}
fn read_tree_to_hashmap(tree: &mut Tree, hashes: &mut HashMap<String, LocalObj>, path: PathBuf) {
while let Some(child) = tree.next() {
hashes.insert(String::from(child.get_hash_path()), child.get_local_obj());
};
}
fn get_diff() -> (HashMap<String, LocalObj>, HashMap<String, LocalObj>, Vec<String>) {
let mut hashes = HashMap::new();
let mut objs: Vec<String> = vec![];
let mut objs_modified: Vec<String> = vec![];
let root = path::repo_root();
let current_p = path::current().unwrap();
// todo use repo_root instead of current
let dist_path = current_p.strip_prefix(root.clone()).unwrap().to_path_buf();
read_tree_to_hashmap(&mut Tree::from_head(), &mut hashes, dist_path.clone());
//if let Ok(lines) = read_lines(head::path()) {
// 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() {
// read virtual tree
read_tree_to_hashmap(&mut Tree::from_path(cur_obj.clone()), &mut hashes, dist_path.clone());
//let mut tree = Tree::from_path(cur_obj.clone());
//if let Some(lines) = tree.get_children() {
//add_to_hashmap(lines, &mut hashes, cur_path.clone());
//}
// read physical tree
if let Ok(entries) = read_folder(obj_path.clone()) {
add_to_vec(entries, &mut objs, root.clone());
}
// remove duplicate
let diff = remove_duplicate(&mut hashes, &mut objs, RemoveSide::Both);
obj_to_analyse.append(&mut diff.clone());
} else {
if Blob::from_path(cur_path).has_changes() {
objs_modified.push(cur_obj);
}
}
}
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());
let abs_p = path::repo_root().join(p.clone());
// todo name
new_objs_hashes.insert(String::from(hash), LocalObj {
otype: get_otype(abs_p),
name: obj.to_string(),
path: p,
path_from: None,
state: State::New
});
}
(new_objs_hashes, hashes, objs_modified)
}
fn get_otype(p: PathBuf) -> String {
if p.is_dir() {
String::from("tree")
} else {
String::from("blob")
}
}
//fn add_to_hashmap(lines: Lines<BufReader<File>>, hashes: &mut HashMap<String, LocalObj>, 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,
// path_from: None,
// state: State::Default,
// });
// }
// }
// }
//}
fn add_to_vec(entries: Vec<PathBuf>, objects: &mut Vec<String>, root: PathBuf) {
for entry in entries {
if !path::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<LocalObj>, objs: Vec<LocalObj>) {
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 <file>...\" to update what will be pushed)");
for object in objs {
print_object(object);
}
}
}
fn print_status_nostyle(staged_objs: Vec<LocalObj>, objs: Vec<LocalObj>) {
// todo sort
if staged_objs.len() == 0 && objs.len() == 0 {
return;
}
for obj in staged_objs {
if obj.state == State::Deleted {
println!("deleted: {}", obj.name);
} else if obj.state == State::New {
println!("new: {}", obj.name);
} else if obj.state == State::Modified {
println!("modified: {}", obj.name);
} else if obj.state == State::Moved {
println!("moved: {} => {}", path_buf_to_string(obj.path_from.unwrap()), path_buf_to_string(obj.path));
} else if obj.state == State::Copied {
println!("copied: {} => {}", path_buf_to_string(obj.path_from.unwrap()), path_buf_to_string(obj.path));
}
}
}
fn print_object(obj: LocalObj) {
if obj.state == State::Deleted {
println!(" {} {}", String::from("deleted:").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());
} else if obj.state == State::Moved {
println!(" {} {} => {}", String::from("moved:").red(), path_buf_to_string(obj.path_from.unwrap()).red(), path_buf_to_string(obj.path).red());
} else if obj.state == State::Copied {
println!(" {} {} => {}", String::from("copied:").red(), path_buf_to_string(obj.path_from.unwrap()), path_buf_to_string(obj.path).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::New {
println!(" {} {}", String::from("new:").green(), obj.name.green());
} else if obj.state == State::Modified {
println!(" {} {}", String::from("modified:").green(), obj.name.green());
} else if obj.state == State::Moved {
println!(" {} {} => {}", String::from("moved:").green(), path_buf_to_string(obj.path_from.unwrap()).green(), path_buf_to_string(obj.path).green());
} else if obj.state == State::Copied {
println!(" {} {} => {}", String::from("copied:"), path_buf_to_string(obj.path_from.unwrap()).green(), path_buf_to_string(obj.path).green());
}
}
fn remove_duplicate(hashes: &mut HashMap<String, LocalObj>, objects: &mut Vec<String>, remove_option: RemoveSide) -> Vec<String> {
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
}
#[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(""),
path_from: None,
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<String> = 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"]);
}
}