git2/
tree.rs

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