@@ -18,19 +18,15 @@ 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 ) ,
23- #[ error( "A commit could not be found" ) ]
24- FindExistingCommit ( #[ from] gix_object:: find:: existing_iter:: Error ) ,
25- #[ error( "A commit could not be decoded during traversal" ) ]
26- Decode ( #[ from] gix_object:: decode:: Error ) ,
21+ #[ error( "A commit could not be inserted into the graph" ) ]
22+ InsertCommit ( #[ from] gix_revwalk:: graph:: get_or_insert_default:: Error ) ,
2723}
2824
2925pub ( crate ) mod function {
3026 use super :: Error ;
3127 use crate :: { merge_base:: Flags , Graph , PriorityQueue } ;
3228 use gix_hash:: ObjectId ;
33- use gix_revwalk:: graph:: LazyCommit ;
29+ use gix_revwalk:: graph;
3430 use std:: cmp:: Ordering ;
3531
3632 /// Given a commit at `first` id, traverse the commit `graph` and return all possible merge-base between it and `others`,
@@ -39,19 +35,23 @@ pub(crate) mod function {
3935 ///
4036 /// Note that this function doesn't do any work if `first` is contained in `others`, which is when `first` will be returned
4137 /// as only merge-base right away. This is even the case if some commits of `others` are disjoint.
38+ ///
39+ /// # Performance
40+ ///
41+ /// For repeated calls, be sure to re-use `graph` as its content will be kept and reused for a great speed-up. The contained flags
42+ /// will automatically be cleared.
4243 pub fn merge_base (
4344 first : ObjectId ,
4445 others : & [ ObjectId ] ,
45- graph : & mut Graph < ' _ , ' _ , Flags > ,
46+ graph : & mut Graph < ' _ , ' _ , graph :: Commit < Flags > > ,
4647 ) -> Result < Option < Vec < ObjectId > > , Error > {
4748 let _span = gix_trace:: coarse!( "gix_revision::merge_base()" , ?first, ?others) ;
4849 if others. is_empty ( ) || others. contains ( & first) {
4950 return Ok ( Some ( vec ! [ first] ) ) ;
5051 }
5152
52- graph. clear ( ) ;
53+ graph. clear_commit_data ( |f| * f = Flags :: empty ( ) ) ;
5354 let bases = paint_down_to_common ( first, others, graph) ?;
54- graph. clear ( ) ;
5555
5656 let bases = remove_redundant ( & bases, graph) ?;
5757 Ok ( ( !bases. is_empty ( ) ) . then_some ( bases) )
@@ -61,11 +61,12 @@ pub(crate) mod function {
6161 /// That way, we return only the topologically most recent commits in `commits`.
6262 fn remove_redundant (
6363 commits : & [ ( ObjectId , GenThenTime ) ] ,
64- graph : & mut Graph < ' _ , ' _ , Flags > ,
64+ graph : & mut Graph < ' _ , ' _ , graph :: Commit < Flags > > ,
6565 ) -> Result < Vec < ObjectId > , Error > {
6666 if commits. is_empty ( ) {
6767 return Ok ( Vec :: new ( ) ) ;
6868 }
69+ graph. clear_commit_data ( |f| * f = Flags :: empty ( ) ) ;
6970 let _span = gix_trace:: detail!( "gix_revision::remove_redundant()" , num_commits = %commits. len( ) ) ;
7071 let sorted_commits = {
7172 let mut v = commits. to_vec ( ) ;
@@ -77,36 +78,46 @@ pub(crate) mod function {
7778
7879 let mut walk_start = Vec :: with_capacity ( commits. len ( ) ) ;
7980 for ( id, _) in commits {
80- graph. insert_parents_with_lookup ( id, & mut |parent_id, parent_data, maybe_flags| -> Result < _ , Error > {
81- if maybe_flags. map_or ( true , |flags| !flags. contains ( Flags :: STALE ) ) {
82- walk_start. push ( ( parent_id, GenThenTime :: try_from ( parent_data) ?) ) ;
83- }
84- Ok ( Flags :: empty ( ) )
85- } ) ?;
86- graph. insert ( * id, Flags :: RESULT ) ;
81+ let commit = graph. get_mut ( id) . expect ( "previously added" ) ;
82+ commit. data |= Flags :: RESULT ;
83+ for parent_id in commit. parents . clone ( ) {
84+ graph. get_or_insert_full_commit ( parent_id, |parent| {
85+ // prevent double-addition
86+ if !parent. data . contains ( Flags :: STALE ) {
87+ parent. data |= Flags :: STALE ;
88+ walk_start. push ( ( parent_id, GenThenTime :: from ( & * parent) ) ) ;
89+ }
90+ } ) ?;
91+ }
8792 }
8893 walk_start. sort_by ( |a, b| a. 0 . cmp ( & b. 0 ) ) ;
94+ // allow walking everything at first.
95+ walk_start
96+ . iter_mut ( )
97+ . for_each ( |( id, _) | graph. get_mut ( id) . expect ( "added previously" ) . data . remove ( Flags :: STALE ) ) ;
8998 let mut count_still_independent = commits. len ( ) ;
9099
91100 let mut stack = Vec :: new ( ) ;
92101 while let Some ( ( commit_id, commit_info) ) = walk_start. pop ( ) . filter ( |_| count_still_independent > 1 ) {
93102 stack. clear ( ) ;
94- graph. insert ( commit_id, Flags :: STALE ) ;
103+ graph. get_mut ( & commit_id) . expect ( "added" ) . data |= Flags :: STALE ;
95104 stack. push ( ( commit_id, commit_info) ) ;
96105
97106 while let Some ( ( commit_id, commit_info) ) = stack. last ( ) . copied ( ) {
98- let flags = graph. get_mut ( & commit_id) . expect ( "all commits have been added" ) ;
99- if flags. contains ( Flags :: RESULT ) {
100- flags. remove ( Flags :: RESULT ) ;
107+ let commit = graph. get_mut ( & commit_id) . expect ( "all commits have been added" ) ;
108+ let commit_parents = commit. parents . clone ( ) ;
109+ if commit. data . contains ( Flags :: RESULT ) {
110+ commit. data . remove ( Flags :: RESULT ) ;
101111 count_still_independent -= 1 ;
102112 if count_still_independent <= 1 {
103113 break ;
104114 }
105- if commit_id == sorted_commits[ min_gen_pos] . 0 {
115+ if * commit_id == * sorted_commits[ min_gen_pos] . 0 {
106116 while min_gen_pos < commits. len ( ) - 1
107117 && graph
108118 . get ( & sorted_commits[ min_gen_pos] . 0 )
109119 . expect ( "already added" )
120+ . data
110121 . contains ( Flags :: STALE )
111122 {
112123 min_gen_pos += 1 ;
@@ -120,87 +131,81 @@ pub(crate) mod function {
120131 continue ;
121132 }
122133
123- let mut pushed_one_parent = false ;
124- graph. insert_parents_with_lookup ( & commit_id, & mut |parent_id,
125- parent_data,
126- maybe_flags|
127- -> Result < _ , Error > {
128- let is_new_parent = !pushed_one_parent
129- && maybe_flags. map_or ( true , |flags| {
130- let res = !flags. contains ( Flags :: STALE ) ;
131- * flags |= Flags :: STALE ;
132- res
133- } ) ;
134- if is_new_parent {
135- stack. push ( ( parent_id, GenThenTime :: try_from ( parent_data) ?) ) ;
136- pushed_one_parent = true ;
134+ let previous_len = stack. len ( ) ;
135+ for parent_id in & commit_parents {
136+ if graph
137+ . get_or_insert_full_commit ( * parent_id, |parent| {
138+ if !parent. data . contains ( Flags :: STALE ) {
139+ parent. data |= Flags :: STALE ;
140+ stack. push ( ( * parent_id, GenThenTime :: from ( & * parent) ) ) ;
141+ }
142+ } ) ?
143+ . is_some ( )
144+ {
145+ break ;
137146 }
138- Ok ( Flags :: STALE )
139- } ) ?;
147+ }
140148
141- if !pushed_one_parent {
149+ if previous_len == stack . len ( ) {
142150 stack. pop ( ) ;
143151 }
144152 }
145153 }
146154
147155 Ok ( commits
148156 . iter ( )
149- . filter_map ( |( id, _info) | graph. get ( id) . filter ( |flags| !flags. contains ( Flags :: STALE ) ) . map ( |_| * id) )
157+ . filter_map ( |( id, _info) | {
158+ graph
159+ . get ( id)
160+ . filter ( |commit| !commit. data . contains ( Flags :: STALE ) )
161+ . map ( |_| * id)
162+ } )
150163 . collect ( ) )
151164 }
152165
153166 fn paint_down_to_common (
154167 first : ObjectId ,
155168 others : & [ ObjectId ] ,
156- graph : & mut Graph < ' _ , ' _ , Flags > ,
169+ graph : & mut Graph < ' _ , ' _ , graph :: Commit < Flags > > ,
157170 ) -> Result < Vec < ( ObjectId , GenThenTime ) > , Error > {
158171 let mut queue = PriorityQueue :: < GenThenTime , ObjectId > :: new ( ) ;
159- graph. insert_data ( first, |commit| -> Result < _ , Error > {
160- queue . insert ( commit. try_into ( ) ? , first ) ;
161- Ok ( Flags :: COMMIT1 )
172+ graph. get_or_insert_full_commit ( first, |commit| {
173+ commit. data |= Flags :: COMMIT1 ;
174+ queue . insert ( GenThenTime :: from ( & * commit ) , first ) ;
162175 } ) ?;
163176
164177 for other in others {
165- graph. insert_data ( * other, |commit| -> Result < _ , Error > {
166- queue . insert ( commit. try_into ( ) ? , * other ) ;
167- Ok ( Flags :: COMMIT2 )
178+ graph. get_or_insert_full_commit ( * other, |commit| {
179+ commit. data |= Flags :: COMMIT2 ;
180+ queue . insert ( GenThenTime :: from ( & * commit ) , * other ) ;
168181 } ) ?;
169182 }
170183
171184 let mut out = Vec :: new ( ) ;
172- while queue
173- . iter_unordered ( )
174- . any ( |id| graph. get ( id) . map_or ( false , |data| !data. contains ( Flags :: STALE ) ) )
175- {
185+ while queue. iter_unordered ( ) . any ( |id| {
186+ graph
187+ . get ( id)
188+ . map_or ( false , |commit| !commit. data . contains ( Flags :: STALE ) )
189+ } ) {
176190 let ( info, commit_id) = queue. pop ( ) . expect ( "we have non-stale" ) ;
177- let flags_mut = graph. get_mut ( & commit_id) . expect ( "everything queued is in graph" ) ;
178- let mut flags_without_result = * flags_mut & ( Flags :: COMMIT1 | Flags :: COMMIT2 | Flags :: STALE ) ;
191+ let commit = graph. get_mut ( & commit_id) . expect ( "everything queued is in graph" ) ;
192+ let mut flags_without_result = commit . data & ( Flags :: COMMIT1 | Flags :: COMMIT2 | Flags :: STALE ) ;
179193 if flags_without_result == ( Flags :: COMMIT1 | Flags :: COMMIT2 ) {
180- if !flags_mut . contains ( Flags :: RESULT ) {
181- * flags_mut |= Flags :: RESULT ;
194+ if !commit . data . contains ( Flags :: RESULT ) {
195+ commit . data |= Flags :: RESULT ;
182196 out. push ( ( commit_id, info) ) ;
183197 }
184198 flags_without_result |= Flags :: STALE ;
185199 }
186200
187- graph. insert_parents_with_lookup ( & commit_id, & mut |parent_id, parent, ex_flags| -> Result < _ , Error > {
188- let queue_info = match ex_flags {
189- Some ( ex_flags) => {
190- if ( * ex_flags & flags_without_result) != flags_without_result {
191- * ex_flags |= flags_without_result;
192- Some ( GenThenTime :: try_from ( parent) ?)
193- } else {
194- None
195- }
201+ for parent_id in commit. parents . clone ( ) {
202+ graph. get_or_insert_full_commit ( parent_id, |parent| {
203+ if ( parent. data & flags_without_result) != flags_without_result {
204+ parent. data |= flags_without_result;
205+ queue. insert ( GenThenTime :: from ( & * parent) , parent_id) ;
196206 }
197- None => Some ( GenThenTime :: try_from ( parent) ?) ,
198- } ;
199- if let Some ( info) = queue_info {
200- queue. insert ( info, parent_id) ;
201- }
202- Ok ( flags_without_result)
203- } ) ?;
207+ } ) ?;
208+ }
204209 }
205210
206211 Ok ( out)
@@ -215,15 +220,12 @@ pub(crate) mod function {
215220 time : gix_date:: SecondsSinceUnixEpoch ,
216221 }
217222
218- impl TryFrom < gix_revwalk:: graph:: LazyCommit < ' _ , ' _ > > for GenThenTime {
219- type Error = gix_object:: decode:: Error ;
220-
221- fn try_from ( commit : LazyCommit < ' _ , ' _ > ) -> Result < Self , Self :: Error > {
222- let ( generation, timestamp) = commit. generation_and_timestamp ( ) ?;
223- Ok ( GenThenTime {
224- generation : generation. unwrap_or ( gix_commitgraph:: GENERATION_NUMBER_INFINITY ) ,
225- time : timestamp,
226- } )
223+ impl From < & graph:: Commit < Flags > > for GenThenTime {
224+ fn from ( commit : & graph:: Commit < Flags > ) -> Self {
225+ GenThenTime {
226+ generation : commit. generation . unwrap_or ( gix_commitgraph:: GENERATION_NUMBER_INFINITY ) ,
227+ time : commit. commit_time ,
228+ }
227229 }
228230 }
229231
0 commit comments