1use libc::{c_char, c_int, c_void};
2use std::cmp::Ordering;
3use std::ffi::{CStr, CString};
4use std::iter::FusedIterator;
5use std::marker;
6use std::mem;
7use std::ops::Range;
8use std::path::Path;
9use std::ptr;
10use std::str;
11
12use crate::util::{c_cmp_to_ordering, path_to_repo_path, Binding};
13use crate::{panic, raw, Error, Object, ObjectType, Oid, Repository};
14
15pub struct Tree<'repo> {
19 raw: *mut raw::git_tree,
20 _marker: marker::PhantomData<Object<'repo>>,
21}
22
23pub struct TreeEntry<'tree> {
26 raw: *mut raw::git_tree_entry,
27 owned: bool,
28 _marker: marker::PhantomData<&'tree raw::git_tree_entry>,
29}
30
31pub struct TreeIter<'tree> {
33 range: Range<usize>,
34 tree: &'tree Tree<'tree>,
35}
36
37#[derive(Clone, Copy)]
40pub enum TreeWalkMode {
41 PreOrder = 0,
43 PostOrder = 1,
45}
46
47#[repr(i32)]
49pub enum TreeWalkResult {
50 Ok = 0,
52 Skip = 1,
54 Abort = raw::GIT_EUSER,
56}
57
58impl Into<i32> for TreeWalkResult {
59 fn into(self) -> i32 {
60 self as i32
61 }
62}
63
64impl Into<raw::git_treewalk_mode> for TreeWalkMode {
65 #[cfg(target_env = "msvc")]
66 fn into(self) -> raw::git_treewalk_mode {
67 self as i32
68 }
69 #[cfg(not(target_env = "msvc"))]
70 fn into(self) -> raw::git_treewalk_mode {
71 self as u32
72 }
73}
74
75impl<'repo> Tree<'repo> {
76 pub fn id(&self) -> Oid {
78 unsafe { Binding::from_raw(raw::git_tree_id(&*self.raw)) }
79 }
80
81 pub fn len(&self) -> usize {
83 unsafe { raw::git_tree_entrycount(&*self.raw) as usize }
84 }
85
86 pub fn is_empty(&self) -> bool {
88 self.len() == 0
89 }
90
91 pub fn iter(&self) -> TreeIter<'_> {
93 TreeIter {
94 range: 0..self.len(),
95 tree: self,
96 }
97 }
98
99 pub fn walk<C, T>(&self, mode: TreeWalkMode, mut callback: C) -> Result<(), Error>
122 where
123 C: FnMut(&str, &TreeEntry<'_>) -> T,
124 T: Into<i32>,
125 {
126 unsafe {
127 let mut data = TreeWalkCbData {
128 callback: &mut callback,
129 };
130 try_call!(raw::git_tree_walk(
131 self.raw(),
132 mode as raw::git_treewalk_mode,
133 treewalk_cb::<T>,
134 &mut data as *mut _ as *mut c_void
135 ));
136 Ok(())
137 }
138 }
139
140 pub fn get_id(&self, id: Oid) -> Option<TreeEntry<'_>> {
142 unsafe {
143 let ptr = raw::git_tree_entry_byid(&*self.raw(), &*id.raw());
144 if ptr.is_null() {
145 None
146 } else {
147 Some(entry_from_raw_const(ptr))
148 }
149 }
150 }
151
152 pub fn get(&self, n: usize) -> Option<TreeEntry<'_>> {
154 unsafe {
155 let ptr = raw::git_tree_entry_byindex(&*self.raw(), n as libc::size_t);
156 if ptr.is_null() {
157 None
158 } else {
159 Some(entry_from_raw_const(ptr))
160 }
161 }
162 }
163
164 pub fn get_name(&self, filename: &str) -> Option<TreeEntry<'_>> {
166 self.get_name_bytes(filename.as_bytes())
167 }
168
169 pub fn get_name_bytes(&self, filename: &[u8]) -> Option<TreeEntry<'_>> {
173 let filename = CString::new(filename).unwrap();
174 unsafe {
175 let ptr = call!(raw::git_tree_entry_byname(&*self.raw(), filename));
176 if ptr.is_null() {
177 None
178 } else {
179 Some(entry_from_raw_const(ptr))
180 }
181 }
182 }
183
184 pub fn get_path(&self, path: &Path) -> Result<TreeEntry<'static>, Error> {
187 let path = path_to_repo_path(path)?;
188 let mut ret = ptr::null_mut();
189 unsafe {
190 try_call!(raw::git_tree_entry_bypath(&mut ret, &*self.raw(), path));
191 Ok(Binding::from_raw(ret))
192 }
193 }
194
195 pub fn as_object(&self) -> &Object<'repo> {
197 unsafe { &*(self as *const _ as *const Object<'repo>) }
198 }
199
200 pub fn into_object(self) -> Object<'repo> {
202 assert_eq!(mem::size_of_val(&self), mem::size_of::<Object<'_>>());
203 unsafe { mem::transmute(self) }
204 }
205}
206
207type TreeWalkCb<'a, T> = dyn FnMut(&str, &TreeEntry<'_>) -> T + 'a;
208
209struct TreeWalkCbData<'a, T> {
210 callback: &'a mut TreeWalkCb<'a, T>,
211}
212
213extern "C" fn treewalk_cb<T: Into<i32>>(
214 root: *const c_char,
215 entry: *const raw::git_tree_entry,
216 payload: *mut c_void,
217) -> c_int {
218 match panic::wrap(|| unsafe {
219 let root = match CStr::from_ptr(root).to_str() {
220 Ok(value) => value,
221 _ => return -1,
222 };
223 let entry = entry_from_raw_const(entry);
224 let payload = &mut *(payload as *mut TreeWalkCbData<'_, T>);
225 let callback = &mut payload.callback;
226 callback(root, &entry).into()
227 }) {
228 Some(value) => value,
229 None => -1,
230 }
231}
232
233impl<'repo> Binding for Tree<'repo> {
234 type Raw = *mut raw::git_tree;
235
236 unsafe fn from_raw(raw: *mut raw::git_tree) -> Tree<'repo> {
237 Tree {
238 raw,
239 _marker: marker::PhantomData,
240 }
241 }
242 fn raw(&self) -> *mut raw::git_tree {
243 self.raw
244 }
245}
246
247impl<'repo> std::fmt::Debug for Tree<'repo> {
248 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
249 f.debug_struct("Tree").field("id", &self.id()).finish()
250 }
251}
252
253impl<'repo> Clone for Tree<'repo> {
254 fn clone(&self) -> Self {
255 self.as_object().clone().into_tree().ok().unwrap()
256 }
257}
258
259impl<'repo> Drop for Tree<'repo> {
260 fn drop(&mut self) {
261 unsafe { raw::git_tree_free(self.raw) }
262 }
263}
264
265impl<'repo, 'iter> IntoIterator for &'iter Tree<'repo> {
266 type Item = TreeEntry<'iter>;
267 type IntoIter = TreeIter<'iter>;
268 fn into_iter(self) -> Self::IntoIter {
269 self.iter()
270 }
271}
272
273pub unsafe fn entry_from_raw_const<'tree>(raw: *const raw::git_tree_entry) -> TreeEntry<'tree> {
278 TreeEntry {
279 raw: raw as *mut raw::git_tree_entry,
280 owned: false,
281 _marker: marker::PhantomData,
282 }
283}
284
285impl<'tree> TreeEntry<'tree> {
286 pub fn id(&self) -> Oid {
288 unsafe { Binding::from_raw(raw::git_tree_entry_id(&*self.raw)) }
289 }
290
291 pub fn name(&self) -> Option<&str> {
295 str::from_utf8(self.name_bytes()).ok()
296 }
297
298 pub fn name_bytes(&self) -> &[u8] {
300 unsafe { crate::opt_bytes(self, raw::git_tree_entry_name(&*self.raw())).unwrap() }
301 }
302
303 pub fn to_object<'a>(&self, repo: &'a Repository) -> Result<Object<'a>, Error> {
305 let mut ret = ptr::null_mut();
306 unsafe {
307 try_call!(raw::git_tree_entry_to_object(
308 &mut ret,
309 repo.raw(),
310 &*self.raw()
311 ));
312 Ok(Binding::from_raw(ret))
313 }
314 }
315
316 pub fn kind(&self) -> Option<ObjectType> {
318 ObjectType::from_raw(unsafe { raw::git_tree_entry_type(&*self.raw) })
319 }
320
321 pub fn filemode(&self) -> i32 {
323 unsafe { raw::git_tree_entry_filemode(&*self.raw) as i32 }
324 }
325
326 pub fn filemode_raw(&self) -> i32 {
328 unsafe { raw::git_tree_entry_filemode_raw(&*self.raw) as i32 }
329 }
330
331 pub fn to_owned(&self) -> TreeEntry<'static> {
336 unsafe {
337 let me = mem::transmute::<&TreeEntry<'tree>, &TreeEntry<'static>>(self);
338 me.clone()
339 }
340 }
341}
342
343impl<'a> Binding for TreeEntry<'a> {
344 type Raw = *mut raw::git_tree_entry;
345 unsafe fn from_raw(raw: *mut raw::git_tree_entry) -> TreeEntry<'a> {
346 TreeEntry {
347 raw,
348 owned: true,
349 _marker: marker::PhantomData,
350 }
351 }
352 fn raw(&self) -> *mut raw::git_tree_entry {
353 self.raw
354 }
355}
356
357impl<'a> Clone for TreeEntry<'a> {
358 fn clone(&self) -> TreeEntry<'a> {
359 let mut ret = ptr::null_mut();
360 unsafe {
361 assert_eq!(raw::git_tree_entry_dup(&mut ret, &*self.raw()), 0);
362 Binding::from_raw(ret)
363 }
364 }
365}
366
367impl<'a> PartialOrd for TreeEntry<'a> {
368 fn partial_cmp(&self, other: &TreeEntry<'a>) -> Option<Ordering> {
369 Some(self.cmp(other))
370 }
371}
372impl<'a> Ord for TreeEntry<'a> {
373 fn cmp(&self, other: &TreeEntry<'a>) -> Ordering {
374 c_cmp_to_ordering(unsafe { raw::git_tree_entry_cmp(&*self.raw(), &*other.raw()) })
375 }
376}
377
378impl<'a> PartialEq for TreeEntry<'a> {
379 fn eq(&self, other: &TreeEntry<'a>) -> bool {
380 self.cmp(other) == Ordering::Equal
381 }
382}
383impl<'a> Eq for TreeEntry<'a> {}
384
385impl<'a> Drop for TreeEntry<'a> {
386 fn drop(&mut self) {
387 if self.owned {
388 unsafe { raw::git_tree_entry_free(self.raw) }
389 }
390 }
391}
392
393impl<'tree> Iterator for TreeIter<'tree> {
394 type Item = TreeEntry<'tree>;
395 fn next(&mut self) -> Option<TreeEntry<'tree>> {
396 self.range.next().and_then(|i| self.tree.get(i))
397 }
398 fn size_hint(&self) -> (usize, Option<usize>) {
399 self.range.size_hint()
400 }
401 fn nth(&mut self, n: usize) -> Option<TreeEntry<'tree>> {
402 self.range.nth(n).and_then(|i| self.tree.get(i))
403 }
404}
405impl<'tree> DoubleEndedIterator for TreeIter<'tree> {
406 fn next_back(&mut self) -> Option<TreeEntry<'tree>> {
407 self.range.next_back().and_then(|i| self.tree.get(i))
408 }
409}
410impl<'tree> FusedIterator for TreeIter<'tree> {}
411impl<'tree> ExactSizeIterator for TreeIter<'tree> {}
412
413#[cfg(test)]
414mod tests {
415 use super::{TreeWalkMode, TreeWalkResult};
416 use crate::{Object, ObjectType, Repository, Tree, TreeEntry};
417 use std::fs::File;
418 use std::io::prelude::*;
419 use std::path::Path;
420 use tempfile::TempDir;
421
422 pub struct TestTreeIter<'a> {
423 entries: Vec<TreeEntry<'a>>,
424 repo: &'a Repository,
425 }
426
427 impl<'a> Iterator for TestTreeIter<'a> {
428 type Item = TreeEntry<'a>;
429
430 fn next(&mut self) -> Option<TreeEntry<'a>> {
431 if self.entries.is_empty() {
432 None
433 } else {
434 let entry = self.entries.remove(0);
435
436 match entry.kind() {
437 Some(ObjectType::Tree) => {
438 let obj: Object<'a> = entry.to_object(self.repo).unwrap();
439
440 let tree: &Tree<'a> = obj.as_tree().unwrap();
441
442 for entry in tree.iter() {
443 self.entries.push(entry.to_owned());
444 }
445 }
446 _ => {}
447 }
448
449 Some(entry)
450 }
451 }
452 }
453
454 fn tree_iter<'repo>(tree: &Tree<'repo>, repo: &'repo Repository) -> TestTreeIter<'repo> {
455 let mut initial = vec![];
456
457 for entry in tree.iter() {
458 initial.push(entry.to_owned());
459 }
460
461 TestTreeIter {
462 entries: initial,
463 repo: repo,
464 }
465 }
466
467 #[test]
468 fn smoke_tree_iter() {
469 let (td, repo) = crate::test::repo_init();
470
471 setup_repo(&td, &repo);
472
473 let head = repo.head().unwrap();
474 let target = head.target().unwrap();
475 let commit = repo.find_commit(target).unwrap();
476
477 let tree = repo.find_tree(commit.tree_id()).unwrap();
478 assert_eq!(tree.id(), commit.tree_id());
479 assert_eq!(tree.len(), 8);
480
481 for entry in tree_iter(&tree, &repo) {
482 println!("iter entry {:?}", entry.name());
483 }
484 }
485
486 #[test]
487 fn smoke_tree_nth() {
488 let (td, repo) = crate::test::repo_init();
489
490 setup_repo(&td, &repo);
491
492 let head = repo.head().unwrap();
493 let target = head.target().unwrap();
494 let commit = repo.find_commit(target).unwrap();
495
496 let tree = repo.find_tree(commit.tree_id()).unwrap();
497 assert_eq!(tree.id(), commit.tree_id());
498 assert_eq!(tree.len(), 8);
499 let mut it = tree.iter();
500 let e = it.nth(4).unwrap();
501 assert_eq!(e.name(), Some("f4"));
502 }
503
504 fn setup_repo(td: &TempDir, repo: &Repository) {
505 let mut index = repo.index().unwrap();
506 for n in 0..8 {
507 let name = format!("f{n}");
508 File::create(&td.path().join(&name))
509 .unwrap()
510 .write_all(name.as_bytes())
511 .unwrap();
512 index.add_path(Path::new(&name)).unwrap();
513 }
514 let id = index.write_tree().unwrap();
515 let sig = repo.signature().unwrap();
516 let tree = repo.find_tree(id).unwrap();
517 let parent = repo
518 .find_commit(repo.head().unwrap().target().unwrap())
519 .unwrap();
520 repo.commit(
521 Some("HEAD"),
522 &sig,
523 &sig,
524 "another commit",
525 &tree,
526 &[&parent],
527 )
528 .unwrap();
529 }
530
531 #[test]
532 fn smoke() {
533 let (td, repo) = crate::test::repo_init();
534
535 setup_repo(&td, &repo);
536
537 let head = repo.head().unwrap();
538 let target = head.target().unwrap();
539 let commit = repo.find_commit(target).unwrap();
540
541 let tree = repo.find_tree(commit.tree_id()).unwrap();
542 assert_eq!(tree.id(), commit.tree_id());
543 assert_eq!(tree.len(), 8);
544 {
545 let e0 = tree.get(0).unwrap();
546 assert!(e0 == tree.get_id(e0.id()).unwrap());
547 assert!(e0 == tree.get_name("f0").unwrap());
548 assert!(e0 == tree.get_name_bytes(b"f0").unwrap());
549 assert!(e0 == tree.get_path(Path::new("f0")).unwrap());
550 assert_eq!(e0.name(), Some("f0"));
551 e0.to_object(&repo).unwrap();
552
553 let e1 = tree.get(1).unwrap();
554 assert!(e1 == tree.get_id(e1.id()).unwrap());
555 assert!(e1 == tree.get_name("f1").unwrap());
556 assert!(e1 == tree.get_name_bytes(b"f1").unwrap());
557 assert!(e1 == tree.get_path(Path::new("f1")).unwrap());
558 assert_eq!(e1.name(), Some("f1"));
559 e1.to_object(&repo).unwrap();
560 }
561 tree.into_object();
562
563 repo.find_object(commit.tree_id(), None)
564 .unwrap()
565 .as_tree()
566 .unwrap();
567 repo.find_object(commit.tree_id(), None)
568 .unwrap()
569 .into_tree()
570 .ok()
571 .unwrap();
572 }
573
574 #[test]
575 fn tree_walk() {
576 let (td, repo) = crate::test::repo_init();
577
578 setup_repo(&td, &repo);
579
580 let head = repo.head().unwrap();
581 let target = head.target().unwrap();
582 let commit = repo.find_commit(target).unwrap();
583 let tree = repo.find_tree(commit.tree_id()).unwrap();
584
585 let mut ct = 0;
586 tree.walk(TreeWalkMode::PreOrder, |_, entry| {
587 assert_eq!(entry.name(), Some(format!("f{ct}").as_str()));
588 ct += 1;
589 0
590 })
591 .unwrap();
592 assert_eq!(ct, 8);
593
594 let mut ct = 0;
595 tree.walk(TreeWalkMode::PreOrder, |_, entry| {
596 assert_eq!(entry.name(), Some(format!("f{ct}").as_str()));
597 ct += 1;
598 TreeWalkResult::Ok
599 })
600 .unwrap();
601 assert_eq!(ct, 8);
602 }
603
604 #[test]
605 fn tree_walk_error() {
606 let (td, repo) = crate::test::repo_init();
607
608 setup_repo(&td, &repo);
609
610 let head = repo.head().unwrap();
611 let target = head.target().unwrap();
612 let commit = repo.find_commit(target).unwrap();
613 let tree = repo.find_tree(commit.tree_id()).unwrap();
614 let e = tree.walk(TreeWalkMode::PreOrder, |_, _| -1).unwrap_err();
615 assert_eq!(e.class(), crate::ErrorClass::Callback);
616 }
617}