summaryrefslogtreecommitdiffstats
path: root/vendor/git2/src/tree.rs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--vendor/git2/src/tree.rs570
1 files changed, 570 insertions, 0 deletions
diff --git a/vendor/git2/src/tree.rs b/vendor/git2/src/tree.rs
new file mode 100644
index 0000000..9a38244
--- /dev/null
+++ b/vendor/git2/src/tree.rs
@@ -0,0 +1,570 @@
+use libc::{self, c_char, c_int, c_void};
+use std::cmp::Ordering;
+use std::ffi::{CStr, CString};
+use std::iter::FusedIterator;
+use std::marker;
+use std::mem;
+use std::ops::Range;
+use std::path::Path;
+use std::ptr;
+use std::str;
+
+use crate::util::{c_cmp_to_ordering, path_to_repo_path, Binding};
+use crate::{panic, raw, Error, Object, ObjectType, Oid, Repository};
+
+/// A structure to represent a git [tree][1]
+///
+/// [1]: http://git-scm.com/book/en/Git-Internals-Git-Objects
+pub struct Tree<'repo> {
+ raw: *mut raw::git_tree,
+ _marker: marker::PhantomData<Object<'repo>>,
+}
+
+/// A structure representing an entry inside of a tree. An entry is borrowed
+/// from a tree.
+pub struct TreeEntry<'tree> {
+ raw: *mut raw::git_tree_entry,
+ owned: bool,
+ _marker: marker::PhantomData<&'tree raw::git_tree_entry>,
+}
+
+/// An iterator over the entries in a tree.
+pub struct TreeIter<'tree> {
+ range: Range<usize>,
+ tree: &'tree Tree<'tree>,
+}
+
+/// A binary indicator of whether a tree walk should be performed in pre-order
+/// or post-order.
+pub enum TreeWalkMode {
+ /// Runs the traversal in pre-order.
+ PreOrder = 0,
+ /// Runs the traversal in post-order.
+ PostOrder = 1,
+}
+
+/// Possible return codes for tree walking callback functions.
+#[repr(i32)]
+pub enum TreeWalkResult {
+ /// Continue with the traversal as normal.
+ Ok = 0,
+ /// Skip the current node (in pre-order mode).
+ Skip = 1,
+ /// Completely stop the traversal.
+ Abort = raw::GIT_EUSER,
+}
+
+impl Into<i32> for TreeWalkResult {
+ fn into(self) -> i32 {
+ self as i32
+ }
+}
+
+impl Into<raw::git_treewalk_mode> for TreeWalkMode {
+ #[cfg(target_env = "msvc")]
+ fn into(self) -> raw::git_treewalk_mode {
+ self as i32
+ }
+ #[cfg(not(target_env = "msvc"))]
+ fn into(self) -> raw::git_treewalk_mode {
+ self as u32
+ }
+}
+
+impl<'repo> Tree<'repo> {
+ /// Get the id (SHA1) of a repository object
+ pub fn id(&self) -> Oid {
+ unsafe { Binding::from_raw(raw::git_tree_id(&*self.raw)) }
+ }
+
+ /// Get the number of entries listed in this tree.
+ pub fn len(&self) -> usize {
+ unsafe { raw::git_tree_entrycount(&*self.raw) as usize }
+ }
+
+ /// Return `true` if there is not entry
+ pub fn is_empty(&self) -> bool {
+ self.len() == 0
+ }
+
+ /// Returns an iterator over the entries in this tree.
+ pub fn iter(&self) -> TreeIter<'_> {
+ TreeIter {
+ range: 0..self.len(),
+ tree: self,
+ }
+ }
+
+ /// Traverse the entries in a tree and its subtrees in post or pre-order.
+ /// The callback function will be run on each node of the tree that's
+ /// walked. The return code of this function will determine how the walk
+ /// continues.
+ ///
+ /// libgit2 requires that the callback be an integer, where 0 indicates a
+ /// successful visit, 1 skips the node, and -1 aborts the traversal completely.
+ /// You may opt to use the enum [`TreeWalkResult`](TreeWalkResult) instead.
+ ///
+ /// ```ignore
+ /// let mut ct = 0;
+ /// tree.walk(TreeWalkMode::PreOrder, |_, entry| {
+ /// assert_eq!(entry.name(), Some("foo"));
+ /// ct += 1;
+ /// TreeWalkResult::Ok
+ /// }).unwrap();
+ /// assert_eq!(ct, 1);
+ /// ```
+ ///
+ /// See [libgit2 documentation][1] for more information.
+ ///
+ /// [1]: https://libgit2.org/libgit2/#HEAD/group/tree/git_tree_walk
+ pub fn walk<C, T>(&self, mode: TreeWalkMode, mut callback: C) -> Result<(), Error>
+ where
+ C: FnMut(&str, &TreeEntry<'_>) -> T,
+ T: Into<i32>,
+ {
+ unsafe {
+ let mut data = TreeWalkCbData {
+ callback: &mut callback,
+ };
+ raw::git_tree_walk(
+ self.raw(),
+ mode.into(),
+ Some(treewalk_cb::<T>),
+ &mut data as *mut _ as *mut c_void,
+ );
+ Ok(())
+ }
+ }
+
+ /// Lookup a tree entry by SHA value.
+ pub fn get_id(&self, id: Oid) -> Option<TreeEntry<'_>> {
+ unsafe {
+ let ptr = raw::git_tree_entry_byid(&*self.raw(), &*id.raw());
+ if ptr.is_null() {
+ None
+ } else {
+ Some(entry_from_raw_const(ptr))
+ }
+ }
+ }
+
+ /// Lookup a tree entry by its position in the tree
+ pub fn get(&self, n: usize) -> Option<TreeEntry<'_>> {
+ unsafe {
+ let ptr = raw::git_tree_entry_byindex(&*self.raw(), n as libc::size_t);
+ if ptr.is_null() {
+ None
+ } else {
+ Some(entry_from_raw_const(ptr))
+ }
+ }
+ }
+
+ /// Lookup a tree entry by its filename
+ pub fn get_name(&self, filename: &str) -> Option<TreeEntry<'_>> {
+ self.get_name_bytes(filename.as_bytes())
+ }
+
+ /// Lookup a tree entry by its filename, specified as bytes.
+ ///
+ /// This allows for non-UTF-8 filenames.
+ pub fn get_name_bytes(&self, filename: &[u8]) -> Option<TreeEntry<'_>> {
+ let filename = CString::new(filename).unwrap();
+ unsafe {
+ let ptr = call!(raw::git_tree_entry_byname(&*self.raw(), filename));
+ if ptr.is_null() {
+ None
+ } else {
+ Some(entry_from_raw_const(ptr))
+ }
+ }
+ }
+
+ /// Retrieve a tree entry contained in a tree or in any of its subtrees,
+ /// given its relative path.
+ pub fn get_path(&self, path: &Path) -> Result<TreeEntry<'static>, Error> {
+ let path = path_to_repo_path(path)?;
+ let mut ret = ptr::null_mut();
+ unsafe {
+ try_call!(raw::git_tree_entry_bypath(&mut ret, &*self.raw(), path));
+ Ok(Binding::from_raw(ret))
+ }
+ }
+
+ /// Casts this Tree to be usable as an `Object`
+ pub fn as_object(&self) -> &Object<'repo> {
+ unsafe { &*(self as *const _ as *const Object<'repo>) }
+ }
+
+ /// Consumes this Tree to be returned as an `Object`
+ pub fn into_object(self) -> Object<'repo> {
+ assert_eq!(mem::size_of_val(&self), mem::size_of::<Object<'_>>());
+ unsafe { mem::transmute(self) }
+ }
+}
+
+type TreeWalkCb<'a, T> = dyn FnMut(&str, &TreeEntry<'_>) -> T + 'a;
+
+struct TreeWalkCbData<'a, T> {
+ callback: &'a mut TreeWalkCb<'a, T>,
+}
+
+extern "C" fn treewalk_cb<T: Into<i32>>(
+ root: *const c_char,
+ entry: *const raw::git_tree_entry,
+ payload: *mut c_void,
+) -> c_int {
+ match panic::wrap(|| unsafe {
+ let root = match CStr::from_ptr(root).to_str() {
+ Ok(value) => value,
+ _ => return -1,
+ };
+ let entry = entry_from_raw_const(entry);
+ let payload = &mut *(payload as *mut TreeWalkCbData<'_, T>);
+ let callback = &mut payload.callback;
+ callback(root, &entry).into()
+ }) {
+ Some(value) => value,
+ None => -1,
+ }
+}
+
+impl<'repo> Binding for Tree<'repo> {
+ type Raw = *mut raw::git_tree;
+
+ unsafe fn from_raw(raw: *mut raw::git_tree) -> Tree<'repo> {
+ Tree {
+ raw,
+ _marker: marker::PhantomData,
+ }
+ }
+ fn raw(&self) -> *mut raw::git_tree {
+ self.raw
+ }
+}
+
+impl<'repo> std::fmt::Debug for Tree<'repo> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
+ f.debug_struct("Tree").field("id", &self.id()).finish()
+ }
+}
+
+impl<'repo> Clone for Tree<'repo> {
+ fn clone(&self) -> Self {
+ self.as_object().clone().into_tree().ok().unwrap()
+ }
+}
+
+impl<'repo> Drop for Tree<'repo> {
+ fn drop(&mut self) {
+ unsafe { raw::git_tree_free(self.raw) }
+ }
+}
+
+impl<'repo, 'iter> IntoIterator for &'iter Tree<'repo> {
+ type Item = TreeEntry<'iter>;
+ type IntoIter = TreeIter<'iter>;
+ fn into_iter(self) -> Self::IntoIter {
+ self.iter()
+ }
+}
+
+/// Create a new tree entry from the raw pointer provided.
+///
+/// The lifetime of the entry is tied to the tree provided and the function
+/// is unsafe because the validity of the pointer cannot be guaranteed.
+pub unsafe fn entry_from_raw_const<'tree>(raw: *const raw::git_tree_entry) -> TreeEntry<'tree> {
+ TreeEntry {
+ raw: raw as *mut raw::git_tree_entry,
+ owned: false,
+ _marker: marker::PhantomData,
+ }
+}
+
+impl<'tree> TreeEntry<'tree> {
+ /// Get the id of the object pointed by the entry
+ pub fn id(&self) -> Oid {
+ unsafe { Binding::from_raw(raw::git_tree_entry_id(&*self.raw)) }
+ }
+
+ /// Get the filename of a tree entry
+ ///
+ /// Returns `None` if the name is not valid utf-8
+ pub fn name(&self) -> Option<&str> {
+ str::from_utf8(self.name_bytes()).ok()
+ }
+
+ /// Get the filename of a tree entry
+ pub fn name_bytes(&self) -> &[u8] {
+ unsafe { crate::opt_bytes(self, raw::git_tree_entry_name(&*self.raw())).unwrap() }
+ }
+
+ /// Convert a tree entry to the object it points to.
+ pub fn to_object<'a>(&self, repo: &'a Repository) -> Result<Object<'a>, Error> {
+ let mut ret = ptr::null_mut();
+ unsafe {
+ try_call!(raw::git_tree_entry_to_object(
+ &mut ret,
+ repo.raw(),
+ &*self.raw()
+ ));
+ Ok(Binding::from_raw(ret))
+ }
+ }
+
+ /// Get the type of the object pointed by the entry
+ pub fn kind(&self) -> Option<ObjectType> {
+ ObjectType::from_raw(unsafe { raw::git_tree_entry_type(&*self.raw) })
+ }
+
+ /// Get the UNIX file attributes of a tree entry
+ pub fn filemode(&self) -> i32 {
+ unsafe { raw::git_tree_entry_filemode(&*self.raw) as i32 }
+ }
+
+ /// Get the raw UNIX file attributes of a tree entry
+ pub fn filemode_raw(&self) -> i32 {
+ unsafe { raw::git_tree_entry_filemode_raw(&*self.raw) as i32 }
+ }
+
+ /// Convert this entry of any lifetime into an owned signature with a static
+ /// lifetime.
+ ///
+ /// This will use the `Clone::clone` implementation under the hood.
+ pub fn to_owned(&self) -> TreeEntry<'static> {
+ unsafe {
+ let me = mem::transmute::<&TreeEntry<'tree>, &TreeEntry<'static>>(self);
+ me.clone()
+ }
+ }
+}
+
+impl<'a> Binding for TreeEntry<'a> {
+ type Raw = *mut raw::git_tree_entry;
+ unsafe fn from_raw(raw: *mut raw::git_tree_entry) -> TreeEntry<'a> {
+ TreeEntry {
+ raw,
+ owned: true,
+ _marker: marker::PhantomData,
+ }
+ }
+ fn raw(&self) -> *mut raw::git_tree_entry {
+ self.raw
+ }
+}
+
+impl<'a> Clone for TreeEntry<'a> {
+ fn clone(&self) -> TreeEntry<'a> {
+ let mut ret = ptr::null_mut();
+ unsafe {
+ assert_eq!(raw::git_tree_entry_dup(&mut ret, &*self.raw()), 0);
+ Binding::from_raw(ret)
+ }
+ }
+}
+
+impl<'a> PartialOrd for TreeEntry<'a> {
+ fn partial_cmp(&self, other: &TreeEntry<'a>) -> Option<Ordering> {
+ Some(self.cmp(other))
+ }
+}
+impl<'a> Ord for TreeEntry<'a> {
+ fn cmp(&self, other: &TreeEntry<'a>) -> Ordering {
+ c_cmp_to_ordering(unsafe { raw::git_tree_entry_cmp(&*self.raw(), &*other.raw()) })
+ }
+}
+
+impl<'a> PartialEq for TreeEntry<'a> {
+ fn eq(&self, other: &TreeEntry<'a>) -> bool {
+ self.cmp(other) == Ordering::Equal
+ }
+}
+impl<'a> Eq for TreeEntry<'a> {}
+
+impl<'a> Drop for TreeEntry<'a> {
+ fn drop(&mut self) {
+ if self.owned {
+ unsafe { raw::git_tree_entry_free(self.raw) }
+ }
+ }
+}
+
+impl<'tree> Iterator for TreeIter<'tree> {
+ type Item = TreeEntry<'tree>;
+ fn next(&mut self) -> Option<TreeEntry<'tree>> {
+ self.range.next().and_then(|i| self.tree.get(i))
+ }
+ fn size_hint(&self) -> (usize, Option<usize>) {
+ self.range.size_hint()
+ }
+}
+impl<'tree> DoubleEndedIterator for TreeIter<'tree> {
+ fn next_back(&mut self) -> Option<TreeEntry<'tree>> {
+ self.range.next_back().and_then(|i| self.tree.get(i))
+ }
+}
+impl<'tree> FusedIterator for TreeIter<'tree> {}
+impl<'tree> ExactSizeIterator for TreeIter<'tree> {}
+
+#[cfg(test)]
+mod tests {
+ use super::{TreeWalkMode, TreeWalkResult};
+ use crate::{Object, ObjectType, Repository, Tree, TreeEntry};
+ use std::fs::File;
+ use std::io::prelude::*;
+ use std::path::Path;
+ use tempfile::TempDir;
+
+ pub struct TestTreeIter<'a> {
+ entries: Vec<TreeEntry<'a>>,
+ repo: &'a Repository,
+ }
+
+ impl<'a> Iterator for TestTreeIter<'a> {
+ type Item = TreeEntry<'a>;
+
+ fn next(&mut self) -> Option<TreeEntry<'a>> {
+ if self.entries.is_empty() {
+ None
+ } else {
+ let entry = self.entries.remove(0);
+
+ match entry.kind() {
+ Some(ObjectType::Tree) => {
+ let obj: Object<'a> = entry.to_object(self.repo).unwrap();
+
+ let tree: &Tree<'a> = obj.as_tree().unwrap();
+
+ for entry in tree.iter() {
+ self.entries.push(entry.to_owned());
+ }
+ }
+ _ => {}
+ }
+
+ Some(entry)
+ }
+ }
+ }
+
+ fn tree_iter<'repo>(tree: &Tree<'repo>, repo: &'repo Repository) -> TestTreeIter<'repo> {
+ let mut initial = vec![];
+
+ for entry in tree.iter() {
+ initial.push(entry.to_owned());
+ }
+
+ TestTreeIter {
+ entries: initial,
+ repo: repo,
+ }
+ }
+
+ #[test]
+ fn smoke_tree_iter() {
+ let (td, repo) = crate::test::repo_init();
+
+ setup_repo(&td, &repo);
+
+ let head = repo.head().unwrap();
+ let target = head.target().unwrap();
+ let commit = repo.find_commit(target).unwrap();
+
+ let tree = repo.find_tree(commit.tree_id()).unwrap();
+ assert_eq!(tree.id(), commit.tree_id());
+ assert_eq!(tree.len(), 1);
+
+ for entry in tree_iter(&tree, &repo) {
+ println!("iter entry {:?}", entry.name());
+ }
+ }
+
+ fn setup_repo(td: &TempDir, repo: &Repository) {
+ let mut index = repo.index().unwrap();
+ File::create(&td.path().join("foo"))
+ .unwrap()
+ .write_all(b"foo")
+ .unwrap();
+ index.add_path(Path::new("foo")).unwrap();
+ let id = index.write_tree().unwrap();
+ let sig = repo.signature().unwrap();
+ let tree = repo.find_tree(id).unwrap();
+ let parent = repo
+ .find_commit(repo.head().unwrap().target().unwrap())
+ .unwrap();
+ repo.commit(
+ Some("HEAD"),
+ &sig,
+ &sig,
+ "another commit",
+ &tree,
+ &[&parent],
+ )
+ .unwrap();
+ }
+
+ #[test]
+ fn smoke() {
+ let (td, repo) = crate::test::repo_init();
+
+ setup_repo(&td, &repo);
+
+ let head = repo.head().unwrap();
+ let target = head.target().unwrap();
+ let commit = repo.find_commit(target).unwrap();
+
+ let tree = repo.find_tree(commit.tree_id()).unwrap();
+ assert_eq!(tree.id(), commit.tree_id());
+ assert_eq!(tree.len(), 1);
+ {
+ let e1 = tree.get(0).unwrap();
+ assert!(e1 == tree.get_id(e1.id()).unwrap());
+ assert!(e1 == tree.get_name("foo").unwrap());
+ assert!(e1 == tree.get_name_bytes(b"foo").unwrap());
+ assert!(e1 == tree.get_path(Path::new("foo")).unwrap());
+ assert_eq!(e1.name(), Some("foo"));
+ e1.to_object(&repo).unwrap();
+ }
+ tree.into_object();
+
+ repo.find_object(commit.tree_id(), None)
+ .unwrap()
+ .as_tree()
+ .unwrap();
+ repo.find_object(commit.tree_id(), None)
+ .unwrap()
+ .into_tree()
+ .ok()
+ .unwrap();
+ }
+
+ #[test]
+ fn tree_walk() {
+ let (td, repo) = crate::test::repo_init();
+
+ setup_repo(&td, &repo);
+
+ let head = repo.head().unwrap();
+ let target = head.target().unwrap();
+ let commit = repo.find_commit(target).unwrap();
+ let tree = repo.find_tree(commit.tree_id()).unwrap();
+
+ let mut ct = 0;
+ tree.walk(TreeWalkMode::PreOrder, |_, entry| {
+ assert_eq!(entry.name(), Some("foo"));
+ ct += 1;
+ 0
+ })
+ .unwrap();
+ assert_eq!(ct, 1);
+
+ let mut ct = 0;
+ tree.walk(TreeWalkMode::PreOrder, |_, entry| {
+ assert_eq!(entry.name(), Some("foo"));
+ ct += 1;
+ TreeWalkResult::Ok
+ })
+ .unwrap();
+ assert_eq!(ct, 1);
+ }
+}