]> git.proxmox.com Git - rustc.git/blame - extra/git2/src/tree.rs
New upstream version 1.73.0+dfsg1
[rustc.git] / extra / git2 / src / tree.rs
CommitLineData
0a29b90c
FG
1use libc::{self, 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
15/// A structure to represent a git [tree][1]
16///
17/// [1]: http://git-scm.com/book/en/Git-Internals-Git-Objects
18pub 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.
25pub 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.
32pub 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.
39pub 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)]
48pub 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
57impl Into<i32> for TreeWalkResult {
58 fn into(self) -> i32 {
59 self as i32
60 }
61}
62
63impl 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
74impl<'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
206type TreeWalkCb<'a, T> = dyn FnMut(&str, &TreeEntry<'_>) -> T + 'a;
207
208struct TreeWalkCbData<'a, T> {
209 callback: &'a mut TreeWalkCb<'a, T>,
210}
211
212extern "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
232impl<'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
246impl<'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
252impl<'repo> Clone for Tree<'repo> {
253 fn clone(&self) -> Self {
254 self.as_object().clone().into_tree().ok().unwrap()
255 }
256}
257
258impl<'repo> Drop for Tree<'repo> {
259 fn drop(&mut self) {
260 unsafe { raw::git_tree_free(self.raw) }
261 }
262}
263
264impl<'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.
276pub 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
284impl<'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
342impl<'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
356impl<'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
366impl<'a> PartialOrd for TreeEntry<'a> {
367 fn partial_cmp(&self, other: &TreeEntry<'a>) -> Option<Ordering> {
368 Some(self.cmp(other))
369 }
370}
371impl<'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
377impl<'a> PartialEq for TreeEntry<'a> {
378 fn eq(&self, other: &TreeEntry<'a>) -> bool {
379 self.cmp(other) == Ordering::Equal
380 }
381}
382impl<'a> Eq for TreeEntry<'a> {}
383
384impl<'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
392impl<'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}
401impl<'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}
406impl<'tree> FusedIterator for TreeIter<'tree> {}
407impl<'tree> ExactSizeIterator for TreeIter<'tree> {}
408
409#[cfg(test)]
410mod 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}