Compare commits

..

5 Commits

Author SHA1 Message Date
grimhilt
fa65b6b071 test(add): implicit dir 2024-03-10 17:29:50 +01:00
grimhilt
34dee1ceb6 fix(add): add directory implicitly 2024-03-10 17:29:37 +01:00
grimhilt
fe628ffc9f test(add): first tests 2024-03-10 16:49:21 +01:00
grimhilt
6b7a82bec6 fix: prevent adding nextsync config files 2024-03-10 16:49:06 +01:00
grimhilt
fdcd4633e5 fix: allow to push explicit directory 2024-03-10 16:19:23 +01:00
12 changed files with 218 additions and 42 deletions

View File

@ -4,6 +4,7 @@ use clap::Values;
use glob::glob; use glob::glob;
use crate::store::{self, object::Object}; use crate::store::{self, object::Object};
use crate::utils::{self, path}; use crate::utils::{self, path};
use crate::store::object::object::{Obj, ObjMethods};
use crate::utils::nextsyncignore::{self, ignore_file}; use crate::utils::nextsyncignore::{self, ignore_file};
use crate::utils::path::{normalize_relative, repo_root, path_buf_to_string}; use crate::utils::path::{normalize_relative, repo_root, path_buf_to_string};
@ -39,35 +40,19 @@ pub fn add(args: AddArgs) {
} }
}; };
// check if the file must be ignored
if !args.force && ignore_file(&f, rules.clone(), &mut ignored_f) {
continue;
}
let path = repo_root().join(Path::new(&f)); let path = repo_root().join(Path::new(&f));
match path.exists() { match path.exists() {
true => { true => {
if path.is_dir() { add_entry(path, args.force, &mut added_files, rules.clone(), &mut ignored_f);
add_folder_content(path.to_path_buf(), &mut added_files);
}
added_files.push(f);
}, },
false => { false => {
if Object::new(path.to_str().unwrap()).exists() { if Object::new(path.to_str().unwrap()).exists() {
// object is deleted so not a present file but can still be added // object is deleted so not present but can still be added for deletion
added_files.push(String::from(f)); added_files.push(String::from(f));
} else { } else {
// try globbing if nothing has been found
for entry in try_globbing(path) { for entry in try_globbing(path) {
if path::is_nextsync_config(entry.clone()) { add_entry(entry, args.force, &mut added_files, rules.clone(), &mut ignored_f);
continue;
}
if !args.force && ignore_file(&path_buf_to_string(entry.clone()), rules.clone(), &mut ignored_f) {
continue;
}
if entry.is_dir() {
add_folder_content(entry.to_path_buf(), &mut added_files);
}
added_files.push(path_buf_to_string(entry.strip_prefix(repo_root()).unwrap().to_path_buf()));
} }
} }
} }
@ -78,6 +63,41 @@ pub fn add(args: AddArgs) {
write_added_files(added_files); write_added_files(added_files);
} }
fn add_entry(entry: PathBuf, force: bool, added_files: &mut Vec<String>, rules: Vec<String>, ignored_f: &mut Vec<String>) {
// ignore nextsync config files
if path::is_nextsync_config(entry.clone()) {
return;
}
// check if the file must be ignored
if !force && ignore_file(&path_buf_to_string(entry.clone()), rules, ignored_f) {
return;
}
// add the parent if there is one and it is not already created
add_parent(entry.clone(), added_files);
added_files.push(path_buf_to_string(entry.strip_prefix(repo_root()).unwrap().to_path_buf()));
if entry.is_dir() {
add_folder_content(entry.to_path_buf(), added_files);
}
}
fn add_parent(entry: PathBuf, added_files: &mut Vec<String>) {
let test_parent = entry.strip_prefix(repo_root()).unwrap().parent();
if test_parent.is_none() || test_parent.unwrap() == PathBuf::new() {
return;
}
let parent = entry.parent().unwrap();
if !Obj::from_path(parent).exists_on_remote() {
add_parent(parent.to_path_buf(), added_files);
added_files.push(path_buf_to_string(parent.strip_prefix(repo_root()).unwrap().to_path_buf()));
}
}
fn print_ignored_files(ignored_files: Vec<String>) { fn print_ignored_files(ignored_files: Vec<String>) {
if ignored_files.len() > 0 { if ignored_files.len() > 0 {
// todo multiple nextsyncignore // todo multiple nextsyncignore

View File

@ -32,6 +32,7 @@ impl PushChange for New {
match res { match res {
Err(ApiError::IncorrectRequest(err)) => { Err(ApiError::IncorrectRequest(err)) => {
dbg!(&err);
eprintln!("fatal: error pushing file '{}': {}", obj.name, err.status()); eprintln!("fatal: error pushing file '{}': {}", obj.name, err.status());
std::process::exit(1); std::process::exit(1);
}, },

View File

@ -31,6 +31,7 @@ impl PushChange for NewDir {
} }
fn push(&self) -> io::Result<()> { fn push(&self) -> io::Result<()> {
dbg!("pushing new dir");
let obj = &self.obj; let obj = &self.obj;
let res = CreateFolder::new() let res = CreateFolder::new()
.set_url(obj.path.to_str().unwrap()) .set_url(obj.path.to_str().unwrap())

View File

@ -148,7 +148,7 @@ fn add_node(path: &Path, node: &str) -> io::Result<()> {
root.push(dir); root.push(dir);
if !root.exists() { if !root.exists() {
todo!(); //todo!();
} }
root.push(rest); root.push(rest);

View File

@ -22,6 +22,7 @@ pub trait ObjMethods {
fn get_obj_path(&self) -> PathBuf; fn get_obj_path(&self) -> PathBuf;
fn get_file_path(&self) -> PathBuf; fn get_file_path(&self) -> PathBuf;
fn get_relative_file_path(&self) -> PathBuf; fn get_relative_file_path(&self) -> PathBuf;
fn get_repo_file_path(&self) -> PathBuf;
fn get_name(&self) -> String; fn get_name(&self) -> String;
fn get_hash_path(&self) -> String; fn get_hash_path(&self) -> String;
fn get_local_obj(&self) -> LocalObj; fn get_local_obj(&self) -> LocalObj;
@ -30,6 +31,7 @@ pub trait ObjMethods {
fn rm(&mut self) -> io::Result<()>; fn rm(&mut self) -> io::Result<()>;
fn rm_node(&mut self) -> io::Result<()>; fn rm_node(&mut self) -> io::Result<()>;
fn rm_node_down(&mut self) -> io::Result<()>; fn rm_node_down(&mut self) -> io::Result<()>;
fn exists_on_remote(&mut self) -> bool;
} }
pub struct Obj { pub struct Obj {
@ -38,6 +40,7 @@ pub struct Obj {
obj_type: ObjType, obj_type: ObjType,
file_path: PathBuf, // file here is used as both file and directory file_path: PathBuf, // file here is used as both file and directory
relative_file_path: PathBuf, relative_file_path: PathBuf,
repo_file_path: PathBuf,
hash_path: String, // hash of the relative path of the file hash_path: String, // hash of the relative path of the file
} }
@ -59,6 +62,10 @@ impl ObjMethods for Obj {
self.relative_file_path.clone() self.relative_file_path.clone()
} }
fn get_repo_file_path(&self) -> PathBuf {
self.repo_file_path.clone()
}
fn get_local_obj(&self) -> LocalObj { fn get_local_obj(&self) -> LocalObj {
LocalObj { LocalObj {
otype: match self.obj_type { otype: match self.obj_type {
@ -67,7 +74,7 @@ impl ObjMethods for Obj {
ObjType::DEFAULT => String::from("default"), ObjType::DEFAULT => String::from("default"),
}, },
name: self.get_name(), name: self.get_name(),
path: self.get_file_path(), path: self.get_repo_file_path(),
path_from: None, path_from: None,
state: State::New state: State::New
} }
@ -120,6 +127,10 @@ impl ObjMethods for Obj {
eprintln!("rm: tried to do this on Obj"); eprintln!("rm: tried to do this on Obj");
Ok(()) Ok(())
} }
fn exists_on_remote(&mut self) -> bool {
PathBuf::from(self.get_hash_path()).exists()
}
} }
impl ObjMethods for Blob { impl ObjMethods for Blob {
@ -139,6 +150,10 @@ impl ObjMethods for Blob {
self.obj.get_relative_file_path() self.obj.get_relative_file_path()
} }
fn get_repo_file_path(&self) -> PathBuf {
self.obj.get_repo_file_path()
}
fn get_local_obj(&self) -> LocalObj { fn get_local_obj(&self) -> LocalObj {
self.obj.get_local_obj() self.obj.get_local_obj()
} }
@ -177,6 +192,10 @@ impl ObjMethods for Blob {
fs::remove_file(self.get_file_path())?; fs::remove_file(self.get_file_path())?;
Ok(()) Ok(())
} }
fn exists_on_remote(&mut self) -> bool {
self.obj.exists_on_remote()
}
} }
impl ObjMethods for Tree { impl ObjMethods for Tree {
@ -196,6 +215,10 @@ impl ObjMethods for Tree {
self.obj.get_relative_file_path() self.obj.get_relative_file_path()
} }
fn get_repo_file_path(&self) -> PathBuf {
self.obj.get_repo_file_path()
}
fn get_local_obj(&self) -> LocalObj { fn get_local_obj(&self) -> LocalObj {
self.obj.get_local_obj() self.obj.get_local_obj()
} }
@ -244,6 +267,10 @@ impl ObjMethods for Tree {
fs::remove_dir_all(self.get_file_path())?; fs::remove_dir_all(self.get_file_path())?;
Ok(()) Ok(())
} }
fn exists_on_remote(&mut self) -> bool {
self.obj.exists_on_remote()
}
} }
impl Obj { impl Obj {
@ -254,7 +281,8 @@ impl Obj {
file_path: PathBuf::new(), file_path: PathBuf::new(),
obj_type: ObjType::DEFAULT, obj_type: ObjType::DEFAULT,
hash_path: String::new(), hash_path: String::new(),
relative_file_path: PathBuf::new() relative_file_path: PathBuf::new(),
repo_file_path: PathBuf::new()
} }
} }
@ -285,7 +313,8 @@ impl Obj {
false => ObjType::DEFAULT false => ObjType::DEFAULT
}, },
file_path: abs_path, file_path: abs_path,
relative_file_path: path, relative_file_path: path.clone(),
repo_file_path: path,
hash_path: hash, hash_path: hash,
} }
} }
@ -324,7 +353,8 @@ impl Obj {
_ => ObjType::DEFAULT _ => ObjType::DEFAULT
}, },
file_path: abs_path, file_path: abs_path,
relative_file_path: path, relative_file_path: path.clone(),
repo_file_path: path,
hash_path: String::from(hash_path), hash_path: String::from(hash_path),
}; };
@ -342,6 +372,7 @@ impl Obj {
obj_type: ObjType::TREE, obj_type: ObjType::TREE,
file_path: PathBuf::new(), file_path: PathBuf::new(),
relative_file_path: PathBuf::new(), relative_file_path: PathBuf::new(),
repo_file_path: PathBuf::new(),
hash_path: String::new(), hash_path: String::new(),
} }
} }

View File

@ -85,10 +85,10 @@ impl Tree {
// create tree object // create tree object
let mut content = format!("{} {}", self.get_name(), date); let mut content = format!("{} {}", self.get_name(), date);
//create_obj(self.get_hash_path(), &content)?; //create_obj(self.get_hash_path(), &content)?;
todo!(); // todo!();
// update date for all parent // update date for all parent
todo!(); //todo!();
//if up_parent { //if up_parent {
// update_dates(path, date)?; // update_dates(path, date)?;
//} //}

98
tests/add.rs Normal file
View File

@ -0,0 +1,98 @@
use std::str;
mod utils;
use utils::{utils::*, client::ClientTest};
fn line_should_contains(lines: &Vec<String>, nb: usize, str: &str) {
if lines[nb].find(str).is_none()
{
eprintln!("'{}' not found in '{}'", str, lines[nb]);
dbg!(lines);
}
assert!(lines[nb].find(str).is_some());
}
fn lines_should_not_contains(lines: Vec<String>, str: &str) {
for line in lines {
if line.find("Changes not staged for push").is_some() {
return;
}
if line.find(str).is_some() {
eprintln!("'{}' found in '{}'", str, line);
}
assert!(line.find(str).is_none());
}
}
fn collect_status_lines(client: &mut ClientTest) -> Vec<String> {
let out = client.run_cmd("status");
str::from_utf8(&out.stdout)
.unwrap()
.split("\n")
.map(|s| s.to_owned())
.collect()
}
#[cfg(test)]
mod push_tests {
use super::*;
#[test]
fn simple_add() {
let id = get_random_test_id();
let mut client = ClientTest::new(id).init();
let _ = client.add_file("file1", "foo");
client.run_cmd_ok("add file1");
let lines = collect_status_lines(&mut client);
// test
line_should_contains(&lines, 2, "file1");
client.clean();
}
#[test]
fn add_config_file() {
let id = get_random_test_id();
let mut client = ClientTest::new(id).init();
let _ = client.add_file("file1", "foo");
client.run_cmd_ok("add .nextsync -f");
let lines = collect_status_lines(&mut client);
// test
lines_should_not_contains(lines, ".nextsync");
client.clean();
}
#[test]
fn add_dir_implicit() {
let id = get_random_test_id();
let mut client = ClientTest::new(id).init();
let _ = client.add_dir("dir");
let _ = client.add_file("dir/file1", "foo");
// adding the file should add the dir
client.run_cmd_ok("add dir/file1");
let lines = collect_status_lines(&mut client);
// tests
line_should_contains(&lines, 2, "dir");
line_should_contains(&lines, 3, "dir/file1");
client.clean();
}
}

View File

@ -1,19 +1,8 @@
use rand::{distributions::Alphanumeric, Rng};
mod utils; mod utils;
use utils::{server::ServerTest, client::ClientTest}; use utils::{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)] #[cfg(test)]
mod push_tests { mod push_tests {
use super::*; use super::*;
@ -62,7 +51,7 @@ mod push_tests {
} }
#[test] #[test]
fn push_dir() { fn push_dir_explicit() {
let id = get_random_test_id(); let id = get_random_test_id();
let mut server = ServerTest::new(id.clone()).init(); let mut server = ServerTest::new(id.clone()).init();
let mut client = ClientTest::new(id).init(); let mut client = ClientTest::new(id).init();
@ -71,7 +60,27 @@ mod push_tests {
let _ = client.add_file("dir/file2", "bar"); let _ = client.add_file("dir/file2", "bar");
// push dir and file2 // push dir and file2
client.run_cmd_ok("add file2"); client.run_cmd_ok("add dir");
client.run_cmd_ok("push");
// tests
assert!(server.has_file("dir/file2", "bar"));
client.clean();
server.clean();
}
#[test]
fn push_dir_implicit() {
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 dir/file2");
client.run_cmd_ok("push"); client.run_cmd_ok("push");
// tests // tests

View File

@ -3,3 +3,6 @@ pub mod server;
#[path = "utils/client.rs"] #[path = "utils/client.rs"]
pub mod client; pub mod client;
#[path = "utils/utils.rs"]
pub mod utils;

View File

@ -13,7 +13,6 @@ pub struct ClientTest {
impl ClientTest { impl ClientTest {
pub fn new(id: String) -> Self { pub fn new(id: String) -> Self {
// create a directory in /tmp with the given id // create a directory in /tmp with the given id
let mut vol = String::from("/tmp/"); let mut vol = String::from("/tmp/");
vol.push_str(&id); vol.push_str(&id);
@ -56,6 +55,7 @@ impl ClientTest {
pub fn run_cmd_ok(&mut self, args: &str) -> Output { pub fn run_cmd_ok(&mut self, args: &str) -> Output {
let output = self.run_cmd(args); let output = self.run_cmd(args);
if !output.status.success() { if !output.status.success() {
println!("id: {}", self.test_id.clone());
println!("Failed to execute: '{}'", args); println!("Failed to execute: '{}'", args);
println!("stderr: {}", String::from_utf8_lossy(&output.stderr)); println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
println!("stdout: {}", String::from_utf8_lossy(&output.stdout)); println!("stdout: {}", String::from_utf8_lossy(&output.stdout));

View File

@ -93,6 +93,7 @@ impl ServerTest {
let full_path = self.volume.clone().join(file); let full_path = self.volume.clone().join(file);
if !full_path.exists() { if !full_path.exists() {
println!("id: {}", self.test_id.clone());
eprintln!("File '{}' doesn't exists on the server", file); eprintln!("File '{}' doesn't exists on the server", file);
return false; return false;
} }
@ -101,6 +102,7 @@ impl ServerTest {
for line in BufReader::new(f).lines(){ for line in BufReader::new(f).lines(){
if let Ok(line) = line { if let Ok(line) = line {
if line != content { if line != content {
println!("id: {}", self.test_id.clone());
eprintln!("File '{}' is not equal, {} != {}", file, line, content); eprintln!("File '{}' is not equal, {} != {}", file, line, content);
return false; return false;
} }

11
tests/utils/utils.rs Normal file
View File

@ -0,0 +1,11 @@
use rand::{distributions::Alphanumeric, Rng};
pub 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()
}