]> git.proxmox.com Git - rustc.git/blob - extra/git2/src/treebuilder.rs
New upstream version 1.73.0+dfsg1
[rustc.git] / extra / git2 / src / treebuilder.rs
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 (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
18 pub struct TreeBuilder<'repo> {
19 raw: *mut raw::git_treebuilder,
20 _marker: marker::PhantomData<&'repo Repository>,
21 }
22
23 impl<'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
127 type FilterCb<'a> = dyn FnMut(&TreeEntry<'_>) -> bool + 'a;
128
129 extern "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
147 impl<'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
161 impl<'repo> Drop for TreeBuilder<'repo> {
162 fn drop(&mut self) {
163 unsafe { raw::git_treebuilder_free(self.raw) }
164 }
165 }
166
167 #[cfg(test)]
168 mod 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 }