1
1
use std:: { borrow:: BorrowMut , collections:: VecDeque } ;
2
2
3
- use gix_object:: { tree:: EntryRef , FindExt } ;
3
+ use gix_object:: { tree:: EntryRef , FindExt , TreeRefIter } ;
4
4
5
5
use crate :: tree:: visit:: { ChangeId , Relation } ;
6
- use crate :: {
7
- tree,
8
- tree:: { visit:: Change , TreeInfoTuple } ,
9
- } ;
6
+ use crate :: tree:: { visit:: Change , Error , State , TreeInfoTuple , Visit } ;
10
7
11
- /// The error returned by [`tree::Changes::needed_to_obtain()`].
12
- #[ derive( Debug , thiserror:: Error ) ]
13
- #[ allow( missing_docs) ]
14
- pub enum Error {
15
- #[ error( transparent) ]
16
- Find ( #[ from] gix_object:: find:: existing_iter:: Error ) ,
17
- #[ error( "The delegate cancelled the operation" ) ]
18
- Cancelled ,
19
- #[ error( transparent) ]
20
- EntriesDecode ( #[ from] gix_object:: decode:: Error ) ,
21
- }
22
-
23
- impl tree:: Changes < ' _ > {
24
- /// Calculate the changes that would need to be applied to `self` to get `other` using `objects` to obtain objects as needed for traversal.
25
- ///
26
- /// * The `state` maybe owned or mutably borrowed to allow reuses allocated data structures through multiple runs.
27
- /// * `locate` is a function `f(object_id, &mut buffer) -> Option<TreeIter>` to return a `TreeIter` for the given object id backing
28
- /// its data in the given buffer. Returning `None` is unexpected as these trees are obtained during iteration, and in a typical
29
- /// database errors are not expected either which is why the error case is omitted. To allow proper error reporting, [`Error::Find`]
30
- /// should be converted into a more telling error.
31
- /// * `delegate` will receive the computed changes, see the [`Visit`][`tree::Visit`] trait for more information on what to expect.
32
- ///
33
- /// # Notes
34
- ///
35
- /// * To obtain progress, implement it within the `delegate`.
36
- /// * Tree entries are expected to be ordered using [`tree-entry-comparison`][git_cmp_c] (the same [in Rust][git_cmp_rs])
37
- /// * it does a breadth first iteration as buffer space only fits two trees, the current one on the one we compare with.
38
- /// * does not do rename tracking but attempts to reduce allocations to zero (so performance is mostly determined
39
- /// by the delegate implementation which should be as specific as possible. Rename tracking can be computed on top of the changes
40
- /// received by the `delegate`.
41
- /// * cycle checking is not performed, but can be performed in the delegate which can return [`tree::visit::Action::Cancel`] to stop the traversal.
42
- /// * [`std::mem::ManuallyDrop`] is used because `Peekable` is needed. When using it as wrapper around our no-drop iterators, all of the sudden
43
- /// borrowcheck complains as Drop is present (even though it's not)
44
- ///
45
- /// [git_cmp_c]: https://github.com/git/git/blob/311531c9de557d25ac087c1637818bd2aad6eb3a/tree-diff.c#L49:L65
46
- /// [git_cmp_rs]: https://github.com/Byron/gitoxide/blob/a4d5f99c8dc99bf814790928a3bf9649cd99486b/gix-object/src/mutable/tree.rs#L52-L55
47
- pub fn needed_to_obtain < StateMut > (
48
- mut self ,
49
- other : gix_object:: TreeRefIter < ' _ > ,
50
- mut state : StateMut ,
51
- objects : impl gix_object:: Find ,
52
- delegate : & mut impl tree:: Visit ,
53
- ) -> Result < ( ) , Error >
54
- where
55
- StateMut : BorrowMut < tree:: State > ,
56
- {
57
- let state = state. borrow_mut ( ) ;
58
- state. clear ( ) ;
59
- let mut lhs_entries = peekable ( self . 0 . take ( ) . unwrap_or_default ( ) ) ;
60
- let mut rhs_entries = peekable ( other) ;
61
- let mut relation = None ;
62
- let mut pop_path = false ;
8
+ /// Calculate the changes that would need to be applied to `lhs` to get `rhs` using `objects` to obtain objects as needed for traversal.
9
+ /// `state` can be used between multiple calls to re-use memory.
10
+ ///
11
+ /// * The `state` maybe owned or mutably borrowed to allow reuses allocated data structures through multiple runs.
12
+ /// * `delegate` will receive the computed changes, see the [`Visit`] trait for more information on what to expect.
13
+ ///
14
+ /// # Notes
15
+ ///
16
+ /// * `lhs` can be an empty tree to simulate what would happen if the left-hand side didn't exist.
17
+ /// * To obtain progress, implement it within the `delegate`.
18
+ /// * Tree entries are expected to be ordered using [`tree-entry-comparison`][git_cmp_c] (the same [in Rust][git_cmp_rs])
19
+ /// * it does a breadth first iteration as buffer space only fits two trees, the current one on the one we compare with.
20
+ /// * does not do rename tracking but attempts to reduce allocations to zero (so performance is mostly determined
21
+ /// by the delegate implementation which should be as specific as possible. Rename tracking can be computed on top of the changes
22
+ /// received by the `delegate`.
23
+ /// * cycle checking is not performed, but can be performed in the delegate which can return
24
+ /// [`tree::visit::Action::Cancel`](crate::tree::visit::Action::Cancel) to stop the traversal.
25
+ ///
26
+ /// [git_cmp_c]: https://github.com/git/git/blob/311531c9de557d25ac087c1637818bd2aad6eb3a/tree-diff.c#L49:L65
27
+ /// [git_cmp_rs]: https://github.com/Byron/gitoxide/blob/a4d5f99c8dc99bf814790928a3bf9649cd99486b/gix-object/src/mutable/tree.rs#L52-L55
28
+ #[ doc( alias = "diff_tree_to_tree" , alias = "git2" ) ]
29
+ pub fn diff < StateMut > (
30
+ lhs : TreeRefIter < ' _ > ,
31
+ rhs : TreeRefIter < ' _ > ,
32
+ mut state : StateMut ,
33
+ objects : impl gix_object:: Find ,
34
+ delegate : & mut impl Visit ,
35
+ ) -> Result < ( ) , Error >
36
+ where
37
+ StateMut : BorrowMut < State > ,
38
+ {
39
+ let state = state. borrow_mut ( ) ;
40
+ state. clear ( ) ;
41
+ let mut lhs_entries = peekable ( lhs) ;
42
+ let mut rhs_entries = peekable ( rhs) ;
43
+ let mut relation = None ;
44
+ let mut pop_path = false ;
63
45
64
- loop {
65
- if pop_path {
66
- delegate. pop_path_component ( ) ;
67
- }
68
- pop_path = true ;
46
+ loop {
47
+ if pop_path {
48
+ delegate. pop_path_component ( ) ;
49
+ }
50
+ pop_path = true ;
69
51
70
- match ( lhs_entries. next ( ) , rhs_entries. next ( ) ) {
71
- ( None , None ) => {
72
- match state. trees . pop_front ( ) {
73
- Some ( ( None , Some ( rhs) , relation_to_propagate) ) => {
74
- delegate. pop_front_tracked_path_and_set_current ( ) ;
75
- relation = relation_to_propagate;
76
- rhs_entries = peekable ( objects. find_tree_iter ( & rhs, & mut state. buf2 ) ?) ;
77
- }
78
- Some ( ( Some ( lhs) , Some ( rhs) , relation_to_propagate) ) => {
79
- delegate. pop_front_tracked_path_and_set_current ( ) ;
80
- lhs_entries = peekable ( objects. find_tree_iter ( & lhs, & mut state. buf1 ) ?) ;
81
- rhs_entries = peekable ( objects. find_tree_iter ( & rhs, & mut state. buf2 ) ?) ;
82
- relation = relation_to_propagate;
83
- }
84
- Some ( ( Some ( lhs) , None , relation_to_propagate) ) => {
85
- delegate. pop_front_tracked_path_and_set_current ( ) ;
86
- lhs_entries = peekable ( objects. find_tree_iter ( & lhs, & mut state. buf1 ) ?) ;
87
- relation = relation_to_propagate;
88
- }
89
- Some ( ( None , None , _) ) => unreachable ! ( "BUG: it makes no sense to fill the stack with empties" ) ,
90
- None => return Ok ( ( ) ) ,
91
- } ;
92
- pop_path = false ;
93
- }
94
- ( Some ( lhs) , Some ( rhs) ) => {
95
- use std:: cmp:: Ordering :: * ;
96
- let ( lhs, rhs) = ( lhs?, rhs?) ;
97
- match compare ( & lhs, & rhs) {
98
- Equal => handle_lhs_and_rhs_with_equal_filenames (
99
- lhs,
100
- rhs,
101
- & mut state. trees ,
102
- & mut state. change_id ,
103
- relation,
104
- delegate,
105
- ) ?,
106
- Less => catchup_lhs_with_rhs (
107
- & mut lhs_entries,
108
- lhs,
109
- rhs,
110
- & mut state. trees ,
111
- & mut state. change_id ,
112
- relation,
113
- delegate,
114
- ) ?,
115
- Greater => catchup_rhs_with_lhs (
116
- & mut rhs_entries,
117
- lhs,
118
- rhs,
119
- & mut state. trees ,
120
- & mut state. change_id ,
121
- relation,
122
- delegate,
123
- ) ?,
52
+ match ( lhs_entries. next ( ) , rhs_entries. next ( ) ) {
53
+ ( None , None ) => {
54
+ match state. trees . pop_front ( ) {
55
+ Some ( ( None , Some ( rhs) , relation_to_propagate) ) => {
56
+ delegate. pop_front_tracked_path_and_set_current ( ) ;
57
+ relation = relation_to_propagate;
58
+ rhs_entries = peekable ( objects. find_tree_iter ( & rhs, & mut state. buf2 ) ?) ;
124
59
}
60
+ Some ( ( Some ( lhs) , Some ( rhs) , relation_to_propagate) ) => {
61
+ delegate. pop_front_tracked_path_and_set_current ( ) ;
62
+ lhs_entries = peekable ( objects. find_tree_iter ( & lhs, & mut state. buf1 ) ?) ;
63
+ rhs_entries = peekable ( objects. find_tree_iter ( & rhs, & mut state. buf2 ) ?) ;
64
+ relation = relation_to_propagate;
65
+ }
66
+ Some ( ( Some ( lhs) , None , relation_to_propagate) ) => {
67
+ delegate. pop_front_tracked_path_and_set_current ( ) ;
68
+ lhs_entries = peekable ( objects. find_tree_iter ( & lhs, & mut state. buf1 ) ?) ;
69
+ relation = relation_to_propagate;
70
+ }
71
+ Some ( ( None , None , _) ) => unreachable ! ( "BUG: it makes no sense to fill the stack with empties" ) ,
72
+ None => return Ok ( ( ) ) ,
73
+ } ;
74
+ pop_path = false ;
75
+ }
76
+ ( Some ( lhs) , Some ( rhs) ) => {
77
+ use std:: cmp:: Ordering :: * ;
78
+ let ( lhs, rhs) = ( lhs?, rhs?) ;
79
+ match compare ( & lhs, & rhs) {
80
+ Equal => handle_lhs_and_rhs_with_equal_filenames (
81
+ lhs,
82
+ rhs,
83
+ & mut state. trees ,
84
+ & mut state. change_id ,
85
+ relation,
86
+ delegate,
87
+ ) ?,
88
+ Less => catchup_lhs_with_rhs (
89
+ & mut lhs_entries,
90
+ lhs,
91
+ rhs,
92
+ & mut state. trees ,
93
+ & mut state. change_id ,
94
+ relation,
95
+ delegate,
96
+ ) ?,
97
+ Greater => catchup_rhs_with_lhs (
98
+ & mut rhs_entries,
99
+ lhs,
100
+ rhs,
101
+ & mut state. trees ,
102
+ & mut state. change_id ,
103
+ relation,
104
+ delegate,
105
+ ) ?,
125
106
}
126
- ( Some ( lhs ) , None ) => {
127
- let lhs = lhs? ;
128
- delete_entry_schedule_recursion ( lhs, & mut state . trees , & mut state . change_id , relation , delegate ) ?;
129
- }
130
- ( None , Some ( rhs ) ) => {
131
- let rhs = rhs? ;
132
- add_entry_schedule_recursion ( rhs, & mut state . trees , & mut state . change_id , relation , delegate ) ?;
133
- }
107
+ }
108
+ ( Some ( lhs) , None ) => {
109
+ let lhs = lhs ?;
110
+ delete_entry_schedule_recursion ( lhs , & mut state . trees , & mut state . change_id , relation , delegate ) ? ;
111
+ }
112
+ ( None , Some ( rhs) ) => {
113
+ let rhs = rhs ?;
114
+ add_entry_schedule_recursion ( rhs , & mut state . trees , & mut state . change_id , relation , delegate ) ? ;
134
115
}
135
116
}
136
117
}
@@ -150,7 +131,7 @@ fn delete_entry_schedule_recursion(
150
131
queue : & mut VecDeque < TreeInfoTuple > ,
151
132
change_id : & mut ChangeId ,
152
133
relation_to_propagate : Option < Relation > ,
153
- delegate : & mut impl tree :: Visit ,
134
+ delegate : & mut impl Visit ,
154
135
) -> Result < ( ) , Error > {
155
136
delegate. push_path_component ( entry. filename ) ;
156
137
let relation = relation_to_propagate. or_else ( || {
@@ -182,7 +163,7 @@ fn add_entry_schedule_recursion(
182
163
queue : & mut VecDeque < TreeInfoTuple > ,
183
164
change_id : & mut ChangeId ,
184
165
relation_to_propagate : Option < Relation > ,
185
- delegate : & mut impl tree :: Visit ,
166
+ delegate : & mut impl Visit ,
186
167
) -> Result < ( ) , Error > {
187
168
delegate. push_path_component ( entry. filename ) ;
188
169
let relation = relation_to_propagate. or_else ( || {
@@ -210,13 +191,13 @@ fn add_entry_schedule_recursion(
210
191
}
211
192
212
193
fn catchup_rhs_with_lhs (
213
- rhs_entries : & mut IteratorType < gix_object :: TreeRefIter < ' _ > > ,
194
+ rhs_entries : & mut IteratorType < TreeRefIter < ' _ > > ,
214
195
lhs : EntryRef < ' _ > ,
215
196
rhs : EntryRef < ' _ > ,
216
197
queue : & mut VecDeque < TreeInfoTuple > ,
217
198
change_id : & mut ChangeId ,
218
199
relation_to_propagate : Option < Relation > ,
219
- delegate : & mut impl tree :: Visit ,
200
+ delegate : & mut impl Visit ,
220
201
) -> Result < ( ) , Error > {
221
202
use std:: cmp:: Ordering :: * ;
222
203
add_entry_schedule_recursion ( rhs, queue, change_id, relation_to_propagate, delegate) ?;
@@ -259,13 +240,13 @@ fn catchup_rhs_with_lhs(
259
240
}
260
241
261
242
fn catchup_lhs_with_rhs (
262
- lhs_entries : & mut IteratorType < gix_object :: TreeRefIter < ' _ > > ,
243
+ lhs_entries : & mut IteratorType < TreeRefIter < ' _ > > ,
263
244
lhs : EntryRef < ' _ > ,
264
245
rhs : EntryRef < ' _ > ,
265
246
queue : & mut VecDeque < TreeInfoTuple > ,
266
247
change_id : & mut ChangeId ,
267
248
relation_to_propagate : Option < Relation > ,
268
- delegate : & mut impl tree :: Visit ,
249
+ delegate : & mut impl Visit ,
269
250
) -> Result < ( ) , Error > {
270
251
use std:: cmp:: Ordering :: * ;
271
252
delete_entry_schedule_recursion ( lhs, queue, change_id, relation_to_propagate, delegate) ?;
@@ -313,7 +294,7 @@ fn handle_lhs_and_rhs_with_equal_filenames(
313
294
queue : & mut VecDeque < TreeInfoTuple > ,
314
295
change_id : & mut ChangeId ,
315
296
relation_to_propagate : Option < Relation > ,
316
- delegate : & mut impl tree :: Visit ,
297
+ delegate : & mut impl Visit ,
317
298
) -> Result < ( ) , Error > {
318
299
match ( lhs. mode . is_tree ( ) , rhs. mode . is_tree ( ) ) {
319
300
( true , true ) => {
@@ -413,7 +394,7 @@ fn handle_lhs_and_rhs_with_equal_filenames(
413
394
Ok ( ( ) )
414
395
}
415
396
416
- type IteratorType < I > = std:: mem :: ManuallyDrop < std :: iter:: Peekable < I > > ;
397
+ type IteratorType < I > = std:: iter:: Peekable < I > ;
417
398
418
399
fn to_child ( r : Option < Relation > ) -> Option < Relation > {
419
400
r. map ( |r| match r {
@@ -423,7 +404,7 @@ fn to_child(r: Option<Relation>) -> Option<Relation> {
423
404
}
424
405
425
406
fn peekable < I : Iterator > ( iter : I ) -> IteratorType < I > {
426
- std :: mem :: ManuallyDrop :: new ( iter. peekable ( ) )
407
+ iter. peekable ( )
427
408
}
428
409
429
410
#[ cfg( test) ]
0 commit comments