1
1
bitflags:: bitflags! {
2
2
/// The flags used in the graph for finding [merge bases](crate::merge_base()).
3
- #[ derive( Debug , Default , Copy , Clone ) ]
3
+ #[ derive( Debug , Default , Copy , Clone , Eq , PartialEq ) ]
4
4
pub struct Flags : u8 {
5
5
/// The commit belongs to the graph reachable by the first commit
6
6
const COMMIT1 = 1 << 0 ;
@@ -18,6 +18,8 @@ bitflags::bitflags! {
18
18
#[ derive( Debug , thiserror:: Error ) ]
19
19
#[ allow( missing_docs) ]
20
20
pub enum Error {
21
+ #[ error( transparent) ]
22
+ IterParents ( #[ from] gix_revwalk:: graph:: commit:: iter_parents:: Error ) ,
21
23
#[ error( "A commit could not be found" ) ]
22
24
FindExistingCommit ( #[ from] gix_object:: find:: existing_iter:: Error ) ,
23
25
#[ error( "A commit could not be decoded during traversal" ) ]
@@ -37,20 +39,120 @@ pub(crate) mod function {
37
39
///
38
40
/// Note that this function doesn't do any work if `first` is contained in `others`, which is when `first` will be returned
39
41
/// as only merge-base right away. This is even the case if some commits of `others` are disjoint.
40
- pub fn merge_base < ' name > (
42
+ pub fn merge_base (
41
43
first : ObjectId ,
42
44
others : & [ ObjectId ] ,
43
45
graph : & mut Graph < ' _ , Flags > ,
44
46
) -> Result < Option < Vec < ObjectId > > , Error > {
45
- let _span = gix_trace:: coarse!(
46
- "gix_revision::merge_base()" ,
47
- %first,
48
- %others,
49
- ) ;
47
+ let _span = gix_trace:: coarse!( "gix_revision::merge_base()" , ?first, ?others, ) ;
50
48
if others. is_empty ( ) || others. contains ( & first) {
51
49
return Ok ( Some ( vec ! [ first] ) ) ;
52
50
}
53
51
52
+ let bases = paint_down_to_common ( first, others, graph) ?;
53
+ graph. clear ( ) ;
54
+
55
+ let bases = remove_redundant ( & bases, graph) ?;
56
+ Ok ( ( !bases. is_empty ( ) ) . then_some ( bases) )
57
+ }
58
+
59
+ /// Remove all those commits from `commits` if they are in the history of another commit in `commits`.
60
+ /// That way, we return only the topologically most recent commits in `commits`.
61
+ fn remove_redundant (
62
+ commits : & [ ( ObjectId , GenThenTime ) ] ,
63
+ graph : & mut Graph < ' _ , Flags > ,
64
+ ) -> Result < Vec < ObjectId > , Error > {
65
+ if commits. is_empty ( ) {
66
+ return Ok ( Vec :: new ( ) ) ;
67
+ }
68
+ let sorted_commits = {
69
+ let mut v = commits. to_vec ( ) ;
70
+ v. sort_by ( |a, b| a. 1 . cmp ( & b. 1 ) ) ;
71
+ v
72
+ } ;
73
+ let mut min_gen_pos = 0 ;
74
+ let mut min_gen = sorted_commits[ min_gen_pos] . 1 . generation ;
75
+
76
+ let mut walk_start = Vec :: with_capacity ( commits. len ( ) ) ;
77
+ for ( id, _) in commits {
78
+ graph. insert ( * id, Flags :: RESULT ) ;
79
+ graph. insert_parents_with_lookup ( id, & mut |parent_id, parent_data, maybe_flags| -> Result < _ , Error > {
80
+ if maybe_flags. is_none ( ) {
81
+ walk_start. push ( ( parent_id, GenThenTime :: try_from ( parent_data) ?) ) ;
82
+ }
83
+ Ok ( Flags :: empty ( ) )
84
+ } ) ?;
85
+ }
86
+ walk_start. sort_by ( |a, b| a. 0 . cmp ( & b. 0 ) ) ;
87
+ let mut count_still_independent = commits. len ( ) ;
88
+
89
+ let mut stack = Vec :: new ( ) ;
90
+ while let Some ( ( commit_id, commit_info) ) = walk_start. pop ( ) . filter ( |_| count_still_independent > 1 ) {
91
+ stack. clear ( ) ;
92
+ graph. insert ( commit_id, Flags :: STALE ) ;
93
+ stack. push ( ( commit_id, commit_info) ) ;
94
+
95
+ while let Some ( ( commit_id, commit_info) ) = stack. last ( ) . copied ( ) {
96
+ let flags = graph. get_mut ( & commit_id) . expect ( "all commits have been added" ) ;
97
+ if flags. contains ( Flags :: RESULT ) {
98
+ flags. remove ( Flags :: RESULT ) ;
99
+ count_still_independent -= 1 ;
100
+ if count_still_independent <= 1 {
101
+ break ;
102
+ }
103
+ if commit_id == sorted_commits[ min_gen_pos] . 0 {
104
+ while min_gen_pos < commits. len ( ) - 1
105
+ && graph
106
+ . get ( & sorted_commits[ min_gen_pos] . 0 )
107
+ . expect ( "already added" )
108
+ . contains ( Flags :: STALE )
109
+ {
110
+ min_gen_pos += 1 ;
111
+ }
112
+ min_gen = sorted_commits[ min_gen_pos] . 1 . generation ;
113
+ }
114
+ }
115
+
116
+ if commit_info. generation < min_gen {
117
+ stack. pop ( ) ;
118
+ continue ;
119
+ }
120
+
121
+ let mut pushed_one_parent = false ;
122
+ graph. insert_parents_with_lookup ( & commit_id, & mut |parent_id,
123
+ parent_data,
124
+ maybe_flags|
125
+ -> Result < _ , Error > {
126
+ if !pushed_one_parent
127
+ && maybe_flags. map_or ( true , |flags| {
128
+ let res = !flags. contains ( Flags :: STALE ) ;
129
+ * flags |= Flags :: STALE ;
130
+ res
131
+ } )
132
+ {
133
+ stack. push ( ( parent_id, GenThenTime :: try_from ( parent_data) ?) ) ;
134
+ pushed_one_parent = true ;
135
+ }
136
+ Ok ( Flags :: STALE )
137
+ } ) ?;
138
+
139
+ if !pushed_one_parent {
140
+ stack. pop ( ) ;
141
+ }
142
+ }
143
+ }
144
+
145
+ Ok ( commits
146
+ . iter ( )
147
+ . filter_map ( |( id, _info) | graph. get ( id) . filter ( |flags| !flags. contains ( Flags :: STALE ) ) . map ( |_| * id) )
148
+ . collect ( ) )
149
+ }
150
+
151
+ fn paint_down_to_common (
152
+ first : ObjectId ,
153
+ others : & [ ObjectId ] ,
154
+ graph : & mut Graph < ' _ , Flags > ,
155
+ ) -> Result < Vec < ( ObjectId , GenThenTime ) > , Error > {
54
156
let mut queue = PriorityQueue :: < GenThenTime , ObjectId > :: new ( ) ;
55
157
graph. insert_data ( first, |commit| -> Result < _ , Error > {
56
158
queue. insert ( commit. try_into ( ) ?, first) ;
@@ -63,10 +165,47 @@ pub(crate) mod function {
63
165
Ok ( Flags :: COMMIT2 )
64
166
} ) ?;
65
167
}
66
- Ok ( None )
168
+
169
+ let mut out = Vec :: new ( ) ;
170
+ while queue
171
+ . iter_unordered ( )
172
+ . any ( |id| graph. get ( id) . map_or ( false , |data| !data. contains ( Flags :: STALE ) ) )
173
+ {
174
+ let ( info, commit_id) = queue. pop ( ) . expect ( "we have non-stale" ) ;
175
+ let flags_mut = graph. get_mut ( & commit_id) . expect ( "everything queued is in graph" ) ;
176
+ let mut flags_without_result = * flags_mut & ( Flags :: COMMIT1 | Flags :: COMMIT2 | Flags :: STALE ) ;
177
+ if flags_without_result == ( Flags :: COMMIT1 | Flags :: COMMIT2 ) {
178
+ if !flags_mut. contains ( Flags :: RESULT ) {
179
+ * flags_mut |= Flags :: RESULT ;
180
+ out. push ( ( commit_id, info) ) ;
181
+ }
182
+ flags_without_result |= Flags :: STALE ;
183
+ }
184
+
185
+ graph. insert_parents_with_lookup ( & commit_id, & mut |parent_id, parent, ex_flags| -> Result < _ , Error > {
186
+ let queue_info = match ex_flags {
187
+ Some ( ex_flags) => {
188
+ if ( * ex_flags & flags_without_result) != flags_without_result {
189
+ * ex_flags |= flags_without_result;
190
+ Some ( GenThenTime :: try_from ( parent) ?)
191
+ } else {
192
+ None
193
+ }
194
+ }
195
+ None => Some ( GenThenTime :: try_from ( parent) ?) ,
196
+ } ;
197
+ if let Some ( info) = queue_info {
198
+ queue. insert ( info, parent_id) ;
199
+ }
200
+ Ok ( flags_without_result)
201
+ } ) ?;
202
+ }
203
+
204
+ Ok ( out)
67
205
}
68
206
69
207
// TODO(ST): Should this type be used for `describe` as well?
208
+ #[ derive( Debug , Clone , Copy ) ]
70
209
struct GenThenTime {
71
210
/// Note that the special [`GENERATION_NUMBER_INFINITY`](gix_commitgraph::GENERATION_NUMBER_INFINITY) is used to indicate
72
211
/// that no commitgraph is avaialble.
@@ -97,7 +236,7 @@ pub(crate) mod function {
97
236
98
237
impl PartialOrd < Self > for GenThenTime {
99
238
fn partial_cmp ( & self , other : & Self ) -> Option < Ordering > {
100
- self . cmp ( & other) . into ( )
239
+ Some ( self . cmp ( other) )
101
240
}
102
241
}
103
242
0 commit comments