1
1
use std:: mem;
2
2
use std:: cmp:: Ordering ;
3
- use std:: ffi:: CString ;
3
+ use std:: ffi:: { CStr , CString } ;
4
4
use std:: ops:: Range ;
5
5
use std:: marker;
6
6
use std:: path:: Path ;
7
7
use std:: ptr;
8
8
use std:: str;
9
- use libc;
9
+ use libc:: { self , c_int , c_char , c_void } ;
10
10
11
- use { raw, Oid , Repository , Error , Object , ObjectType } ;
11
+ use { panic , raw, Oid , Repository , Error , Object , ObjectType } ;
12
12
use util:: { Binding , IntoCString } ;
13
13
14
14
/// A structure to represent a git [tree][1]
@@ -33,6 +33,43 @@ pub struct TreeIter<'tree> {
33
33
tree : & ' tree Tree < ' tree > ,
34
34
}
35
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
+ #[ 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
+
36
73
impl < ' repo > Tree < ' repo > {
37
74
/// Get the id (SHA1) of a repository object
38
75
pub fn id ( & self ) -> Oid {
@@ -54,6 +91,49 @@ impl<'repo> Tree<'repo> {
54
91
TreeIter { range : 0 ..self . len ( ) , tree : self }
55
92
}
56
93
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
+
57
137
/// Lookup a tree entry by SHA value.
58
138
pub fn get_id ( & self , id : Oid ) -> Option < TreeEntry > {
59
139
unsafe {
@@ -119,6 +199,23 @@ impl<'repo> Tree<'repo> {
119
199
}
120
200
}
121
201
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
+
122
219
impl < ' repo > Binding for Tree < ' repo > {
123
220
type Raw = * mut raw:: git_tree ;
124
221
@@ -294,6 +391,7 @@ impl<'tree> ExactSizeIterator for TreeIter<'tree> {}
294
391
#[ cfg( test) ]
295
392
mod tests {
296
393
use { Repository , Tree , TreeEntry , ObjectType , Object } ;
394
+ use super :: { TreeWalkMode , TreeWalkResult } ;
297
395
use tempdir:: TempDir ;
298
396
use std:: fs:: File ;
299
397
use std:: io:: prelude:: * ;
@@ -403,4 +501,32 @@ mod tests {
403
501
repo. find_object ( commit. tree_id ( ) , None ) . unwrap ( ) . as_tree ( ) . unwrap ( ) ;
404
502
repo. find_object ( commit. tree_id ( ) , None ) . unwrap ( ) . into_tree ( ) . ok ( ) . unwrap ( ) ;
405
503
}
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
+ }
406
532
}
0 commit comments