From 5f0550bef4ae67225e2e08b6eedf322928dad523 Mon Sep 17 00:00:00 2001 From: m4rio <92288535+mario-eth@users.noreply.github.com> Date: Sun, 30 Jun 2024 05:08:45 -0700 Subject: [PATCH] Improved the way gitignore/soldeerignore works (#75) * Improved the way gitignore/soldeerignore works (now it works like gitignore). --- Cargo.lock | 12 ++ Cargo.toml | 1 + src/lib.rs | 7 +- src/versioning.rs | 338 +++++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 334 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e4366dd..ef7093b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1546,6 +1546,7 @@ dependencies = [ "uuid", "walkdir", "yansi", + "yash-fnmatch", "zip 2.1.3", "zip-extract", ] @@ -2138,6 +2139,17 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" +[[package]] +name = "yash-fnmatch" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "697c20b479d2e6419e9a073bfdd20e90cbd8540d6c683ee46712e13de650e54f" +dependencies = [ + "regex", + "regex-syntax", + "thiserror", +] + [[package]] name = "zeroize" version = "1.7.0" diff --git a/Cargo.toml b/Cargo.toml index a9903e7..280dfbd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,7 @@ toml_edit = "0.22.13" uuid = {version = "1.8.0", features = ["serde", "v4"]} walkdir = "2.5.0" yansi = "1.0.1" +yash-fnmatch = "1.1.1" zip = {version = "2.1.3", default-features = false, features = ["deflate"]} zip-extract = "0.1.3" diff --git a/src/lib.rs b/src/lib.rs index 96ab1cd..1a0bf50 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -570,6 +570,11 @@ libs = ["dependencies"] fn soldeer_push_dry_run() { let _ = remove_dir_all(DEPENDENCY_DIR.clone()); let _ = remove_file(LOCK_FILE.clone()); + // in case this exists we clean it before setting up the tests + let path_dependency = env::current_dir().unwrap().join("test").join("test.zip"); + if path_dependency.exists() { + let _ = remove_file(&path_dependency); + } let command = Subcommands::Push(Push { dependency: "@test~1.1".to_string(), @@ -587,8 +592,6 @@ libs = ["dependencies"] } } - let path_dependency = env::current_dir().unwrap().join("test").join("test.zip"); - assert!(Path::new(&path_dependency).exists()); let archive = File::open(&path_dependency); let archive = ZipArchive::new(archive.unwrap()); diff --git a/src/versioning.rs b/src/versioning.rs index cc8fcec..151bec3 100644 --- a/src/versioning.rs +++ b/src/versioning.rs @@ -36,6 +36,10 @@ use std::{ }; use walkdir::WalkDir; use yansi::Paint; +use yash_fnmatch::{ + without_escape, + Pattern, +}; use zip::{ write::SimpleFileOptions, CompressionMethod, @@ -68,7 +72,7 @@ pub async fn push_version( )) ); - let files_to_copy: Vec = filter_filles_to_copy(&root_directory_path); + let files_to_copy: Vec = filter_files_to_copy(&root_directory_path); let zip_archive = zip_file( dependency_name, dependency_version, @@ -162,7 +166,7 @@ fn zip_file( Ok(zip_file_path) } -fn filter_filles_to_copy(root_directory_path: &Path) -> Vec { +fn filter_files_to_copy(root_directory_path: &Path) -> Vec { let ignore_files: Vec = read_ignore_file(); let root_directory: &str = &(root_directory_path.to_str().unwrap().to_owned() + "/"); @@ -171,27 +175,27 @@ fn filter_filles_to_copy(root_directory_path: &Path) -> Vec { .into_iter() .filter_map(|e| e.ok()) { - let file_name = entry - .path() - .to_str() - .unwrap() - .to_string() - .replace(root_directory, ""); - if file_name.is_empty() { + let is_dir = entry.path().is_dir(); + let file_path: String = entry.path().to_str().unwrap().to_string(); + if file_path.is_empty() || is_dir { continue; } let mut found: bool = false; for ignore_file in ignore_files.iter() { - if file_name.contains(ignore_file) { + let p = Pattern::parse(without_escape(ignore_file)).unwrap(); + let exists = p.find(&file_path); + if exists.is_some() { found = true; break; } } + if found { continue; } + files_to_copy.push(FilePair { - name: file_name, + name: String::from(entry.path().file_name().unwrap().to_str().unwrap()), path: entry.path().to_str().unwrap().to_string(), }); } @@ -199,21 +203,38 @@ fn filter_filles_to_copy(root_directory_path: &Path) -> Vec { } fn read_ignore_file() -> Vec { - let gitignore = get_current_working_dir().join(".gitignore"); - let soldeerignore = get_current_working_dir().join(".soldeerignore"); + let mut current_dir = get_current_working_dir(); + if cfg!(test) { + current_dir = get_current_working_dir().join("test"); + } + let gitignore = current_dir.join(".gitignore"); + let soldeerignore = current_dir.join(".soldeerignore"); + + let mut files: Vec = Vec::new(); - if !gitignore.exists() && !soldeerignore.exists() { - return Vec::new(); + if soldeerignore.exists() { + let contents = read_file_to_string(&soldeerignore.to_str().unwrap().to_string()); + let current_read_file = contents.lines(); + files.append(&mut escape_lines(current_read_file.collect())); } - let gitignore = read_file_to_string(&gitignore.to_str().unwrap().to_string()); - let soldeerignore = read_file_to_string(&soldeerignore.to_str().unwrap().to_string()); + if gitignore.exists() { + let contents = read_file_to_string(&gitignore.to_str().unwrap().to_string()); + let current_read_file = contents.lines(); + files.append(&mut escape_lines(current_read_file.collect())); + } + + files +} - soldeerignore - .lines() - .chain(gitignore.lines()) - .map(ToString::to_string) - .collect() +fn escape_lines(lines: Vec<&str>) -> Vec { + let mut escaped_liens: Vec = vec![]; + for line in lines { + if !line.trim().is_empty() { + escaped_liens.push(line.trim().to_string()); + } + } + escaped_liens } async fn push_to_repo( @@ -325,3 +346,276 @@ async fn push_to_repo( } Ok(()) } + +#[cfg(test)] +mod tests { + use std::fs::{ + self, + create_dir_all, + remove_dir_all, + remove_file, + }; + + use serial_test::serial; + + use super::*; + use rand::{ + distributions::Alphanumeric, + Rng, + }; + + #[test] + #[serial] + fn read_ignore_files_only_soldeerignore() { + let soldeerignore = define_ignore_file(false); + let gitignore = define_ignore_file(true); + let _ = remove_file(gitignore); + let ignore_contents = r#" +*.toml +*.zip + "#; + write_to_ignore(&soldeerignore, ignore_contents); + let expected_results: Vec = vec!["*.toml".to_string(), "*.zip".to_string()]; + + assert_eq!(read_ignore_file(), expected_results); + let _ = remove_file(soldeerignore); + } + + #[test] + #[serial] + fn read_ignore_files_only_gitignore() { + let soldeerignore = define_ignore_file(false); + let gitignore = define_ignore_file(true); + let _ = remove_file(soldeerignore); + + let ignore_contents = r#" +*.toml +*.zip + "#; + write_to_ignore(&gitignore, ignore_contents); + let expected_results: Vec = vec!["*.toml".to_string(), "*.zip".to_string()]; + + assert_eq!(read_ignore_file(), expected_results); + let _ = remove_file(gitignore); + } + + #[test] + #[serial] + fn read_ignore_files_both_gitignore_soldeerignore() { + let soldeerignore = define_ignore_file(false); + let gitignore = define_ignore_file(true); + let _ = remove_file(&soldeerignore); + let _ = remove_file(&gitignore); + + let ignore_contents_git = r#" +*.toml +*.zip + "#; + write_to_ignore(&gitignore, ignore_contents_git); + + let ignore_contents_soldeer = r#" + *.sol + *.txt + "#; + write_to_ignore(&soldeerignore, ignore_contents_soldeer); + + let expected_results: Vec = vec![ + "*.sol".to_string(), + "*.txt".to_string(), + "*.toml".to_string(), + "*.zip".to_string(), + ]; + + assert_eq!(read_ignore_file(), expected_results); + let _ = remove_file(gitignore); + let _ = remove_file(soldeerignore); + } + + #[test] + #[serial] + fn filter_only_files_success() { + let target_dir = get_current_working_dir().join("test").join("test_push"); + let _ = remove_dir_all(&target_dir); + let _ = create_dir_all(&target_dir); + + let soldeerignore = define_ignore_file(false); + let gitignore = define_ignore_file(true); + let _ = remove_file(soldeerignore); + + let mut ignored_files = vec![]; + let mut filtered_files = vec![]; + ignored_files.push(create_random_file(&target_dir, "toml".to_string())); + ignored_files.push(create_random_file(&target_dir, "zip".to_string())); + ignored_files.push(create_random_file(&target_dir, "toml".to_string())); + filtered_files.push(create_random_file(&target_dir, "txt".to_string())); + + let ignore_contents_git = r#" +*.toml +*.zip + "#; + write_to_ignore(&gitignore, ignore_contents_git); + + let result = filter_files_to_copy(&target_dir); + assert_eq!(filtered_files.len(), result.len()); + let file = Path::new(&filtered_files[0]); + assert_eq!( + String::from(file.file_name().unwrap().to_str().unwrap()), + result[0].name + ); + + let _ = remove_file(gitignore); + let _ = remove_dir_all(target_dir); + } + + #[test] + #[serial] + fn filter_files_and_dir_success() { + let target_dir = get_current_working_dir().join("test").join("test_push"); + let _ = remove_dir_all(&target_dir); + let _ = create_dir_all(&target_dir); + + let soldeerignore = define_ignore_file(false); + let gitignore = define_ignore_file(true); + let _ = remove_file(soldeerignore); + + // divide ignored vs filtered files to check them later + let mut ignored_files = vec![]; + let mut filtered_files = vec![]; + + // initial dir to test the ignore + let target_dir = get_current_working_dir().join("test").join("test_push"); + + // we create various test files structure + // - test_push/ + // --- random_dir/ <= not ignored + // --- --- random.toml <= ignored + // --- --- random.zip <= not ignored + // --- broadcast/ <= not ignored + // --- --- random.toml <= ignored + // --- --- random.zip <= not ignored + // --- --- 31337/ <= ignored + // --- --- --- random.toml <= ignored + // --- --- --- random.zip <= ignored + // --- --- random_dir_in_broadcast/ <= not ignored + // --- --- --- random.zip <= not ignored + // --- --- --- random.toml <= ignored + // --- --- --- dry_run/ <= ignored + // --- --- --- --- zip <= ignored + // --- --- --- --- toml <= ignored + + let random_dir = PathBuf::from(create_random_directory(&target_dir, "".to_string())); + let broadcast_dir = PathBuf::from(create_random_directory( + &target_dir, + "broadcast".to_string(), + )); + + let the_31337_dir = + PathBuf::from(create_random_directory(&broadcast_dir, "31337".to_string())); + let random_dir_in_broadcast = + PathBuf::from(create_random_directory(&broadcast_dir, "".to_string())); + let dry_run_dir = PathBuf::from(create_random_directory( + &random_dir_in_broadcast, + "dry_run".to_string(), + )); + + ignored_files.push(create_random_file(&random_dir, "toml".to_string())); + filtered_files.push(create_random_file(&random_dir, "zip".to_string())); + + ignored_files.push(create_random_file(&broadcast_dir, "toml".to_string())); + filtered_files.push(create_random_file(&broadcast_dir, "zip".to_string())); + + ignored_files.push(create_random_file(&the_31337_dir, "toml".to_string())); + ignored_files.push(create_random_file(&the_31337_dir, "zip".to_string())); + + filtered_files.push(create_random_file( + &random_dir_in_broadcast, + "zip".to_string(), + )); + filtered_files.push(create_random_file( + &random_dir_in_broadcast, + "toml".to_string(), + )); + + ignored_files.push(create_random_file(&dry_run_dir, "zip".to_string())); + ignored_files.push(create_random_file(&dry_run_dir, "toml".to_string())); + + let ignore_contents_git = r#" +*.toml +!/broadcast +/broadcast/31337/ +/broadcast/*/dry_run/ + "#; + write_to_ignore(&gitignore, ignore_contents_git); + + let result = filter_files_to_copy(&target_dir); + + // for each result we just just to see if a file (not a dir) is in the filtered results + for res in result { + if PathBuf::from(&res.path).is_dir() { + continue; + } + + assert!(filtered_files.contains(&res.path)); + } + + let _ = remove_file(gitignore); + let _ = remove_dir_all(target_dir); + } + + fn define_ignore_file(git: bool) -> PathBuf { + let mut target = ".soldeerignore"; + if git { + target = ".gitignore"; + } + get_current_working_dir().join("test").join(target) + } + + fn write_to_ignore(target_file: &PathBuf, content: &str) { + if target_file.exists() { + let _ = remove_file(target_file); + } + let mut file: std::fs::File = fs::OpenOptions::new() + .create_new(true) + .write(true) + .open(target_file) + .unwrap(); + if let Err(e) = write!(file, "{}", content) { + eprintln!("Couldn't write to the config file: {}", e); + } + } + + fn create_random_file(target_dir: &Path, extension: String) -> String { + let s: String = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(7) + .map(char::from) + .collect(); + let target = target_dir.join(format!("random{}.{}", s, extension)); + let mut file: std::fs::File = fs::OpenOptions::new() + .create_new(true) + .write(true) + .open(&target) + .unwrap(); + if let Err(e) = write!(file, "this is a test file") { + eprintln!("Couldn't write to the config file: {}", e); + } + String::from(target.to_str().unwrap()) + } + fn create_random_directory(target_dir: &Path, name: String) -> String { + let s: String = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(7) + .map(char::from) + .collect(); + + if name.is_empty() { + let target = target_dir.join(format!("random{}", s)); + let _ = create_dir_all(&target); + return String::from(target.to_str().unwrap()); + } else { + let target = target_dir.join(name); + let _ = create_dir_all(&target); + return String::from(target.to_str().unwrap()); + } + } +}