use libc::size_t;
use std::iter::{FusedIterator, IntoIterator};
use std::marker;
use std::ops::Range;
use std::path::Path;
use std::ptr;
use crate::util::{path_to_repo_path, Binding};
use crate::{raw, Diff, DiffDelta, Error, Index, IntoCString, PathspecFlags, Repository, Tree};
pub struct Pathspec {
raw: *mut raw::git_pathspec,
}
pub struct PathspecMatchList<'ps> {
raw: *mut raw::git_pathspec_match_list,
_marker: marker::PhantomData<&'ps Pathspec>,
}
pub struct PathspecEntries<'list> {
range: Range<usize>,
list: &'list PathspecMatchList<'list>,
}
pub struct PathspecDiffEntries<'list> {
range: Range<usize>,
list: &'list PathspecMatchList<'list>,
}
pub struct PathspecFailedEntries<'list> {
range: Range<usize>,
list: &'list PathspecMatchList<'list>,
}
impl Pathspec {
pub fn new<I, T>(specs: I) -> Result<Pathspec, Error>
where
T: IntoCString,
I: IntoIterator<Item = T>,
{
crate::init();
let (_a, _b, arr) = crate::util::iter2cstrs_paths(specs)?;
unsafe {
let mut ret = ptr::null_mut();
try_call!(raw::git_pathspec_new(&mut ret, &arr));
Ok(Binding::from_raw(ret))
}
}
pub fn match_diff(
&self,
diff: &Diff<'_>,
flags: PathspecFlags,
) -> Result<PathspecMatchList<'_>, Error> {
let mut ret = ptr::null_mut();
unsafe {
try_call!(raw::git_pathspec_match_diff(
&mut ret,
diff.raw(),
flags.bits(),
self.raw
));
Ok(Binding::from_raw(ret))
}
}
pub fn match_tree(
&self,
tree: &Tree<'_>,
flags: PathspecFlags,
) -> Result<PathspecMatchList<'_>, Error> {
let mut ret = ptr::null_mut();
unsafe {
try_call!(raw::git_pathspec_match_tree(
&mut ret,
tree.raw(),
flags.bits(),
self.raw
));
Ok(Binding::from_raw(ret))
}
}
pub fn match_index(
&self,
index: &Index,
flags: PathspecFlags,
) -> Result<PathspecMatchList<'_>, Error> {
let mut ret = ptr::null_mut();
unsafe {
try_call!(raw::git_pathspec_match_index(
&mut ret,
index.raw(),
flags.bits(),
self.raw
));
Ok(Binding::from_raw(ret))
}
}
pub fn match_workdir(
&self,
repo: &Repository,
flags: PathspecFlags,
) -> Result<PathspecMatchList<'_>, Error> {
let mut ret = ptr::null_mut();
unsafe {
try_call!(raw::git_pathspec_match_workdir(
&mut ret,
repo.raw(),
flags.bits(),
self.raw
));
Ok(Binding::from_raw(ret))
}
}
pub fn matches_path(&self, path: &Path, flags: PathspecFlags) -> bool {
let path = path_to_repo_path(path).unwrap();
unsafe { raw::git_pathspec_matches_path(&*self.raw, flags.bits(), path.as_ptr()) == 1 }
}
}
impl Binding for Pathspec {
type Raw = *mut raw::git_pathspec;
unsafe fn from_raw(raw: *mut raw::git_pathspec) -> Pathspec {
Pathspec { raw }
}
fn raw(&self) -> *mut raw::git_pathspec {
self.raw
}
}
impl Drop for Pathspec {
fn drop(&mut self) {
unsafe { raw::git_pathspec_free(self.raw) }
}
}
impl<'ps> PathspecMatchList<'ps> {
fn entrycount(&self) -> usize {
unsafe { raw::git_pathspec_match_list_entrycount(&*self.raw) as usize }
}
fn failed_entrycount(&self) -> usize {
unsafe { raw::git_pathspec_match_list_failed_entrycount(&*self.raw) as usize }
}
pub fn entries(&self) -> PathspecEntries<'_> {
let n = self.entrycount();
let n = if n > 0 && self.entry(0).is_none() {
0
} else {
n
};
PathspecEntries {
range: 0..n,
list: self,
}
}
pub fn entry(&self, i: usize) -> Option<&[u8]> {
unsafe {
let ptr = raw::git_pathspec_match_list_entry(&*self.raw, i as size_t);
crate::opt_bytes(self, ptr)
}
}
pub fn diff_entries(&self) -> PathspecDiffEntries<'_> {
let n = self.entrycount();
let n = if n > 0 && self.diff_entry(0).is_none() {
0
} else {
n
};
PathspecDiffEntries {
range: 0..n,
list: self,
}
}
pub fn diff_entry(&self, i: usize) -> Option<DiffDelta<'_>> {
unsafe {
let ptr = raw::git_pathspec_match_list_diff_entry(&*self.raw, i as size_t);
Binding::from_raw_opt(ptr as *mut _)
}
}
pub fn failed_entries(&self) -> PathspecFailedEntries<'_> {
let n = self.failed_entrycount();
let n = if n > 0 && self.failed_entry(0).is_none() {
0
} else {
n
};
PathspecFailedEntries {
range: 0..n,
list: self,
}
}
pub fn failed_entry(&self, i: usize) -> Option<&[u8]> {
unsafe {
let ptr = raw::git_pathspec_match_list_failed_entry(&*self.raw, i as size_t);
crate::opt_bytes(self, ptr)
}
}
}
impl<'ps> Binding for PathspecMatchList<'ps> {
type Raw = *mut raw::git_pathspec_match_list;
unsafe fn from_raw(raw: *mut raw::git_pathspec_match_list) -> PathspecMatchList<'ps> {
PathspecMatchList {
raw,
_marker: marker::PhantomData,
}
}
fn raw(&self) -> *mut raw::git_pathspec_match_list {
self.raw
}
}
impl<'ps> Drop for PathspecMatchList<'ps> {
fn drop(&mut self) {
unsafe { raw::git_pathspec_match_list_free(self.raw) }
}
}
impl<'list> Iterator for PathspecEntries<'list> {
type Item = &'list [u8];
fn next(&mut self) -> Option<&'list [u8]> {
self.range.next().and_then(|i| self.list.entry(i))
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.range.size_hint()
}
}
impl<'list> DoubleEndedIterator for PathspecEntries<'list> {
fn next_back(&mut self) -> Option<&'list [u8]> {
self.range.next_back().and_then(|i| self.list.entry(i))
}
}
impl<'list> FusedIterator for PathspecEntries<'list> {}
impl<'list> ExactSizeIterator for PathspecEntries<'list> {}
impl<'list> Iterator for PathspecDiffEntries<'list> {
type Item = DiffDelta<'list>;
fn next(&mut self) -> Option<DiffDelta<'list>> {
self.range.next().and_then(|i| self.list.diff_entry(i))
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.range.size_hint()
}
}
impl<'list> DoubleEndedIterator for PathspecDiffEntries<'list> {
fn next_back(&mut self) -> Option<DiffDelta<'list>> {
self.range.next_back().and_then(|i| self.list.diff_entry(i))
}
}
impl<'list> FusedIterator for PathspecDiffEntries<'list> {}
impl<'list> ExactSizeIterator for PathspecDiffEntries<'list> {}
impl<'list> Iterator for PathspecFailedEntries<'list> {
type Item = &'list [u8];
fn next(&mut self) -> Option<&'list [u8]> {
self.range.next().and_then(|i| self.list.failed_entry(i))
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.range.size_hint()
}
}
impl<'list> DoubleEndedIterator for PathspecFailedEntries<'list> {
fn next_back(&mut self) -> Option<&'list [u8]> {
self.range
.next_back()
.and_then(|i| self.list.failed_entry(i))
}
}
impl<'list> FusedIterator for PathspecFailedEntries<'list> {}
impl<'list> ExactSizeIterator for PathspecFailedEntries<'list> {}
#[cfg(test)]
mod tests {
use super::Pathspec;
use crate::PathspecFlags;
use std::fs::File;
use std::path::Path;
#[test]
fn smoke() {
let ps = Pathspec::new(["a"].iter()).unwrap();
assert!(ps.matches_path(Path::new("a"), PathspecFlags::DEFAULT));
assert!(ps.matches_path(Path::new("a/b"), PathspecFlags::DEFAULT));
assert!(!ps.matches_path(Path::new("b"), PathspecFlags::DEFAULT));
assert!(!ps.matches_path(Path::new("ab/c"), PathspecFlags::DEFAULT));
let (td, repo) = crate::test::repo_init();
let list = ps.match_workdir(&repo, PathspecFlags::DEFAULT).unwrap();
assert_eq!(list.entries().len(), 0);
assert_eq!(list.diff_entries().len(), 0);
assert_eq!(list.failed_entries().len(), 0);
File::create(&td.path().join("a")).unwrap();
let list = ps
.match_workdir(&repo, crate::PathspecFlags::FIND_FAILURES)
.unwrap();
assert_eq!(list.entries().len(), 1);
assert_eq!(list.entries().next(), Some("a".as_bytes()));
}
}