Compare commits

...

7 Commits

Author SHA1 Message Date
grimhilt
06bb51476b fix(push): push folder and return error when tcp fail 2024-03-01 17:56:52 +01:00
grimhilt
d8b2116aeb feat(remote): list remote with verbose option 2024-03-01 15:35:38 +01:00
grimhilt
8ed86a05ea style(obj): minor fixes 2024-02-29 09:36:52 +01:00
grimhilt
7951ad0520 refactor(tree): create impl Tree 2024-02-25 17:34:16 +01:00
grimhilt
faf7341525 refactor(blob): use object trait to create blob 2024-02-24 18:52:00 +01:00
grimhilt
642c358737 feat(test): allow multiple tests 2024-02-22 14:00:13 +01:00
grimhilt
e67082b85a refactor(test): use subdir 2024-02-22 13:02:22 +01:00
29 changed files with 1170 additions and 695 deletions

View File

@ -12,4 +12,4 @@ timestamp2: timestamp of file locally to know when the file has changed on the s
folder_name timestamp
tree hash_path folder_name
blob hash_path file_name
```
```

View File

@ -12,7 +12,7 @@ use crate::global::global::{DIR_PATH, set_dir_path};
use crate::services::api::ApiError;
use crate::services::api_call::ApiCall;
use crate::services::req_props::{ReqProps, ObjProps};
use crate::store::object::{tree, blob::Blob};
use crate::store::object::{tree::Tree, blob::Blob};
use crate::commands::config;
use crate::commands::init;
@ -91,7 +91,7 @@ pub fn clone(args: CloneArgs) {
// add tree
let path_folder = p.strip_prefix(ref_path.clone()).unwrap();
let lastmodified = folder.lastmodified.unwrap().timestamp_millis();
if let Err(err) = tree::add(path_folder.to_path_buf(), &lastmodified.to_string(), false) {
if let Err(err) = Tree::from_path(path_folder.to_path_buf()).create(&lastmodified.to_string(), false) {
eprintln!("err: saving ref of {} ({})", path_folder.display(), err);
}
}
@ -107,7 +107,7 @@ fn save_blob(obj: ObjProps) {
let relative_s = &obj.clone().relative_s.unwrap();
let relative_p = PathBuf::from(&relative_s);
let lastmodified = obj.clone().lastmodified.unwrap().timestamp_millis();
if let Err(err) = Blob::new(relative_p).create(&lastmodified.to_string(), false) {
if let Err(err) = Blob::from_path(relative_p).create(&lastmodified.to_string(), false) {
eprintln!("err: saving ref of {} ({})", relative_s.clone(), err);
}
}

View File

@ -30,14 +30,13 @@ pub fn config_set(args: ConfigSetArgs) {
pub fn find_option_in_cat(category: &str, option: &str) -> Option<String> {
let mut root = path::nextsync();
root.push("config");
let mut config = path::nextsync();
config.push("config");
let mut in_target_category = false;
if let Ok(mut lines) = read::read_lines(root) {
if let Ok(mut lines) = read::read_lines(config) {
for line in lines {
if let Ok(line) = line {
let trimmed_line = line.trim();
@ -58,14 +57,14 @@ pub fn find_option_in_cat(category: &str, option: &str) -> Option<String> {
}
pub fn write_option_in_cat(category: &str, option: &str, value: &str) -> io::Result<()> {
let mut root = path::nextsync();
root.push("config");
let mut config = path::nextsync();
config.push("config");
let mut file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(&root)?;
.open(&config)?;
let mut in_target_category = false;
let mut option_found = false;
@ -129,8 +128,7 @@ pub fn write_option_in_cat(category: &str, option: &str, value: &str) -> io::Res
}
pub fn add_remote(name: &str, url: &str) -> io::Result<()> {
let mut root = path::nextsync();
root.push("config");
let mut config = path::config();
// check if there is already a remote with this name
if get_remote(name).is_some()
@ -144,7 +142,7 @@ pub fn add_remote(name: &str, url: &str) -> io::Result<()> {
.write(true)
.create(true)
.append(true)
.open(root)?;
.open(config)?;
writeln!(file, "[remote \"{}\"]", name)?;
writeln!(file, "\turl = {}", url)?;
@ -156,20 +154,55 @@ pub fn get_remote(name: &str) -> Option<String> {
find_option_in_cat(&format!("remote \"{}\"", name), "url")
}
/// return a vector of remote found in config file (e.g: ("origin", "https://example.com"))
pub fn get_all_remote() -> Vec<(String, String)> {
let config = path::config();
let mut remotes: Vec<(String, String)> = vec![];
let mut in_remote = false;
let mut remote_name = String::new();
if let Ok(lines) = read::read_lines(config) {
for line in lines {
if let Ok(line) = line {
let trimmed_line = line.trim();
if trimmed_line.starts_with("[remote ") {
in_remote = true;
remote_name = trimmed_line.strip_prefix("[remote \"").unwrap().strip_suffix("\"]").unwrap().to_string();
}
else if trimmed_line.starts_with('[')
{
in_remote = false;
}
else if in_remote {
let parts: Vec<&str> = trimmed_line.splitn(2, '=').collect();
if parts.len() == 2 {
remotes.push((remote_name.to_string(), parts[1].trim().to_string()))
}
}
}
}
}
remotes
}
pub fn get_core(name: &str) -> Option<String> {
find_option_in_cat("core", name)
}
pub fn add_core(name: &str, value: &str) -> io::Result<()> {
let mut root = path::nextsync();
root.push("config");
let mut config = path::nextsync();
config.push("config");
let mut file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.append(true)
.open(root)?;
.open(config)?;
writeln!(file, "[core]")?;
writeln!(file, "\t{} = {}", name, value)?;
@ -178,10 +211,9 @@ pub fn add_core(name: &str, value: &str) -> io::Result<()> {
}
pub fn get(var: &str) -> Option<String> {
let mut root = path::nextsync();
root.push("config");
let mut config = path::config();
if let Ok(lines) = read::read_lines(root) {
if let Ok(lines) = read::read_lines(config) {
for line in lines {
if let Ok(l) = line {
if l.starts_with(var) {

View File

@ -4,7 +4,7 @@ use std::fs::DirBuilder;
use crate::services::downloader::Downloader;
use crate::services::req_props::ObjProps;
use crate::store::object::blob::Blob;
use crate::store::object::tree;
use crate::store::object::tree::Tree;
use crate::utils::api::get_api_props;
use crate::utils::path;
use crate::commands::remote_diff::get_diff;
@ -29,7 +29,7 @@ pub fn pull() {
// add tree
let path_folder = p.strip_prefix(ref_p.clone()).unwrap();
let lastmodified = folder.lastmodified.unwrap().timestamp_millis();
if let Err(err) = tree::add(path_folder.to_path_buf(), &lastmodified.to_string(), false) {
if let Err(err) = Tree::from_path(path_folder.clone()).create(&lastmodified.to_string(), false) {
eprintln!("err: saving ref of {} ({})", path_folder.display(), err);
}
}
@ -50,7 +50,7 @@ fn update_blob(obj: ObjProps) {
let relative_p = PathBuf::from(&relative_s);
let lastmodified = obj.clone().lastmodified.unwrap().timestamp_millis();
// todo update function
if let Err(err) = Blob::new(relative_p).create(&lastmodified.to_string(), false) {
if let Err(err) = Blob::from_path(relative_p).create(&lastmodified.to_string(), false) {
eprintln!("err: saving ref of {} ({})", relative_s.clone(), err);
}
}

View File

@ -40,9 +40,11 @@ pub fn push() {
let mut whitelist: Option<PathBuf> = None;
for obj in staged_objs {
dbg!(obj.clone());
if obj.otype == String::from("tree") {
let push_factory = PushFactory.new_dir(obj.clone());
let res = push_factory.can_push(&mut whitelist);
dbg!(&res);
match res {
PushState::Valid => {
match push_factory.push() {

View File

@ -70,7 +70,7 @@ impl PushChange for Copied {
let lastmodified = prop.lastmodified.unwrap().timestamp_millis();
// create destination blob
if let Err(err) = Blob::new(obj.path.clone()).create(&lastmodified.to_string(), false) {
if let Err(err) = Blob::from_path(obj.path.clone()).create(&lastmodified.to_string(), false) {
eprintln!("err: creating ref of {}: {}", obj.name.clone(), err);
}

View File

@ -7,6 +7,7 @@ use crate::store::index;
use crate::store::object::blob::Blob;
use crate::commands::status::LocalObj;
use crate::commands::push::push_factory::{PushState, PushChange, PushFlowState};
use crate::store::object::object::ObjMethods;
pub struct Deleted {
pub obj: LocalObj
@ -43,7 +44,7 @@ impl PushChange for Deleted {
// update tree
// todo date
Blob::new(obj.path.clone()).rm()?;
Blob::from_path(obj.path.clone()).rm()?;
// remove index
index::rm_line(obj.path.to_str().unwrap())?;

View File

@ -68,7 +68,7 @@ impl PushChange for Modified {
let lastmodified = prop.lastmodified.unwrap().timestamp_millis();
// update blob
Blob::new(obj.path.clone()).update(&lastmodified.to_string())?;
Blob::from_path(obj.path.clone()).update(&lastmodified.to_string())?;
Ok(())
}

View File

@ -8,6 +8,7 @@ use crate::commands::status::LocalObj;
use crate::commands::push::push_factory::{PushState, PushChange, PushFlowState};
use crate::store::object::blob::Blob;
use crate::utils::path::path_buf_to_string;
use crate::store::object::object::ObjMethods;
pub struct Moved {
pub obj: LocalObj,
@ -70,10 +71,10 @@ impl PushChange for Moved {
let lastmodified = prop.lastmodified.unwrap().timestamp_millis();
// delete source and create destination blob
if let Err(err) = Blob::new(obj.path.clone()).create(&lastmodified.to_string(), false) {
if let Err(err) = Blob::from_path(obj.path.clone()).create(&lastmodified.to_string(), false) {
eprintln!("err: creating ref of {}: {}", obj.name.clone(), err);
}
if let Err(err) = Blob::new(obj.path_from.clone().unwrap()).rm() {
if let Err(err) = Blob::from_path(obj.path_from.clone().unwrap()).rm() {
eprintln!("err: removing ref of {}: {}", obj.name.clone(), err);
}

View File

@ -68,7 +68,7 @@ impl PushChange for New {
let lastmodified = prop.lastmodified.unwrap().timestamp_millis();
// create new blob
Blob::new(obj.path.clone()).create(&lastmodified.to_string(), false)?;
Blob::from_path(obj.path.clone()).create(&lastmodified.to_string(), false)?;
Ok(())
}

View File

@ -5,7 +5,7 @@ use crate::services::api_call::ApiCall;
use crate::services::req_props::ReqProps;
use crate::services::create_folder::CreateFolder;
use crate::store::index;
use crate::store::object::tree;
use crate::store::object::tree::Tree;
use crate::commands::status::LocalObj;
use crate::commands::push::push_factory::{PushState, PushChange, PushFlowState};
@ -75,7 +75,7 @@ impl PushChange for NewDir {
let lastmodified = prop.lastmodified.unwrap().timestamp_millis();
// update tree
tree::add(obj.path.clone(), &lastmodified.to_string(), true)?;
Tree::from_path(obj.path.clone()).create(&lastmodified.to_string(), true)?;
// remove index
index::rm_line(obj.path.to_str().unwrap())?;

View File

@ -1,3 +1,4 @@
use std::error::Error;
use std::path::PathBuf;
use std::io;
use crate::commands::status::{State, LocalObj};
@ -73,7 +74,7 @@ pub trait PushChange {
};
// check if remote is newest
let last_sync_ts = Blob::new(obj.path.clone())
let last_sync_ts = Blob::from_path(obj.path.clone())
.saved_remote_ts()
.parse::<i64>().unwrap();
let remote_ts = obj_data.lastmodified.unwrap().timestamp_millis();

View File

@ -4,9 +4,10 @@ use crate::services::api::ApiError;
use crate::services::api_call::ApiCall;
use crate::services::delete_path::DeletePath;
use crate::store::index;
use crate::store::object::tree;
use crate::store::object::tree::Tree;
use crate::commands::status::LocalObj;
use crate::commands::push::push_factory::{PushState, PushChange, PushFlowState};
use crate::store::object::object::ObjMethods;
pub struct RmDir {
pub obj: LocalObj
@ -49,7 +50,7 @@ impl PushChange for RmDir {
// update tree
// todo update date
tree::rm(obj.path.clone())?;
Tree::from_path(obj.path.clone()).rm()?;
// remove index
index::rm_line(obj.path.to_str().unwrap())?;

View File

@ -2,6 +2,8 @@ use clap::Values;
use crate::commands::config;
use super::config::get_all_remote;
pub struct RemoteArgs<'a> {
pub name: Option<Values<'a>>,
pub url: Option<Values<'a>>,
@ -19,3 +21,17 @@ pub fn remote_add(args: RemoteArgs) {
let _ = config::add_remote(name, url);
}
pub fn remote_list(verbose: bool) {
let remotes = get_all_remote();
for remote in remotes {
if verbose
{
println!("{} {}", remote.0, remote.1);
}
else
{
println!("{}", remote.0);
}
}
}

View File

@ -1,16 +1,15 @@
use std::fs::File;
use std::path::PathBuf;
use std::io::{Lines, BufReader};
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::head;
use crate::store::object::blob::Blob;
use crate::utils::read::{read_folder, read_lines};
use crate::store::object::tree;
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,
@ -97,7 +96,7 @@ fn should_retain(hasher: &mut Sha1, key: String, obj: LocalObj, move_copy_hashes
{
return true;
}
let mut blob = Blob::new(obj.path.clone());
let mut blob = Blob::from_path(obj.path.clone());
let mut flag = true;
let identical_blobs = blob.get_all_identical_blobs();
@ -161,20 +160,15 @@ pub struct LocalObj {
}
pub fn get_all_staged() -> 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 staged_objs = vec![];
for line in lines {
let obj = Blob::new(line).get_local_obj();
if obj.state != State::Default {
staged_objs.push(obj);
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);
}
}
}
@ -227,20 +221,27 @@ fn get_staged(hashes: &mut HashMap<String, LocalObj>) -> Vec<LocalObj> {
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();
if let Ok(lines) = read_lines(head::path()) {
add_to_hashmap(lines, &mut hashes, dist_path.clone());
}
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());
@ -255,22 +256,27 @@ fn get_diff() -> (HashMap<String, LocalObj>, HashMap<String, LocalObj>, Vec<Stri
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());
}
// 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::new(cur_path).has_change() {
if Blob::from_path(cur_path).has_changes() {
objs_modified.push(cur_obj);
}
}
}
for (_, elt) in &mut hashes {
@ -308,24 +314,24 @@ fn get_otype(p: PathBuf) -> String {
}
}
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_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 {

View File

@ -1,9 +1,10 @@
use std::error::Error;
use lazy_static::lazy_static;
use std::sync::Mutex;
use reqwest::Client;
use reqwest::RequestBuilder;
use reqwest::multipart::Form;
use reqwest::{Response, Error, Method};
use reqwest::{Response, Method};
use reqwest::header::{HeaderValue, CONTENT_TYPE, HeaderMap, IntoHeaderName};
use crate::utils::api::ApiProps;
use crate::commands::config;
@ -184,7 +185,7 @@ impl ApiBuilder {
self.set_request_manager();
}
let res = tokio::runtime::Runtime::new().unwrap().block_on(async {
let res_req = tokio::runtime::Runtime::new().unwrap().block_on(async {
match self.request.take() {
None => {
eprintln!("fatal: incorrect request");
@ -199,7 +200,16 @@ impl ApiBuilder {
}
},
}
}).map_err(ApiError::RequestError)?;
});
// handle request error
let res = match res_req {
Err(err) => {
eprintln!("fatal: {}", err.source().unwrap());
std::process::exit(1);
},
Ok(res) => res,
};
if res.status().is_success() {
if need_text {
@ -215,7 +225,7 @@ impl ApiBuilder {
}
}
pub async fn old_send(&mut self) -> Result<Response, Error> {
pub async fn old_send(&mut self) -> Result<Response, reqwest::Error> {
let mut request_manager = get_request_manager().lock().unwrap();
let request_manager = request_manager.as_mut().unwrap();
if !self.host.is_none()
@ -237,9 +247,9 @@ impl ApiBuilder {
Some(req) => {
if let Some(headers) = &self.headers {
req.headers(headers.clone())
.send().await.map_err(Error::from)
.send().await.map_err(reqwest::Error::from)
} else {
req.send().await.map_err(Error::from)
req.send().await.map_err(reqwest::Error::from)
}
},
}

View File

@ -9,6 +9,7 @@ use crate::utils::{read, path};
pub mod tree;
pub mod blob;
pub mod object;
pub struct Object {
path: PathBuf,

View File

@ -4,76 +4,202 @@ use std::io::Write;
use std::fs::OpenOptions;
use std::path::PathBuf;
use std::time::SystemTime;
use crypto::sha1::Sha1;
use crypto::digest::Digest;
use crate::commands::status::{LocalObj, State};
use crate::commands::status::{State};
use crate::utils::into::IntoPathBuf;
use crate::utils::path::path_buf_to_string;
use crate::utils::{path, read};
use crate::store::head;
use crate::store::object::{update_dates, add_node, rm_node};
use crate::store::object::object::ObjMethods;
use crate::store::object::object::Obj;
const HASH_EMPTY: &str = "d41d8cd98f00b204e9800998ecf8427e";
pub struct Blob {
r_path: PathBuf, // relative path
a_path: PathBuf, // absolute path
hash: String, // hash of relative path
file_hash: Option<String>,
obj_p: PathBuf, // path of the object file
data: Vec<String>, // content of the blob
pub obj: Obj,
data: Vec<String>, // content of the ref file
file_hash: Option<String>, // hash of the file's content
}
//pub struct Blob {
// r_path: PathBuf, // relative path
// a_path: PathBuf, // absolute path
// hash: String, // hash of relative path
// file_hash: Option<String>,
// obj_p: PathBuf, // path of the object file
// data: Vec<String>, // content of the blob
//}
impl Blob {
pub fn new<S>(r_path: S) -> Blob where S: IntoPathBuf {
let r_path = r_path.into();
if r_path.is_dir() {
eprintln!("{}: is a directory not a blob", r_path.display());
}
let mut hasher = Sha1::new();
hasher.input_str(r_path.to_str().unwrap());
let hash = hasher.result_str();
let (dir, res) = hash.split_at(2);
let mut obj_p = path::objects();
obj_p.push(dir);
obj_p.push(res);
let root = path::repo_root();
let a_path = root.join(r_path.clone());
Blob {
r_path,
a_path,
hash,
file_hash: None,
obj_p,
pub fn new(obj: Obj) -> Self {
Self {
obj,
data: vec![],
}
file_hash: None,
}
}
fn get_line_filename(&mut self) -> (String, String) {
let file_name = self.r_path.file_name().unwrap().to_str().unwrap().to_owned();
let mut line = String::from("blob");
line.push_str(" ");
line.push_str(&self.hash);
line.push_str(" ");
line.push_str(&file_name);
(line, file_name)
pub fn from_path<S>(r_path: S) -> Blob where S: IntoPathBuf {
let r_path = r_path.into();
Self {
obj: Obj::from_path(r_path),
data: vec![],
file_hash: None,
}
}
fn get_file_hash(&mut self) -> String {
if self.file_hash.is_none() {
let bytes = std::fs::read(self.a_path.clone()).unwrap();
let bytes = std::fs::read(self.get_file_path()).unwrap();
let hash = md5::compute(&bytes);
self.file_hash = Some(format!("{:x}", hash))
}
self.file_hash.clone().unwrap()
}
fn create_blob_ref(&mut self, file_name: String, ts_remote: &str) -> io::Result<()> {
let metadata = fs::metadata(self.a_path.clone())?;
/// read line of blob to get all informations and store them in self.data
pub fn read_data(&mut self) {
if self.data.len() == 0 {
if let Ok(mut file) = File::open(self.get_obj_path()) {
let mut buffer = String::new();
let _ = file.read_to_string(&mut buffer);
let data = buffer.rsplit(' ').collect::<Vec<_>>();
for e in data {
self.data.push(String::from(e));
}
self.data.reverse();
// remove \n of last element
if let Some(last) = self.data.last_mut() {
if last.ends_with("\n") {
last.pop();
}
}
}
}
}
fn get_data_index(&mut self, index: usize) -> String {
self.read_data();
if self.data.len() >= index + 1 {
self.data[index].clone()
} else {
String::new()
}
}
fn saved_filename(&mut self) -> String {
self.get_data_index(0)
}
pub fn saved_remote_ts(&mut self) -> String {
self.get_data_index(1)
}
fn saved_local_size(&mut self) -> String {
self.get_data_index(2)
}
fn saved_local_ts(&mut self) -> u64 {
match self.get_data_index(3).as_str() {
"" => 0,
str => str.parse::<u64>().unwrap()
}
}
fn saved_hash(&mut self) -> String {
self.get_data_index(4)
}
fn has_same_size(&mut self) -> bool {
let metadata = match fs::metadata(self.get_file_path()) {
Ok(m) => m,
Err(_) => return true,
};
if self.saved_local_size() == String::new() { return true; }
metadata.len().to_string() == self.saved_local_size()
}
fn is_newer(&mut self) -> bool {
let metadata = match fs::metadata(self.get_file_path()) {
Ok(m) => m,
Err(_) => return true,
};
let secs = metadata
.modified()
.unwrap()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs();
if self.saved_local_ts() == 0 { return true; }
secs > self.saved_local_ts()
}
fn has_same_hash(&mut self) -> bool {
if self.saved_hash() == String::new() { return false; }
let file_hash = self.get_file_hash().clone();
self.saved_hash() == file_hash
}
pub fn has_changes(&mut self) -> bool {
!self.has_same_size() || (self.is_newer() && !self.has_same_hash())
}
pub fn get_all_identical_blobs(&mut self) -> Vec<String> {
// an empty file is a new file not the copy of another empty file
if self.get_file_hash() == HASH_EMPTY {
return vec![];
}
let refs_p = self.get_obj_path();
let mut blobs: Vec<String> = vec![];
if let Ok(lines) = read::read_lines(refs_p) {
for line in lines {
if let Ok(l) = line {
blobs.push(l.clone());
}
}
}
blobs
}
pub fn status(&mut self, path_from: &mut Option<PathBuf>) -> State {
let has_obj_ref = self.get_obj_path().exists();
let blob_exists = self.get_file_path().exists();
if has_obj_ref && !blob_exists {
State::Deleted
} else if !has_obj_ref && blob_exists {
let identical_blobs = self.get_all_identical_blobs();
if identical_blobs.len() != 0 {
let identical_blob = Blob::from_path(identical_blobs[0].clone()).get_local_obj();
if identical_blob.state == State::Deleted {
*path_from = Some(identical_blob.path);
State::Moved
} else if identical_blob.state == State::Default {
*path_from = Some(identical_blob.path);
State::Copied
} else {
State::New
}
} else {
State::New
}
} else if !has_obj_ref && !blob_exists {
State::Default
} else if self.has_changes() {
State::Modified
} else {
State::Default
}
}
fn create_blob_ref(&mut self, ts_remote: &str) -> io::Result<()> {
let metadata = fs::metadata(self.get_file_path())?;
let secs = metadata
.modified()
.unwrap()
@ -81,30 +207,28 @@ impl Blob {
.unwrap()
.as_secs();
let mut content = file_name.clone();
content.push_str(" ");
content.push_str(ts_remote);
content.push_str(" ");
content.push_str(&metadata.len().to_string());
content.push_str(" ");
content.push_str(&secs.to_string());
content.push_str(" ");
content.push_str(&self.get_file_hash());
// build line with all needed properties
let content = format!("{} {} {} {} {}",
self.get_name(),
ts_remote,
metadata.len().to_string(),
secs.to_string(),
self.get_file_hash());
let binding = self.obj_p.clone();
let child = binding.file_name();
self.obj_p.pop();
if !self.obj_p.clone().exists() {
fs::create_dir_all(self.obj_p.clone())?;
// create parent dir if needed
let mut obj_path = self.get_obj_path();
obj_path.pop();
if !obj_path.exists() {
fs::create_dir_all(obj_path)?;
}
self.obj_p.push(child.unwrap().to_str().unwrap());
// open ref file
let mut file = OpenOptions::new()
.create_new(true)
.write(true)
.open(self.obj_p.clone())?;
.open(self.get_obj_path())?;
writeln!(file, "{}", &content)?;
writeln!(file, "{}", content)?;
Ok(())
}
@ -134,261 +258,74 @@ impl Blob {
.open(refs_p)?;
// todo deal with duplicate content
writeln!(file, "{}", self.r_path.clone().to_str().unwrap())?;
writeln!(file, "{}", self.get_relative_file_path().to_str().unwrap())?;
Ok(())
}
pub fn get_all_identical_blobs(&mut self) -> Vec<String> {
// an empty file is a new file not the copy of another empty file
if self.get_file_hash() == HASH_EMPTY {
return vec![];
}
let refs_p = self.get_file_ref();
let mut blobs: Vec<String> = vec![];
if let Ok(lines) = read::read_lines(refs_p) {
for line in lines {
if let Ok(l) = line {
blobs.push(l.clone());
}
}
}
blobs
}
pub fn create(&mut self, ts_remote: &str, up_parent: bool) -> io::Result<()> {
let (line, file_name) = self.get_line_filename();
// add blob reference to parent
if self.r_path.iter().count() == 1 {
head::add_line(line)?;
} else {
add_node(self.r_path.parent().unwrap(), &line)?;
}
self.add_ref_to_parent();
if let Err(err) = self.create_blob_ref(file_name.clone(), ts_remote.clone()) {
eprintln!("err: saving blob ref of {}: {}", self.r_path.clone().display(), err);
if let Err(err) = self.create_blob_ref(ts_remote.clone()) {
eprintln!("err: saving blob ref of {}: {}", self.get_relative_file_path().display(), err);
}
if let Err(err) = self.create_hash_ref() {
eprintln!("err: saving hash ref of {}: {}", self.r_path.clone().display(), err);
eprintln!("err: saving hash ref of {}: {}", self.get_relative_file_path().display(), err);
}
// update date for all parent
if up_parent {
if let Err(err) = update_dates(self.r_path.clone(), ts_remote) {
eprintln!("err: updating parent date of {}: {}", self.r_path.clone().display(), err);
if let Err(err) = update_dates(self.get_relative_file_path(), ts_remote) {
eprintln!("err: updating parent date of {}: {}", self.get_relative_file_path().display(), err);
}
}
Ok(())
}
pub fn rm(&mut self) -> io::Result<()> {
let (line, _) = self.get_line_filename();
// remove blob reference to parent
if self.r_path.iter().count() == 1 {
head::rm_line(&line)?;
} else {
rm_node(self.r_path.parent().unwrap(), &line)?;
}
// remove blob object
fs::remove_file(self.obj_p.clone())?;
Ok(())
}
pub fn update(&mut self, ts_remote: &str) -> io::Result<()> {
// remove old hash ref
let mut refs_p = path::refs();
let binding = self.saved_hash();
let (dir, res) = binding.split_at(2);
refs_p.push(dir);
refs_p.push(res);
if let Err(err) = fs::remove_file(refs_p) {
eprintln!("err: removing hash ref of {}: {}", self.r_path.clone().display(), err);
}
// creating new hash ref
if let Err(err) = self.create_hash_ref() {
eprintln!("err: saving hash ref of {}: {}", self.r_path.clone().display(), err);
}
// updating content of blob's ref
let metadata = fs::metadata(self.a_path.clone())?;
let secs = metadata
.modified()
.unwrap()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs();
let mut content = self.saved_filename();
content.push_str(" ");
content.push_str(ts_remote);
content.push_str(" ");
content.push_str(&metadata.len().to_string());
content.push_str(" ");
content.push_str(&secs.to_string());
content.push_str(" ");
content.push_str(&self.get_file_hash());
let mut file = OpenOptions::new()
.write(true)
.open(self.obj_p.clone())?;
writeln!(file, "{}", &content)?;
// // remove old hash ref
// let mut refs_p = path::refs();
// let binding = self.saved_hash();
// let (dir, res) = binding.split_at(2);
// refs_p.push(dir);
// refs_p.push(res);
// if let Err(err) = fs::remove_file(refs_p) {
// eprintln!("err: removing hash ref of {}: {}", self.r_path.clone().display(), err);
// }
//
// // creating new hash ref
// if let Err(err) = self.create_hash_ref() {
// eprintln!("err: saving hash ref of {}: {}", self.r_path.clone().display(), err);
// }
//
// // updating content of blob's ref
// let metadata = fs::metadata(self.a_path.clone())?;
// let secs = metadata
// .modified()
// .unwrap()
// .duration_since(SystemTime::UNIX_EPOCH)
// .unwrap()
// .as_secs();
//
// let mut content = self.saved_filename();
// content.push_str(" ");
// content.push_str(ts_remote);
// content.push_str(" ");
// content.push_str(&metadata.len().to_string());
// content.push_str(" ");
// content.push_str(&secs.to_string());
// content.push_str(" ");
// content.push_str(&self.get_file_hash());
//
// let mut file = OpenOptions::new()
// .write(true)
// .open(self.obj_p.clone())?;
//
// writeln!(file, "{}", &content)?;
Ok(())
}
pub fn read_data(&mut self) {
if self.data.len() == 0 {
if let Ok(mut file) = File::open(self.obj_p.clone()) {
let mut buffer = String::new();
let _ = file.read_to_string(&mut buffer);
let data = buffer.rsplit(' ').collect::<Vec<_>>();
for e in data {
self.data.push(String::from(e));
}
self.data.reverse();
// remove \n of last element
if let Some(last) = self.data.last_mut() {
if last.ends_with("\n") {
last.pop();
}
}
}
}
}
fn saved_filename(&mut self) -> String {
self.read_data();
if self.data.len() >= 1 {
self.data[0].clone()
} else {
String::new()
}
}
pub fn saved_remote_ts(&mut self) -> String {
self.read_data();
if self.data.len() >= 2 {
self.data[1].clone()
} else {
String::new()
}
}
fn saved_local_size(&mut self) -> String {
self.read_data();
if self.data.len() >= 3 {
self.data[2].clone()
} else {
String::new()
}
}
fn saved_local_ts(&mut self) -> u64 {
self.read_data();
if self.data.len() >= 4 {
self.data[3].parse::<u64>().unwrap()
} else {
0
}
}
fn saved_hash(&mut self) -> String {
self.read_data();
if self.data.len() >= 5 {
self.data[4].clone()
} else {
String::new()
}
}
fn has_same_size(&mut self) -> bool {
let metadata = match fs::metadata(self.a_path.clone()) {
Ok(m) => m,
Err(_) => return true,
};
if self.saved_local_size() == String::new() { return true; }
metadata.len().to_string() == self.saved_local_size()
}
fn is_newer(&mut self) -> bool {
let metadata = match fs::metadata(self.a_path.clone()) {
Ok(m) => m,
Err(_) => return true,
};
let secs = metadata
.modified()
.unwrap()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs();
if self.saved_local_ts() == 0 { return true; }
secs > self.saved_local_ts()
}
fn has_same_hash(&mut self) -> bool {
if self.saved_hash() == String::new() { return false; }
let file_hash = self.get_file_hash().clone();
self.saved_hash() == file_hash
}
pub fn has_change(&mut self) -> bool {
!self.has_same_size() || (self.is_newer() && !self.has_same_hash())
}
pub fn status(&mut self, path_from: &mut Option<PathBuf>) -> State {
let has_obj_ref = self.obj_p.clone().exists();
let blob_exists = self.a_path.clone().exists();
if has_obj_ref && !blob_exists {
State::Deleted
} else if !has_obj_ref && blob_exists {
let identical_blobs = self.get_all_identical_blobs();
if identical_blobs.len() != 0 {
let identical_blob = Blob::new(identical_blobs[0].clone())
.get_local_obj();
if identical_blob.state == State::Deleted {
*path_from = Some(identical_blob.path);
State::Moved
} else if identical_blob.state == State::Default {
*path_from = Some(identical_blob.path);
State::Copied
} else {
State::New
}
} else {
State::New
}
} else if !has_obj_ref && !blob_exists {
State::Default
} else if self.has_change() {
State::Modified
} else {
State::Default
}
}
pub fn get_local_obj(&mut self) -> LocalObj {
let mut path_from = None;
let state = self.status(&mut path_from);
LocalObj {
otype: String::from("blob"),
name: path_buf_to_string(self.r_path.clone()),
path: self.r_path.clone(),
path_from,
state
}
}
}

349
src/store/object/object.rs Normal file
View File

@ -0,0 +1,349 @@
use std::io;
use std::fs;
use std::path::PathBuf;
use crate::utils::path;
use crate::store::head;
use crate::store::object::{add_node, rm_node};
use crypto::sha1::Sha1;
use crypto::digest::Digest;
use crate::utils::into::IntoPathBuf;
use crate::store::object::{blob::Blob, tree::Tree};
use crate::commands::status::{State, LocalObj};
#[derive(Clone, Copy)]
enum ObjType {
TREE,
BLOB,
DEFAULT
}
pub trait ObjMethods {
fn get_type(&self) -> ObjType;
fn get_obj_path(&self) -> PathBuf;
fn get_file_path(&self) -> PathBuf;
fn get_relative_file_path(&self) -> PathBuf;
fn get_name(&self) -> String;
fn get_hash_path(&self) -> String;
fn get_local_obj(&self) -> LocalObj;
fn get_line(&self) -> String;
fn add_ref_to_parent(&self) -> io::Result<()>;
fn rm(&mut self) -> io::Result<()>;
fn rm_node(&mut self) -> io::Result<()>;
fn rm_node_down(&mut self) -> io::Result<()>;
}
pub struct Obj {
name: String,
obj_path: PathBuf,
obj_type: ObjType,
file_path: PathBuf, // file here is used as both file and directory
relative_file_path: PathBuf,
hash_path: String, // hash of the relative path of the file
}
impl ObjMethods for Obj {
fn get_type(&self) -> ObjType {
self.obj_type
}
fn get_obj_path(&self) -> PathBuf {
self.obj_path.clone()
}
fn get_file_path(&self) -> PathBuf {
self.file_path.clone()
}
fn get_relative_file_path(&self) -> PathBuf {
self.relative_file_path.clone()
}
fn get_local_obj(&self) -> LocalObj {
LocalObj {
otype: match self.obj_type {
ObjType::BLOB => String::from("blob"),
ObjType::TREE => String::from("tree"),
ObjType::DEFAULT => String::from("default"),
},
name: self.get_name(),
path: self.get_file_path(),
path_from: None,
state: State::New
}
}
fn get_name(&self) -> String {
self.name.clone()
}
fn get_hash_path(&self) -> String {
self.hash_path.clone()
}
// build line for parent reference
fn get_line(&self) -> String {
format!("tree {} {}", self.get_hash_path(), self.get_name())
}
fn add_ref_to_parent(&self) -> io::Result<()> {
let line = self.get_line();
if self.get_relative_file_path().iter().count() == 1 {
head::add_line(line)?;
} else {
add_node(self.get_relative_file_path().parent().unwrap(), &line)?;
}
Ok(())
}
fn rm_node(&mut self) -> io::Result<()> {
// remove self object and children object
self.rm_node_down();
// remove parent reference to self
let line = self.get_line();
if self.get_relative_file_path().iter().count() == 1 {
head::rm_line(&line)?;
} else {
rm_node(self.get_relative_file_path().parent().unwrap(), &line)?;
}
Ok(())
}
fn rm_node_down(&mut self) -> io::Result<()> {
eprintln!("rm_node_down: tried to do this on Obj");
Ok(())
}
fn rm(&mut self) -> io::Result<()> {
eprintln!("rm: tried to do this on Obj");
Ok(())
}
}
impl ObjMethods for Blob {
fn get_type(&self) -> ObjType {
self.obj.get_type()
}
fn get_obj_path(&self) -> PathBuf {
self.obj.get_obj_path()
}
fn get_file_path(&self) -> PathBuf {
self.obj.get_file_path()
}
fn get_relative_file_path(&self) -> PathBuf {
self.obj.get_relative_file_path()
}
fn get_local_obj(&self) -> LocalObj {
self.obj.get_local_obj()
}
fn get_name(&self) -> String {
self.obj.get_name()
}
fn get_hash_path(&self) -> String {
self.obj.get_hash_path()
}
fn get_line(&self) -> String {
self.obj.get_line()
}
fn add_ref_to_parent(&self) -> io::Result<()> {
self.obj.add_ref_to_parent()
}
fn rm_node(&mut self) -> io::Result<()> {
self.obj.rm_node()
}
fn rm_node_down(&mut self) -> io::Result<()> {
// remove reference to self
fs::remove_file(self.get_obj_path())?;
Ok(())
}
fn rm(&mut self) -> io::Result<()> {
// remove all references, including children's one
self.rm_node()?;
// remove file
fs::remove_file(self.get_file_path())?;
Ok(())
}
}
impl ObjMethods for Tree {
fn get_type(&self) -> ObjType {
self.obj.get_type()
}
fn get_obj_path(&self) -> PathBuf {
self.obj.get_obj_path()
}
fn get_file_path(&self) -> PathBuf {
self.obj.get_file_path()
}
fn get_relative_file_path(&self) -> PathBuf {
self.obj.get_relative_file_path()
}
fn get_local_obj(&self) -> LocalObj {
self.obj.get_local_obj()
}
fn get_name(&self) -> String {
self.obj.get_name()
}
fn get_hash_path(&self) -> String {
self.obj.get_hash_path()
}
fn get_line(&self) -> String {
self.obj.get_line()
}
fn add_ref_to_parent(&self) -> io::Result<()> {
self.obj.add_ref_to_parent()
}
fn rm_node(&mut self) -> io::Result<()> {
self.obj.rm_node()
}
/// remove objects and children but not parent reference to self
fn rm_node_down(&mut self) -> io::Result<()> {
// remove children
while let Some(mut child) = self.next() {
match child.get_type() {
ObjType::TREE => child.rm_node_down(),
ObjType::BLOB => child.rm_node_down(),
_ => Ok(())
}?;
};
// remove reference to self
fs::remove_file(self.get_obj_path())?;
Ok(())
}
fn rm(&mut self) -> io::Result<()> {
// remove all references, including children's one
self.rm_node()?;
// remove directory and all subfiles
fs::remove_dir_all(self.get_file_path())?;
Ok(())
}
}
impl Obj {
fn new() -> Self {
Obj {
name: String::new(),
obj_path: PathBuf::new(),
file_path: PathBuf::new(),
obj_type: ObjType::DEFAULT,
hash_path: String::new(),
relative_file_path: PathBuf::new()
}
}
pub fn from_path<S>(path: S) -> Obj where S: IntoPathBuf {
let path = path.into();
let mut hasher = Sha1::new();
hasher.input_str(path.to_str().unwrap());
let hash = hasher.result_str();
let (dir, res) = hash.split_at(2);
let mut obj_path = path::objects();
obj_path.push(dir);
obj_path.push(res);
let root = path::repo_root();
let abs_path = root.join(path.clone());
Obj {
name: match abs_path.file_name() {
None => String::new(),
Some(name) => name.to_str().unwrap().to_owned()
},
obj_path,
obj_type: match path.exists() {
true => match path.is_dir() {
true => ObjType::TREE,
false => ObjType::BLOB
},
false => ObjType::DEFAULT
},
file_path: abs_path,
relative_file_path: path,
hash_path: hash,
}
}
/// load from the information line stored in the object
pub fn from_line(line: String, base_dir: Option<PathBuf>) -> Box<dyn ObjMethods> {
let mut split = line.rsplit(' ');
if split.clone().count() != 3 {
eprintln!("fatal: invalid object(s)");
std::process::exit(1);
}
let name = split.next().unwrap();
let hash_path = split.next().unwrap();
let obj_type = split.next().unwrap();
let (dir, res) = hash_path.split_at(2);
let mut obj_path = path::objects();
obj_path.push(dir);
obj_path.push(res);
let path = match base_dir {
Some(dir) => dir.join(name),
None => PathBuf::from(name),
};
let root = path::repo_root();
let abs_path = root.join(path.clone());
let obj = Obj {
name: String::from(name),
obj_path,
obj_type: match obj_type {
"tree" => ObjType::TREE,
"blob" => ObjType::BLOB,
_ => ObjType::DEFAULT
},
file_path: abs_path,
relative_file_path: path,
hash_path: String::from(hash_path),
};
match obj.obj_type {
ObjType::TREE => Box::new(Tree::new(obj)),
ObjType::BLOB => Box::new(Blob::new(obj)),
ObjType::DEFAULT => Box::new(Tree::new(obj))
}
}
pub fn from_head() -> Self {
Obj {
name: String::new(),
obj_path: head::path(),
obj_type: ObjType::TREE,
file_path: PathBuf::new(),
relative_file_path: PathBuf::new(),
hash_path: String::new(),
}
}
}

View File

@ -1,106 +1,101 @@
use std::fs::File;
use std::io;
use crate::utils::into::IntoPathBuf;
use crate::store::object::object::Obj;
use std::path::PathBuf;
use crate::utils::path::path_buf_to_string;
use crate::utils::{read, path};
use crate::store::head;
use crate::store::object::{self, update_dates, parse_path, hash_obj, add_node, create_obj};
use crate::store::object::object::ObjMethods;
use std::fs::{self, File, OpenOptions};
use std::io::{self, BufRead, BufReader, Write, Lines};
pub fn add(path: PathBuf, date: &str, up_parent: bool) -> io::Result<()> {
let (line, hash, name) = parse_path(path.clone(), false);
// add tree reference to parent
if path.iter().count() == 1 {
head::add_line(line)?;
} else {
add_node(path.parent().unwrap(), &line)?;
}
// create tree object
let mut content = name;
content.push_str(" ");
content.push_str(date);
create_obj(hash, &content)?;
// update date for all parent
if up_parent {
update_dates(path, date)?;
}
Ok(())
pub struct Tree {
pub obj: Obj,
pub buf_reader: Option<BufReader<File>>,
is_head: bool,
}
pub fn rm(path: PathBuf) -> io::Result<()> {
let (_, lines) = read(path_buf_to_string(path.to_path_buf())).unwrap();
for line in lines {
let (ftype, hash, _) = parse_line(line.unwrap());
if ftype == String::from("blob") {
object::rm(&hash)?;
} else {
rm_hash(hash)?;
impl Tree {
pub fn new(obj: Obj) -> Self {
Tree {
obj,
buf_reader: None,
is_head: false,
}
}
Ok(())
}
fn rm_hash(hash: String) -> io::Result<()> {
let mut obj_p = path::objects();
let (dir, res) = hash.split_at(2);
obj_p.push(dir);
obj_p.push(res);
pub fn from_head() -> Self {
Tree {
obj: Obj::from_head(),
buf_reader: None,
is_head: true,
}
}
match read::read_lines(obj_p) {
Ok(mut reader) => {
reader.next();
for line in reader {
let (ftype, hash, _) = parse_line(line.unwrap());
if ftype == String::from("blob") {
object::rm(&hash)?;
} else {
rm_hash(hash)?;
pub fn from_path<S>(r_path: S) -> Tree where S: IntoPathBuf {
Tree {
obj: Obj::from_path(r_path.into()),
buf_reader: None,
is_head: false,
}
}
pub fn read(&mut self) {
if self.buf_reader.is_none() {
if let Ok(file) = File::open(self.get_obj_path()) {
self.buf_reader = Some(BufReader::new(file));
// skip first line if is head
if !self.is_head {
self.next();
}
}
},
Err(err) => {
eprintln!("error reading tree: {}", err);
},
}
Ok(())
}
pub fn read(tree: String) -> Option<(String, io::Lines<io::BufReader<File>>)> {
let mut obj_p = path::objects();
let (dir, res) = hash_obj(&tree);
obj_p.push(dir);
obj_p.push(res);
match read::read_lines(obj_p) {
Ok(mut reader) => {
let name = match reader.next() {
Some(Ok(line)) => line,
_ => String::new(),
};
Some((name, reader))
},
Err(err) => {
eprintln!("error reading tree: {}", err);
None
},
}
}
pub fn parse_line(line: String) -> (String, String, String) {
let mut split = line.rsplit(' ');
if split.clone().count() != 3 {
eprintln!("fatal: invalid object(s)");
std::process::exit(1);
}
}
let name = split.next().unwrap();
let hash = split.next().unwrap();
let ftype = split.next().unwrap();
(String::from(ftype), String::from(hash), String::from(name))
pub fn next(&mut self) -> Option<Box<dyn ObjMethods>> {
self.read();
//if let Some(ref mut file) = self.buf_reader {
// let mut line = String::new();
// match file.read_line(&mut line) {
// Ok(0) => Ok(None), // End of file
// Ok(_) => Ok(Some(line.trim_end().len())), // Return length of line
// Err(e) => Err(e),
// }
//} else {
// Ok(None) // If file is None, return None
//}
match self.buf_reader {
Some(ref mut file) => {
let mut line = String::new();
match file.read_line(&mut line) {
Ok(0) => None,
Ok(_) => Some(Obj::from_line(line, Some(self.get_relative_file_path()))),
Err(e) => {
eprintln!("tree::next: failed to read next line: {}", e);
None
}
}
},
None => None
}
}
pub fn create(&self, date: &str, up_parent: bool) -> io::Result<()> {
// add tree reference to parent
self.add_ref_to_parent();
// create tree object
let mut content = format!("{} {}", self.get_name(), date);
//create_obj(self.get_hash_path(), &content)?;
todo!();
// update date for all parent
todo!();
//if up_parent {
// update_dates(path, date)?;
//}
Ok(())
}
// create
}

View File

@ -22,6 +22,14 @@ pub fn create() -> App<'static, 'static> {
)
.about("Add a new remote to this repository")
)
.arg(
Arg::with_name("verbose")
.short("v")
.long("verbose")
.required(false)
.takes_value(false)
.help("Be a little more verbose and show remote url after name.")
)
}
pub fn handler(args: &ArgMatches<'_>) {
@ -32,6 +40,8 @@ pub fn handler(args: &ArgMatches<'_>) {
url: add_matches.values_of("url"),
});
}
_ => println!("Invalid or missing subcommand for 'remote'"),
_ => {
commands::remote::remote_list(args.is_present("verbose"));
}
}
}

View File

@ -1,4 +1,4 @@
use std::path::PathBuf;
use std::path::{PathBuf, Path};
pub trait IntoPathBuf {
fn into(self) -> PathBuf;
@ -10,6 +10,12 @@ impl IntoPathBuf for PathBuf {
}
}
impl IntoPathBuf for &Path {
fn into(self) -> PathBuf {
PathBuf::from(self)
}
}
impl IntoPathBuf for String {
fn into(self) -> PathBuf {
PathBuf::from(self)

View File

@ -114,6 +114,13 @@ pub fn nextsync() -> PathBuf {
path
}
pub fn config() -> PathBuf {
let mut path = repo_root();
path.push(".nextsync");
path.push("config");
path
}
pub fn objects() -> PathBuf {
let mut path = repo_root();
path.push(".nextsync");

View File

@ -208,7 +208,7 @@ fn get_non_new_local_element(iter: &mut dyn Iterator<Item = &PathBuf>) -> Option
!Object::new(el.unwrap().clone().to_str().unwrap()).exists()
} else {
// ignore newly created file (not sync)
Blob::new(el.unwrap().clone()).status(&mut None) == State::New
Blob::from_path(el.unwrap().clone()).status(&mut None) == State::New
}
} {
el = iter.next();

105
tests/push.rs Normal file
View File

@ -0,0 +1,105 @@
use rand::{distributions::Alphanumeric, Rng};
mod utils;
use utils::{server::ServerTest, client::ClientTest};
fn get_random_test_id() -> String {
let mut id: String = rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(7)
.map(char::from)
.collect();
id.push_str("_nextsync");
id.to_owned()
}
#[cfg(test)]
mod push_tests {
use super::*;
#[test]
fn simple_push() {
let id = get_random_test_id();
let mut server = ServerTest::new(id.clone()).init();
let mut client = ClientTest::new(id).init();
let _ = client.add_file("file1", "foo");
client.run_cmd_ok("add file1");
client.run_cmd_ok("push");
// tests
assert!(server.has_file("file1", "foo"));
client.clean();
server.clean();
}
#[test]
fn push_update() {
let id = get_random_test_id();
let mut server = ServerTest::new(id.clone()).init();
let mut client = ClientTest::new(id).init();
// init content of file1
let _ = client.add_file("file1", "foo");
client.run_cmd_ok("add file1");
client.run_cmd_ok("push");
// tests
assert!(server.has_file("file1", "foo"));
// change content of file1
let _ = client.add_file("file1", "bar");
client.run_cmd_ok("add file1");
client.run_cmd_ok("push");
// tests
assert!(server.has_file("file1", "bar"));
client.clean();
server.clean();
}
#[test]
fn push_dir() {
let id = get_random_test_id();
let mut server = ServerTest::new(id.clone()).init();
let mut client = ClientTest::new(id).init();
let _ = client.add_dir("dir");
let _ = client.add_file("dir/file2", "bar");
// push dir and file2
client.run_cmd_ok("add file2");
client.run_cmd_ok("push");
// tests
assert!(server.has_file("dir/file2", "bar"));
client.clean();
server.clean();
}
#[test]
fn push_all() {
let id = get_random_test_id();
let mut server = ServerTest::new(id.clone()).init();
let mut client = ClientTest::new(id).init();
let _ = client.add_file("file1", "foo");
let _ = client.add_dir("dir");
let _ = client.add_file("dir/file2", "bar");
// push dir and file2
client.run_cmd_ok("add *");
client.run_cmd_ok("push");
// tests
assert!(server.has_file("file1", "foo"));
assert!(server.has_file("dir/file2", "bar"));
client.clean();
server.clean();
}
}

View File

@ -1,216 +0,0 @@
use std::process::{Command, Output};
use std::os::unix::fs::PermissionsExt;
use rand::{distributions::Alphanumeric, Rng}; // 0.8
use std::fs::{self, File, Permissions};
use std::io::{Write, BufReader, BufRead};
use std::env;
use std::path::PathBuf;
struct ServerTest {
user: String,
volume: PathBuf,
test_id: String
}
impl ServerTest {
fn new(id: String) -> Self {
let mut volume = env::current_dir().unwrap();
volume = volume.join("tests/data/admin/files");
ServerTest {
user: String::from("admin"),
volume,
test_id: id
}
}
fn init(mut self) -> Self {
self.add_dir(self.test_id.clone());
self.volume = self.volume.join(self.test_id.clone());
self.sync_test()
}
fn clean(mut self) -> Self {
self.remove_dir(self.test_id.clone());
self.sync_root()
}
fn add_dir(&mut self, path: String) -> &mut ServerTest {
let mut full_path = self.volume.clone();
full_path.push(path);
match fs::create_dir(&full_path) {
Ok(_) => {
// Set permissions to 777 to allow nextcloud to access it (workaround avoiding to
// set group and owner to www-data)
if let Err(e) = fs::set_permissions(&full_path, Permissions::from_mode(0o777)) {
eprintln!("Error setting permissions: {}", e);
}
},
Err(e) => eprintln!("Error creating directory: {}", e),
}
self
}
fn remove_dir(&mut self, path: String) -> &mut ServerTest {
let mut full_path = self.volume.clone();
full_path.push(path);
let _ = fs::remove_dir_all(&full_path);
self
}
fn sync_root(self) -> Self {
self.sync("")
}
fn sync_test(self) -> Self {
let test_id = self.test_id.clone();
self.sync(&test_id)
}
fn sync(self, path: &str) -> Self {
// perform the occ files:scan command inside the nextcloud docker container
let nextcloud_docker = "master-nextcloud-1";
let mut args = String::from("exec -ti --user www-data");
args.push_str(nextcloud_docker);
args.push_str("/var/www/html/occ files:scan --path=/");
args.push_str(&self.user);
args.push_str("files/");
args.push_str(path);
let _output = Command::new("docker")
.args(args.split(" "))
.output()
.expect("Could not execute nextsync command");
self
}
fn has_file(&mut self, file: &str, content: &str) -> bool {
let full_path = self.volume.clone().join(file);
if !full_path.exists() {
eprintln!("File '{}' does't exists", file);
return false;
}
let file = File::open(full_path).unwrap();
for line in BufReader::new(file).lines(){
if let Ok(line) = line {
return line == content;
}
}
return false;
}
}
struct ClientTest {
user: String,
volume: String,
test_id: String,
exe_path: PathBuf,
}
impl ClientTest {
fn new(id: String) -> Self {
// create a directory in /tmp with the given id
let mut vol = String::from("/tmp/");
vol.push_str(&id);
let _ = fs::create_dir(vol.clone());
// get nextsync path
let mut exe_path = env::current_dir().unwrap();
exe_path = exe_path.join("target/debug/nextsync");
let _ = env::set_current_dir(vol.clone());
// build the client
ClientTest {
user: String::from("admin"),
volume: vol,
test_id: id,
exe_path
}
}
fn init(mut self) -> Self {
self.run_cmd_ok("init");
// set remote url
let url = String::from(format!("{}@nextcloud.local/{}", self.user, self.test_id));
self.run_cmd_ok(&format!("remote add origin {}", url));
// set force_unsecure as debug server has not certificate
self.run_cmd_ok("config set force_insecure true");
// set token for request
self.run_cmd_ok(&format!("credential add {} {}", self.user, self.user));
self
}
fn clean(self) -> Self {
let _ = fs::remove_dir_all(&self.volume);
self
}
fn run_cmd_ok(&mut self, args: &str) -> Output {
let output = self.run_cmd(args);
if !output.status.success() {
println!("Failed to execute: '{}'", args);
println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
}
assert!(output.status.success());
output
}
fn run_cmd(&mut self, args: &str) -> Output {
let output = Command::new(self.exe_path.to_str().unwrap())
.args(args.split(" "))
.output()
.expect("Could not execute nextsync command");
return output;
}
fn add_file(&mut self, name: &str, content: &str) -> std::io::Result<()> {
let mut path = self.volume.clone();
path.push_str("/");
path.push_str(name);
let mut file = File::create(path)?;
file.write_all(content.as_bytes())?;
Ok(())
}
}
fn get_random_test_id() -> String {
let mut id: String = rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(7)
.map(char::from)
.collect();
id.push_str("_nextsync");
id.to_owned()
}
#[test]
fn test1() {
let id = get_random_test_id();
dbg!(id.clone());
let mut server = ServerTest::new(id.clone()).init();
let mut client = ClientTest::new(id).init();
let _ = client.add_file("file1", "foo");
client.run_cmd_ok("add file1");
client.run_cmd_ok("push");
// tests
assert!(server.has_file("file1", "foo"));
client.clean();
server.clean();
}

5
tests/utils.rs Normal file
View File

@ -0,0 +1,5 @@
#[path = "utils/server.rs"]
pub mod server;
#[path = "utils/client.rs"]
pub mod client;

93
tests/utils/client.rs Normal file
View File

@ -0,0 +1,93 @@
use std::process::{Command, Output};
use std::fs::{self, File};
use std::io::Write;
use std::env;
use std::path::PathBuf;
pub struct ClientTest {
user: String, // the nextcloud user
volume: String, // temp dir for the test
test_id: String, // name of the test (e.g nextsync_rand)
exe_path: PathBuf, // absolute path of nextsync executable
}
impl ClientTest {
pub fn new(id: String) -> Self {
// create a directory in /tmp with the given id
let mut vol = String::from("/tmp/");
vol.push_str(&id);
let _ = fs::create_dir(vol.clone());
// get nextsync path
let mut exe_path = env::current_dir().unwrap();
exe_path = exe_path.join("target/debug/nextsync");
// build the client
ClientTest {
user: String::from("admin"),
volume: vol,
test_id: id,
exe_path
}
}
pub fn init(mut self) -> Self {
self.run_cmd_ok("init");
// set remote url
let url = String::from(format!("{}@nextcloud.local/{}", self.user, self.test_id));
self.run_cmd_ok(&format!("remote add origin {}", url));
// set force_unsecure as debug server has not certificate
self.run_cmd_ok("config set force_insecure true");
// set token for request
self.run_cmd_ok(&format!("credential add {} {}", self.user, self.user));
self
}
pub fn clean(self) -> Self {
let _ = fs::remove_dir_all(&self.volume);
self
}
pub fn run_cmd_ok(&mut self, args: &str) -> Output {
let output = self.run_cmd(args);
if !output.status.success() {
println!("Failed to execute: '{}'", args);
println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
}
assert!(output.status.success());
output
}
pub fn run_cmd(&mut self, args: &str) -> Output {
let output = Command::new(self.exe_path.to_str().unwrap())
.current_dir(self.volume.clone())
.args(args.split(" "))
.output()
.expect("Could not execute nextsync command");
return output;
}
pub fn add_dir(&mut self, name: &str) -> std::io::Result<()> {
let mut path = self.volume.clone();
path.push_str("/");
path.push_str(name);
let _ = fs::create_dir_all(path)?;
Ok(())
}
pub fn add_file(&mut self, name: &str, content: &str) -> std::io::Result<()> {
let mut path = self.volume.clone();
path.push_str("/");
path.push_str(name);
let mut file = File::create(path)?;
file.write_all(content.as_bytes())?;
Ok(())
}
}

113
tests/utils/server.rs Normal file
View File

@ -0,0 +1,113 @@
use std::process::Command;
use std::os::unix::fs::PermissionsExt;
use std::fs::{self, File, Permissions};
use std::io::{BufReader, BufRead};
use std::env;
use std::path::PathBuf;
#[cfg(test)]
pub struct ServerTest {
user: String,
volume: PathBuf,
test_id: String
}
#[cfg(test)]
impl ServerTest {
pub fn new(id: String) -> Self {
let mut volume = env::current_dir().unwrap();
volume = volume.join("tests/data/admin/files");
ServerTest {
user: String::from("admin"),
volume,
test_id: id
}
}
pub fn init(mut self) -> Self {
self.add_dir(self.test_id.clone());
self.volume = self.volume.join(self.test_id.clone());
self.sync_test()
}
pub fn clean(mut self) -> Self {
self.remove_dir(self.test_id.clone());
self.sync_root()
}
pub fn add_dir(&mut self, path: String) -> &mut ServerTest {
let mut full_path = self.volume.clone();
full_path.push(path);
match fs::create_dir(&full_path) {
Ok(_) => {
// Set permissions to 777 to allow nextcloud to access it (workaround avoiding to
// set group and owner to www-data)
if let Err(e) = fs::set_permissions(&full_path, Permissions::from_mode(0o777)) {
eprintln!("Error setting permissions: {}", e);
}
},
Err(e) => eprintln!("Error creating directory: {}", e),
}
self
}
pub fn remove_dir(&mut self, path: String) -> &mut ServerTest {
let mut full_path = self.volume.clone();
full_path.push(path);
let _ = fs::remove_dir_all(&full_path);
self
}
fn sync_root(self) -> Self {
self.sync("")
}
fn sync_test(self) -> Self {
let test_id = self.test_id.clone();
self.sync(&test_id)
}
fn sync(self, path: &str) -> Self {
// perform the occ files:scan command inside the nextcloud docker container
let nextcloud_docker = "master-nextcloud-1";
let mut args = String::from("exec -ti --user www-data");
args.push_str(nextcloud_docker);
args.push_str("/var/www/html/occ files:scan --path=/");
args.push_str(&self.user);
args.push_str("files/");
args.push_str(path);
let _output = Command::new("docker")
.args(args.split(" "))
.output()
.expect("Could not execute nextsync command");
self
}
pub fn has_file(&mut self, file: &str, content: &str) -> bool {
let full_path = self.volume.clone().join(file);
if !full_path.exists() {
eprintln!("File '{}' doesn't exists on the server", file);
return false;
}
let f = File::open(full_path).unwrap();
for line in BufReader::new(f).lines(){
if let Ok(line) = line {
if line != content {
eprintln!("File '{}' is not equal, {} != {}", file, line, content);
return false;
}
return line == content;
}
}
return true;
}
}