Compare commits

..

6 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
grimhilt
3420634bea chore: update clap 2024-03-31 22:17:26 +02:00
35 changed files with 444 additions and 334 deletions

122
Cargo.lock generated
View File

@@ -42,23 +42,51 @@ dependencies = [
] ]
[[package]] [[package]]
name = "ansi_term" name = "anstream"
version = "0.12.1" version = "0.6.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb"
dependencies = [ dependencies = [
"winapi", "anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"utf8parse",
] ]
[[package]] [[package]]
name = "atty" name = "anstyle"
version = "0.2.14" version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc"
[[package]]
name = "anstyle-parse"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
dependencies = [ dependencies = [
"hermit-abi 0.1.19", "utf8parse",
"libc", ]
"winapi",
[[package]]
name = "anstyle-query"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
dependencies = [
"anstyle",
"windows-sys 0.52.0",
] ]
[[package]] [[package]]
@@ -140,19 +168,37 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "2.34.0" version = "4.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0"
dependencies = [ dependencies = [
"ansi_term", "clap_builder",
"atty",
"bitflags 1.3.2",
"strsim",
"textwrap 0.11.0",
"unicode-width",
"vec_map",
] ]
[[package]]
name = "clap_builder"
version = "4.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_lex"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
[[package]]
name = "colorchoice"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[package]] [[package]]
name = "colored" name = "colored"
version = "2.1.0" version = "2.1.0"
@@ -386,15 +432,6 @@ version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "hermit-abi" name = "hermit-abi"
version = "0.2.6" version = "0.2.6"
@@ -706,7 +743,7 @@ dependencies = [
"rust-crypto", "rust-crypto",
"rustc-serialize", "rustc-serialize",
"tempfile", "tempfile",
"textwrap 0.16.1", "textwrap",
"tokio", "tokio",
"xml-rs", "xml-rs",
] ]
@@ -726,7 +763,7 @@ version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
dependencies = [ dependencies = [
"hermit-abi 0.2.6", "hermit-abi",
"libc", "libc",
] ]
@@ -1235,9 +1272,9 @@ dependencies = [
[[package]] [[package]]
name = "strsim" name = "strsim"
version = "0.8.0" version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01"
[[package]] [[package]]
name = "syn" name = "syn"
@@ -1289,15 +1326,6 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
dependencies = [
"unicode-width",
]
[[package]] [[package]]
name = "textwrap" name = "textwrap"
version = "0.16.1" version = "0.16.1"
@@ -1497,18 +1525,18 @@ dependencies = [
"percent-encoding", "percent-encoding",
] ]
[[package]]
name = "utf8parse"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]] [[package]]
name = "vcpkg" name = "vcpkg"
version = "0.2.15" version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "vec_map"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]] [[package]]
name = "version_check" name = "version_check"
version = "0.9.4" version = "0.9.4"

View File

@@ -10,7 +10,7 @@ rustc-serialize="0.3.25"
reqwest = { version = "0.12", features = ["stream", "json", "multipart"] } reqwest = { version = "0.12", features = ["stream", "json", "multipart"] }
tokio = { version = "1.37", features = ["full"] } tokio = { version = "1.37", features = ["full"] }
dotenv ="0.15.0" dotenv ="0.15.0"
clap = "2.34.0" clap = "4.5.4"
rust-crypto = "0.2.36" rust-crypto = "0.2.36"
colored = "2.1.0" colored = "2.1.0"
xml-rs = "0.8.19" xml-rs = "0.8.19"

View File

@@ -1,6 +1,5 @@
use std::io::Write; use std::io::Write;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
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};
@@ -8,8 +7,8 @@ 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};
pub struct AddArgs<'a> { pub struct AddArgs {
pub files: Option<Values<'a>>, pub files: Vec<String>,
pub force: bool, pub force: bool,
pub all: bool, pub all: bool,
} }
@@ -18,13 +17,13 @@ pub struct AddArgs<'a> {
pub fn add(args: AddArgs) { pub fn add(args: AddArgs) {
let mut pattern: String; let mut pattern: String;
let file_vec: Vec<&str> = match args.all { let file_vec: Vec<String> = match args.all {
true => { true => {
pattern = path_buf_to_string(repo_root()); pattern = path_buf_to_string(repo_root());
pattern.push_str("/*"); pattern.push_str("/*");
vec![&pattern] vec![pattern]
}, },
false => args.files.unwrap().collect(), false => args.files,
}; };
let mut added_files: Vec<String> = vec![]; let mut added_files: Vec<String> = vec![];
@@ -32,8 +31,7 @@ pub fn add(args: AddArgs) {
let rules = nextsyncignore::get_rules(); let rules = nextsyncignore::get_rules();
for file in file_vec { for file in file_vec {
dbg!(&file); let f = match normalize_relative(&file) {
let f = match normalize_relative(file) {
Ok(f) => f, Ok(f) => f,
Err(err) => { Err(err) => {
eprintln!("err: {} {}", file, err); eprintln!("err: {} {}", file, err);
@@ -44,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 => {
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); 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

@@ -2,7 +2,6 @@ use std::io;
use std::io::prelude::*; use std::io::prelude::*;
use std::fs::DirBuilder; use std::fs::DirBuilder;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use clap::Values;
use regex::Regex; use regex::Regex;
use crate::services::downloader::Downloader; use crate::services::downloader::Downloader;
use crate::utils::api::ApiProps; use crate::utils::api::ApiProps;
@@ -18,16 +17,16 @@ use crate::commands::init;
pub const DEPTH: &str = "3"; pub const DEPTH: &str = "3";
pub struct CloneArgs<'a> { pub struct CloneArgs {
pub remote: Values<'a>, pub remote: String,
pub depth: Option<String>, pub depth: Option<String>,
} }
pub fn clone(args: CloneArgs) { pub fn clone(args: CloneArgs) {
let d = DIR_PATH.lock().unwrap().clone(); let d = DIR_PATH.lock().unwrap().clone();
let url = args.remote.clone().next().unwrap(); let url = args.remote.clone();
let (host, tmp_user, dist_path_str) = get_url_props(url); let (host, tmp_user, dist_path_str) = get_url_props(&url);
let username = match tmp_user { let username = match tmp_user {
Some(u) => u.to_string(), Some(u) => u.to_string(),
None => { None => {

View File

@@ -1,12 +1,11 @@
use std::fs::OpenOptions; use std::fs::OpenOptions;
use clap::Values;
use std::io::{self, Write, BufRead, Seek, SeekFrom}; use std::io::{self, Write, BufRead, Seek, SeekFrom};
use crate::utils::{path, read}; use crate::utils::{path, read};
use std::collections::HashMap; use std::collections::HashMap;
pub struct ConfigSetArgs<'a> { pub struct ConfigSetArgs {
pub name: Option<Values<'a>>, pub name: String,
pub value: Option<Values<'a>>, pub value: String,
} }
pub fn config_set(args: ConfigSetArgs) { pub fn config_set(args: ConfigSetArgs) {
@@ -15,17 +14,14 @@ pub fn config_set(args: ConfigSetArgs) {
option_categories.insert("force_insecure", "core"); option_categories.insert("force_insecure", "core");
option_categories.insert("token", "core"); option_categories.insert("token", "core");
let name = args.name.unwrap().next().unwrap();
let value = args.value.unwrap().next().unwrap();
// get category of option // get category of option
let category = option_categories.get(name); let category = option_categories.get(args.name.as_str());
if category.is_none() { if category.is_none() {
eprintln!("fatal: '{}' is not a valid option.", name); eprintln!("fatal: '{}' is not a valid option.", args.name.clone());
std::process::exit(1); std::process::exit(1);
} }
let _ = write_option_in_cat(category.unwrap(), name, value); let _ = write_option_in_cat(category.unwrap(), &args.name, &args.value);
} }

View File

@@ -1,4 +1,3 @@
use clap::Values;
use crate::commands::clone::get_url_props; use crate::commands::clone::get_url_props;
use crate::services::api::ApiError::RequestError; use crate::services::api::ApiError::RequestError;
@@ -6,9 +5,9 @@ use crate::services::login::Login;
use crate::services::api_call::ApiCall; use crate::services::api_call::ApiCall;
use crate::commands::config; use crate::commands::config;
pub struct CredentialArgs<'a> { pub struct CredentialArgs {
pub username: Option<Values<'a>>, pub username: String,
pub password: Option<Values<'a>>, pub password: Option<String>,
} }
pub fn credential_add(args: CredentialArgs) { pub fn credential_add(args: CredentialArgs) {
@@ -23,9 +22,9 @@ pub fn credential_add(args: CredentialArgs) {
let (host, _, _) = get_url_props(&remote); let (host, _, _) = get_url_props(&remote);
// get username and password // get username and password
let username = args.username.unwrap().next().unwrap(); let username = args.username.to_owned();
let password = match args.password { let password = match args.password {
Some(mut pwd) => pwd.next().unwrap().to_owned(), Some(mut pwd) => pwd.to_owned(),
None => { None => {
println!("Please enter the password for {}: ", username); println!("Please enter the password for {}: ", username);
rpassword::read_password().unwrap() rpassword::read_password().unwrap()
@@ -34,7 +33,7 @@ pub fn credential_add(args: CredentialArgs) {
// get token // get token
let get_token = Login::new() let get_token = Login::new()
.set_auth(username, &password) .set_auth(&username, &password)
.set_host(Some(host)) .set_host(Some(host))
.send_login(); .send_login();

View File

@@ -1,7 +1,6 @@
use std::env; use std::env;
use std::fs::{DirBuilder, File}; use std::fs::{DirBuilder, File};
use std::path::PathBuf; use std::path::PathBuf;
use crate::utils::read::read_folder;
use crate::global::global::DIR_PATH; use crate::global::global::DIR_PATH;
pub fn init() { pub fn init() {
@@ -29,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");
@@ -60,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 = {
if obj.otype == String::from("blob") {
Blob::from_path(obj.path.clone())
.saved_remote_ts() .saved_remote_ts()
.parse::<i64>().unwrap(); .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

@@ -1,24 +1,14 @@
use clap::Values;
use crate::commands::config; use crate::commands::config;
use super::config::get_all_remote; use super::config::get_all_remote;
pub struct RemoteArgs<'a> { pub struct RemoteArgs {
pub name: Option<Values<'a>>, pub name: String,
pub url: Option<Values<'a>>, pub url: String,
} }
pub fn remote_add(args: RemoteArgs) { pub fn remote_add(args: RemoteArgs) {
if args.name.is_none() || args.url.is_none() { let _ = config::add_remote(&args.name, &args.url);
eprintln!("Missing argument: remote add command need a name and an url");
return;
}
let name = args.name.unwrap().next().unwrap();
let url = args.url.unwrap().next().unwrap();
let _ = config::add_remote(name, url);
} }
pub fn remote_list(verbose: bool) { pub fn remote_list(verbose: bool) {

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

@@ -1,4 +1,4 @@
use clap::{App, SubCommand}; use clap::Command;
mod subcommands; mod subcommands;
@@ -9,42 +9,41 @@ mod global;
mod store; mod store;
fn main() { fn main() {
let app = App::new("Nextsync") let app = Command::new("Nextsync")
.version("1.0") .version("1.0")
.author("grimhilt") .author("grimhilt")
.about("A git-line command line tool to interact with nextcloud") .about("A git-line command line tool to interact with nextcloud")
.setting(clap::AppSettings::SubcommandRequiredElseHelp) .subcommands([
.subcommand(subcommands::clone::create()) subcommands::clone::create(),
.subcommand(subcommands::init::create()) subcommands::init::create(),
.subcommand(subcommands::status::create()) subcommands::status::create(),
.subcommand(subcommands::add::create()) subcommands::add::create(),
.subcommand(subcommands::push::create()) subcommands::push::create(),
.subcommand(subcommands::reset::create()) subcommands::reset::create(),
.subcommand(subcommands::remote::create()) subcommands::remote::create(),
.subcommand(subcommands::config::create()) subcommands::config::create(),
.subcommand(subcommands::remote_diff::create()) subcommands::remote_diff::create(),
.subcommand(subcommands::pull::create()) subcommands::pull::create(),
.subcommand(subcommands::credential::create()) subcommands::credential::create(),
.subcommand( ]);
SubCommand::with_name("test") // .setting(clap::AppSettings::SubcommandRequiredElseHelp);
);
let matches = app.get_matches(); let matches = app.get_matches();
match matches.subcommand() { match matches.subcommand() {
("init", Some(args)) => subcommands::init::handler(args), Some(("init", args)) => subcommands::init::handler(args),
("status", Some(args)) => subcommands::status::handler(args), Some(("status", args)) => subcommands::status::handler(args),
("add", Some(args)) => subcommands::add::handler(args), Some(("add", args)) => subcommands::add::handler(args),
("reset", Some(_)) => commands::reset::reset(), Some(("reset", _)) => commands::reset::reset(),
("clone", Some(args)) => subcommands::clone::handler(args), Some(("clone", args)) => subcommands::clone::handler(args),
("push", Some(_)) => commands::push::push(), Some(("push", _)) => commands::push::push(),
("config", Some(args)) => subcommands::config::handler(args), Some(("config", args)) => subcommands::config::handler(args),
("remote-diff", Some(args)) => subcommands::remote_diff::handler(args), Some(("remote-diff", args)) => subcommands::remote_diff::handler(args),
("pull", Some(args)) => subcommands::pull::handler(args), Some(("pull", args)) => subcommands::pull::handler(args),
("remote", Some(args)) => subcommands::remote::handler(args), Some(("remote", args)) => subcommands::remote::handler(args),
("credential", Some(args)) => subcommands::credential::handler(args), Some(("credential", args)) => subcommands::credential::handler(args),
Some((_, _)) => {},
(_, _) => {}, None => {},
}; };
} }

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,38 +1,42 @@
use clap::{App, Arg, SubCommand, ArgMatches}; use clap::{Arg, ArgMatches, Command, ArgAction};
use crate::commands; use crate::commands;
use crate::commands::add::AddArgs; use crate::commands::add::AddArgs;
pub fn create() -> App<'static, 'static> { pub fn create() -> Command {
SubCommand::with_name("add") Command::new("add")
.arg( .arg(
Arg::with_name("files") Arg::new("files")
.required(true) .required_unless_present("all")
.conflicts_with("all") .conflicts_with("all")
.multiple(true) .num_args(1..)
.takes_value(true)
.value_name("FILE") .value_name("FILE")
.help("Files to add"), .help("Files to add"),
) )
.arg( .arg(
Arg::with_name("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::with_name("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")
} }
pub fn handler(args: &ArgMatches<'_>) { pub fn handler(args: &ArgMatches) {
commands::add::add(AddArgs { commands::add::add(AddArgs {
files: args.values_of("files"), files: match args.get_many::<String>("files") {
force: args.is_present("force"), None => vec![],
all: args.is_present("all"), Some(vals) => vals.map(|s| s.to_string()).collect(),
},
force: *args.get_one::<bool>("force").unwrap(),
all: *args.get_one::<bool>("all").unwrap(),
}); });
} }

View File

@@ -1,4 +1,4 @@
use clap::{App, Arg, SubCommand, ArgMatches}; use clap::{Arg, Command, ArgMatches};
// use textwrap::{fill, Options}; // use textwrap::{fill, Options};
use crate::commands::clone::CloneArgs; use crate::commands::clone::CloneArgs;
@@ -10,45 +10,43 @@ use crate::commands;
// "ok" // "ok"
// } // }
pub fn create() -> App<'static, 'static> { pub fn create() -> Command {
// let remote_desc = sized_str(&format!("The repository to clone from. See the NEXTSYNC URLS section below for more information on specifying repositories.")); // let remote_desc = sized_str(&format!("The repository to clone from. See the NEXTSYNC URLS section below for more information on specifying repositories."));
// let depth_desc = sized_str(&format!("Depth of the recursive fetch of object properties. This value should be lower when there are a lot of files per directory and higher when there are a lot of subdirectories with fewer files. (Default: {})", clone::DEPTH)); // let depth_desc = sized_str(&format!("Depth of the recursive fetch of object properties. This value should be lower when there are a lot of files per directory and higher when there are a lot of subdirectories with fewer files. (Default: {})", clone::DEPTH));
SubCommand::with_name("clone") Command::new("clone")
.arg( .arg(
Arg::with_name("remote") Arg::new("remote")
.required(true) .required(true)
.takes_value(true) .num_args(1)
.value_name("REMOTE") .value_name("REMOTE")
//.help(_desc) //.help(_desc)
) )
.arg( .arg(
Arg::with_name("depth") Arg::new("depth")
.short("d") .short('d')
.long("depth") .long("depth")
.required(false) .required(false)
.takes_value(true) .num_args(1)
//.help(&depth_desc) //.help(&depth_desc)
) )
.arg( .arg(
Arg::with_name("directory") Arg::new("directory")
.required(false) .required(false)
.takes_value(true) .num_args(1)
.value_name("DIRECTORY") .value_name("DIRECTORY")
) )
.about("Clone a repository into a new directory") .about("Clone a repository into a new directory")
.after_help("NEXTSYNC URLS\nThe following syntaxes may be used:\n\t- user@host.xz/path/to/repo\n\t- http[s]://host.xz/apps/files/?dir=/path/to/repo&fileid=111111\n\t- [http[s]://]host.xz/remote.php/dav/files/user/path/to/repo\n") .after_help("NEXTSYNC URLS\nThe following syntaxes may be used:\n\t- user@host.xz/path/to/repo\n\t- http[s]://host.xz/apps/files/?dir=/path/to/repo&fileid=111111\n\t- [http[s]://]host.xz/remote.php/dav/files/user/path/to/repo\n")
} }
pub fn handler(args: &ArgMatches<'_>) { pub fn handler(args: &ArgMatches) {
if let Some(val) = args.values_of("directory") { if let Some(val) = args.get_one::<String>("directory") {
global::global::set_dir_path(String::from(val.clone().next().unwrap())); global::global::set_dir_path(String::from(val.to_string()));
} }
if let Some(remote) = args.values_of("remote") { if let Some(remote) = args.get_one::<String>("remote") {
commands::clone::clone(CloneArgs { commands::clone::clone(CloneArgs {
remote, remote: remote.to_string(),
depth: args.values_of("depth").map( depth: args.get_one::<String>("depth").cloned(),
|mut val| val.next().unwrap().to_owned()
),
}); });
} }
} }

View File

@@ -1,32 +1,32 @@
use clap::{App, Arg, SubCommand, ArgMatches}; use clap::{Arg, Command, ArgMatches};
use crate::commands::config::ConfigSetArgs; use crate::commands::config::ConfigSetArgs;
use crate::commands; use crate::commands;
pub fn create() -> App<'static, 'static> { pub fn create() -> Command {
SubCommand::with_name("config") Command::new("config")
.about("Get and set repository or global options") .about("Get and set repository or global options")
.subcommand( .subcommand(
SubCommand::with_name("get") Command::new("get")
.about("Get the value of a configuration variable") .about("Get the value of a configuration variable")
.arg( .arg(
Arg::with_name("name") Arg::new("name")
.help("The name of the configuration variable") .help("The name of the configuration variable")
.required(true) .required(true)
.index(1) .index(1)
) )
) )
.subcommand( .subcommand(
SubCommand::with_name("set") Command::new("set")
.about("Set a configuration variable") .about("Set a configuration variable")
.arg( .arg(
Arg::with_name("name") Arg::new("name")
.help("The name of the configuration variable") .help("The name of the configuration variable")
.required(true) .required(true)
.index(1) .index(1)
) )
.arg( .arg(
Arg::with_name("value") Arg::new("value")
.help("The value to set") .help("The value to set")
.required(true) .required(true)
.index(2) .index(2)
@@ -34,20 +34,15 @@ pub fn create() -> App<'static, 'static> {
) )
} }
pub fn handler(args: &ArgMatches<'_>) { pub fn handler(args: &ArgMatches) {
match args.subcommand() { match args.subcommand() {
("set", Some(set_matches)) => { Some(("set", set_matches)) => {
commands::config::config_set(ConfigSetArgs { commands::config::config_set(ConfigSetArgs {
name: set_matches.values_of("name"), name: set_matches.get_one::<String>("name").unwrap().to_string(),
value: set_matches.values_of("value"), value: set_matches.get_one::<String>("value").unwrap().to_string(),
}); });
} }
_ => println!("Invalid or missing subcommand for 'config'"), _ => println!("Invalid or missing subcommand for 'config'"),
} }
// AddArgs {
// files: args.values_of("files"),
// force: args.is_present("force"),
// all: args.is_present("all"),
// });
} }

View File

@@ -1,24 +1,24 @@
use clap::{App, Arg, SubCommand, ArgMatches}; use clap::{Arg, Command, ArgMatches};
use crate::commands; use crate::commands;
use crate::commands::credential::CredentialArgs; use crate::commands::credential::CredentialArgs;
pub fn create() -> App<'static, 'static> { pub fn create() -> Command {
SubCommand::with_name("credential") Command::new("credential")
.about("Manage set of credentials") .about("Manage set of credentials")
.subcommand( .subcommand(
SubCommand::with_name("add") Command::new("add")
.arg( .arg(
Arg::with_name("username") Arg::new("username")
.required(true) .required(true)
.takes_value(true) .num_args(1)
.value_name("NAME") .value_name("NAME")
.help("The username used to connect to nextcloud"), .help("The username used to connect to nextcloud"),
) )
.arg( .arg(
Arg::with_name("password") Arg::new("password")
.required(false) .required(false)
.takes_value(true) .num_args(1)
.value_name("PASSWORD") .value_name("PASSWORD")
.help("The passowd used to connect to nextcloud (optional)"), .help("The passowd used to connect to nextcloud (optional)"),
) )
@@ -26,12 +26,12 @@ pub fn create() -> App<'static, 'static> {
) )
} }
pub fn handler(args: &ArgMatches<'_>) { pub fn handler(args: &ArgMatches) {
match args.subcommand() { match args.subcommand() {
("add", Some(add_matches)) => { Some(("add", add_matches)) => {
commands::credential::credential_add(CredentialArgs { commands::credential::credential_add(CredentialArgs {
username: add_matches.values_of("username"), username: add_matches.get_one::<String>("username").unwrap().to_string(),
password: add_matches.values_of("password"), password: add_matches.get_one::<String>("password").cloned(),
}); });
} }
_ => println!("Invalid or missing subcommand for 'credential'"), _ => println!("Invalid or missing subcommand for 'credential'"),

View File

@@ -1,23 +1,23 @@
use clap::{App, Arg, SubCommand, ArgMatches}; use clap::{Arg, Command, ArgMatches};
use crate::global; use crate::global;
use crate::commands; use crate::commands;
pub fn create() -> App<'static, 'static> { pub fn create() -> Command {
SubCommand::with_name("init") Command::new("init")
.arg( .arg(
Arg::with_name("directory") Arg::new("directory")
.required(false) .required(false)
.takes_value(true) .num_args(1)
.value_name("DIRECTORY") .value_name("DIRECTORY")
) )
.about("Create an empty Nextsync repository") .about("Create an empty Nextsync repository")
// Create an empty nextsync repository or reinitialize an existing one // Create an empty nextsync repository or reinitialize an existing one
} }
pub fn handler(args: &ArgMatches<'_>) { pub fn handler(args: &ArgMatches) {
if let Some(val) = args.values_of("directory") { if let Some(val) = args.get_one::<String>("directory") {
global::global::set_dir_path(String::from(val.clone().next().unwrap())); global::global::set_dir_path(val.to_string());
} }
commands::init::init(); commands::init::init();
} }

View File

@@ -1,23 +1,23 @@
use clap::{App, Arg, SubCommand, ArgMatches}; use clap::{Arg, Command, ArgMatches};
use crate::global; use crate::global;
use crate::commands; use crate::commands;
pub fn create() -> App<'static, 'static> { pub fn create() -> Command {
SubCommand::with_name("pull") Command::new("pull")
.arg( .arg(
Arg::with_name("path") Arg::new("path")
.required(false) .required(false)
.takes_value(true) .num_args(1)
.value_name("PATH") .value_name("PATH")
.help("The path to pull."), .help("The path to pull."),
) )
.about("Fetch and integrate changes from the nextcloud server.") .about("Fetch and integrate changes from the nextcloud server.")
} }
pub fn handler(args: &ArgMatches<'_>) { pub fn handler(args: &ArgMatches) {
if let Some(val) = args.values_of("path") { if let Some(val) = args.get_one::<String>("path") {
global::global::set_dir_path(String::from(val.clone().next().unwrap())); global::global::set_dir_path(val.to_string());
} }
commands::pull::pull(); commands::pull::pull();
} }

View File

@@ -1,6 +1,6 @@
use clap::{App, SubCommand}; use clap::Command;
pub fn create() -> App<'static, 'static> { pub fn create() -> Command {
SubCommand::with_name("push") Command::new("push")
.about("Push changes on nextcloud") .about("Push changes on nextcloud")
} }

View File

@@ -1,21 +1,21 @@
use clap::{App, Arg, SubCommand, ArgMatches}; use clap::{Arg, Command, ArgMatches, ArgAction};
use crate::commands; use crate::commands;
use crate::commands::remote::RemoteArgs; use crate::commands::remote::RemoteArgs;
pub fn create() -> App<'static, 'static> { pub fn create() -> Command {
SubCommand::with_name("remote") Command::new("remote")
.about("Manage set of tracked repositories") .about("Manage set of tracked repositories")
.subcommand( .subcommand(
SubCommand::with_name("add") Command::new("add")
.arg( .arg(
Arg::with_name("name") Arg::new("name")
.required(true) .required(true)
.index(1) .index(1)
.help("The name of the remote"), .help("The name of the remote"),
) )
.arg( .arg(
Arg::with_name("url") Arg::new("url")
.required(true) .required(true)
.index(2) .index(2)
.help("The url of the remote"), .help("The url of the remote"),
@@ -23,25 +23,24 @@ pub fn create() -> App<'static, 'static> {
.about("Add a new remote to this repository") .about("Add a new remote to this repository")
) )
.arg( .arg(
Arg::with_name("verbose") Arg::new("verbose")
.short("v") .short('v')
.long("verbose") .long("verbose")
.required(false) .action(ArgAction::SetTrue)
.takes_value(false)
.help("Be a little more verbose and show remote url after name.") .help("Be a little more verbose and show remote url after name.")
) )
} }
pub fn handler(args: &ArgMatches<'_>) { pub fn handler(args: &ArgMatches) {
match args.subcommand() { match args.subcommand() {
("add", Some(add_matches)) => { Some(("add", add_matches)) => {
commands::remote::remote_add(RemoteArgs { commands::remote::remote_add(RemoteArgs {
name: add_matches.values_of("name"), name: add_matches.get_one::<String>("name").unwrap().to_string(),
url: add_matches.values_of("url"), url: add_matches.get_one::<String>("url").unwrap().to_string(),
}); });
} }
_ => { _ => {
commands::remote::remote_list(args.is_present("verbose")); commands::remote::remote_list(*args.get_one::<bool>("verbose").unwrap());
} }
} }
} }

View File

@@ -1,14 +1,14 @@
use clap::{App, Arg, SubCommand, ArgMatches}; use clap::{Arg, Command, ArgMatches};
use crate::global; use crate::global;
use crate::commands; use crate::commands;
pub fn create() -> App<'static, 'static> { pub fn create() -> Command {
SubCommand::with_name("remote-diff") Command::new("remote-diff")
.arg( .arg(
Arg::with_name("path") Arg::new("path")
.required(false) .required(false)
.takes_value(true) .num_args(1)
.value_name("PATH") .value_name("PATH")
.help("The path to pull."), .help("The path to pull."),
) )
@@ -16,9 +16,9 @@ pub fn create() -> App<'static, 'static> {
} }
pub fn handler(args: &ArgMatches<'_>) { pub fn handler(args: &ArgMatches) {
if let Some(val) = args.values_of("path") { if let Some(val) = args.get_one::<String>("path") {
global::global::set_dir_path(String::from(val.clone().next().unwrap())); global::global::set_dir_path(val.to_string());
} }
commands::remote_diff::remote_diff(); commands::remote_diff::remote_diff();
} }

View File

@@ -1,6 +1,6 @@
use clap::{App, SubCommand}; use clap::Command;
pub fn create() -> App<'static, 'static> { pub fn create() -> Command {
SubCommand::with_name("reset") Command::new("reset")
.about("Clear the index") .about("Clear the index")
} }

View File

@@ -1,30 +1,30 @@
use clap::{App, Arg, SubCommand, ArgMatches}; use clap::{Arg, Command, ArgMatches};
use crate::global; use crate::global;
use crate::commands; use crate::commands;
use crate::commands::status::StatusArgs; use crate::commands::status::StatusArgs;
pub fn create() -> App<'static, 'static> { pub fn create() -> Command {
SubCommand::with_name("status") Command::new("status")
.arg( .arg(
Arg::with_name("directory") Arg::new("directory")
.required(false) .num_args(1)
.takes_value(true)
.value_name("DIRECTORY") .value_name("DIRECTORY")
) )
.arg( .arg(
Arg::with_name("nostyle") Arg::new("nostyle")
.long("nostyle") .long("nostyle")
.help("Status with minium information and style"), .help("Status with minium information and style"),
) )
.about("Show the working tree status") .about("Show the working tree status")
} }
pub fn handler(args: &ArgMatches<'_>) { pub fn handler(args: &ArgMatches) {
if let Some(val) = args.values_of("directory") { if let Some(val) = args.get_one::<String>("directory") {
global::global::set_dir_path(String::from(val.clone().next().unwrap())); global::global::set_dir_path(val.to_string());
} }
commands::status::status(StatusArgs { commands::status::status(StatusArgs {
nostyle: args.is_present("nostyle"), nostyle: args.contains_id("nostyle"),
}); });
} }

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();
}