1use libc::size_t;
2use std::iter::FusedIterator;
3use std::marker;
4use std::ops::Range;
5use std::path::Path;
6use std::ptr;
7
8use crate::util::{path_to_repo_path, Binding};
9use crate::{raw, Diff, DiffDelta, Error, Index, IntoCString, PathspecFlags, Repository, Tree};
10
11pub struct Pathspec {
14    raw: *mut raw::git_pathspec,
15}
16
17pub struct PathspecMatchList<'ps> {
19    raw: *mut raw::git_pathspec_match_list,
20    _marker: marker::PhantomData<&'ps Pathspec>,
21}
22
23pub struct PathspecEntries<'list> {
25    range: Range<usize>,
26    list: &'list PathspecMatchList<'list>,
27}
28
29pub struct PathspecDiffEntries<'list> {
31    range: Range<usize>,
32    list: &'list PathspecMatchList<'list>,
33}
34
35pub struct PathspecFailedEntries<'list> {
37    range: Range<usize>,
38    list: &'list PathspecMatchList<'list>,
39}
40
41impl Pathspec {
42    pub fn new<I, T>(specs: I) -> Result<Pathspec, Error>
44    where
45        T: IntoCString,
46        I: IntoIterator<Item = T>,
47    {
48        crate::init();
49        let (_a, _b, arr) = crate::util::iter2cstrs_paths(specs)?;
50        unsafe {
51            let mut ret = ptr::null_mut();
52            try_call!(raw::git_pathspec_new(&mut ret, &arr));
53            Ok(Binding::from_raw(ret))
54        }
55    }
56
57    pub fn match_diff(
64        &self,
65        diff: &Diff<'_>,
66        flags: PathspecFlags,
67    ) -> Result<PathspecMatchList<'_>, Error> {
68        let mut ret = ptr::null_mut();
69        unsafe {
70            try_call!(raw::git_pathspec_match_diff(
71                &mut ret,
72                diff.raw(),
73                flags.bits(),
74                self.raw
75            ));
76            Ok(Binding::from_raw(ret))
77        }
78    }
79
80    pub fn match_tree(
87        &self,
88        tree: &Tree<'_>,
89        flags: PathspecFlags,
90    ) -> Result<PathspecMatchList<'_>, Error> {
91        let mut ret = ptr::null_mut();
92        unsafe {
93            try_call!(raw::git_pathspec_match_tree(
94                &mut ret,
95                tree.raw(),
96                flags.bits(),
97                self.raw
98            ));
99            Ok(Binding::from_raw(ret))
100        }
101    }
102
103    pub fn match_index(
110        &self,
111        index: &Index,
112        flags: PathspecFlags,
113    ) -> Result<PathspecMatchList<'_>, Error> {
114        let mut ret = ptr::null_mut();
115        unsafe {
116            try_call!(raw::git_pathspec_match_index(
117                &mut ret,
118                index.raw(),
119                flags.bits(),
120                self.raw
121            ));
122            Ok(Binding::from_raw(ret))
123        }
124    }
125
126    pub fn match_workdir(
139        &self,
140        repo: &Repository,
141        flags: PathspecFlags,
142    ) -> Result<PathspecMatchList<'_>, Error> {
143        let mut ret = ptr::null_mut();
144        unsafe {
145            try_call!(raw::git_pathspec_match_workdir(
146                &mut ret,
147                repo.raw(),
148                flags.bits(),
149                self.raw
150            ));
151            Ok(Binding::from_raw(ret))
152        }
153    }
154
155    pub fn matches_path(&self, path: &Path, flags: PathspecFlags) -> bool {
162        let path = path_to_repo_path(path).unwrap();
163        unsafe { raw::git_pathspec_matches_path(&*self.raw, flags.bits(), path.as_ptr()) == 1 }
164    }
165}
166
167impl Binding for Pathspec {
168    type Raw = *mut raw::git_pathspec;
169
170    unsafe fn from_raw(raw: *mut raw::git_pathspec) -> Pathspec {
171        Pathspec { raw }
172    }
173    fn raw(&self) -> *mut raw::git_pathspec {
174        self.raw
175    }
176}
177
178impl Drop for Pathspec {
179    fn drop(&mut self) {
180        unsafe { raw::git_pathspec_free(self.raw) }
181    }
182}
183
184impl<'ps> PathspecMatchList<'ps> {
185    fn entrycount(&self) -> usize {
186        unsafe { raw::git_pathspec_match_list_entrycount(&*self.raw) as usize }
187    }
188
189    fn failed_entrycount(&self) -> usize {
190        unsafe { raw::git_pathspec_match_list_failed_entrycount(&*self.raw) as usize }
191    }
192
193    pub fn entries(&self) -> PathspecEntries<'_> {
195        let n = self.entrycount();
196        let n = if n > 0 && self.entry(0).is_none() {
197            0
198        } else {
199            n
200        };
201        PathspecEntries {
202            range: 0..n,
203            list: self,
204        }
205    }
206
207    pub fn entry(&self, i: usize) -> Option<&[u8]> {
212        unsafe {
213            let ptr = raw::git_pathspec_match_list_entry(&*self.raw, i as size_t);
214            crate::opt_bytes(self, ptr)
215        }
216    }
217
218    pub fn diff_entries(&self) -> PathspecDiffEntries<'_> {
220        let n = self.entrycount();
221        let n = if n > 0 && self.diff_entry(0).is_none() {
222            0
223        } else {
224            n
225        };
226        PathspecDiffEntries {
227            range: 0..n,
228            list: self,
229        }
230    }
231
232    pub fn diff_entry(&self, i: usize) -> Option<DiffDelta<'_>> {
237        unsafe {
238            let ptr = raw::git_pathspec_match_list_diff_entry(&*self.raw, i as size_t);
239            Binding::from_raw_opt(ptr as *mut _)
240        }
241    }
242
243    pub fn failed_entries(&self) -> PathspecFailedEntries<'_> {
245        let n = self.failed_entrycount();
246        let n = if n > 0 && self.failed_entry(0).is_none() {
247            0
248        } else {
249            n
250        };
251        PathspecFailedEntries {
252            range: 0..n,
253            list: self,
254        }
255    }
256
257    pub fn failed_entry(&self, i: usize) -> Option<&[u8]> {
259        unsafe {
260            let ptr = raw::git_pathspec_match_list_failed_entry(&*self.raw, i as size_t);
261            crate::opt_bytes(self, ptr)
262        }
263    }
264}
265
266impl<'ps> Binding for PathspecMatchList<'ps> {
267    type Raw = *mut raw::git_pathspec_match_list;
268
269    unsafe fn from_raw(raw: *mut raw::git_pathspec_match_list) -> PathspecMatchList<'ps> {
270        PathspecMatchList {
271            raw,
272            _marker: marker::PhantomData,
273        }
274    }
275    fn raw(&self) -> *mut raw::git_pathspec_match_list {
276        self.raw
277    }
278}
279
280impl<'ps> Drop for PathspecMatchList<'ps> {
281    fn drop(&mut self) {
282        unsafe { raw::git_pathspec_match_list_free(self.raw) }
283    }
284}
285
286impl<'list> Iterator for PathspecEntries<'list> {
287    type Item = &'list [u8];
288    fn next(&mut self) -> Option<&'list [u8]> {
289        self.range.next().and_then(|i| self.list.entry(i))
290    }
291    fn size_hint(&self) -> (usize, Option<usize>) {
292        self.range.size_hint()
293    }
294}
295impl<'list> DoubleEndedIterator for PathspecEntries<'list> {
296    fn next_back(&mut self) -> Option<&'list [u8]> {
297        self.range.next_back().and_then(|i| self.list.entry(i))
298    }
299}
300impl<'list> FusedIterator for PathspecEntries<'list> {}
301impl<'list> ExactSizeIterator for PathspecEntries<'list> {}
302
303impl<'list> Iterator for PathspecDiffEntries<'list> {
304    type Item = DiffDelta<'list>;
305    fn next(&mut self) -> Option<DiffDelta<'list>> {
306        self.range.next().and_then(|i| self.list.diff_entry(i))
307    }
308    fn size_hint(&self) -> (usize, Option<usize>) {
309        self.range.size_hint()
310    }
311}
312impl<'list> DoubleEndedIterator for PathspecDiffEntries<'list> {
313    fn next_back(&mut self) -> Option<DiffDelta<'list>> {
314        self.range.next_back().and_then(|i| self.list.diff_entry(i))
315    }
316}
317impl<'list> FusedIterator for PathspecDiffEntries<'list> {}
318impl<'list> ExactSizeIterator for PathspecDiffEntries<'list> {}
319
320impl<'list> Iterator for PathspecFailedEntries<'list> {
321    type Item = &'list [u8];
322    fn next(&mut self) -> Option<&'list [u8]> {
323        self.range.next().and_then(|i| self.list.failed_entry(i))
324    }
325    fn size_hint(&self) -> (usize, Option<usize>) {
326        self.range.size_hint()
327    }
328}
329impl<'list> DoubleEndedIterator for PathspecFailedEntries<'list> {
330    fn next_back(&mut self) -> Option<&'list [u8]> {
331        self.range
332            .next_back()
333            .and_then(|i| self.list.failed_entry(i))
334    }
335}
336impl<'list> FusedIterator for PathspecFailedEntries<'list> {}
337impl<'list> ExactSizeIterator for PathspecFailedEntries<'list> {}
338
339#[cfg(test)]
340mod tests {
341    use super::Pathspec;
342    use crate::PathspecFlags;
343    use std::fs::File;
344    use std::path::Path;
345
346    #[test]
347    fn smoke() {
348        let ps = Pathspec::new(["a"].iter()).unwrap();
349        assert!(ps.matches_path(Path::new("a"), PathspecFlags::DEFAULT));
350        assert!(ps.matches_path(Path::new("a/b"), PathspecFlags::DEFAULT));
351        assert!(!ps.matches_path(Path::new("b"), PathspecFlags::DEFAULT));
352        assert!(!ps.matches_path(Path::new("ab/c"), PathspecFlags::DEFAULT));
353
354        let (td, repo) = crate::test::repo_init();
355        let list = ps.match_workdir(&repo, PathspecFlags::DEFAULT).unwrap();
356        assert_eq!(list.entries().len(), 0);
357        assert_eq!(list.diff_entries().len(), 0);
358        assert_eq!(list.failed_entries().len(), 0);
359
360        File::create(&td.path().join("a")).unwrap();
361
362        let list = ps
363            .match_workdir(&repo, crate::PathspecFlags::FIND_FAILURES)
364            .unwrap();
365        assert_eq!(list.entries().len(), 1);
366        assert_eq!(list.entries().next(), Some("a".as_bytes()));
367    }
368}