use git2;
use map::OidMap;
use std::io;
use std::fs;
use std::path::{Path, Component};
use std::collections::BTreeMap;
#[derive(Debug, Hash)]
pub struct Filter {
filter: BTreeMap<String, Filter>,
}
impl Filter {
pub fn new() -> Filter {
Filter { filter: BTreeMap::new() }
}
pub fn from_file<P: AsRef<Path>>(path: P) -> io::Result<Filter> {
Self::from_reader(io::BufReader::new(fs::File::open(path)?))
}
pub fn from_reader<R: io::BufRead>(reader: R) -> io::Result<Filter> {
let mut filter = Self::new();
for line in reader.lines() {
let line = line?;
let line = line.trim();
if line.is_empty() || line.starts_with("#") {
continue;
}
filter.insert(Path::new(line));
}
Ok(filter)
}
pub fn insert(&mut self, path: &Path) {
let mut components = path.components();
match components.next() {
Some(Component::Normal(c)) => {
let mut filter = self.filter
.entry(String::from(c.to_str().unwrap()))
.or_insert_with(|| Filter::new());
filter.insert(components.as_path());
},
_ => {},
}
}
pub fn is_empty(&self) -> bool {
self.filter.is_empty()
}
pub fn match_name(pattern: &str, name: &str) -> bool {
pattern == "" || pattern == "**" || pattern == name
}
pub fn match_entry(&self, entry: &git2::TreeEntry) -> Option<&Filter> {
for (pattern, filter) in &self.filter {
if Self::match_name(pattern.as_str(), entry.name().unwrap()) {
return Some(filter);
}
}
None
}
}
pub fn filter_tree(repo: &git2::Repository, map: &mut OidMap,
filter: &Filter, tree: &git2::Tree)
-> Result<git2::Oid, git2::Error>
{
match filter_tree_impl(repo, map, filter, tree)? {
Some(oid) => Ok(oid),
None => repo.treebuilder(None)?.write(),
}
}
fn filter_tree_impl(repo: &git2::Repository, map: &mut OidMap,
filter: &Filter, tree: &git2::Tree)
-> Result<Option<git2::Oid>, git2::Error>
{
if let Some(oid) = map.get(&tree.id()) {
return Ok(*oid);
}
let mut builder = repo.treebuilder(None)?;
for entry in tree {
if let Some(filter) = filter.match_entry(&entry) {
if filter.is_empty() {
builder.insert(
entry.name_bytes(),
entry.id(),
entry.filemode()
)?;
}
else if entry.kind() == Some(git2::ObjectType::Tree) {
let obj = entry.to_object(repo)?;
let tree = obj.as_tree().unwrap();
if let Some(newtree) = filter_tree_impl(repo, map, filter, &tree)? {
builder.insert(
entry.name_bytes(),
newtree,
entry.filemode()
)?;
}
}
}
}
if builder.len() == 0 {
Ok(None)
}
else {
let oid = builder.write()?;
map.insert(tree.id(), Some(oid));
Ok(Some(oid))
}
}