Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions crates/but-graph/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,6 @@ impl Graph {
/// `dst_commit_id` can be provided if the connection is to a future commit that isn't yet available
/// in the `segment`. If `None`, it will be looked up in the `segment` itself.
///
/// `parent_order` is the 0-based position of `dst_commit_id` among the source commit's
/// parents. For a merge commit with parents `[A, B]`, the edge to `A` must use `0`,
/// and the edge to `B` must use `1`, even if traversal discovers `B` before `A`.
///
/// Return the newly added segment.
pub fn connect_new_segment(
&mut self,
Expand All @@ -71,7 +67,6 @@ impl Graph {
dst: Segment,
dst_commit: impl Into<Option<CommitIndex>>,
dst_commit_id: impl Into<Option<gix::ObjectId>>,
parent_order: u32,
) -> SegmentIndex {
let dst = self.inner.add_node(dst);
self.inner[dst].id = dst;
Expand All @@ -82,7 +77,6 @@ impl Graph {
dst,
dst_commit,
dst_commit_id.into(),
parent_order,
);
dst
}
Expand Down Expand Up @@ -168,7 +162,7 @@ impl Graph {
/// excluded side only as far as needed to prove emitted segments are not hidden.
///
/// If `first_parent` is [`FirstParent::Yes`], both the included and excluded traversals follow
/// only segment edges with `parent_order == 0`.
/// only the first-parent edge (the source commit's first parent).
pub fn find_commits_reachable_from_a_not_b(
&self,
included: SegmentIndex,
Expand All @@ -192,7 +186,7 @@ impl Graph {
/// excluded side only as far as needed to prove emitted segments are not hidden.
///
/// If `first_parent` is [`FirstParent::Yes`], both the included and excluded traversals follow
/// only segment edges with `parent_order == 0`.
/// only the first-parent edge (the source commit's first parent).
pub fn find_segments_reachable_from_a_not_b(
&self,
included: SegmentIndex,
Expand Down Expand Up @@ -317,10 +311,16 @@ impl Graph {
segment_id: SegmentIndex,
first_parent_only: bool,
) -> impl Iterator<Item = SegmentIndex> {
// Outgoing edges are kept in first-parent order (see
// `rebuild_outgoing_edges_for_traversal_order`), so first-parent traversal follows just the
// first edge. Matching by the destination commit id instead would dead-end whenever the
// first-parent edge crosses into a commit-less segment (an empty branch), whose edge carries
// no destination id to match against.
let max_edges = if first_parent_only { 1 } else { usize::MAX };
self.inner
.edges_directed(segment_id, Direction::Outgoing)
.filter(move |edge| !first_parent_only || edge.weight().parent_order == 0)
.map(|edge| edge.target())
.take(max_edges)
}

/// Return `true` once the excluded-side frontier cannot still hide any segment already emitted.
Expand Down
30 changes: 20 additions & 10 deletions crates/but-graph/src/init/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -889,7 +889,6 @@ impl Graph {
propagated_flags,
src_sidx,
limit,
0,
)?;
continue;
}
Expand All @@ -910,7 +909,6 @@ impl Graph {
Instruction::ConnectNewSegment {
parent_above,
at_commit,
parent_order,
} => match seen.entry(id) {
Entry::Occupied(_) => {
possibly_split_occupied_segment(
Expand All @@ -921,7 +919,6 @@ impl Graph {
propagated_flags,
parent_above,
limit,
parent_order,
)?;
continue;
}
Expand All @@ -938,7 +935,6 @@ impl Graph {
segment_below,
0,
id,
parent_order,
);
e.insert(segment_below);
segment_below
Expand Down Expand Up @@ -2172,10 +2168,9 @@ impl Graph {
dst: SegmentIndex,
dst_commit: impl Into<Option<CommitIndex>>,
) {
self.connect_segments_with_ids(src, src_commit, None, dst, dst_commit, None, 0)
self.connect_segments_with_ids(src, src_commit, None, dst, dst_commit, None)
}

#[expect(clippy::too_many_arguments)]
pub(crate) fn connect_segments_with_ids(
&mut self,
src: SegmentIndex,
Expand All @@ -2184,7 +2179,6 @@ impl Graph {
dst: SegmentIndex,
dst_commit: impl Into<Option<CommitIndex>>,
dst_id: Option<gix::ObjectId>,
parent_order: u32,
) {
let src_commit = src_commit.into();
let dst_commit = dst_commit.into();
Expand All @@ -2196,12 +2190,17 @@ impl Graph {
src_id: src_id.or_else(|| self[src].commit_id_by_index(src_commit)),
dst: dst_commit,
dst_id: dst_id.or_else(|| self[dst].commit_id_by_index(dst_commit)),
parent_order,
},
);
self.rebuild_outgoing_edges_for_traversal_order(src, new_edge_id);
}

/// Insert the just-added `new_edge_id` into `src`'s outgoing edges so they stay in first-parent
/// order — each destination's position among the source commit's parents. The existing edges
/// are already ordered, so this inserts the new one at its slot rather than re-sorting (a full
/// re-sort would also normalize any pre-existing order, changing the tie-break that downstream
/// stable sorts inherit). Commit-less edges (empty branches) carry no destination id and sort
/// last.
fn rebuild_outgoing_edges_for_traversal_order(
&mut self,
src: SegmentIndex,
Expand All @@ -2225,8 +2224,19 @@ impl Graph {
return;
}

let insert_at = outgoing_edges
.partition_point(|edge| edge.weight.parent_order <= new_edge.weight.parent_order);
let src_parents: &[gix::ObjectId] = self[src]
.commits
.last()
.map(|c| c.parent_ids.as_slice())
.unwrap_or_default();
let order_of = |edge: &EdgeOwned| {
edge.weight
.dst_id()
.and_then(|dst| src_parents.iter().position(|p| *p == dst))
.unwrap_or(usize::MAX)
};
let insert_at =
outgoing_edges.partition_point(|edge| order_of(edge) <= order_of(&new_edge));
outgoing_edges.insert(insert_at, new_edge);

for edge in &outgoing_edges {
Expand Down
Loading
Loading