1 use libc::{self, c_char, c_int, c_void};
2 use std::cmp::Ordering;
3 use std::ffi::{CStr, CString};
4 use std::marker;
5 use std::mem;
6 use std::ops::Range;
7 use std::path::Path;
8 use std::ptr;
9 use std::str;
10 
11 use crate::util::{c_cmp_to_ordering, path_to_repo_path, Binding};
12 use crate::{panic, raw, Error, Object, ObjectType, Oid, Repository};
13 
14 /// A structure to represent a git [tree][1]
15 ///
16 /// [1]: http://git-scm.com/book/en/Git-Internals-Git-Objects
17 pub struct Tree<'repo> {
18     raw: *mut raw::git_tree,
19     _marker: marker::PhantomData<Object<'repo>>,
20 }
21 
22 /// A structure representing an entry inside of a tree. An entry is borrowed
23 /// from a tree.
24 pub struct TreeEntry<'tree> {
25     raw: *mut raw::git_tree_entry,
26     owned: bool,
27     _marker: marker::PhantomData<&'tree raw::git_tree_entry>,
28 }
29 
30 /// An iterator over the entries in a tree.
31 pub struct TreeIter<'tree> {
32     range: Range<usize>,
33     tree: &'tree Tree<'tree>,
34 }
35 
36 /// A binary indicator of whether a tree walk should be performed in pre-order
37 /// or post-order.
38 pub enum TreeWalkMode {
39     /// Runs the traversal in pre order.
40     PreOrder = 0,
41     /// Runs the traversal in post order.
42     PostOrder = 1,
43 }
44 
45 /// Possible return codes for tree walking callback functions.
46 #[repr(i32)]
47 pub enum TreeWalkResult {
48     /// Continue with the traversal as normal.
49     Ok = 0,
50     /// Skip the current node (in pre-order mode).
51     Skip = 1,
52     /// Completely stop the traversal.
53     Abort = raw::GIT_EUSER,
54 }
55 
56 impl Into<i32> for TreeWalkResult {
into(self) -> i3257     fn into(self) -> i32 {
58         self as i32
59     }
60 }
61 
62 impl Into<raw::git_treewalk_mode> for TreeWalkMode {
63     #[cfg(target_env = "msvc")]
into(self) -> raw::git_treewalk_mode64     fn into(self) -> raw::git_treewalk_mode {
65         self as i32
66     }
67     #[cfg(not(target_env = "msvc"))]
into(self) -> raw::git_treewalk_mode68     fn into(self) -> raw::git_treewalk_mode {
69         self as u32
70     }
71 }
72 
73 impl<'repo> Tree<'repo> {
74     /// Get the id (SHA1) of a repository object
id(&self) -> Oid75     pub fn id(&self) -> Oid {
76         unsafe { Binding::from_raw(raw::git_tree_id(&*self.raw)) }
77     }
78 
79     /// Get the number of entries listed in this tree.
len(&self) -> usize80     pub fn len(&self) -> usize {
81         unsafe { raw::git_tree_entrycount(&*self.raw) as usize }
82     }
83 
84     /// Return `true` if there is not entry
is_empty(&self) -> bool85     pub fn is_empty(&self) -> bool {
86         self.len() == 0
87     }
88 
89     /// Returns an iterator over the entries in this tree.
iter(&self) -> TreeIter<'_>90     pub fn iter(&self) -> TreeIter<'_> {
91         TreeIter {
92             range: 0..self.len(),
93             tree: self,
94         }
95     }
96 
97     /// Traverse the entries in a tree and its subtrees in post or pre order.
98     /// The callback function will be run on each node of the tree that's
99     /// walked. The return code of this function will determine how the walk
100     /// continues.
101     ///
102     /// libgit requires that the callback be an integer, where 0 indicates a
103     /// successful visit, 1 skips the node, and -1 aborts the traversal completely.
104     /// You may opt to use the enum [`TreeWalkResult`](TreeWalkResult) instead.
105     ///
106     /// ```ignore
107     /// let mut ct = 0;
108     /// tree.walk(TreeWalkMode::PreOrder, |_, entry| {
109     ///     assert_eq!(entry.name(), Some("foo"));
110     ///     ct += 1;
111     ///     TreeWalkResult::Ok
112     /// }).unwrap();
113     /// assert_eq!(ct, 1);
114     /// ```
115     ///
116     /// See [libgit documentation][1] for more information.
117     ///
118     /// [1]: https://libgit2.org/libgit2/#HEAD/group/tree/git_tree_walk
walk<C, T>(&self, mode: TreeWalkMode, mut callback: C) -> Result<(), Error> where C: FnMut(&str, &TreeEntry<'_>) -> T, T: Into<i32>,119     pub fn walk<C, T>(&self, mode: TreeWalkMode, mut callback: C) -> Result<(), Error>
120     where
121         C: FnMut(&str, &TreeEntry<'_>) -> T,
122         T: Into<i32>,
123     {
124         #[allow(unused)]
125         struct TreeWalkCbData<'a, T> {
126             pub callback: &'a mut TreeWalkCb<'a, T>,
127         }
128         unsafe {
129             let mut data = TreeWalkCbData {
130                 callback: &mut callback,
131             };
132             raw::git_tree_walk(
133                 self.raw(),
134                 mode.into(),
135                 Some(treewalk_cb::<T>),
136                 &mut data as *mut _ as *mut c_void,
137             );
138             Ok(())
139         }
140     }
141 
142     /// Lookup a tree entry by SHA value.
get_id(&self, id: Oid) -> Option<TreeEntry<'_>>143     pub fn get_id(&self, id: Oid) -> Option<TreeEntry<'_>> {
144         unsafe {
145             let ptr = raw::git_tree_entry_byid(&*self.raw(), &*id.raw());
146             if ptr.is_null() {
147                 None
148             } else {
149                 Some(entry_from_raw_const(ptr))
150             }
151         }
152     }
153 
154     /// Lookup a tree entry by its position in the tree
get(&self, n: usize) -> Option<TreeEntry<'_>>155     pub fn get(&self, n: usize) -> Option<TreeEntry<'_>> {
156         unsafe {
157             let ptr = raw::git_tree_entry_byindex(&*self.raw(), n as libc::size_t);
158             if ptr.is_null() {
159                 None
160             } else {
161                 Some(entry_from_raw_const(ptr))
162             }
163         }
164     }
165 
166     /// Lookup a tree entry by its filename
get_name(&self, filename: &str) -> Option<TreeEntry<'_>>167     pub fn get_name(&self, filename: &str) -> Option<TreeEntry<'_>> {
168         let filename = CString::new(filename).unwrap();
169         unsafe {
170             let ptr = call!(raw::git_tree_entry_byname(&*self.raw(), filename));
171             if ptr.is_null() {
172                 None
173             } else {
174                 Some(entry_from_raw_const(ptr))
175             }
176         }
177     }
178 
179     /// Retrieve a tree entry contained in a tree or in any of its subtrees,
180     /// given its relative path.
get_path(&self, path: &Path) -> Result<TreeEntry<'static>, Error>181     pub fn get_path(&self, path: &Path) -> Result<TreeEntry<'static>, Error> {
182         let path = path_to_repo_path(path)?;
183         let mut ret = ptr::null_mut();
184         unsafe {
185             try_call!(raw::git_tree_entry_bypath(&mut ret, &*self.raw(), path));
186             Ok(Binding::from_raw(ret))
187         }
188     }
189 
190     /// Casts this Tree to be usable as an `Object`
as_object(&self) -> &Object<'repo>191     pub fn as_object(&self) -> &Object<'repo> {
192         unsafe { &*(self as *const _ as *const Object<'repo>) }
193     }
194 
195     /// Consumes Commit to be returned as an `Object`
into_object(self) -> Object<'repo>196     pub fn into_object(self) -> Object<'repo> {
197         assert_eq!(mem::size_of_val(&self), mem::size_of::<Object<'_>>());
198         unsafe { mem::transmute(self) }
199     }
200 }
201 
202 type TreeWalkCb<'a, T> = dyn FnMut(&str, &TreeEntry<'_>) -> T + 'a;
203 
treewalk_cb<T: Into<i32>>( root: *const c_char, entry: *const raw::git_tree_entry, payload: *mut c_void, ) -> c_int204 extern "C" fn treewalk_cb<T: Into<i32>>(
205     root: *const c_char,
206     entry: *const raw::git_tree_entry,
207     payload: *mut c_void,
208 ) -> c_int {
209     match panic::wrap(|| unsafe {
210         let root = match CStr::from_ptr(root).to_str() {
211             Ok(value) => value,
212             _ => return -1,
213         };
214         let entry = entry_from_raw_const(entry);
215         let payload = payload as *mut &mut TreeWalkCb<'_, T>;
216         (*payload)(root, &entry).into()
217     }) {
218         Some(value) => value,
219         None => -1,
220     }
221 }
222 
223 impl<'repo> Binding for Tree<'repo> {
224     type Raw = *mut raw::git_tree;
225 
from_raw(raw: *mut raw::git_tree) -> Tree<'repo>226     unsafe fn from_raw(raw: *mut raw::git_tree) -> Tree<'repo> {
227         Tree {
228             raw: raw,
229             _marker: marker::PhantomData,
230         }
231     }
raw(&self) -> *mut raw::git_tree232     fn raw(&self) -> *mut raw::git_tree {
233         self.raw
234     }
235 }
236 
237 impl<'repo> std::fmt::Debug for Tree<'repo> {
fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error>238     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
239         f.debug_struct("Tree").field("id", &self.id()).finish()
240     }
241 }
242 
243 impl<'repo> Clone for Tree<'repo> {
clone(&self) -> Self244     fn clone(&self) -> Self {
245         self.as_object().clone().into_tree().ok().unwrap()
246     }
247 }
248 
249 impl<'repo> Drop for Tree<'repo> {
drop(&mut self)250     fn drop(&mut self) {
251         unsafe { raw::git_tree_free(self.raw) }
252     }
253 }
254 
255 impl<'repo, 'iter> IntoIterator for &'iter Tree<'repo> {
256     type Item = TreeEntry<'iter>;
257     type IntoIter = TreeIter<'iter>;
into_iter(self) -> Self::IntoIter258     fn into_iter(self) -> Self::IntoIter {
259         self.iter()
260     }
261 }
262 
263 /// Create a new tree entry from the raw pointer provided.
264 ///
265 /// The lifetime of the entry is tied to the tree provided and the function
266 /// is unsafe because the validity of the pointer cannot be guaranteed.
entry_from_raw_const<'tree>(raw: *const raw::git_tree_entry) -> TreeEntry<'tree>267 pub unsafe fn entry_from_raw_const<'tree>(raw: *const raw::git_tree_entry) -> TreeEntry<'tree> {
268     TreeEntry {
269         raw: raw as *mut raw::git_tree_entry,
270         owned: false,
271         _marker: marker::PhantomData,
272     }
273 }
274 
275 impl<'tree> TreeEntry<'tree> {
276     /// Get the id of the object pointed by the entry
id(&self) -> Oid277     pub fn id(&self) -> Oid {
278         unsafe { Binding::from_raw(raw::git_tree_entry_id(&*self.raw)) }
279     }
280 
281     /// Get the filename of a tree entry
282     ///
283     /// Returns `None` if the name is not valid utf-8
name(&self) -> Option<&str>284     pub fn name(&self) -> Option<&str> {
285         str::from_utf8(self.name_bytes()).ok()
286     }
287 
288     /// Get the filename of a tree entry
name_bytes(&self) -> &[u8]289     pub fn name_bytes(&self) -> &[u8] {
290         unsafe { crate::opt_bytes(self, raw::git_tree_entry_name(&*self.raw())).unwrap() }
291     }
292 
293     /// Convert a tree entry to the object it points to.
to_object<'a>(&self, repo: &'a Repository) -> Result<Object<'a>, Error>294     pub fn to_object<'a>(&self, repo: &'a Repository) -> Result<Object<'a>, Error> {
295         let mut ret = ptr::null_mut();
296         unsafe {
297             try_call!(raw::git_tree_entry_to_object(
298                 &mut ret,
299                 repo.raw(),
300                 &*self.raw()
301             ));
302             Ok(Binding::from_raw(ret))
303         }
304     }
305 
306     /// Get the type of the object pointed by the entry
kind(&self) -> Option<ObjectType>307     pub fn kind(&self) -> Option<ObjectType> {
308         ObjectType::from_raw(unsafe { raw::git_tree_entry_type(&*self.raw) })
309     }
310 
311     /// Get the UNIX file attributes of a tree entry
filemode(&self) -> i32312     pub fn filemode(&self) -> i32 {
313         unsafe { raw::git_tree_entry_filemode(&*self.raw) as i32 }
314     }
315 
316     /// Get the raw UNIX file attributes of a tree entry
filemode_raw(&self) -> i32317     pub fn filemode_raw(&self) -> i32 {
318         unsafe { raw::git_tree_entry_filemode_raw(&*self.raw) as i32 }
319     }
320 
321     /// Convert this entry of any lifetime into an owned signature with a static
322     /// lifetime.
323     ///
324     /// This will use the `Clone::clone` implementation under the hood.
to_owned(&self) -> TreeEntry<'static>325     pub fn to_owned(&self) -> TreeEntry<'static> {
326         unsafe {
327             let me = mem::transmute::<&TreeEntry<'tree>, &TreeEntry<'static>>(self);
328             me.clone()
329         }
330     }
331 }
332 
333 impl<'a> Binding for TreeEntry<'a> {
334     type Raw = *mut raw::git_tree_entry;
from_raw(raw: *mut raw::git_tree_entry) -> TreeEntry<'a>335     unsafe fn from_raw(raw: *mut raw::git_tree_entry) -> TreeEntry<'a> {
336         TreeEntry {
337             raw: raw,
338             owned: true,
339             _marker: marker::PhantomData,
340         }
341     }
raw(&self) -> *mut raw::git_tree_entry342     fn raw(&self) -> *mut raw::git_tree_entry {
343         self.raw
344     }
345 }
346 
347 impl<'a> Clone for TreeEntry<'a> {
clone(&self) -> TreeEntry<'a>348     fn clone(&self) -> TreeEntry<'a> {
349         let mut ret = ptr::null_mut();
350         unsafe {
351             assert_eq!(raw::git_tree_entry_dup(&mut ret, &*self.raw()), 0);
352             Binding::from_raw(ret)
353         }
354     }
355 }
356 
357 impl<'a> PartialOrd for TreeEntry<'a> {
partial_cmp(&self, other: &TreeEntry<'a>) -> Option<Ordering>358     fn partial_cmp(&self, other: &TreeEntry<'a>) -> Option<Ordering> {
359         Some(self.cmp(other))
360     }
361 }
362 impl<'a> Ord for TreeEntry<'a> {
cmp(&self, other: &TreeEntry<'a>) -> Ordering363     fn cmp(&self, other: &TreeEntry<'a>) -> Ordering {
364         c_cmp_to_ordering(unsafe { raw::git_tree_entry_cmp(&*self.raw(), &*other.raw()) })
365     }
366 }
367 
368 impl<'a> PartialEq for TreeEntry<'a> {
eq(&self, other: &TreeEntry<'a>) -> bool369     fn eq(&self, other: &TreeEntry<'a>) -> bool {
370         self.cmp(other) == Ordering::Equal
371     }
372 }
373 impl<'a> Eq for TreeEntry<'a> {}
374 
375 impl<'a> Drop for TreeEntry<'a> {
drop(&mut self)376     fn drop(&mut self) {
377         if self.owned {
378             unsafe { raw::git_tree_entry_free(self.raw) }
379         }
380     }
381 }
382 
383 impl<'tree> Iterator for TreeIter<'tree> {
384     type Item = TreeEntry<'tree>;
next(&mut self) -> Option<TreeEntry<'tree>>385     fn next(&mut self) -> Option<TreeEntry<'tree>> {
386         self.range.next().and_then(|i| self.tree.get(i))
387     }
size_hint(&self) -> (usize, Option<usize>)388     fn size_hint(&self) -> (usize, Option<usize>) {
389         self.range.size_hint()
390     }
391 }
392 impl<'tree> DoubleEndedIterator for TreeIter<'tree> {
next_back(&mut self) -> Option<TreeEntry<'tree>>393     fn next_back(&mut self) -> Option<TreeEntry<'tree>> {
394         self.range.next_back().and_then(|i| self.tree.get(i))
395     }
396 }
397 impl<'tree> ExactSizeIterator for TreeIter<'tree> {}
398 
399 #[cfg(test)]
400 mod tests {
401     use super::{TreeWalkMode, TreeWalkResult};
402     use crate::{Object, ObjectType, Repository, Tree, TreeEntry};
403     use std::fs::File;
404     use std::io::prelude::*;
405     use std::path::Path;
406     use tempfile::TempDir;
407 
408     pub struct TestTreeIter<'a> {
409         entries: Vec<TreeEntry<'a>>,
410         repo: &'a Repository,
411     }
412 
413     impl<'a> Iterator for TestTreeIter<'a> {
414         type Item = TreeEntry<'a>;
415 
next(&mut self) -> Option<TreeEntry<'a>>416         fn next(&mut self) -> Option<TreeEntry<'a>> {
417             if self.entries.is_empty() {
418                 None
419             } else {
420                 let entry = self.entries.remove(0);
421 
422                 match entry.kind() {
423                     Some(ObjectType::Tree) => {
424                         let obj: Object<'a> = entry.to_object(self.repo).unwrap();
425 
426                         let tree: &Tree<'a> = obj.as_tree().unwrap();
427 
428                         for entry in tree.iter() {
429                             self.entries.push(entry.to_owned());
430                         }
431                     }
432                     _ => {}
433                 }
434 
435                 Some(entry)
436             }
437         }
438     }
439 
tree_iter<'repo>(tree: &Tree<'repo>, repo: &'repo Repository) -> TestTreeIter<'repo>440     fn tree_iter<'repo>(tree: &Tree<'repo>, repo: &'repo Repository) -> TestTreeIter<'repo> {
441         let mut initial = vec![];
442 
443         for entry in tree.iter() {
444             initial.push(entry.to_owned());
445         }
446 
447         TestTreeIter {
448             entries: initial,
449             repo: repo,
450         }
451     }
452 
453     #[test]
smoke_tree_iter()454     fn smoke_tree_iter() {
455         let (td, repo) = crate::test::repo_init();
456 
457         setup_repo(&td, &repo);
458 
459         let head = repo.head().unwrap();
460         let target = head.target().unwrap();
461         let commit = repo.find_commit(target).unwrap();
462 
463         let tree = repo.find_tree(commit.tree_id()).unwrap();
464         assert_eq!(tree.id(), commit.tree_id());
465         assert_eq!(tree.len(), 1);
466 
467         for entry in tree_iter(&tree, &repo) {
468             println!("iter entry {:?}", entry.name());
469         }
470     }
471 
setup_repo(td: &TempDir, repo: &Repository)472     fn setup_repo(td: &TempDir, repo: &Repository) {
473         let mut index = repo.index().unwrap();
474         File::create(&td.path().join("foo"))
475             .unwrap()
476             .write_all(b"foo")
477             .unwrap();
478         index.add_path(Path::new("foo")).unwrap();
479         let id = index.write_tree().unwrap();
480         let sig = repo.signature().unwrap();
481         let tree = repo.find_tree(id).unwrap();
482         let parent = repo
483             .find_commit(repo.head().unwrap().target().unwrap())
484             .unwrap();
485         repo.commit(
486             Some("HEAD"),
487             &sig,
488             &sig,
489             "another commit",
490             &tree,
491             &[&parent],
492         )
493         .unwrap();
494     }
495 
496     #[test]
smoke()497     fn smoke() {
498         let (td, repo) = crate::test::repo_init();
499 
500         setup_repo(&td, &repo);
501 
502         let head = repo.head().unwrap();
503         let target = head.target().unwrap();
504         let commit = repo.find_commit(target).unwrap();
505 
506         let tree = repo.find_tree(commit.tree_id()).unwrap();
507         assert_eq!(tree.id(), commit.tree_id());
508         assert_eq!(tree.len(), 1);
509         {
510             let e1 = tree.get(0).unwrap();
511             assert!(e1 == tree.get_id(e1.id()).unwrap());
512             assert!(e1 == tree.get_name("foo").unwrap());
513             assert!(e1 == tree.get_path(Path::new("foo")).unwrap());
514             assert_eq!(e1.name(), Some("foo"));
515             e1.to_object(&repo).unwrap();
516         }
517         tree.into_object();
518 
519         repo.find_object(commit.tree_id(), None)
520             .unwrap()
521             .as_tree()
522             .unwrap();
523         repo.find_object(commit.tree_id(), None)
524             .unwrap()
525             .into_tree()
526             .ok()
527             .unwrap();
528     }
529 
530     #[test]
tree_walk()531     fn tree_walk() {
532         let (td, repo) = crate::test::repo_init();
533 
534         setup_repo(&td, &repo);
535 
536         let head = repo.head().unwrap();
537         let target = head.target().unwrap();
538         let commit = repo.find_commit(target).unwrap();
539         let tree = repo.find_tree(commit.tree_id()).unwrap();
540 
541         let mut ct = 0;
542         tree.walk(TreeWalkMode::PreOrder, |_, entry| {
543             assert_eq!(entry.name(), Some("foo"));
544             ct += 1;
545             0
546         })
547         .unwrap();
548         assert_eq!(ct, 1);
549 
550         let mut ct = 0;
551         tree.walk(TreeWalkMode::PreOrder, |_, entry| {
552             assert_eq!(entry.name(), Some("foo"));
553             ct += 1;
554             TreeWalkResult::Ok
555         })
556         .unwrap();
557         assert_eq!(ct, 1);
558     }
559 }
560