Compare commits
3 Commits
49d83c2900
...
9956727cc9
Author | SHA1 | Date | |
---|---|---|---|
|
9956727cc9 | ||
|
ec6f1ebc96 | ||
|
198def6a83 |
7
Cargo.lock
generated
7
Cargo.lock
generated
@ -250,6 +250,12 @@ version = "0.3.55"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2"
|
checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "glob"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "h2"
|
name = "h2"
|
||||||
version = "0.3.19"
|
version = "0.3.19"
|
||||||
@ -523,6 +529,7 @@ dependencies = [
|
|||||||
"clap",
|
"clap",
|
||||||
"colored",
|
"colored",
|
||||||
"dotenv",
|
"dotenv",
|
||||||
|
"glob",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"regex",
|
"regex",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
|
@ -15,3 +15,4 @@ colored = "2.0.0"
|
|||||||
xml-rs = "0.8.0"
|
xml-rs = "0.8.0"
|
||||||
regex = "1.8.3"
|
regex = "1.8.3"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
|
glob = "0.3.1"
|
||||||
|
@ -1,42 +1,74 @@
|
|||||||
use clap::Values;
|
use clap::Values;
|
||||||
use crate::utils;
|
use crate::utils::{self, nextsyncignore};
|
||||||
use crate::store;
|
use crate::store;
|
||||||
use std::path::Path;
|
use std::path::{Path, PathBuf};
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
use glob::glob;
|
||||||
|
|
||||||
pub fn add(files: Values<'_>) {
|
pub struct AddArgs<'a> {
|
||||||
let root = match utils::path::nextsync_root() {
|
pub files: Values<'a>,
|
||||||
Some(path) => path,
|
pub force: bool,
|
||||||
None => {
|
}
|
||||||
eprintln!("fatal: not a nextsync repository (or any of the parent directories): .nextsync");
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut index_path = root.clone();
|
pub fn add(args: AddArgs) {
|
||||||
index_path.push(".nextsync");
|
let mut index_file = store::index::open();
|
||||||
let mut index_file = store::index::open(index_path);
|
let mut added_files: Vec<String> = vec![];
|
||||||
// todo avoid duplicate
|
|
||||||
// ./folder ./folder/file
|
|
||||||
|
|
||||||
let file_vec: Vec<&str> = files.collect();
|
let file_vec: Vec<&str> = args.files.collect();
|
||||||
for file in file_vec {
|
for file in file_vec {
|
||||||
let path = Path::new(file);
|
let path = Path::new(file);
|
||||||
println!("{}", file);
|
|
||||||
match path.exists() {
|
match path.exists() {
|
||||||
true => {
|
true => {
|
||||||
match writeln!(index_file, "{}", path.display()) {
|
if path.is_dir() {
|
||||||
Ok(()) => (),
|
added_files.push(String::from(path.to_str().unwrap()));
|
||||||
Err(err) => eprintln!("{}", err),
|
add_folder_content(path.to_path_buf(), &mut added_files);
|
||||||
|
} else {
|
||||||
|
added_files.push(String::from(path.to_str().unwrap()));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
false => {
|
false => {
|
||||||
match writeln!(index_file, "{}", path.display()) {
|
// todo deleted file/folder verif if exists
|
||||||
Ok(()) => (),
|
added_files.push(String::from(path.to_str().unwrap()));
|
||||||
Err(err) => eprintln!("{}", err),
|
|
||||||
}
|
|
||||||
// todo can be regex
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check ignored file if not forced
|
||||||
|
if !args.force {
|
||||||
|
let (ignored, ignored_files) = nextsyncignore::ignore_files(&mut added_files);
|
||||||
|
if ignored {
|
||||||
|
// todo multiple nextsyncignore
|
||||||
|
println!("The following paths are ignored by your .nextsyncignore file:");
|
||||||
|
for file in ignored_files {
|
||||||
|
println!("{}", file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// save all added_files in index
|
||||||
|
for file in added_files {
|
||||||
|
match writeln!(index_file, "{}", file) {
|
||||||
|
Ok(()) => (),
|
||||||
|
Err(err) => eprintln!("{}", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
drop(index_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_folder_content(path: PathBuf, added_files: &mut Vec<String>) {
|
||||||
|
let mut folders: Vec<PathBuf> = vec![];
|
||||||
|
folders.push(path);
|
||||||
|
|
||||||
|
while let Some(folder) = folders.pop() {
|
||||||
|
if let Ok(entries) = utils::read::read_folder(folder.clone()) {
|
||||||
|
for entry in entries {
|
||||||
|
let path_entry = PathBuf::from(entry);
|
||||||
|
if path_entry.is_dir() {
|
||||||
|
folders.push(path_entry.clone());
|
||||||
|
}
|
||||||
|
added_files.push(String::from(path_entry.to_str().unwrap()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -15,9 +15,7 @@ enum RemoveSide {
|
|||||||
Right,
|
Right,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
#[derive(PartialEq, Debug, Clone)]
|
||||||
#[derive(Debug)]
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub enum State {
|
pub enum State {
|
||||||
Default,
|
Default,
|
||||||
New,
|
New,
|
||||||
@ -39,8 +37,7 @@ pub fn status() {
|
|||||||
print_status(staged_objs, objs);
|
print_status(staged_objs, objs);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Obj {
|
pub struct Obj {
|
||||||
pub otype: String,
|
pub otype: String,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
@ -324,10 +321,16 @@ mod tests {
|
|||||||
let hash4 = hasher.result_str();
|
let hash4 = hasher.result_str();
|
||||||
hasher.reset();
|
hasher.reset();
|
||||||
|
|
||||||
let mut hashes = HashSet::new();
|
let mut hashes = HashMap::new();
|
||||||
hashes.insert(hash1.clone());
|
let default_obj = Obj {
|
||||||
hashes.insert(hash2.clone());
|
otype: String::from("tree"),
|
||||||
hashes.insert(hash4.clone());
|
name: String::from("test"),
|
||||||
|
path: PathBuf::from(""),
|
||||||
|
state: State::Default,
|
||||||
|
};
|
||||||
|
hashes.insert(hash1.clone(), default_obj.clone());
|
||||||
|
hashes.insert(hash2.clone(), default_obj.clone());
|
||||||
|
hashes.insert(hash4.clone(), default_obj.clone());
|
||||||
|
|
||||||
let mut objects: Vec<String> = vec![];
|
let mut objects: Vec<String> = vec![];
|
||||||
objects.push(String::from("file1"));
|
objects.push(String::from("file1"));
|
||||||
@ -336,7 +339,7 @@ mod tests {
|
|||||||
remove_duplicate(&mut hashes, &mut objects, RemoveSide::Both);
|
remove_duplicate(&mut hashes, &mut objects, RemoveSide::Both);
|
||||||
dbg!(hashes.clone());
|
dbg!(hashes.clone());
|
||||||
dbg!(objects.clone());
|
dbg!(objects.clone());
|
||||||
assert_eq!(hashes.contains(&hash4), true);
|
assert_eq!(hashes.contains_key(&hash4), true);
|
||||||
assert_eq!(hashes.len(), 1);
|
assert_eq!(hashes.len(), 1);
|
||||||
assert_eq!(objects, vec!["file3"]);
|
assert_eq!(objects, vec!["file3"]);
|
||||||
}
|
}
|
||||||
|
12
src/main.rs
12
src/main.rs
@ -1,4 +1,5 @@
|
|||||||
use clap::{App, Arg, SubCommand};
|
use clap::{App, Arg, SubCommand};
|
||||||
|
use crate::commands::add::AddArgs;
|
||||||
mod commands;
|
mod commands;
|
||||||
mod utils;
|
mod utils;
|
||||||
mod services;
|
mod services;
|
||||||
@ -55,6 +56,12 @@ fn main() {
|
|||||||
.value_name("FILE")
|
.value_name("FILE")
|
||||||
.help("Files to add"),
|
.help("Files to add"),
|
||||||
)
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("force")
|
||||||
|
.short("f")
|
||||||
|
.long("force")
|
||||||
|
.help("Allow adding otherwise ignored files."),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
SubCommand::with_name("config")
|
SubCommand::with_name("config")
|
||||||
@ -85,7 +92,10 @@ fn main() {
|
|||||||
commands::status::status();
|
commands::status::status();
|
||||||
} else if let Some(matches) = matches.subcommand_matches("add") {
|
} else if let Some(matches) = matches.subcommand_matches("add") {
|
||||||
if let Some(files) = matches.values_of("files") {
|
if let Some(files) = matches.values_of("files") {
|
||||||
commands::add::add(files);
|
commands::add::add(AddArgs {
|
||||||
|
files: files,
|
||||||
|
force: matches.is_present("force"),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} else if let Some(_) = matches.subcommand_matches("reset") {
|
} else if let Some(_) = matches.subcommand_matches("reset") {
|
||||||
commands::reset::reset();
|
commands::reset::reset();
|
||||||
|
@ -11,7 +11,12 @@ pub fn _read_only(mut path: PathBuf) -> File {
|
|||||||
.open(path).expect("Cannot open index file")
|
.open(path).expect("Cannot open index file")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn open(mut path: PathBuf) -> File {
|
pub fn open() -> File {
|
||||||
|
let mut path = match path::nextsync() {
|
||||||
|
Some(p) => p,
|
||||||
|
None => todo!(),
|
||||||
|
};
|
||||||
|
|
||||||
path.push("index");
|
path.push("index");
|
||||||
OpenOptions::new()
|
OpenOptions::new()
|
||||||
.read(true)
|
.read(true)
|
||||||
@ -27,12 +32,11 @@ pub fn read_line(mut path: PathBuf) -> io::Result<io::Lines<io::BufReader<File>>
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn rm_line(line: &str) -> io::Result<()> {
|
pub fn rm_line(line: &str) -> io::Result<()> {
|
||||||
let mut root = match path::nextsync_root() {
|
let mut root = match path::nextsync() {
|
||||||
Some(path) => path,
|
Some(path) => path,
|
||||||
None => todo!(),
|
None => todo!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
root.push(".nextsync");
|
|
||||||
root.push("index");
|
root.push("index");
|
||||||
read::rm_line(root, line)?;
|
read::rm_line(root, line)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
pub mod path;
|
pub mod path;
|
||||||
pub mod read;
|
pub mod read;
|
||||||
|
pub mod nextsyncignore;
|
||||||
|
144
src/utils/nextsyncignore.rs
Normal file
144
src/utils/nextsyncignore.rs
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
use crate::utils::{read, path};
|
||||||
|
use regex::Regex;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::{Cursor, Lines, BufReader, empty, BufRead};
|
||||||
|
|
||||||
|
fn read_lines() -> Result<Vec<String>, ()> {
|
||||||
|
if let Some(path) = path::nextsyncignore() {
|
||||||
|
let file = match File::open(path) {
|
||||||
|
Ok(buffer) => buffer,
|
||||||
|
Err(_) => return Err(()),
|
||||||
|
};
|
||||||
|
let reader = BufReader::new(file);
|
||||||
|
let mut lines = vec![];
|
||||||
|
for line in reader.lines() {
|
||||||
|
if let Ok(l) = line {
|
||||||
|
lines.push(normalize_rule(l.clone()));
|
||||||
|
} else {
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Ok(lines);
|
||||||
|
}
|
||||||
|
Ok(vec![])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ignore_files(files: &mut Vec<String>) -> (bool, Vec<String>) {
|
||||||
|
let mut ignored_f = vec![];
|
||||||
|
if let Some(path) = path::nextsyncignore() {
|
||||||
|
if let Ok(lines) = read_lines() {
|
||||||
|
files.retain(|file| !ignore_file(file, lines.clone(), &mut ignored_f));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(ignored_f.len() > 0, ignored_f)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn normalize_rule(l: String) -> String {
|
||||||
|
let mut line = l;
|
||||||
|
|
||||||
|
// define / as root
|
||||||
|
let re = Regex::new("^(!)?/").unwrap();
|
||||||
|
line = re.replace_all(&line, "$1^").to_string();
|
||||||
|
|
||||||
|
// escape .
|
||||||
|
let re = Regex::new(r"\.").unwrap();
|
||||||
|
line = re.replace_all(&line, r"\.").to_string();
|
||||||
|
|
||||||
|
// add . before *
|
||||||
|
let re = Regex::new(r"\*").unwrap();
|
||||||
|
line = re.replace_all(&line, r".*").to_string();
|
||||||
|
|
||||||
|
// add optional .* at the end of /
|
||||||
|
let re = Regex::new(r"/$").unwrap();
|
||||||
|
line = re.replace_all(&line, r"(/.*)?").to_string();
|
||||||
|
line
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ignore_file(path: &String, lines: Vec<String>, ignored_f: &mut Vec<String>) -> bool {
|
||||||
|
let mut ignored = false;
|
||||||
|
for mut line in lines {
|
||||||
|
if line.starts_with("!") {
|
||||||
|
if !ignored {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let strip_line = line.strip_prefix("!").unwrap();
|
||||||
|
let re = Regex::new(&strip_line).unwrap();
|
||||||
|
if re.is_match(path) {
|
||||||
|
ignored = false;
|
||||||
|
}
|
||||||
|
} else if !ignored {
|
||||||
|
let re = Regex::new(&line).unwrap();
|
||||||
|
if re.is_match(path) {
|
||||||
|
ignored = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ignored {
|
||||||
|
ignored_f.push(path.to_string());
|
||||||
|
}
|
||||||
|
ignored
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ignore_files() {
|
||||||
|
let lines_data = b"*.log\nexclude\n/logs/\n/build/target/\n**/*.swp\nsecret/\n";
|
||||||
|
let cursor = Cursor::new(lines_data);
|
||||||
|
let reader = BufReader::new(cursor);
|
||||||
|
let mut lines = vec![];
|
||||||
|
for line in reader.lines() {
|
||||||
|
if let Ok(l) = line {
|
||||||
|
lines.push(normalize_rule(l.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut ignf = vec![];
|
||||||
|
assert_eq!(ignore_file(&String::from("error.log"), lines.clone(), &mut ignf), true);
|
||||||
|
assert_eq!(ignore_file(&String::from("./error.log"), lines.clone(), &mut ignf), true);
|
||||||
|
assert_eq!(ignore_file(&String::from("dir/error.log"), lines.clone(), &mut ignf), true);
|
||||||
|
|
||||||
|
assert_eq!(ignore_file(&String::from("exclude"), lines.clone(), &mut ignf), true);
|
||||||
|
assert_eq!(ignore_file(&String::from("dir/exclude"), lines.clone(), &mut ignf), true);
|
||||||
|
|
||||||
|
assert_eq!(ignore_file(&String::from("logs/dir/file2"), lines.clone(), &mut ignf), true);
|
||||||
|
assert_eq!(ignore_file(&String::from("dir/logs/dir/file2"), lines.clone(), &mut ignf), false);
|
||||||
|
|
||||||
|
assert_eq!(ignore_file(&String::from("build/target/file1"), lines.clone(), &mut ignf), true);
|
||||||
|
assert_eq!(ignore_file(&String::from("build/target/dir/file1"), lines.clone(), &mut ignf), true);
|
||||||
|
assert_eq!(ignore_file(&String::from("build"), lines.clone(), &mut ignf), false);
|
||||||
|
assert_eq!(ignore_file(&String::from("build/target"), lines.clone(), &mut ignf), true);
|
||||||
|
assert_eq!(ignore_file(&String::from("dir/build/target"), lines.clone(), &mut ignf), false);
|
||||||
|
|
||||||
|
assert_eq!(ignore_file(&String::from("dir/file.swp"), lines.clone(), &mut ignf), true);
|
||||||
|
assert_eq!(ignore_file(&String::from(".swp"), lines.clone(), &mut ignf), false);
|
||||||
|
|
||||||
|
assert_eq!(ignore_file(&String::from("secret"), lines.clone(), &mut ignf), true);
|
||||||
|
assert_eq!(ignore_file(&String::from("dir/secret"), lines.clone(), &mut ignf), true);
|
||||||
|
assert_eq!(ignore_file(&String::from("dir/secret/file"), lines.clone(), &mut ignf), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_ignore_files_negation() {
|
||||||
|
let lines_data = b"*\n!*.log\n!*log.*\n";
|
||||||
|
let cursor = Cursor::new(lines_data);
|
||||||
|
let reader = BufReader::new(cursor);
|
||||||
|
let mut lines = vec![];
|
||||||
|
for line in reader.lines() {
|
||||||
|
if let Ok(l) = line {
|
||||||
|
lines.push(normalize_rule(l.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut ignf = vec![];
|
||||||
|
assert_eq!(ignore_file(&String::from("file"), lines.clone(), &mut ignf), true);
|
||||||
|
assert_eq!(ignore_file(&String::from("dir/file"), lines.clone(), &mut ignf), true);
|
||||||
|
assert_eq!(ignore_file(&String::from("file.log"), lines.clone(), &mut ignf), false);
|
||||||
|
assert_eq!(ignore_file(&String::from("log.file"), lines.clone(), &mut ignf), false);
|
||||||
|
assert_eq!(ignore_file(&String::from("dir/file.log"), lines.clone(), &mut ignf), false);
|
||||||
|
assert_eq!(ignore_file(&String::from("dir/log.file"), lines.clone(), &mut ignf), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -58,3 +58,15 @@ pub fn objects() -> Option<PathBuf> {
|
|||||||
}
|
}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn nextsyncignore() -> Option<PathBuf> {
|
||||||
|
if let Some(mut path) = nextsync_root() {
|
||||||
|
path.push(".nextsyncignore");
|
||||||
|
if path.exists() {
|
||||||
|
return Some(path);
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user