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