Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 119 additions & 29 deletions src/gitignore_filter.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
use crate::rocket_watcher;
use std::path::{Path, PathBuf};
use std::fs;
use std::path::Path;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comments for no reason

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the other comment


// 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<ignore::gitignore::Gitignore>,
Expand All @@ -10,45 +15,130 @@ impl GitignoreFilter {
GitignoreFilter { ignorers }
}

pub fn build(mut dir: PathBuf) -> GitignoreFilter {
// todo: is this an overridable convention we need to respect?
const GITIGNORE_FILENAME: &str = ".gitignore";

let mut ignorers = Vec::new();
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);
}
}

while dir.parent() != None {
let mut builder = ignore::gitignore::GitignoreBuilder::new(dir.as_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.

// 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);
fn get_gitignore(dir: &Path) -> Option<ignore::gitignore::Gitignore> {
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;
}
},
Some(err) => {
println!("error adding {:?}: {:?}", dir, err);
return None;
}
}
}

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_gitignores_recursively(dir: &Path) -> Vec<ignore::gitignore::Gitignore> {
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;
}
let _ = dir.pop();
let _ = dir.pop();
};
ignores.append(&mut get_gitignores_recursively(&entry.path()));
}
match get_gitignore(dir) {
Some(ignorer) => ignores.push(ignorer),
None => {}
}
return ignores;
}

fn get_parent_gitignores(dir: &Path) -> Vec<ignore::gitignore::Gitignore> {
// 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();
}

return GitignoreFilter::new(ignorers);
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);
Expand Down
2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down