Skip to content

Commit 3dbc754

Browse files
authored
Merge pull request #10459 from Byron/fix
follow-up fix for segment naming
2 parents e32d9e8 + bcb22d7 commit 3dbc754

File tree

11 files changed

+414
-322
lines changed

11 files changed

+414
-322
lines changed

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

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -410,11 +410,22 @@ impl Graph {
410410
local_tip_info
411411
{
412412
let local_sidx = graph.insert_root(branch_segment_from_name_and_meta_sibling(
413-
Some((local_ref_name, None)),
413+
None,
414414
Some(target_segment),
415415
meta,
416-
None,
416+
Some((&ctx.refs_by_id, target_local_tip)),
417417
)?);
418+
// We use auto-naming based on ambiguity - if the name ends up something else,
419+
// remove the nodes sibling link.
420+
let has_sibling_link = {
421+
let s = &mut graph[local_sidx];
422+
if s.ref_name.as_ref().is_none_or(|rn| rn != &local_ref_name) {
423+
s.sibling_segment_id = None;
424+
false
425+
} else {
426+
true
427+
}
428+
};
418429
let goal = goals.flag_for(target_local_tip).unwrap_or_default();
419430
_ = next.push_front_exhausted((
420431
target_local_tip,
@@ -425,7 +436,7 @@ impl Graph {
425436
.without_allowance(),
426437
));
427438
next.add_goal_to(tip, goal);
428-
(Some(local_sidx), goal)
439+
(has_sibling_link.then_some(local_sidx), goal)
429440
} else {
430441
(None, CommitFlags::empty())
431442
};
@@ -492,13 +503,18 @@ impl Graph {
492503
else {
493504
continue;
494505
};
495-
// Avoid duplication before we create a new branch segment, these should not inetefere,
506+
// Avoid duplication before we create a new branch segment, these should not interfere,
496507
// just integrate.
497508
if next.iter().any(|t| t.0 == segment_tip) {
498509
continue;
499510
};
500-
let segment =
501-
branch_segment_from_name_and_meta(Some((segment.ref_name, None)), meta, None)?;
511+
// We always want these segments named, we know they are supposed to be in the workspace,
512+
// but don't do so forcefully (follow the rules).
513+
let segment = branch_segment_from_name_and_meta(
514+
None,
515+
meta,
516+
Some((&ctx.refs_by_id, segment_tip.detach())),
517+
)?;
502518
let segment = graph.insert_root(segment);
503519
_ = next.push_back_exhausted((
504520
segment_tip.detach(),

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

Lines changed: 45 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -317,13 +317,6 @@ impl Graph {
317317
.iter()
318318
.filter_map(|s| {
319319
s.base_segment_id().and_then(|sidx| {
320-
let base_is_directly_connected_to_workspace = self
321-
.inner
322-
.neighbors_directed(sidx, Direction::Incoming)
323-
.any(|above_sidx| above_sidx == ws_sidx);
324-
if base_is_directly_connected_to_workspace {
325-
return None;
326-
}
327320
let base_segment = &self[sidx];
328321
// These are naturally in the workspace.
329322
base_segment
@@ -410,61 +403,6 @@ impl Graph {
410403
return Ok(());
411404
};
412405

413-
// If the target branch made it into a stack segment which can also be named differently by preferably a ref with metadata,
414-
// then do that instead. Let's prevent it from showing up as its own node as this leads to undesirable workspaces.
415-
if let Some((target_segment_id, local_tracking_segment_of_target)) =
416-
ws_target.as_ref().and_then(|t| {
417-
self[t.segment_index]
418-
.sibling_segment_id
419-
.map(|sidx| (t.segment_index, sidx))
420-
})
421-
&& let Some(segment_id) = ws_stacks.iter().find_map(|s| {
422-
s.segments.iter().find_map(|s| {
423-
if s.id != local_tracking_segment_of_target {
424-
return None;
425-
}
426-
let first_commit = s.commits.first()?;
427-
(!first_commit.refs.is_empty()).then_some(s.id)
428-
})
429-
})
430-
{
431-
let s = &mut self[segment_id];
432-
let c = s
433-
.commits
434-
.first_mut()
435-
.expect("segment was chosen because it has at least one commit");
436-
let mut candidates = c.refs.clone();
437-
candidates.sort_by(|a, b| {
438-
meta.branch_opt(a.as_ref())
439-
.ok()
440-
.map(|md| md.is_some())
441-
.cmp(&meta.branch_opt(b.as_ref()).ok().map(|md| md.is_some()))
442-
.then_with(|| a.cmp(b))
443-
});
444-
let candidate = candidates
445-
.pop()
446-
.expect("at least one ref or we wouldn't be here");
447-
let current_name = s.ref_name.take();
448-
let index_to_replace = c
449-
.refs
450-
.iter()
451-
.position(|rn| rn == &candidate)
452-
.expect("candidate is from a clone of 'refs', so must be contained");
453-
// effectively swap the names
454-
s.metadata = meta
455-
.branch_opt(candidate.as_ref())?
456-
.map(SegmentMetadata::Branch);
457-
s.ref_name = Some(candidate);
458-
if let Some(rn) = current_name {
459-
c.refs[index_to_replace] = rn;
460-
}
461-
462-
// Make sure we dissolve the sibling relationship for correctness, the node is now not related
463-
// to the target branch anymore.
464-
s.sibling_segment_id = None;
465-
self[target_segment_id].sibling_segment_id = None;
466-
}
467-
468406
// Setup independent stacks, first by looking at potential bases.
469407
let candidates = self.candidates_for_independent_branches_in_workspace(
470408
ws_sidx,
@@ -479,10 +417,14 @@ impl Graph {
479417
let base_segment_name = base_segment.ref_name.clone();
480418
let matching_refs_per_stack: Vec<_> = find_all_desired_stack_refs_in_commit(
481419
&ws_data,
482-
base_segment_name
483-
.as_ref()
484-
.into_iter()
485-
.chain(base_segment.commits[0].refs.iter()),
420+
base_segment_name.as_ref().into_iter().chain(
421+
base_segment
422+
.commits
423+
.first()
424+
.map(|c| c.refs.iter())
425+
.into_iter()
426+
.flatten(),
427+
),
486428
Some((&self.inner, ws_sidx, &ws_stacks, &candidates)),
487429
)
488430
.collect();
@@ -568,10 +510,17 @@ impl Graph {
568510
Some((last_created_segment.unwrap_or(s.id), base_sidx, c))
569511
})
570512
{
571-
let Some(refs_for_dependent_branches) =
572-
find_all_desired_stack_refs_in_commit(&ws_data, commit.refs.iter(), None)
573-
.next()
574-
else {
513+
let Some(refs_for_dependent_branches) = find_all_desired_stack_refs_in_commit(
514+
&ws_data,
515+
self[base_sidx]
516+
.ref_name
517+
.as_ref()
518+
.filter(|_| !commit.refs.is_empty())
519+
.into_iter()
520+
.chain(commit.refs.iter()),
521+
None,
522+
)
523+
.next() else {
575524
continue;
576525
};
577526

@@ -593,12 +542,34 @@ impl Graph {
593542
)?;
594543

595544
// As we didn't allow the previous function to deal with the commit, we do it.
596-
self[base_sidx]
597-
.commits
545+
let s = &mut self[base_sidx];
546+
s.commits
598547
.first_mut()
599548
.expect("we know there is one already")
600549
.refs
601550
.retain(|rn| !refs_for_dependent_branches.contains(rn));
551+
s.ref_name
552+
.take_if(|rn| refs_for_dependent_branches.contains(rn));
553+
if s.ref_name.is_none() {
554+
s.metadata = None;
555+
if let Some(sibling) = s.sibling_segment_id.take() {
556+
self[sibling].sibling_segment_id = None;
557+
}
558+
}
559+
560+
let s = &mut self[base_sidx];
561+
if let Some(refs) = s
562+
.commits
563+
.first_mut()
564+
.filter(|c| !c.refs.is_empty())
565+
.map(|c| &mut c.refs)
566+
&& let Some((name, md)) =
567+
disambiguate_refs_by_branch_metadata(refs.iter(), meta)
568+
{
569+
refs.retain(|rn| rn != &name);
570+
s.ref_name = Some(name);
571+
s.metadata = md;
572+
}
602573
reconnect_edges(
603574
self,
604575
edges_from_segment_above,
@@ -790,6 +761,8 @@ impl Graph {
790761
s.sibling_segment_id = Some(remote_sidx);
791762
let rn = s.ref_name.as_ref().expect("just set it");
792763
s.commits.first_mut().unwrap().refs.retain(|crn| crn != rn);
764+
// Assure the remote is also paired up!
765+
self[remote_sidx].sibling_segment_id = Some(s.id);
793766
}
794767

795768
// NOTE: setting this directly at iteration time isn't great as the post-processing then

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,8 @@ pub fn queue_parents(
386386
/// As convenience, if `ref_name` is `Some` and the metadata is not set, it will look it up for you.
387387
/// If `ref_name` is `None`, and `refs_by_id_lookup` is `Some`, it will try to look up unambiguous
388388
/// references on that object.
389+
/// Note that `ref_name` should only be set if you are sure that it is unambiguous, and otherwise won't interfere with
390+
/// the post-processing or the workspace projection later.
389391
pub fn branch_segment_from_name_and_meta<T: RefMetadata>(
390392
ref_name: Option<(gix::refs::FullName, Option<SegmentMetadata>)>,
391393
meta: &OverlayMetadata<'_, T>,

crates/but-graph/src/projection/stack.rs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,12 @@ impl Stack {
105105

106106
impl std::fmt::Debug for Stack {
107107
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
108-
f.debug_struct(&format!("Stack({})", self.debug_string()))
109-
.field("segments", &self.segments)
110-
.finish()
108+
let mut s = f.debug_struct(&format!("Stack({})", self.debug_string()));
109+
s.field("segments", &self.segments);
110+
if let Some(stack_id) = self.id {
111+
s.field("id", &stack_id);
112+
}
113+
s.finish()
111114
}
112115
}
113116

@@ -198,6 +201,20 @@ impl StackSegment {
198201
pub fn tip(&self) -> Option<gix::ObjectId> {
199202
self.commits.first().map(|c| c.id)
200203
}
204+
205+
/// Returns an iterator over all reachable reference names, that is the name of the segment if present
206+
/// and all ref-names pointing to/stored in local commits.
207+
pub fn ref_names(&self) -> impl Iterator<Item = &gix::refs::FullNameRef> {
208+
self.ref_name
209+
.as_ref()
210+
.map(|r| r.as_ref())
211+
.into_iter()
212+
.chain(
213+
self.commits
214+
.iter()
215+
.flat_map(|c| c.refs.iter().map(|r| r.as_ref())),
216+
)
217+
}
201218
}
202219

203220
impl std::fmt::Debug for StackSegment {

crates/but-graph/src/projection/workspace.rs

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -517,8 +517,9 @@ impl Graph {
517517
}
518518

519519
if ws.has_managed_ref() {
520-
let (_lowest_base, lowest_base_sidx) =
520+
let (lowest_base, lowest_base_sidx) =
521521
ws_lower_bound.map_or((None, None), |(base, sidx)| (Some(base), Some(sidx)));
522+
// dbg!(lowest_base, lowest_base_sidx);
522523
for stack_top_sidx in self
523524
.inner
524525
.neighbors_directed(ws_tip_segment.id, Direction::Outgoing)
@@ -570,9 +571,24 @@ impl Graph {
570571
},
571572
|s| Some(s.id) == ws.lower_bound_segment_id && s.metadata.is_none(),
572573
)?
573-
.map(|segments| {
574+
.and_then(|segments| {
574575
let stack_id = find_matching_stack_id(ws.metadata.as_ref(), &segments);
575-
Stack::from_base_and_segments(&self.inner, segments, stack_id)
576+
// If we find no stack ID, then the segment is not included in the workspace metadata,
577+
// indicating it's ignored. Just to be even more certain, if it starts with a commit
578+
// that is the workspace base, then we definitely don't want to show it - it's unapplied.
579+
if stack_id.is_none()
580+
&& segments
581+
.first()
582+
.is_some_and(|s| s.commits.first().map(|c| c.id) == lowest_base)
583+
{
584+
None
585+
} else {
586+
Some(Stack::from_base_and_segments(
587+
&self.inner,
588+
segments,
589+
stack_id,
590+
))
591+
}
576592
}),
577593
);
578594
}
@@ -702,17 +718,17 @@ fn find_matching_stack_id(
702718
metadata
703719
.stacks
704720
.iter()
705-
.map(|s| {
721+
.filter_map(|s| {
706722
let num_matching_refs = s
707723
.branches
708724
.iter()
709725
.filter(|b| {
710726
segments
711727
.iter()
712-
.any(|s| s.ref_name.as_ref().is_some_and(|rn| rn == &b.ref_name))
728+
.any(|s| s.ref_names().any(|rn| rn == b.ref_name.as_ref()))
713729
})
714730
.count();
715-
(s.id, num_matching_refs)
731+
(num_matching_refs != 0).then_some((s.id, num_matching_refs))
716732
})
717733
.sorted_by(|(_, lhs), (_, rhs)| lhs.cmp(rhs).reverse())
718734
.next()

crates/but-graph/tests/fixtures/scenarios.sh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1109,5 +1109,13 @@ EOF
11091109
git checkout -b A
11101110
create_workspace_commit_once A
11111111
)
1112+
1113+
git init unapplied-branch-on-base
1114+
(cd unapplied-branch-on-base
1115+
commit init
1116+
git branch unapplied
1117+
setup_target_to_match_main
1118+
create_workspace_commit_once main
1119+
)
11121120
)
11131121

0 commit comments

Comments
 (0)