1 use libc::size_t;
2 use std::iter::IntoIterator;
3 use std::marker;
4 use std::ops::Range;
5 use std::path::Path;
6 use std::ptr;
7 
8 use crate::util::{path_to_repo_path, Binding};
9 use crate::{raw, Diff, DiffDelta, Error, Index, IntoCString, PathspecFlags, Repository, Tree};
10 
11 /// Structure representing a compiled pathspec used for matching against various
12 /// structures.
13 pub struct Pathspec {
14     raw: *mut raw::git_pathspec,
15 }
16 
17 /// List of filenames matching a pathspec.
18 pub struct PathspecMatchList<'ps> {
19     raw: *mut raw::git_pathspec_match_list,
20     _marker: marker::PhantomData<&'ps Pathspec>,
21 }
22 
23 /// Iterator over the matched paths in a pathspec.
24 pub struct PathspecEntries<'list> {
25     range: Range<usize>,
26     list: &'list PathspecMatchList<'list>,
27 }
28 
29 /// Iterator over the matching diff deltas.
30 pub struct PathspecDiffEntries<'list> {
31     range: Range<usize>,
32     list: &'list PathspecMatchList<'list>,
33 }
34 
35 /// Iterator over the failed list of pathspec items that did not match.
36 pub struct PathspecFailedEntries<'list> {
37     range: Range<usize>,
38     list: &'list PathspecMatchList<'list>,
39 }
40 
41 impl Pathspec {
42     /// Creates a new pathspec from a list of specs to match against.
new<I, T>(specs: I) -> Result<Pathspec, Error> where T: IntoCString, I: IntoIterator<Item = T>,43     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     /// Match a pathspec against files in a diff.
58     ///
59     /// The list returned contains the list of all matched filenames (unless you
60     /// pass `PATHSPEC_FAILURES_ONLY` in the flags) and may also contain the
61     /// list of pathspecs with no match if the `PATHSPEC_FIND_FAILURES` flag is
62     /// specified.
match_diff( &self, diff: &Diff<'_>, flags: PathspecFlags, ) -> Result<PathspecMatchList<'_>, Error>63     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     /// Match a pathspec against files in a tree.
81     ///
82     /// The list returned contains the list of all matched filenames (unless you
83     /// pass `PATHSPEC_FAILURES_ONLY` in the flags) and may also contain the
84     /// list of pathspecs with no match if the `PATHSPEC_FIND_FAILURES` flag is
85     /// specified.
match_tree( &self, tree: &Tree<'_>, flags: PathspecFlags, ) -> Result<PathspecMatchList<'_>, Error>86     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     /// This matches the pathspec against the files in the repository index.
104     ///
105     /// The list returned contains the list of all matched filenames (unless you
106     /// pass `PATHSPEC_FAILURES_ONLY` in the flags) and may also contain the
107     /// list of pathspecs with no match if the `PATHSPEC_FIND_FAILURES` flag is
108     /// specified.
match_index( &self, index: &Index, flags: PathspecFlags, ) -> Result<PathspecMatchList<'_>, Error>109     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     /// Match a pathspec against the working directory of a repository.
127     ///
128     /// This matches the pathspec against the current files in the working
129     /// directory of the repository. It is an error to invoke this on a bare
130     /// repo. This handles git ignores (i.e. ignored files will not be
131     /// considered to match the pathspec unless the file is tracked in the
132     /// index).
133     ///
134     /// The list returned contains the list of all matched filenames (unless you
135     /// pass `PATHSPEC_FAILURES_ONLY` in the flags) and may also contain the
136     /// list of pathspecs with no match if the `PATHSPEC_FIND_FAILURES` flag is
137     /// specified.
match_workdir( &self, repo: &Repository, flags: PathspecFlags, ) -> Result<PathspecMatchList<'_>, Error>138     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     /// Try to match a path against a pathspec
156     ///
157     /// Unlike most of the other pathspec matching functions, this will not fall
158     /// back on the native case-sensitivity for your platform. You must
159     /// explicitly pass flags to control case sensitivity or else this will fall
160     /// back on being case sensitive.
matches_path(&self, path: &Path, flags: PathspecFlags) -> bool161     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 
167 impl Binding for Pathspec {
168     type Raw = *mut raw::git_pathspec;
169 
from_raw(raw: *mut raw::git_pathspec) -> Pathspec170     unsafe fn from_raw(raw: *mut raw::git_pathspec) -> Pathspec {
171         Pathspec { raw }
172     }
raw(&self) -> *mut raw::git_pathspec173     fn raw(&self) -> *mut raw::git_pathspec {
174         self.raw
175     }
176 }
177 
178 impl Drop for Pathspec {
drop(&mut self)179     fn drop(&mut self) {
180         unsafe { raw::git_pathspec_free(self.raw) }
181     }
182 }
183 
184 impl<'ps> PathspecMatchList<'ps> {
entrycount(&self) -> usize185     fn entrycount(&self) -> usize {
186         unsafe { raw::git_pathspec_match_list_entrycount(&*self.raw) as usize }
187     }
188 
failed_entrycount(&self) -> usize189     fn failed_entrycount(&self) -> usize {
190         unsafe { raw::git_pathspec_match_list_failed_entrycount(&*self.raw) as usize }
191     }
192 
193     /// Returns an iterator over the matching filenames in this list.
entries(&self) -> PathspecEntries<'_>194     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     /// Get a matching filename by position.
208     ///
209     /// If this list was generated from a diff, then the return value will
210     /// always be `None.
entry(&self, i: usize) -> Option<&[u8]>211     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     /// Returns an iterator over the matching diff entries in this list.
diff_entries(&self) -> PathspecDiffEntries<'_>219     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     /// Get a matching diff delta by position.
233     ///
234     /// If the list was not generated from a diff, then the return value will
235     /// always be `None`.
diff_entry(&self, i: usize) -> Option<DiffDelta<'_>>236     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     /// Returns an iterator over the non-matching entries in this list.
failed_entries(&self) -> PathspecFailedEntries<'_>244     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     /// Get an original pathspec string that had no matches.
failed_entry(&self, i: usize) -> Option<&[u8]>258     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 
266 impl<'ps> Binding for PathspecMatchList<'ps> {
267     type Raw = *mut raw::git_pathspec_match_list;
268 
from_raw(raw: *mut raw::git_pathspec_match_list) -> PathspecMatchList<'ps>269     unsafe fn from_raw(raw: *mut raw::git_pathspec_match_list) -> PathspecMatchList<'ps> {
270         PathspecMatchList {
271             raw,
272             _marker: marker::PhantomData,
273         }
274     }
raw(&self) -> *mut raw::git_pathspec_match_list275     fn raw(&self) -> *mut raw::git_pathspec_match_list {
276         self.raw
277     }
278 }
279 
280 impl<'ps> Drop for PathspecMatchList<'ps> {
drop(&mut self)281     fn drop(&mut self) {
282         unsafe { raw::git_pathspec_match_list_free(self.raw) }
283     }
284 }
285 
286 impl<'list> Iterator for PathspecEntries<'list> {
287     type Item = &'list [u8];
next(&mut self) -> Option<&'list [u8]>288     fn next(&mut self) -> Option<&'list [u8]> {
289         self.range.next().and_then(|i| self.list.entry(i))
290     }
size_hint(&self) -> (usize, Option<usize>)291     fn size_hint(&self) -> (usize, Option<usize>) {
292         self.range.size_hint()
293     }
294 }
295 impl<'list> DoubleEndedIterator for PathspecEntries<'list> {
next_back(&mut self) -> Option<&'list [u8]>296     fn next_back(&mut self) -> Option<&'list [u8]> {
297         self.range.next_back().and_then(|i| self.list.entry(i))
298     }
299 }
300 impl<'list> ExactSizeIterator for PathspecEntries<'list> {}
301 
302 impl<'list> Iterator for PathspecDiffEntries<'list> {
303     type Item = DiffDelta<'list>;
next(&mut self) -> Option<DiffDelta<'list>>304     fn next(&mut self) -> Option<DiffDelta<'list>> {
305         self.range.next().and_then(|i| self.list.diff_entry(i))
306     }
size_hint(&self) -> (usize, Option<usize>)307     fn size_hint(&self) -> (usize, Option<usize>) {
308         self.range.size_hint()
309     }
310 }
311 impl<'list> DoubleEndedIterator for PathspecDiffEntries<'list> {
next_back(&mut self) -> Option<DiffDelta<'list>>312     fn next_back(&mut self) -> Option<DiffDelta<'list>> {
313         self.range.next_back().and_then(|i| self.list.diff_entry(i))
314     }
315 }
316 impl<'list> ExactSizeIterator for PathspecDiffEntries<'list> {}
317 
318 impl<'list> Iterator for PathspecFailedEntries<'list> {
319     type Item = &'list [u8];
next(&mut self) -> Option<&'list [u8]>320     fn next(&mut self) -> Option<&'list [u8]> {
321         self.range.next().and_then(|i| self.list.failed_entry(i))
322     }
size_hint(&self) -> (usize, Option<usize>)323     fn size_hint(&self) -> (usize, Option<usize>) {
324         self.range.size_hint()
325     }
326 }
327 impl<'list> DoubleEndedIterator for PathspecFailedEntries<'list> {
next_back(&mut self) -> Option<&'list [u8]>328     fn next_back(&mut self) -> Option<&'list [u8]> {
329         self.range
330             .next_back()
331             .and_then(|i| self.list.failed_entry(i))
332     }
333 }
334 impl<'list> ExactSizeIterator for PathspecFailedEntries<'list> {}
335 
336 #[cfg(test)]
337 mod tests {
338     use super::Pathspec;
339     use crate::PathspecFlags;
340     use std::fs::File;
341     use std::path::Path;
342 
343     #[test]
smoke()344     fn smoke() {
345         let ps = Pathspec::new(["a"].iter()).unwrap();
346         assert!(ps.matches_path(Path::new("a"), PathspecFlags::DEFAULT));
347         assert!(ps.matches_path(Path::new("a/b"), PathspecFlags::DEFAULT));
348         assert!(!ps.matches_path(Path::new("b"), PathspecFlags::DEFAULT));
349         assert!(!ps.matches_path(Path::new("ab/c"), PathspecFlags::DEFAULT));
350 
351         let (td, repo) = crate::test::repo_init();
352         let list = ps.match_workdir(&repo, PathspecFlags::DEFAULT).unwrap();
353         assert_eq!(list.entries().len(), 0);
354         assert_eq!(list.diff_entries().len(), 0);
355         assert_eq!(list.failed_entries().len(), 0);
356 
357         File::create(&td.path().join("a")).unwrap();
358 
359         let list = ps
360             .match_workdir(&repo, crate::PathspecFlags::FIND_FAILURES)
361             .unwrap();
362         assert_eq!(list.entries().len(), 1);
363         assert_eq!(list.entries().next(), Some("a".as_bytes()));
364     }
365 }
366