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}