1
1
use anyhow:: { Context , bail} ;
2
- use but_core:: RefMetadata ;
2
+ use but_core:: { RefMetadata , ref_metadata } ;
3
3
use gix:: {
4
4
hashtable:: hash_map:: Entry ,
5
5
prelude:: { ObjectIdExt , ReferenceExt } ,
@@ -13,12 +13,22 @@ mod walk;
13
13
use walk:: * ;
14
14
15
15
pub ( crate ) mod types;
16
+ use crate :: init:: overlay:: { OverlayMetadata , OverlayRepo } ;
16
17
use types:: { Goals , Instruction , Limit , Queue } ;
17
18
18
19
mod remotes;
19
20
21
+ mod overlay;
20
22
mod post;
21
23
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
+
22
32
pub ( super ) type PetGraph = petgraph:: stable_graph:: StableGraph < Segment , Edge > ;
23
33
24
34
/// Options for use in [`Graph::from_head()`] and [`Graph::from_commit_traversal()`].
@@ -123,9 +133,12 @@ impl Graph {
123
133
let ( tip, maybe_name) = match head. kind {
124
134
gix:: head:: Kind :: Unborn ( ref_name) => {
125
135
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) ;
126
139
graph. insert_root ( branch_segment_from_name_and_meta (
127
140
Some ( ( ref_name, None ) ) ,
128
- meta,
141
+ & meta,
129
142
None ,
130
143
) ?) ;
131
144
return Ok ( graph) ;
@@ -158,22 +171,23 @@ impl Graph {
158
171
} ;
159
172
Ok ( graph)
160
173
}
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.
163
176
/// `ref_name` is assumed to point to `tip` if given.
164
177
///
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 .
166
179
///
167
180
/// ### Features
168
181
///
169
182
/// * discover a Workspace on the fly based on `meta`-data.
170
183
/// * support the notion of a branch to integrate with, the *target*
171
184
/// - *target* branches consist of a local and remote tracking branch, and one can be ahead of the other.
172
185
/// - 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.
173
187
/// * remote tracking branches are seen in relation to their branches.
174
188
/// * the graph of segments assigns each reachable commit to exactly one segment
175
189
/// * 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
177
191
/// in debugging output if edges are now in violation of this constraint.
178
192
///
179
193
/// ### Rules
@@ -183,39 +197,54 @@ impl Graph {
183
197
/// Change the rules as you see fit to accomplish this.
184
198
///
185
199
/// * 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
188
203
/// 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.
192
208
/// * 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* .
194
210
/// * Segments stored in the *workspace metadata* are used/relevant only if they are backed by an existing branch.
195
211
/// * Remote tracking branches are picked up during traversal for any ref that we reached through traversal.
196
212
/// - This implies that remotes aren't relevant for segments added during post-processing, which would typically
197
213
/// be empty anyway.
198
214
/// - 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
202
216
/// * The traversal is always as long as it needs to be to fully reconcile possibly disjoint branches, despite
203
217
/// this sometimes costing some time when the remote is far ahead in a huge repository.
204
- // TODO: review the docs!
205
218
#[ instrument( skip( meta, ref_name) , err( Debug ) ) ]
206
219
pub fn from_commit_traversal (
207
220
tip : gix:: Id < ' _ > ,
208
221
ref_name : impl Into < Option < gix:: refs:: FullName > > ,
209
222
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 {
211
241
collect_tags,
212
242
extra_target_commit_id,
213
243
commits_limit_hint : limit,
214
244
commits_limit_recharge_location : mut max_commits_recharge_location,
215
245
hard_limit,
216
- } : Options ,
217
- ) -> anyhow:: Result < Self > {
218
- let repo = tip. repo ;
246
+ } = options;
247
+
219
248
let max_limit = Limit :: new ( limit) ;
220
249
// TODO: also traverse (outside)-branches that ought to be in the workspace. That way we have the desired ones
221
250
// automatically and just have to find a way to prune the undesired ones.
@@ -231,12 +260,10 @@ impl Graph {
231
260
}
232
261
let commit_graph = repo. commit_graph_if_enabled ( ) ?;
233
262
let mut buf = Vec :: new ( ) ;
234
- let mut graph = Graph :: default ( ) ;
235
263
236
264
let configured_remote_tracking_branches =
237
265
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 (
240
267
std:: iter:: once ( "refs/heads/" ) . chain ( if collect_tags {
241
268
Some ( "refs/tags/" )
242
269
} else {
@@ -250,7 +277,7 @@ impl Graph {
250
277
// The tip transports itself.
251
278
let tip_flags = CommitFlags :: NotInRemote
252
279
| goals
253
- . flag_for ( tip. detach ( ) )
280
+ . flag_for ( tip)
254
281
. expect ( "we more than one bitflags for this" ) ;
255
282
256
283
let target_symbolic_remote_names = {
@@ -283,10 +310,10 @@ impl Graph {
283
310
let current = graph. insert_root ( branch_segment_from_name_and_meta (
284
311
ref_name. clone ( ) . map ( |rn| ( rn, None ) ) ,
285
312
meta,
286
- Some ( ( & ctx. refs_by_id , tip. detach ( ) ) ) ,
313
+ Some ( ( & ctx. refs_by_id , tip) ) ,
287
314
) ?) ;
288
315
_ = next. push_back_exhausted ( (
289
- tip. detach ( ) ,
316
+ tip,
290
317
tip_flags,
291
318
Instruction :: CollectCommit { into : current } ,
292
319
max_limit,
@@ -326,7 +353,7 @@ impl Graph {
326
353
} else {
327
354
(
328
355
CommitFlags :: empty ( ) ,
329
- max_limit. with_indirect_goal ( tip. detach ( ) , & mut goals) ,
356
+ max_limit. with_indirect_goal ( tip, & mut goals) ,
330
357
)
331
358
} ;
332
359
let mut ws_segment =
@@ -369,10 +396,10 @@ impl Graph {
369
396
CommitFlags :: NotInRemote | goal,
370
397
Instruction :: CollectCommit { into : local_sidx } ,
371
398
max_limit
372
- . with_indirect_goal ( tip. detach ( ) , & mut goals)
399
+ . with_indirect_goal ( tip, & mut goals)
373
400
. without_allowance ( ) ,
374
401
) ) ;
375
- next. add_goal_to ( tip. detach ( ) , goal) ;
402
+ next. add_goal_to ( tip, goal) ;
376
403
( Some ( local_sidx) , goal)
377
404
} else {
378
405
( None , CommitFlags :: empty ( ) )
@@ -386,7 +413,7 @@ impl Graph {
386
413
// Once the goal was found, be done immediately,
387
414
// we are not interested in these.
388
415
max_limit
389
- . with_indirect_goal ( tip. detach ( ) , & mut goals)
416
+ . with_indirect_goal ( tip, & mut goals)
390
417
. additional_goal ( local_goal)
391
418
. without_allowance ( ) ,
392
419
) ) ;
@@ -416,7 +443,7 @@ impl Graph {
416
443
into : extra_target_sidx,
417
444
} ,
418
445
max_limit
419
- . with_indirect_goal ( tip. detach ( ) , & mut goals)
446
+ . with_indirect_goal ( tip, & mut goals)
420
447
. without_allowance ( ) ,
421
448
) ) ;
422
449
extra_target_sidx
@@ -468,7 +495,7 @@ impl Graph {
468
495
if max_commits_recharge_location. binary_search ( & id) . is_ok ( ) {
469
496
limit. set_but_keep_goal ( max_limit) ;
470
497
}
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) ?;
472
499
let src_flags = graph[ instruction. segment_idx ( ) ]
473
500
. commits
474
501
. last ( )
@@ -566,7 +593,7 @@ impl Graph {
566
593
limit,
567
594
) ;
568
595
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 ( ) ) ;
570
597
}
571
598
572
599
segment. commits . push (
@@ -587,14 +614,36 @@ impl Graph {
587
614
588
615
for item in remote_items {
589
616
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 ( ) ) ;
591
618
}
592
619
}
593
620
594
621
prune_integrated_tips ( & mut graph, & mut next) ;
595
622
}
596
623
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 ( ) )
598
647
}
599
648
}
600
649
0 commit comments