Compare commits

...

5 Commits

Author SHA1 Message Date
grimhilt
4533b9a72d refactor(tests): use init_test and clean_test 2024-05-07 18:20:32 +02:00
grimhilt
980d2d9a5d feat(add): prevent adding a file without changes 2024-05-07 18:12:05 +02:00
grimhilt
939b6f2fe3 feat: push deletion 2024-05-02 18:36:09 +02:00
grimhilt
4504b98112 fix(push): push deletion 2024-04-18 15:19:35 +02:00
grimhilt
e8c8ab9dfe fix(add): add deleted file 2024-04-16 17:54:25 +02:00
19 changed files with 208 additions and 99 deletions

View File

@ -42,10 +42,13 @@ pub fn add(args: AddArgs) {
let path = repo_root().join(Path::new(&f)); let path = repo_root().join(Path::new(&f));
match path.exists() { match path.exists() {
true => { true => {
add_entry(path, args.force, &mut added_files, rules.clone(), &mut ignored_f); let mut obj = Obj::from_path(f.clone());
if obj.has_changes() {
add_entry(path, args.force, &mut added_files, rules.clone(), &mut ignored_f);
}
}, },
false => { false => {
if Object::new(path.to_str().unwrap()).exists() { if Obj::from_path(file.clone()).exists_on_remote() {
// object is deleted so not present but can still be added for deletion // 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 {

View File

@ -28,7 +28,7 @@ pub fn init() {
path.push(".nextsync"); path.push(".nextsync");
match builder.create(path.clone()) { match builder.create(path.clone()) {
Ok(()) => (), Ok(()) => (),
Err(_) => println!("Error: cannot create .nextsync"), Err(err) => println!("Error: cannot create .nextsync ({})", err),
}; };
path.push("objects"); path.push("objects");
@ -59,12 +59,12 @@ pub fn init() {
} }
// todo // todo
path.pop(); // path.pop();
path.pop(); // path.pop();
path.push(".nextsyncignore"); // path.push(".nextsyncignore");
//
match File::create(path) { // match File::create(path) {
Ok(_) => (), // Ok(_) => (),
Err(_) => println!("Error: cannot create .nextsyncignore"), // Err(_) => println!("Error: cannot create .nextsyncignore"),
} // }
} }

View File

@ -71,9 +71,10 @@ pub fn push() {
}, },
PushState::Done => remove_obj_from_index(obj.clone()), PushState::Done => remove_obj_from_index(obj.clone()),
PushState::Conflict => { PushState::Conflict => {
eprintln!("conflict when pushing blob");
// download file // download file
} }
PushState::Error => (), PushState::Error => (eprintln!("error when pushing changes blob")),
} }
} }
} }

View File

@ -44,7 +44,7 @@ impl PushChange for Deleted {
// update tree // update tree
// todo date // todo date
Blob::from_path(obj.path.clone()).rm()?; Blob::from_path(obj.path.clone()).rm_node()?;
// remove index // remove index
index::rm_line(obj.path.to_str().unwrap())?; index::rm_line(obj.path.to_str().unwrap())?;

View File

@ -73,9 +73,16 @@ pub trait PushChange {
}; };
// check if remote is newest // check if remote is newest
let last_sync_ts = Blob::from_path(obj.path.clone()) let last_sync_ts = {
.saved_remote_ts() if obj.otype == String::from("blob") {
.parse::<i64>().unwrap(); Blob::from_path(obj.path.clone())
.saved_remote_ts()
.parse::<i64>().unwrap()
} else {
// todo timestamp on tree
99999999999999
}
};
let remote_ts = obj_data.lastmodified.unwrap().timestamp_millis(); let remote_ts = obj_data.lastmodified.unwrap().timestamp_millis();
if last_sync_ts < remote_ts { if last_sync_ts < remote_ts {

View File

@ -160,19 +160,8 @@ pub struct LocalObj {
} }
pub fn get_all_staged() -> Vec<LocalObj> { pub fn get_all_staged() -> Vec<LocalObj> {
let mut staged_objs = vec![]; let mut all_hashes = get_all_objs_hashes();
get_staged(&mut all_hashes)
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> { fn get_staged(hashes: &mut HashMap<String, LocalObj>) -> Vec<LocalObj> {

View File

@ -26,12 +26,13 @@ pub trait ObjMethods {
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;
fn get_line(&self) -> String; fn get_line(&self, obj_type: ObjType) -> String;
fn add_ref_to_parent(&self) -> io::Result<()>; fn add_ref_to_parent(&self) -> io::Result<()>;
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; fn exists_on_remote(&mut self) -> bool;
fn has_changes(&mut self) -> bool;
} }
pub struct Obj { pub struct Obj {
@ -89,12 +90,17 @@ impl ObjMethods for Obj {
} }
// build line for parent reference // build line for parent reference
fn get_line(&self) -> String { fn get_line(&self, obj_type: ObjType) -> String {
format!("tree {} {}", self.get_hash_path(), self.get_name()) let type_str = match obj_type {
ObjType::BLOB => "blob",
ObjType::TREE => "tree",
ObjType::DEFAULT => "default",
};
format!("{} {} {}", type_str, self.get_hash_path(), self.get_name())
} }
fn add_ref_to_parent(&self) -> io::Result<()> { fn add_ref_to_parent(&self) -> io::Result<()> {
let line = self.get_line(); let line = self.get_line(self.obj_type);
if self.get_relative_file_path().iter().count() == 1 { if self.get_relative_file_path().iter().count() == 1 {
head::add_line(line)?; head::add_line(line)?;
} else { } else {
@ -104,11 +110,8 @@ impl ObjMethods for Obj {
} }
fn rm_node(&mut self) -> io::Result<()> { fn rm_node(&mut self) -> io::Result<()> {
// remove self object and children object
self.rm_node_down();
// remove parent reference to self // remove parent reference to self
let line = self.get_line(); let line = self.get_line(self.obj_type);
if self.get_relative_file_path().iter().count() == 1 { if self.get_relative_file_path().iter().count() == 1 {
head::rm_line(&line)?; head::rm_line(&line)?;
} else { } else {
@ -129,7 +132,21 @@ impl ObjMethods for Obj {
} }
fn exists_on_remote(&mut self) -> bool { fn exists_on_remote(&mut self) -> bool {
PathBuf::from(self.get_hash_path()).exists() self.obj_path.exists()
}
fn has_changes(&mut self) -> bool {
if !self.obj_path.exists() {
return true;
}
match self.obj_type {
ObjType::BLOB => Blob::from_path(self.relative_file_path.clone()).has_changes(),
ObjType::TREE => Tree::from_path(self.relative_file_path.clone()).has_changes(),
ObjType::DEFAULT => {
unreachable!();
}
}
} }
} }
@ -166,8 +183,8 @@ impl ObjMethods for Blob {
self.obj.get_hash_path() self.obj.get_hash_path()
} }
fn get_line(&self) -> String { fn get_line(&self, _: ObjType) -> String {
self.obj.get_line() self.obj.get_line(ObjType::BLOB)
} }
fn add_ref_to_parent(&self) -> io::Result<()> { fn add_ref_to_parent(&self) -> io::Result<()> {
@ -175,6 +192,8 @@ impl ObjMethods for Blob {
} }
fn rm_node(&mut self) -> io::Result<()> { fn rm_node(&mut self) -> io::Result<()> {
// remove self object and children object
let _ = self.rm_node_down();
self.obj.rm_node() self.obj.rm_node()
} }
@ -196,6 +215,10 @@ impl ObjMethods for Blob {
fn exists_on_remote(&mut self) -> bool { fn exists_on_remote(&mut self) -> bool {
self.obj.exists_on_remote() self.obj.exists_on_remote()
} }
fn has_changes(&mut self) -> bool {
self.obj.has_changes()
}
} }
impl ObjMethods for Tree { impl ObjMethods for Tree {
@ -231,8 +254,8 @@ impl ObjMethods for Tree {
self.obj.get_hash_path() self.obj.get_hash_path()
} }
fn get_line(&self) -> String { fn get_line(&self, _: ObjType) -> String {
self.obj.get_line() self.obj.get_line(ObjType::TREE)
} }
fn add_ref_to_parent(&self) -> io::Result<()> { fn add_ref_to_parent(&self) -> io::Result<()> {
@ -240,6 +263,8 @@ impl ObjMethods for Tree {
} }
fn rm_node(&mut self) -> io::Result<()> { fn rm_node(&mut self) -> io::Result<()> {
// remove self object and children object
let _ = self.rm_node_down();
self.obj.rm_node() self.obj.rm_node()
} }
@ -271,6 +296,10 @@ impl ObjMethods for Tree {
fn exists_on_remote(&mut self) -> bool { fn exists_on_remote(&mut self) -> bool {
self.obj.exists_on_remote() self.obj.exists_on_remote()
} }
fn has_changes(&mut self) -> bool {
self.obj.has_changes()
}
} }
impl Obj { impl Obj {
@ -287,6 +316,7 @@ impl Obj {
} }
pub fn from_path<S>(path: S) -> Obj where S: IntoPathBuf { pub fn from_path<S>(path: S) -> Obj where S: IntoPathBuf {
let path = path.into(); let path = path.into();
let mut hasher = Sha1::new(); let mut hasher = Sha1::new();
hasher.input_str(path.to_str().unwrap()); hasher.input_str(path.to_str().unwrap());
@ -297,8 +327,13 @@ impl Obj {
obj_path.push(dir); obj_path.push(dir);
obj_path.push(res); obj_path.push(res);
// set to absolute path if not already
let root = path::repo_root(); let root = path::repo_root();
let abs_path = root.join(path.clone()); let abs_path = match path.clone().starts_with(root.clone()) {
true => path.clone(),
false => root.join(path.clone())
};
Obj { Obj {
name: match abs_path.file_name() { name: match abs_path.file_name() {
None => String::new(), None => String::new(),
@ -321,9 +356,9 @@ impl Obj {
/// load from the information line stored in the object /// load from the information line stored in the object
pub fn from_line(line: String, base_dir: Option<PathBuf>) -> Box<dyn ObjMethods> { pub fn from_line(line: String, base_dir: Option<PathBuf>) -> Box<dyn ObjMethods> {
let mut split = line.rsplit(' '); let mut split = line.trim().rsplit(' ');
if split.clone().count() != 3 { if split.clone().count() != 3 {
eprintln!("fatal: invalid object(s)"); eprintln!("fatal: invalid object(s) ({})", line.trim());
std::process::exit(1); std::process::exit(1);
} }

View File

@ -42,14 +42,20 @@ impl Tree {
if let Ok(file) = File::open(self.get_obj_path()) { if let Ok(file) = File::open(self.get_obj_path()) {
self.buf_reader = Some(BufReader::new(file)); self.buf_reader = Some(BufReader::new(file));
// skip first line if is head // skip first line (declaration) if is not head
if !self.is_head { if !self.is_head {
self.next(); let mut line = String::new();
self.buf_reader.as_mut().unwrap().read_line(&mut line);
} }
} }
} }
} }
pub fn has_changes(&mut self) -> bool {
todo!();
return true;
}
pub fn next(&mut self) -> Option<Box<dyn ObjMethods>> { pub fn next(&mut self) -> Option<Box<dyn ObjMethods>> {
self.read(); self.read();
//if let Some(ref mut file) = self.buf_reader { //if let Some(ref mut file) = self.buf_reader {
@ -99,11 +105,11 @@ impl Tree {
.open(self.get_obj_path())?; .open(self.get_obj_path())?;
// update date for all parent // update date for all parent
if up_parent { // if up_parent {
if let Err(err) = update_dates(self.get_relative_file_path(), date) { // if let Err(err) = update_dates(self.get_relative_file_path(), date) {
eprintln!("err: updating parent date of {}: {}", self.get_relative_file_path().display(), err); // eprintln!("err: updating parent date of {}: {}", self.get_relative_file_path().display(), err);
} // }
} // }
writeln!(file, "{}", content)?; writeln!(file, "{}", content)?;

View File

@ -1,4 +1,4 @@
use clap::{Arg, ArgMatches, Command}; use clap::{Arg, ArgMatches, Command, ArgAction};
use crate::commands; use crate::commands;
use crate::commands::add::AddArgs; use crate::commands::add::AddArgs;
@ -7,7 +7,7 @@ pub fn create() -> Command {
Command::new("add") Command::new("add")
.arg( .arg(
Arg::new("files") Arg::new("files")
.required(true) .required_unless_present("all")
.conflicts_with("all") .conflicts_with("all")
.num_args(1..) .num_args(1..)
.value_name("FILE") .value_name("FILE")
@ -17,12 +17,14 @@ pub fn create() -> Command {
Arg::new("force") Arg::new("force")
.short('f') .short('f')
.long("force") .long("force")
.action(ArgAction::SetTrue)
.help("Allow adding otherwise ignored files."), .help("Allow adding otherwise ignored files."),
) )
.arg( .arg(
Arg::new("all") Arg::new("all")
.short('A') .short('A')
.long("all") .long("all")
.action(ArgAction::SetTrue)
.help("This adds, modifies, and removes index entries to match the working tree"), .help("This adds, modifies, and removes index entries to match the working tree"),
) )
.about("Add changes to the index") .about("Add changes to the index")
@ -34,7 +36,7 @@ pub fn handler(args: &ArgMatches) {
None => vec![], None => vec![],
Some(vals) => vals.map(|s| s.to_string()).collect(), Some(vals) => vals.map(|s| s.to_string()).collect(),
}, },
force: args.contains_id("force"), force: *args.get_one::<bool>("force").unwrap(),
all: args.contains_id("all"), all: *args.get_one::<bool>("all").unwrap(),
}); });
} }

View File

@ -1,4 +1,4 @@
use clap::{Arg, Command, ArgMatches}; use clap::{Arg, Command, ArgMatches, ArgAction};
use crate::commands; use crate::commands;
use crate::commands::remote::RemoteArgs; use crate::commands::remote::RemoteArgs;
@ -26,6 +26,7 @@ pub fn create() -> Command {
Arg::new("verbose") Arg::new("verbose")
.short('v') .short('v')
.long("verbose") .long("verbose")
.action(ArgAction::SetTrue)
.help("Be a little more verbose and show remote url after name.") .help("Be a little more verbose and show remote url after name.")
) )
} }
@ -39,7 +40,7 @@ pub fn handler(args: &ArgMatches) {
}); });
} }
_ => { _ => {
commands::remote::remote_list(args.contains_id("verbose")); commands::remote::remote_list(*args.get_one::<bool>("verbose").unwrap());
} }
} }
} }

View File

@ -22,3 +22,9 @@ impl IntoPathBuf for String {
} }
} }
impl IntoPathBuf for &str {
fn into(self) -> PathBuf {
PathBuf::from(self)
}
}

View File

@ -40,6 +40,8 @@ fn collect_status_lines(client: &mut ClientTest) -> Vec<String> {
#[cfg(test)] #[cfg(test)]
mod add_tests { mod add_tests {
use crate::utils::{server::ServerTest, status_utils::status_should_be_empty};
use super::*; use super::*;
#[test] #[test]
@ -94,4 +96,21 @@ mod add_tests {
client.clean(); client.clean();
} }
#[test]
fn add_file_no_changes() {
// add a file push it and add it again
let (mut client, mut server) = init_test();
let _ = client.add_file("file1", "foo");
client.run_cmd_ok("add file1");
client.run_cmd_ok("push");
status_should_be_empty(&mut client);
client.run_cmd_ok("add file1");
status_should_be_empty(&mut client);
clean_test(client, &mut server)
}
} }

View File

@ -1,5 +1,5 @@
mod utils; mod utils;
use utils::{utils::*, server::ServerTest, client::ClientTest}; use utils::{utils::*};
#[cfg(test)] #[cfg(test)]
@ -8,10 +8,7 @@ mod pull_tests {
#[test] #[test]
fn simple_pull() { fn simple_pull() {
let id = get_random_test_id(); let (mut client, mut server) = init_test();
let mut server = ServerTest::new(id.clone());
server.init();
let mut client = ClientTest::new(id).init();
let _ = server.add_file("file1", "foo"); let _ = server.add_file("file1", "foo");
client.run_cmd_ok("pull"); client.run_cmd_ok("pull");
@ -19,16 +16,12 @@ mod pull_tests {
// tests // tests
assert!(client.has_file("file1", "foo")); assert!(client.has_file("file1", "foo"));
client.clean(); clean_test(client, &mut server);
server.clean();
} }
#[test] #[test]
fn simple_pull_directory() { fn simple_pull_directory() {
let id = get_random_test_id(); let (mut client, mut server) = init_test();
let mut server = ServerTest::new(id.clone());
server.init();
let mut client = ClientTest::new(id).init();
let _ = server.add_dir("dir"); let _ = server.add_dir("dir");
let _ = server.add_file("dir/file1", "foo"); let _ = server.add_file("dir/file1", "foo");
@ -38,7 +31,6 @@ mod pull_tests {
// tests // tests
assert!(client.has_file("dir/file1", "foo")); assert!(client.has_file("dir/file1", "foo"));
client.clean(); clean_test(client, &mut server);
server.clean();
} }
} }

View File

@ -1,15 +1,5 @@
mod utils; mod utils;
use utils::{utils::*, status_utils::*, server::ServerTest, client::ClientTest}; use utils::{utils::*, status_utils::*};
fn init_test() -> (ClientTest, ServerTest) {
let id = get_random_test_id();
let mut server = ServerTest::new(id.clone());
server.init();
let client = ClientTest::new(id).init();
(client, server)
}
#[cfg(test)] #[cfg(test)]
mod push_tests { mod push_tests {
@ -29,8 +19,7 @@ mod push_tests {
lines_should_not_contains(staged, "file1"); lines_should_not_contains(staged, "file1");
lines_should_not_contains(not_staged, "file1"); lines_should_not_contains(not_staged, "file1");
client.clean(); clean_test(client, &mut server);
server.clean();
} }
#[test] #[test]
@ -60,9 +49,7 @@ mod push_tests {
lines_should_not_contains(staged, "file1"); lines_should_not_contains(staged, "file1");
lines_should_not_contains(not_staged, "file1"); lines_should_not_contains(not_staged, "file1");
clean_test(client, &mut server);
client.clean();
server.clean();
} }
#[test] #[test]
@ -84,8 +71,7 @@ mod push_tests {
lines_should_not_contains(not_staged.clone(), "file2"); lines_should_not_contains(not_staged.clone(), "file2");
lines_should_not_contains(not_staged, "foo"); lines_should_not_contains(not_staged, "foo");
client.clean(); clean_test(client, &mut server);
server.clean();
} }
#[test] #[test]
@ -107,8 +93,7 @@ mod push_tests {
lines_should_not_contains(not_staged.clone(), "file2"); lines_should_not_contains(not_staged.clone(), "file2");
lines_should_not_contains(not_staged, "foo"); lines_should_not_contains(not_staged, "foo");
client.clean(); clean_test(client, &mut server);
server.clean();
} }
#[test] #[test]
@ -130,8 +115,7 @@ mod push_tests {
assert!(staged.len() == 0); assert!(staged.len() == 0);
assert!(not_staged.len() == 0); assert!(not_staged.len() == 0);
client.clean(); clean_test(client, &mut server);
server.clean();
} }
#[test] #[test]
@ -151,14 +135,34 @@ mod push_tests {
// remove it // remove it
let _ = client.remove_file("file1"); let _ = client.remove_file("file1");
client.run_cmd_ok("add file1"); client.run_cmd_ok("add file1");
dbg!(client.get_status());
client.run_cmd_ok("push"); client.run_cmd_ok("push");
// tests // tests
assert!(server.has_not_file("file1")); assert!(server.has_not_file("file1"));
status_should_be_empty(&mut client); status_should_be_empty(&mut client);
client.clean(); clean_test(client, &mut server);
server.clean(); }
#[test]
fn push_dir_deletion() {
let (mut client, mut server) = init_test();
// push dir and file2
let _ = client.add_dir("dir");
let _ = client.add_file("dir/file2", "bar");
client.run_cmd_ok("add dir");
client.run_cmd_ok("push");
// tests
assert!(server.has_file("dir/file2", "bar"));
// push deletion
let _ = client.remove_dir("dir");
client.run_cmd_ok("add dir");
client.run_cmd_ok("push");
assert!(server.has_not_dir("dir"));
clean_test(client, &mut server);
} }
} }

View File

@ -100,8 +100,15 @@ impl ClientTest {
let mut path = self.volume.clone(); let mut path = self.volume.clone();
path.push_str("/"); path.push_str("/");
path.push_str(name); path.push_str(name);
fs::remove_file(path)?;
Ok(())
}
fs::remove_file(name)?; pub fn remove_dir(&mut self, name: &str) -> std::io::Result<()> {
let mut path = self.volume.clone();
path.push_str("/");
path.push_str(name);
fs::remove_dir_all(path)?;
Ok(()) Ok(())
} }

View File

@ -36,3 +36,15 @@ pub fn has_not_file(full_path: PathBuf, file: &str, test_id: String) -> bool
return true; return true;
} }
#[cfg(test)]
pub fn has_not_dir(full_path: PathBuf, dir: &str, test_id: String) -> bool
{
if full_path.exists() {
println!("id: {}", test_id.clone());
eprintln!("Dir '{}' exists but it shouldn't", dir);
return false;
}
return true;
}

View File

@ -113,5 +113,11 @@ impl ServerTest {
let full_path = self.volume.clone().join(file); let full_path = self.volume.clone().join(file);
files_utils::has_not_file(full_path, file, self.test_id.clone()) files_utils::has_not_file(full_path, file, self.test_id.clone())
} }
pub fn has_not_dir(&mut self, dir: &str) -> bool {
let full_path = self.volume.clone().join(dir);
dbg!(full_path.clone());
files_utils::has_not_file(full_path, dir, self.test_id.clone())
}
} }

View File

@ -15,13 +15,13 @@ pub fn status_should_be_empty(client: &mut ClientTest) {
let (staged, not_staged) = client.get_status(); let (staged, not_staged) = client.get_status();
if staged.len() != 0 { if staged.len() != 0 {
eprintln!("id: {}", client.test_id.clone()); eprintln!("id: {}", client.test_id.clone());
eprintln!("Staged should be empty but has '{}'", staged.len()); eprintln!("Staged should be empty but has '{}' line(s)", staged.len());
assert!(staged.len() == 0); assert!(staged.len() == 0);
} }
if staged.len() != 0 { if staged.len() != 0 {
eprintln!("id: {}", client.test_id.clone()); eprintln!("id: {}", client.test_id.clone());
eprintln!("Not Staged should be empty but has '{}'", not_staged.len()); eprintln!("Not Staged should be empty but has '{}' line(s)", not_staged.len());
assert!(not_staged.len() == 0); assert!(not_staged.len() == 0);
} }
} }

View File

@ -1,4 +1,6 @@
use rand::{distributions::Alphanumeric, Rng}; use rand::{distributions::Alphanumeric, Rng};
use super::client::ClientTest;
use super::server::ServerTest;
#[cfg(test)] #[cfg(test)]
pub fn get_random_test_id() -> String { pub fn get_random_test_id() -> String {
@ -10,3 +12,20 @@ pub fn get_random_test_id() -> String {
id.push_str("_nextsync"); id.push_str("_nextsync");
id.to_owned() id.to_owned()
} }
#[cfg(test)]
pub fn init_test() -> (ClientTest, ServerTest) {
let id = get_random_test_id();
let mut server = ServerTest::new(id.clone());
server.init();
let client = ClientTest::new(id).init();
(client, server)
}
#[cfg(test)]
pub fn clean_test(client: ClientTest, server: &mut ServerTest) {
client.clean();
server.clean();
}