Skip to content

Commit 164b92a

Browse files
committed
Avoid propagating limit goals upward by rather propagating goals downward.
This is a per-goal bitflag
1 parent b480ba6 commit 164b92a

File tree

10 files changed

+439
-353
lines changed

10 files changed

+439
-353
lines changed

crates/but-graph/src/api.rs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,8 @@ impl Graph {
215215
entrypoint_in_workspace,
216216
segments_behind_of_entrypoint,
217217
segments_ahead_of_entrypoint,
218+
segments_on_top,
219+
segments_at_bottom,
218220
connections,
219221
commits,
220222
commit_references,
@@ -223,6 +225,8 @@ impl Graph {
223225

224226
*segments = self.inner.node_count();
225227
*connections = self.inner.edge_count();
228+
*segments_on_top = self.tip_segments().count();
229+
*segments_at_bottom = self.base_segments().count();
226230

227231
if let Ok(ep) = self.lookup_entrypoint() {
228232
*entrypoint_in_workspace = ep
@@ -287,7 +291,7 @@ impl Graph {
287291
{
288292
*segments_in_workspace_and_integrated += 1
289293
}
290-
if c.flags.is_empty() {
294+
if c.flags.is_remote() {
291295
*segments_remote += 1;
292296
}
293297
}
@@ -484,12 +488,20 @@ impl Graph {
484488
let entrypoint = self.entrypoint;
485489
let node_attrs = |_: &PetGraph, (sidx, s): (SegmentIndex, &Segment)| {
486490
let name = format!(
487-
"{}{maybe_centering_newline}",
491+
"{}{remote}{maybe_centering_newline}",
488492
s.ref_name
489493
.as_ref()
490494
.map(Self::ref_debug_string)
491495
.unwrap_or_else(|| "<anon>".into()),
492-
maybe_centering_newline = if s.commits.is_empty() { "" } else { "\n" }
496+
maybe_centering_newline = if s.commits.is_empty() { "" } else { "\n" },
497+
remote = if let Some(remote_ref_name) = s.remote_tracking_ref_name.as_ref() {
498+
format!(
499+
" <> {remote_name}",
500+
remote_name = Self::ref_debug_string(remote_ref_name)
501+
)
502+
} else {
503+
"".into()
504+
}
493505
);
494506
// Reduce noise by preferring ref-based entry-points.
495507
let show_segment_entrypoint = s.ref_name.is_some()

crates/but-graph/src/init/mod.rs

Lines changed: 71 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -206,15 +206,18 @@ impl Graph {
206206
}),
207207
)?;
208208
let (workspaces, target_refs, desired_refs) =
209-
obtain_workspace_infos(ref_name.as_ref().map(|rn| rn.as_ref()), meta)?;
209+
obtain_workspace_infos(repo, ref_name.as_ref().map(|rn| rn.as_ref()), meta)?;
210210
let mut seen = gix::revwalk::graph::IdMap::<SegmentIndex>::default();
211-
let tip_flags = CommitFlags::NotInRemote;
211+
let mut goals = Goals::default();
212+
let tip_limit_with_goal = limit.with_goal(tip.detach(), &mut goals);
213+
// The tip transports itself.
214+
let tip_flags = CommitFlags::NotInRemote | tip_limit_with_goal.goal;
212215

213216
let target_symbolic_remote_names = {
214217
let remote_names = repo.remote_names();
215218
let mut v: Vec<_> = workspaces
216219
.iter()
217-
.filter_map(|(_, data)| {
220+
.filter_map(|(_, _, data)| {
218221
let target_ref = data.target_ref.as_ref()?;
219222
remotes::extract_remote_name(target_ref.as_ref(), &remote_names)
220223
})
@@ -227,7 +230,7 @@ impl Graph {
227230
let mut next = Queue::new_with_limit(hard_limit);
228231
if !workspaces
229232
.iter()
230-
.any(|(wsrn, _)| Some(wsrn) == ref_name.as_ref())
233+
.any(|(_, wsrn, _)| Some(wsrn) == ref_name.as_ref())
231234
{
232235
let current = graph.insert_root(branch_segment_from_name_and_meta(
233236
ref_name.clone(),
@@ -243,14 +246,7 @@ impl Graph {
243246
return Ok(graph.with_hard_limit());
244247
}
245248
}
246-
for (ws_ref, workspace_info) in workspaces {
247-
let Some(ws_tip) = try_refname_to_id(repo, ws_ref.as_ref())? else {
248-
tracing::warn!(
249-
"Ignoring stale workspace ref '{ws_ref}', which didn't exist in Git but still had workspace data",
250-
ws_ref = ws_ref.as_bstr()
251-
);
252-
continue;
253-
};
249+
for (ws_tip, ws_ref, workspace_info) in workspaces {
254250
let target = workspace_info.target_ref.as_ref().and_then(|trn| {
255251
try_refname_to_id(repo, trn.as_ref())
256252
.map_err(|err| {
@@ -265,15 +261,16 @@ impl Graph {
265261
.map(|tid| (trn.clone(), tid))
266262
});
267263

268-
let add_flags = if Some(&ws_ref) == ref_name.as_ref() {
269-
tip_flags
264+
let (ws_flags, ws_limit) = if Some(&ws_ref) == ref_name.as_ref() {
265+
(tip_flags, limit)
270266
} else {
271-
CommitFlags::empty()
267+
(CommitFlags::empty(), tip_limit_with_goal)
272268
};
273269
let mut ws_segment = branch_segment_from_name_and_meta(Some(ws_ref), meta, None)?;
274-
// Drop the limit if we have a target ref
275-
let limit = if workspace_info.target_ref.is_some() {
276-
limit.with_goal(tip.detach())
270+
// The limits for the target ref and the worktree ref are synced so they can always find each other,
271+
// while being able to stop when the entrypoint is included.
272+
let target_limit = if workspace_info.target_ref.is_some() {
273+
tip_limit_with_goal
277274
} else {
278275
limit
279276
};
@@ -287,9 +284,9 @@ impl Graph {
287284
// We only allow workspaces that are not remote, and that are not target refs.
288285
// Theoretically they can still cross-reference each other, but then we'd simply ignore
289286
// their status for now.
290-
CommitFlags::NotInRemote | add_flags,
287+
CommitFlags::NotInRemote | ws_flags,
291288
Instruction::CollectCommit { into: ws_segment },
292-
limit,
289+
ws_limit,
293290
)) {
294291
return Ok(graph.with_hard_limit());
295292
}
@@ -305,8 +302,7 @@ impl Graph {
305302
Instruction::CollectCommit {
306303
into: target_segment,
307304
},
308-
/* unlimited traversal for integrated commits */
309-
limit.with_goal(tip.detach()),
305+
target_limit,
310306
)) {
311307
return Ok(graph.with_hard_limit());
312308
}
@@ -319,7 +315,7 @@ impl Graph {
319315
let max_limit = limit;
320316
while let Some((id, mut propagated_flags, instruction, mut limit)) = next.pop_front() {
321317
if max_commits_recharge_location.binary_search(&id).is_ok() {
322-
limit.inner = max_limit.inner;
318+
limit.set_but_keep_goal(max_limit);
323319
}
324320
let info = find(commit_graph.as_ref(), repo, id, &mut buf)?;
325321
let src_flags = graph[instruction.segment_idx()]
@@ -390,8 +386,23 @@ impl Graph {
390386
},
391387
};
392388

389+
let refs_at_commit_before_removal = refs_by_id.remove(&id).unwrap_or_default();
390+
let (remote_items, maybe_goal_for_id) = try_queue_remote_tracking_branches(
391+
repo,
392+
&refs_at_commit_before_removal,
393+
&mut graph,
394+
&target_symbolic_remote_names,
395+
&configured_remote_tracking_branches,
396+
&target_refs,
397+
meta,
398+
id,
399+
limit,
400+
&mut goals,
401+
)?;
402+
393403
let segment = &mut graph[segment_idx_for_id];
394404
let commit_idx_for_possible_fork = segment.commits.len();
405+
let propagated_flags = propagated_flags | maybe_goal_for_id;
395406
let hard_limit_hit = queue_parents(
396407
&mut next,
397408
&info.parent_ids,
@@ -404,7 +415,6 @@ impl Graph {
404415
return Ok(graph.with_hard_limit());
405416
}
406417

407-
let refs_at_commit_before_removal = refs_by_id.remove(&id).unwrap_or_default();
408418
segment.commits.push(
409419
info.into_commit(
410420
repo,
@@ -422,20 +432,10 @@ impl Graph {
422432
)?,
423433
);
424434

425-
let hard_limit_hit = try_queue_remote_tracking_branches(
426-
repo,
427-
&refs_at_commit_before_removal,
428-
&mut next,
429-
&mut graph,
430-
&target_symbolic_remote_names,
431-
&configured_remote_tracking_branches,
432-
&target_refs,
433-
meta,
434-
id,
435-
limit,
436-
)?;
437-
if hard_limit_hit {
438-
return Ok(graph.with_hard_limit());
435+
for item in remote_items {
436+
if next.push_back_exhausted(item) {
437+
return Ok(graph.with_hard_limit());
438+
}
439439
}
440440

441441
prune_integrated_tips(&mut graph, &mut next, &desired_refs, max_limit);
@@ -469,7 +469,39 @@ struct Queue {
469469
struct Limit {
470470
inner: Option<usize>,
471471
/// The commit we want to see to be able to assume normal limits. Until then there is no limit.
472-
goal: Option<gix::ObjectId>,
472+
/// This is represented by bitflag, one for each goal.
473+
/// The flag is empty if no goal is set.
474+
goal: CommitFlags,
475+
}
476+
477+
/// A set of commits to keep track of in bitflags.
478+
#[derive(Default)]
479+
struct Goals(Vec<gix::ObjectId>);
480+
481+
impl Goals {
482+
/// Return the bitflag for `goal`, or `None` if we can't track any more goals.
483+
fn flag_for(&mut self, goal: gix::ObjectId) -> Option<CommitFlags> {
484+
let existing_flags = CommitFlags::all().iter().count();
485+
let max_goals = size_of::<CommitFlags>() * 8 - existing_flags;
486+
487+
let goals = &mut self.0;
488+
let goal_index = match goals.iter().position(|existing| existing == &goal) {
489+
None => {
490+
let idx = goals.len();
491+
goals.push(goal);
492+
idx
493+
}
494+
Some(idx) => idx,
495+
};
496+
if goal_index >= max_goals {
497+
tracing::warn!("Goals limit reached, cannot track {goal}");
498+
None
499+
} else {
500+
Some(CommitFlags::from_bits_retain(
501+
1 << (existing_flags + goal_index),
502+
))
503+
}
504+
}
473505
}
474506

475507
#[derive(Debug, Copy, Clone)]

0 commit comments

Comments
 (0)