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: 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: 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