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