git2/
treebuilder.rs

1use std::marker;
2use std::ptr;
3
4use libc::{c_int, c_void};
5
6use crate::util::{Binding, IntoCString};
7use crate::{panic, raw, tree, Error, Oid, Repository, TreeEntry};
8
9/// Constructor for in-memory trees (low-level)
10///
11/// You probably want to use [`build::TreeUpdateBuilder`] instead.
12///
13/// This is the more raw of the two tree update facilities.  It
14/// handles only one level of a nested tree structure at a time.  Each
15/// path passed to `insert` etc. must be a single component.
16///
17/// [`build::TreeUpdateBuilder`]: crate::build::TreeUpdateBuilder
18pub struct TreeBuilder<'repo> {
19    raw: *mut raw::git_treebuilder,
20    _marker: marker::PhantomData<&'repo Repository>,
21}
22
23impl<'repo> TreeBuilder<'repo> {
24    /// Clear all the entries in the builder
25    pub fn clear(&mut self) -> Result<(), Error> {
26        unsafe {
27            try_call!(raw::git_treebuilder_clear(self.raw));
28        }
29        Ok(())
30    }
31
32    /// Get the number of entries
33    pub fn len(&self) -> usize {
34        unsafe { raw::git_treebuilder_entrycount(self.raw) as usize }
35    }
36
37    /// Return `true` if there is no entry
38    pub fn is_empty(&self) -> bool {
39        self.len() == 0
40    }
41
42    /// Get en entry from the builder from its filename
43    pub fn get<P>(&self, filename: P) -> Result<Option<TreeEntry<'_>>, Error>
44    where
45        P: IntoCString,
46    {
47        let filename = filename.into_c_string()?;
48        unsafe {
49            let ret = raw::git_treebuilder_get(self.raw, filename.as_ptr());
50            if ret.is_null() {
51                Ok(None)
52            } else {
53                Ok(Some(tree::entry_from_raw_const(ret)))
54            }
55        }
56    }
57
58    /// Add or update an entry in the builder
59    ///
60    /// No attempt is made to ensure that the provided Oid points to
61    /// an object of a reasonable type (or any object at all).
62    ///
63    /// The mode given must be one of 0o040000, 0o100644, 0o100755, 0o120000 or
64    /// 0o160000 currently.
65    pub fn insert<P: IntoCString>(
66        &mut self,
67        filename: P,
68        oid: Oid,
69        filemode: i32,
70    ) -> Result<TreeEntry<'_>, Error> {
71        let filename = filename.into_c_string()?;
72        let filemode = filemode as raw::git_filemode_t;
73
74        let mut ret = ptr::null();
75        unsafe {
76            try_call!(raw::git_treebuilder_insert(
77                &mut ret,
78                self.raw,
79                filename,
80                oid.raw(),
81                filemode
82            ));
83            Ok(tree::entry_from_raw_const(ret))
84        }
85    }
86
87    /// Remove an entry from the builder by its filename
88    pub fn remove<P: IntoCString>(&mut self, filename: P) -> Result<(), Error> {
89        let filename = filename.into_c_string()?;
90        unsafe {
91            try_call!(raw::git_treebuilder_remove(self.raw, filename));
92        }
93        Ok(())
94    }
95
96    /// Selectively remove entries from the tree
97    ///
98    /// Values for which the filter returns `true` will be kept.  Note
99    /// that this behavior is different from the libgit2 C interface.
100    pub fn filter<F>(&mut self, mut filter: F) -> Result<(), Error>
101    where
102        F: FnMut(&TreeEntry<'_>) -> bool,
103    {
104        let mut cb: &mut FilterCb<'_> = &mut filter;
105        let ptr = &mut cb as *mut _;
106        let cb: raw::git_treebuilder_filter_cb = Some(filter_cb);
107        unsafe {
108            try_call!(raw::git_treebuilder_filter(self.raw, cb, ptr as *mut _));
109            panic::check();
110        }
111        Ok(())
112    }
113
114    /// Write the contents of the TreeBuilder as a Tree object and
115    /// return its Oid
116    pub fn write(&self) -> Result<Oid, Error> {
117        let mut raw = raw::git_oid {
118            id: [0; raw::GIT_OID_RAWSZ],
119        };
120        unsafe {
121            try_call!(raw::git_treebuilder_write(&mut raw, self.raw()));
122            Ok(Binding::from_raw(&raw as *const _))
123        }
124    }
125}
126
127type FilterCb<'a> = dyn FnMut(&TreeEntry<'_>) -> bool + 'a;
128
129extern "C" fn filter_cb(entry: *const raw::git_tree_entry, payload: *mut c_void) -> c_int {
130    let ret = panic::wrap(|| unsafe {
131        // There's no way to return early from git_treebuilder_filter.
132        if panic::panicked() {
133            true
134        } else {
135            let entry = tree::entry_from_raw_const(entry);
136            let payload = payload as *mut &mut FilterCb<'_>;
137            (*payload)(&entry)
138        }
139    });
140    if ret == Some(false) {
141        1
142    } else {
143        0
144    }
145}
146
147impl<'repo> Binding for TreeBuilder<'repo> {
148    type Raw = *mut raw::git_treebuilder;
149
150    unsafe fn from_raw(raw: *mut raw::git_treebuilder) -> TreeBuilder<'repo> {
151        TreeBuilder {
152            raw,
153            _marker: marker::PhantomData,
154        }
155    }
156    fn raw(&self) -> *mut raw::git_treebuilder {
157        self.raw
158    }
159}
160
161impl<'repo> Drop for TreeBuilder<'repo> {
162    fn drop(&mut self) {
163        unsafe { raw::git_treebuilder_free(self.raw) }
164    }
165}
166
167#[cfg(test)]
168mod tests {
169    use crate::ObjectType;
170
171    #[test]
172    fn smoke() {
173        let (_td, repo) = crate::test::repo_init();
174
175        let mut builder = repo.treebuilder(None).unwrap();
176        assert_eq!(builder.len(), 0);
177        let blob = repo.blob(b"data").unwrap();
178        {
179            let entry = builder.insert("a", blob, 0o100644).unwrap();
180            assert_eq!(entry.kind(), Some(ObjectType::Blob));
181        }
182        builder.insert("b", blob, 0o100644).unwrap();
183        assert_eq!(builder.len(), 2);
184        builder.remove("a").unwrap();
185        assert_eq!(builder.len(), 1);
186        assert_eq!(builder.get("b").unwrap().unwrap().id(), blob);
187        builder.clear().unwrap();
188        assert_eq!(builder.len(), 0);
189    }
190
191    #[test]
192    fn write() {
193        let (_td, repo) = crate::test::repo_init();
194
195        let mut builder = repo.treebuilder(None).unwrap();
196        let data = repo.blob(b"data").unwrap();
197        builder.insert("name", data, 0o100644).unwrap();
198        let tree = builder.write().unwrap();
199        let tree = repo.find_tree(tree).unwrap();
200        let entry = tree.get(0).unwrap();
201        assert_eq!(entry.name(), Some("name"));
202        let blob = entry.to_object(&repo).unwrap();
203        let blob = blob.as_blob().unwrap();
204        assert_eq!(blob.content(), b"data");
205
206        let builder = repo.treebuilder(Some(&tree)).unwrap();
207        assert_eq!(builder.len(), 1);
208    }
209
210    #[test]
211    fn filter() {
212        let (_td, repo) = crate::test::repo_init();
213
214        let mut builder = repo.treebuilder(None).unwrap();
215        let blob = repo.blob(b"data").unwrap();
216        let tree = {
217            let head = repo.head().unwrap().peel(ObjectType::Commit).unwrap();
218            let head = head.as_commit().unwrap();
219            head.tree_id()
220        };
221        builder.insert("blob", blob, 0o100644).unwrap();
222        builder.insert("dir", tree, 0o040000).unwrap();
223        builder.insert("dir2", tree, 0o040000).unwrap();
224
225        builder.filter(|_| true).unwrap();
226        assert_eq!(builder.len(), 3);
227        builder
228            .filter(|e| e.kind().unwrap() != ObjectType::Blob)
229            .unwrap();
230        assert_eq!(builder.len(), 2);
231        builder.filter(|_| false).unwrap();
232        assert_eq!(builder.len(), 0);
233    }
234}