git2/
pathspec.rs

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
11/// Structure representing a compiled pathspec used for matching against various
12/// structures.
13pub struct Pathspec {
14    raw: *mut raw::git_pathspec,
15}
16
17/// List of filenames matching a pathspec.
18pub 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.
24pub struct PathspecEntries<'list> {
25    range: Range<usize>,
26    list: &'list PathspecMatchList<'list>,
27}
28
29/// Iterator over the matching diff deltas.
30pub 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.
36pub struct PathspecFailedEntries<'list> {
37    range: Range<usize>,
38    list: &'list PathspecMatchList<'list>,
39}
40
41impl Pathspec {
42    /// Creates a new pathspec from a list of specs to match against.
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.
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.
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.
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.
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.
161    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    /// Returns an iterator over the matching filenames in this list.
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.
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.
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`.
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.
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.
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
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}