Skip to content

Commit 27deee9

Browse files
Michaelalexcrichton
Michael
authored andcommitted
Adding tree walk (rust-lang#343)
* Added treewalk function, no tests yet. * hopefully this satisfies msvc * add test for tree_walk * sample implementation using TreeWalkResult enum * documentation * remove unnecessary panic check
1 parent d8d3825 commit 27deee9

File tree

2 files changed

+130
-4
lines changed

2 files changed

+130
-4
lines changed

src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ pub use stash::{StashApplyOptions, StashCb, StashApplyProgressCb};
118118
pub use submodule::{Submodule, SubmoduleUpdateOptions};
119119
pub use tag::Tag;
120120
pub use time::{Time, IndexTime};
121-
pub use tree::{Tree, TreeEntry, TreeIter};
121+
pub use tree::{Tree, TreeEntry, TreeIter, TreeWalkMode, TreeWalkResult};
122122
pub use treebuilder::TreeBuilder;
123123
pub use odb::{Odb, OdbObject, OdbReader, OdbWriter};
124124
pub use util::IntoCString;

src/tree.rs

+129-3
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
use std::mem;
22
use std::cmp::Ordering;
3-
use std::ffi::CString;
3+
use std::ffi::{CStr, CString};
44
use std::ops::Range;
55
use std::marker;
66
use std::path::Path;
77
use std::ptr;
88
use std::str;
9-
use libc;
9+
use libc::{self, c_int, c_char, c_void};
1010

11-
use {raw, Oid, Repository, Error, Object, ObjectType};
11+
use {panic, raw, Oid, Repository, Error, Object, ObjectType};
1212
use util::{Binding, IntoCString};
1313

1414
/// A structure to represent a git [tree][1]
@@ -33,6 +33,43 @@ pub struct TreeIter<'tree> {
3333
tree: &'tree Tree<'tree>,
3434
}
3535

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+
#[allow(dead_code)]
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 = -1,
54+
}
55+
56+
impl Into<i32> for TreeWalkResult {
57+
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")]
64+
fn into(self) -> raw::git_treewalk_mode {
65+
self as i32
66+
}
67+
#[cfg(not(target_env = "msvc"))]
68+
fn into(self) -> raw::git_treewalk_mode {
69+
self as u32
70+
}
71+
}
72+
3673
impl<'repo> Tree<'repo> {
3774
/// Get the id (SHA1) of a repository object
3875
pub fn id(&self) -> Oid {
@@ -54,6 +91,49 @@ impl<'repo> Tree<'repo> {
5491
TreeIter { range: 0..self.len(), tree: self }
5592
}
5693

94+
/// Traverse the entries in a tree and its subtrees in post or pre order.
95+
/// The callback function will be run on each node of the tree that's
96+
/// walked. The return code of this function will determine how the walk
97+
/// continues.
98+
///
99+
/// libgit requires that the callback be an integer, where 0 indicates a
100+
/// successful visit, 1 skips the node, and -1 aborts the traversal completely.
101+
/// You may opt to use the enum [`TreeWalkResult`](TreeWalkResult) instead.
102+
///
103+
/// ```ignore
104+
/// let mut ct = 0;
105+
/// tree.walk(TreeWalkMode::PreOrder, |_, entry| {
106+
/// assert_eq!(entry.name(), Some("foo"));
107+
/// ct += 1;
108+
/// TreeWalkResult::Ok
109+
/// }).unwrap();
110+
/// assert_eq!(ct, 1);
111+
/// ```
112+
///
113+
/// See [libgit documentation][1] for more information.
114+
///
115+
/// [1]: https://libgit2.org/libgit2/#HEAD/group/tree/git_tree_walk
116+
pub fn walk<C, T>(&self, mode: TreeWalkMode, mut callback: C) -> Result<(), Error>
117+
where
118+
C: FnMut(&str, &TreeEntry) -> T,
119+
T: Into<i32>,
120+
{
121+
#[allow(unused)]
122+
struct TreeWalkCbData<'a, T: 'a> {
123+
pub callback: &'a mut TreeWalkCb<'a, T>
124+
}
125+
unsafe {
126+
let mut data = TreeWalkCbData { callback: &mut callback };
127+
raw::git_tree_walk(
128+
self.raw(),
129+
mode.into(),
130+
treewalk_cb,
131+
&mut data as *mut _ as *mut c_void,
132+
);
133+
Ok(())
134+
}
135+
}
136+
57137
/// Lookup a tree entry by SHA value.
58138
pub fn get_id(&self, id: Oid) -> Option<TreeEntry> {
59139
unsafe {
@@ -119,6 +199,23 @@ impl<'repo> Tree<'repo> {
119199
}
120200
}
121201

202+
type TreeWalkCb<'a, T> = FnMut(&str, &TreeEntry) -> T + 'a;
203+
204+
extern fn treewalk_cb(root: *const c_char, entry: *const raw::git_tree_entry, payload: *mut c_void) -> c_int {
205+
match panic::wrap(|| unsafe {
206+
let root = match CStr::from_ptr(root).to_str() {
207+
Ok(value) => value,
208+
_ => return -1,
209+
};
210+
let entry = entry_from_raw_const(entry);
211+
let payload = payload as *mut &mut TreeWalkCb<_>;
212+
(*payload)(root, &entry)
213+
}) {
214+
Some(value) => value,
215+
None => -1,
216+
}
217+
}
218+
122219
impl<'repo> Binding for Tree<'repo> {
123220
type Raw = *mut raw::git_tree;
124221

@@ -294,6 +391,7 @@ impl<'tree> ExactSizeIterator for TreeIter<'tree> {}
294391
#[cfg(test)]
295392
mod tests {
296393
use {Repository,Tree,TreeEntry,ObjectType,Object};
394+
use super::{TreeWalkMode, TreeWalkResult};
297395
use tempdir::TempDir;
298396
use std::fs::File;
299397
use std::io::prelude::*;
@@ -403,4 +501,32 @@ mod tests {
403501
repo.find_object(commit.tree_id(), None).unwrap().as_tree().unwrap();
404502
repo.find_object(commit.tree_id(), None).unwrap().into_tree().ok().unwrap();
405503
}
504+
505+
#[test]
506+
fn tree_walk() {
507+
let (td, repo) = ::test::repo_init();
508+
509+
setup_repo(&td, &repo);
510+
511+
let head = repo.head().unwrap();
512+
let target = head.target().unwrap();
513+
let commit = repo.find_commit(target).unwrap();
514+
let tree = repo.find_tree(commit.tree_id()).unwrap();
515+
516+
let mut ct = 0;
517+
tree.walk(TreeWalkMode::PreOrder, |_, entry| {
518+
assert_eq!(entry.name(), Some("foo"));
519+
ct += 1;
520+
0
521+
}).unwrap();
522+
assert_eq!(ct, 1);
523+
524+
let mut ct = 0;
525+
tree.walk(TreeWalkMode::PreOrder, |_, entry| {
526+
assert_eq!(entry.name(), Some("foo"));
527+
ct += 1;
528+
TreeWalkResult::Ok
529+
}).unwrap();
530+
assert_eq!(ct, 1);
531+
}
406532
}

0 commit comments

Comments
 (0)