From a798525fabc19ba423420f5e7c6ce4a66d419e8e Mon Sep 17 00:00:00 2001 From: Thomas Showers Date: Fri, 14 May 2021 15:08:41 -0300 Subject: [PATCH 1/4] handle ancestor/decendant .gitignores and repo root --- src/gitignore_filter.rs | 136 ++++++++++++++++++++++++++++++++-------- 1 file changed, 110 insertions(+), 26 deletions(-) diff --git a/src/gitignore_filter.rs b/src/gitignore_filter.rs index ba12921..3d1b7e6 100644 --- a/src/gitignore_filter.rs +++ b/src/gitignore_filter.rs @@ -1,6 +1,11 @@ use crate::rocket_watcher; +use std::fs; use std::path::{Path, PathBuf}; +// todo: are these an overridable conventions we need to respect? +const GITIGNORE_FILENAME: &str = ".gitignore"; +const GIT_METADATA_DIR_NAME: &str = ".git"; + pub struct GitignoreFilter { ignorers: Vec, } @@ -11,44 +16,123 @@ impl GitignoreFilter { } pub fn build(mut dir: PathBuf) -> GitignoreFilter { - // todo: is this an overridable convention we need to respect? - const GITIGNORE_FILENAME: &str = ".gitignore"; + // Get all the child gitignores then add the parents. - let mut ignorers = Vec::new(); + let mut ignorers = get_all_gitignores(dir.as_path()); - while dir.parent() != None { - let mut builder = ignore::gitignore::GitignoreBuilder::new(dir.as_path()); + // We only want ancestors up to and including the root of the repo. If we don't find a root + // we'll ignore all the ancestors we found. + let mut ancestors = Vec::new(); + let mut found_root = false; + while dir.pop() { + match get_gitignore(dir.as_path()) { + Some(ignorer) => ancestors.push(ignorer), + None => {} + } + if is_git_repo_root(&dir) { + found_root = true; + break; + } + } + if found_root { + ignorers.append(&mut ancestors); + } + + return GitignoreFilter::new(ignorers); + } +} - // Update the dir to have the file name instead of making a copy and we'll .pop() twice to - // traverse upward to the parent dir. - dir.push(GITIGNORE_FILENAME); - println!( - "looking for {}", - dir.to_str() - .expect("if you use an OS where paths aren't unicode, your mom's a hoe") - ); - let path = dir.as_path(); - println!("path = {:?}", path); +// todo: Handle errors better. +// Right now any error is (logged, then) treated like there is no .gitignore. We should probably +// return any non-not-found errors instead since we're not sure we're accurately representing the +// filters described by the .gitignores. - match builder.add(path) { - None => match builder.build() { - Ok(ignorer) => ignorers.push(ignorer), - Err(err) => println!("error building ignorer for {:?}: {:?}", path, err), - }, - Some(err) => { - println!("error adding {:?}: {:?}", path, err); - } +fn get_gitignore(dir: &Path) -> Option { + if path_in_git_metadata(dir) { + return None; + } + let mut builder = ignore::gitignore::GitignoreBuilder::new(dir); + let mut filepath = dir.to_path_buf(); + filepath.push(GITIGNORE_FILENAME); + match builder.add(filepath) { + None => match builder.build() { + Ok(ignorer) => return Some(ignorer), + Err(err) => { + println!("error building ignorer for {:?}: {:?}", dir, err); + return None; } - let _ = dir.pop(); - let _ = dir.pop(); + }, + Some(err) => { + println!("error adding {:?}: {:?}", dir, err); + return None; } + } +} - return GitignoreFilter::new(ignorers); +fn get_all_gitignores(dir: &Path) -> Vec { + let mut ignores = Vec::new(); + if !dir.is_dir() || path_in_git_metadata(dir) { + return ignores; } + let readdir = match fs::read_dir(dir) { + Ok(readdir) => readdir, + Err(err) => { + println!("error reading filesystem entries for '{:?}': {}", dir, err); + return ignores; + } + }; + for entry in readdir { + let entry = match entry { + Ok(entry) => entry, + Err(err) => { + println!("error reading filesystem entry: {}", err); + continue; + } + }; + ignores.append(&mut get_all_gitignores(&entry.path())); + } + match get_gitignore(dir) { + Some(ignorer) => ignores.push(ignorer), + None => {} + } + return ignores; +} + +fn path_in_git_metadata(path: &Path) -> bool { + return path.iter().any(|e| e == GIT_METADATA_DIR_NAME); +} + +fn is_git_repo_root(path: &Path) -> bool { + if !path.is_dir() { + return false; + } + let readdir = match fs::read_dir(path) { + Ok(readdir) => readdir, + Err(err) => { + println!("error reading filesystem entries for '{:?}': {}", path, err); + return false; + } + }; + for entry in readdir { + let entry = match entry { + Ok(entry) => entry, + Err(err) => { + println!("error reading filesystem entry: {}", err); + return false; + } + }; + if entry.file_name() == GIT_METADATA_DIR_NAME { + return true; + } + } + return false; } impl rocket_watcher::PathFilter for GitignoreFilter { fn exclude(&self, path: &Path) -> bool { + if path_in_git_metadata(path) { + return true; + } for ignorer in &self.ignorers { // todo: figure out how to distinguish files from directories let resp = ignorer.matched(path, true); From 7f29eda5665f2e5f0c92f55aad49cd4cfab651fa Mon Sep 17 00:00:00 2001 From: Thomas Showers Date: Fri, 14 May 2021 15:21:04 -0300 Subject: [PATCH 2/4] check if current dir is repo root before getting ancestors --- src/gitignore_filter.rs | 54 +++++++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/src/gitignore_filter.rs b/src/gitignore_filter.rs index 3d1b7e6..17afa1e 100644 --- a/src/gitignore_filter.rs +++ b/src/gitignore_filter.rs @@ -16,28 +16,9 @@ impl GitignoreFilter { } pub fn build(mut dir: PathBuf) -> GitignoreFilter { - // Get all the child gitignores then add the parents. - - let mut ignorers = get_all_gitignores(dir.as_path()); - - // We only want ancestors up to and including the root of the repo. If we don't find a root - // we'll ignore all the ancestors we found. - let mut ancestors = Vec::new(); - let mut found_root = false; - while dir.pop() { - match get_gitignore(dir.as_path()) { - Some(ignorer) => ancestors.push(ignorer), - None => {} - } - if is_git_repo_root(&dir) { - found_root = true; - break; - } - } - if found_root { - ignorers.append(&mut ancestors); - } - + let mut ignorers = get_gitignores_recursively(dir.as_path()); + let mut ancestors = get_parent_gitignores(&dir); + ignorers.append(&mut ancestors); return GitignoreFilter::new(ignorers); } } @@ -69,7 +50,7 @@ fn get_gitignore(dir: &Path) -> Option { } } -fn get_all_gitignores(dir: &Path) -> Vec { +fn get_gitignores_recursively(dir: &Path) -> Vec { let mut ignores = Vec::new(); if !dir.is_dir() || path_in_git_metadata(dir) { return ignores; @@ -89,7 +70,7 @@ fn get_all_gitignores(dir: &Path) -> Vec { continue; } }; - ignores.append(&mut get_all_gitignores(&entry.path())); + ignores.append(&mut get_gitignores_recursively(&entry.path())); } match get_gitignore(dir) { Some(ignorer) => ignores.push(ignorer), @@ -98,6 +79,31 @@ fn get_all_gitignores(dir: &Path) -> Vec { return ignores; } +fn get_parent_gitignores(dir: &Path) -> Vec { + // We only want ancestors up to and including the root of the containing repo. If we find that + // we're not in a git repo then we'll ignore all ancestors. + let mut ancestors = Vec::new(); + if is_git_repo_root(dir) { + return ancestors; + } + let mut dir = dir.to_owned(); + let mut found_root = false; + while dir.pop() { + match get_gitignore(&dir) { + Some(ignorer) => ancestors.push(ignorer), + None => {} + } + if is_git_repo_root(&dir) { + found_root = true; + break; + } + } + if found_root { + return ancestors; + } + return Vec::new(); +} + fn path_in_git_metadata(path: &Path) -> bool { return path.iter().any(|e| e == GIT_METADATA_DIR_NAME); } From c7b79e2823e59cf5995a8936cf25e026d721a046 Mon Sep 17 00:00:00 2001 From: Thomas Showers Date: Fri, 14 May 2021 15:25:42 -0300 Subject: [PATCH 3/4] Accept &Path in GitignoreFilter::build --- src/gitignore_filter.rs | 4 ++-- src/main.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/gitignore_filter.rs b/src/gitignore_filter.rs index 17afa1e..400d4a2 100644 --- a/src/gitignore_filter.rs +++ b/src/gitignore_filter.rs @@ -15,8 +15,8 @@ impl GitignoreFilter { GitignoreFilter { ignorers } } - pub fn build(mut dir: PathBuf) -> GitignoreFilter { - let mut ignorers = get_gitignores_recursively(dir.as_path()); + pub fn build(dir: &Path) -> GitignoreFilter { + let mut ignorers = get_gitignores_recursively(dir); let mut ancestors = get_parent_gitignores(&dir); ignorers.append(&mut ancestors); return GitignoreFilter::new(ignorers); diff --git a/src/main.rs b/src/main.rs index e5db28f..a20ce3e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -35,7 +35,7 @@ fn main() { } }; - let filter = GitignoreFilter::build(watch_dir.clone()); + let filter = GitignoreFilter::build(&watch_dir); let watchy = RocketWatch::new(filter); watchy.watch_directory( From 910bcedac01da7e1ddf9c308281dd38a5874495c Mon Sep 17 00:00:00 2001 From: Thomas Showers Date: Fri, 14 May 2021 15:28:27 -0300 Subject: [PATCH 4/4] Remove unused use --- src/gitignore_filter.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gitignore_filter.rs b/src/gitignore_filter.rs index 400d4a2..980accb 100644 --- a/src/gitignore_filter.rs +++ b/src/gitignore_filter.rs @@ -1,6 +1,6 @@ use crate::rocket_watcher; use std::fs; -use std::path::{Path, PathBuf}; +use std::path::Path; // todo: are these an overridable conventions we need to respect? const GITIGNORE_FILENAME: &str = ".gitignore";