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