11use anyhow:: { Context , bail} ;
2- use but_core:: RefMetadata ;
2+ use but_core:: { RefMetadata , ref_metadata } ;
33use gix:: {
44 hashtable:: hash_map:: Entry ,
55 prelude:: { ObjectIdExt , ReferenceExt } ,
@@ -13,12 +13,22 @@ mod walk;
1313use walk:: * ;
1414
1515pub ( crate ) mod types;
16+ use crate :: init:: overlay:: { OverlayMetadata , OverlayRepo } ;
1617use types:: { Goals , Instruction , Limit , Queue } ;
1718
1819mod remotes;
1920
21+ mod overlay;
2022mod post;
2123
24+ /// A way to define information to be served from memory, instead of from the underlying data source, when
25+ /// [initializing](Graph::from_commit_traversal()) the graph.
26+ #[ derive( Debug , Default ) ]
27+ pub struct Overlay {
28+ nonoverriding_references : Vec < gix:: refs:: Reference > ,
29+ meta_branches : Vec < ( gix:: refs:: FullName , ref_metadata:: Branch ) > ,
30+ }
31+
2232pub ( super ) type PetGraph = petgraph:: stable_graph:: StableGraph < Segment , Edge > ;
2333
2434/// Options for use in [`Graph::from_head()`] and [`Graph::from_commit_traversal()`].
@@ -123,9 +133,12 @@ impl Graph {
123133 let ( tip, maybe_name) = match head. kind {
124134 gix:: head:: Kind :: Unborn ( ref_name) => {
125135 let mut graph = Graph :: default ( ) ;
136+ // It's OK to default-initialise this here as overlays are only used when redoing
137+ // the traversal.
138+ let ( _repo, meta) = Overlay :: default ( ) . into_parts ( repo, meta) ;
126139 graph. insert_root ( branch_segment_from_name_and_meta (
127140 Some ( ( ref_name, None ) ) ,
128- meta,
141+ & meta,
129142 None ,
130143 ) ?) ;
131144 return Ok ( graph) ;
@@ -158,22 +171,23 @@ impl Graph {
158171 } ;
159172 Ok ( graph)
160173 }
161- /// Produce a minimal-effort representation of the commit-graph reachable from the commit at `tip` such the returned instance
162- /// can represent everything that's observed, without loosing information.
174+ /// Produce a minimal but usable representation of the commit-graph reachable from the commit at `tip` such the returned instance
175+ /// can represent everything that's observed, without losing information.
163176 /// `ref_name` is assumed to point to `tip` if given.
164177 ///
165- /// `meta` is used to learn more about the encountered references.
178+ /// `meta` is used to learn more about the encountered references, and `options` is used for additional configuration .
166179 ///
167180 /// ### Features
168181 ///
169182 /// * discover a Workspace on the fly based on `meta`-data.
170183 /// * support the notion of a branch to integrate with, the *target*
171184 /// - *target* branches consist of a local and remote tracking branch, and one can be ahead of the other.
172185 /// - workspaces are relative to the local tracking branch of the target.
186+ /// - options contain an [`extra_target_commit_id`](Options::extra_target_commit_id) for an additional target location.
173187 /// * remote tracking branches are seen in relation to their branches.
174188 /// * the graph of segments assigns each reachable commit to exactly one segment
175189 /// * one can use [`petgraph::algo`] and [`petgraph::visit`]
176- /// - It maintains information about the intended connections, so modifications afterwards will show
190+ /// - It maintains information about the intended connections, so modifications afterward will show
177191 /// in debugging output if edges are now in violation of this constraint.
178192 ///
179193 /// ### Rules
@@ -183,39 +197,54 @@ impl Graph {
183197 /// Change the rules as you see fit to accomplish this.
184198 ///
185199 /// * a commit can be governed by multiple workspaces
186- /// * as workspaces and entrypoints "grow" together, we don't know anything about workspaces until the every end,
187- /// or when two streams touch. This means we can't make decisions based on [flags](CommitFlags) until the traversal
200+ /// * as workspaces and entry-points "grow" together, we don't know anything about workspaces until the very end,
201+ /// or when two partitions of commits touch.
202+ /// This means we can't make decisions based on [flags](CommitFlags) until the traversal
188203 /// is finished.
189- /// * an entrypoint always causes the start of a segment.
190- /// * Segments are always named if their first commit has a single local branch pointing to it.
191- /// * Anonymous segments are created if there are more than one local branches pointing to it.
204+ /// * an entrypoint always causes the start of a [`Segment`].
205+ /// * Segments are always named if their first commit has a single local branch pointing to it, or a branch that
206+ /// otherwise can be disambiguated.
207+ /// * Anonymous segments are created if their name is ambiguous.
192208 /// * Anonymous segments are created if another segment connects to a commit that it contains that is not the first one.
193- /// - This means, all connections go from the last commit in a segment to the first commit in another segment.
209+ /// - This means, all connections go * from the last commit in a segment to the first commit in another segment* .
194210 /// * Segments stored in the *workspace metadata* are used/relevant only if they are backed by an existing branch.
195211 /// * Remote tracking branches are picked up during traversal for any ref that we reached through traversal.
196212 /// - This implies that remotes aren't relevant for segments added during post-processing, which would typically
197213 /// be empty anyway.
198214 /// - Remotes never take commits that are already owned.
199- /// * The traversal is cut short when there is only tips which are integrated, even though named segments that are
200- /// supposed to be in the workspace will be fully traversed (implying they will stop at the first anon segment
201- /// as will happen at merge commits).
215+ /// * The traversal is cut short when there is only tips which are integrated
202216 /// * The traversal is always as long as it needs to be to fully reconcile possibly disjoint branches, despite
203217 /// this sometimes costing some time when the remote is far ahead in a huge repository.
204- // TODO: review the docs!
205218 #[ instrument( skip( meta, ref_name) , err( Debug ) ) ]
206219 pub fn from_commit_traversal (
207220 tip : gix:: Id < ' _ > ,
208221 ref_name : impl Into < Option < gix:: refs:: FullName > > ,
209222 meta : & impl RefMetadata ,
210- Options {
223+ options : Options ,
224+ ) -> anyhow:: Result < Self > {
225+ let ( repo, meta) = Overlay :: default ( ) . into_parts ( tip. repo , meta) ;
226+ Graph :: from_commit_traversal_inner ( tip. detach ( ) , & repo, ref_name, & meta, options)
227+ }
228+
229+ fn from_commit_traversal_inner < T : RefMetadata > (
230+ tip : gix:: ObjectId ,
231+ repo : & OverlayRepo < ' _ > ,
232+ ref_name : impl Into < Option < gix:: refs:: FullName > > ,
233+ meta : & OverlayMetadata < ' _ , T > ,
234+ options : Options ,
235+ ) -> anyhow:: Result < Self > {
236+ let mut graph = Graph {
237+ options : options. clone ( ) ,
238+ ..Graph :: default ( )
239+ } ;
240+ let Options {
211241 collect_tags,
212242 extra_target_commit_id,
213243 commits_limit_hint : limit,
214244 commits_limit_recharge_location : mut max_commits_recharge_location,
215245 hard_limit,
216- } : Options ,
217- ) -> anyhow:: Result < Self > {
218- let repo = tip. repo ;
246+ } = options;
247+
219248 let max_limit = Limit :: new ( limit) ;
220249 // TODO: also traverse (outside)-branches that ought to be in the workspace. That way we have the desired ones
221250 // automatically and just have to find a way to prune the undesired ones.
@@ -231,12 +260,10 @@ impl Graph {
231260 }
232261 let commit_graph = repo. commit_graph_if_enabled ( ) ?;
233262 let mut buf = Vec :: new ( ) ;
234- let mut graph = Graph :: default ( ) ;
235263
236264 let configured_remote_tracking_branches =
237265 remotes:: configured_remote_tracking_branches ( repo) ?;
238- let refs_by_id = collect_ref_mapping_by_prefix (
239- repo,
266+ let refs_by_id = repo. collect_ref_mapping_by_prefix (
240267 std:: iter:: once ( "refs/heads/" ) . chain ( if collect_tags {
241268 Some ( "refs/tags/" )
242269 } else {
@@ -250,7 +277,7 @@ impl Graph {
250277 // The tip transports itself.
251278 let tip_flags = CommitFlags :: NotInRemote
252279 | goals
253- . flag_for ( tip. detach ( ) )
280+ . flag_for ( tip)
254281 . expect ( "we more than one bitflags for this" ) ;
255282
256283 let target_symbolic_remote_names = {
@@ -283,10 +310,10 @@ impl Graph {
283310 let current = graph. insert_root ( branch_segment_from_name_and_meta (
284311 ref_name. clone ( ) . map ( |rn| ( rn, None ) ) ,
285312 meta,
286- Some ( ( & ctx. refs_by_id , tip. detach ( ) ) ) ,
313+ Some ( ( & ctx. refs_by_id , tip) ) ,
287314 ) ?) ;
288315 _ = next. push_back_exhausted ( (
289- tip. detach ( ) ,
316+ tip,
290317 tip_flags,
291318 Instruction :: CollectCommit { into : current } ,
292319 max_limit,
@@ -326,7 +353,7 @@ impl Graph {
326353 } else {
327354 (
328355 CommitFlags :: empty ( ) ,
329- max_limit. with_indirect_goal ( tip. detach ( ) , & mut goals) ,
356+ max_limit. with_indirect_goal ( tip, & mut goals) ,
330357 )
331358 } ;
332359 let mut ws_segment =
@@ -369,10 +396,10 @@ impl Graph {
369396 CommitFlags :: NotInRemote | goal,
370397 Instruction :: CollectCommit { into : local_sidx } ,
371398 max_limit
372- . with_indirect_goal ( tip. detach ( ) , & mut goals)
399+ . with_indirect_goal ( tip, & mut goals)
373400 . without_allowance ( ) ,
374401 ) ) ;
375- next. add_goal_to ( tip. detach ( ) , goal) ;
402+ next. add_goal_to ( tip, goal) ;
376403 ( Some ( local_sidx) , goal)
377404 } else {
378405 ( None , CommitFlags :: empty ( ) )
@@ -386,7 +413,7 @@ impl Graph {
386413 // Once the goal was found, be done immediately,
387414 // we are not interested in these.
388415 max_limit
389- . with_indirect_goal ( tip. detach ( ) , & mut goals)
416+ . with_indirect_goal ( tip, & mut goals)
390417 . additional_goal ( local_goal)
391418 . without_allowance ( ) ,
392419 ) ) ;
@@ -416,7 +443,7 @@ impl Graph {
416443 into : extra_target_sidx,
417444 } ,
418445 max_limit
419- . with_indirect_goal ( tip. detach ( ) , & mut goals)
446+ . with_indirect_goal ( tip, & mut goals)
420447 . without_allowance ( ) ,
421448 ) ) ;
422449 extra_target_sidx
@@ -468,7 +495,7 @@ impl Graph {
468495 if max_commits_recharge_location. binary_search ( & id) . is_ok ( ) {
469496 limit. set_but_keep_goal ( max_limit) ;
470497 }
471- let info = find ( commit_graph. as_ref ( ) , repo, id, & mut buf) ?;
498+ let info = find ( commit_graph. as_ref ( ) , repo. for_find_only ( ) , id, & mut buf) ?;
472499 let src_flags = graph[ instruction. segment_idx ( ) ]
473500 . commits
474501 . last ( )
@@ -566,7 +593,7 @@ impl Graph {
566593 limit,
567594 ) ;
568595 if hard_limit_hit {
569- return graph. post_processed ( meta, tip. detach ( ) , ctx. with_hard_limit ( ) ) ;
596+ return graph. post_processed ( meta, tip, ctx. with_hard_limit ( ) ) ;
570597 }
571598
572599 segment. commits . push (
@@ -587,14 +614,36 @@ impl Graph {
587614
588615 for item in remote_items {
589616 if next. push_back_exhausted ( item) {
590- return graph. post_processed ( meta, tip. detach ( ) , ctx. with_hard_limit ( ) ) ;
617+ return graph. post_processed ( meta, tip, ctx. with_hard_limit ( ) ) ;
591618 }
592619 }
593620
594621 prune_integrated_tips ( & mut graph, & mut next) ;
595622 }
596623
597- graph. post_processed ( meta, tip. detach ( ) , ctx)
624+ graph. post_processed ( meta, tip, ctx)
625+ }
626+
627+ /// Repeat the traversal that generated this graph using `repo` and `meta`, but allow to set an in-memory
628+ /// `overlay` to amend the data available from `repo` and `meta`.
629+ /// This way, one can see this graph as it will be in the future once the changes to `repo` and `meta` are actually made.
630+ pub fn redo_traversal_with_overlay (
631+ & self ,
632+ repo : & gix:: Repository ,
633+ meta : & impl RefMetadata ,
634+ overlay : Overlay ,
635+ ) -> anyhow:: Result < Self > {
636+ let ( repo, meta) = overlay. into_parts ( repo, meta) ;
637+ let tip_sidx = self
638+ . entrypoint
639+ . context ( "BUG: entrypoint must always be set" ) ?
640+ . 0 ;
641+ let tip = self
642+ . tip_skip_empty ( tip_sidx)
643+ . context ( "BUG: entrypoint must eventually point to a commit" ) ?
644+ . id ;
645+ let ref_name = self [ tip_sidx] . ref_name . clone ( ) ;
646+ Graph :: from_commit_traversal_inner ( tip, & repo, ref_name, & meta, self . options . clone ( ) )
598647 }
599648}
600649
0 commit comments