11bitflags:: bitflags! {
22 /// 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 ) ]
44 pub struct Flags : u8 {
55 /// The commit belongs to the graph reachable by the first commit
66 const COMMIT1 = 1 << 0 ;
@@ -18,6 +18,8 @@ bitflags::bitflags! {
1818#[ derive( Debug , thiserror:: Error ) ]
1919#[ allow( missing_docs) ]
2020pub enum Error {
21+ #[ error( transparent) ]
22+ IterParents ( #[ from] gix_revwalk:: graph:: commit:: iter_parents:: Error ) ,
2123 #[ error( "A commit could not be found" ) ]
2224 FindExistingCommit ( #[ from] gix_object:: find:: existing_iter:: Error ) ,
2325 #[ error( "A commit could not be decoded during traversal" ) ]
@@ -37,20 +39,120 @@ pub(crate) mod function {
3739 ///
3840 /// Note that this function doesn't do any work if `first` is contained in `others`, which is when `first` will be returned
3941 /// 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 (
4143 first : ObjectId ,
4244 others : & [ ObjectId ] ,
4345 graph : & mut Graph < ' _ , Flags > ,
4446 ) -> 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, ) ;
5048 if others. is_empty ( ) || others. contains ( & first) {
5149 return Ok ( Some ( vec ! [ first] ) ) ;
5250 }
5351
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+ let is_new_parent = !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+ if is_new_parent {
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 > {
54156 let mut queue = PriorityQueue :: < GenThenTime , ObjectId > :: new ( ) ;
55157 graph. insert_data ( first, |commit| -> Result < _ , Error > {
56158 queue. insert ( commit. try_into ( ) ?, first) ;
@@ -63,10 +165,47 @@ pub(crate) mod function {
63165 Ok ( Flags :: COMMIT2 )
64166 } ) ?;
65167 }
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)
67205 }
68206
69207 // TODO(ST): Should this type be used for `describe` as well?
208+ #[ derive( Debug , Clone , Copy ) ]
70209 struct GenThenTime {
71210 /// Note that the special [`GENERATION_NUMBER_INFINITY`](gix_commitgraph::GENERATION_NUMBER_INFINITY) is used to indicate
72211 /// that no commitgraph is avaialble.
@@ -97,7 +236,7 @@ pub(crate) mod function {
97236
98237 impl PartialOrd < Self > for GenThenTime {
99238 fn partial_cmp ( & self , other : & Self ) -> Option < Ordering > {
100- self . cmp ( & other) . into ( )
239+ Some ( self . cmp ( other) )
101240 }
102241 }
103242
0 commit comments