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