gitbutlerapp/gitbutler · error

swap connections of nodes as well

Error message

swap connections of nodes as well

What it means

`swap_commits_and_connections` in but-graph's init/walk swaps the commit vectors of two segments, but only when neither segment has graph edges. If either segment has connections (PetGraph edges), it panics with `todo!` - rewiring the connections during the swap was never implemented (crates/but-graph/src/init/walk/mod.rs:352).

Source

Thrown at crates/but-graph/src/init/walk/mod.rs:352

pub fn swap_queued_segments(queue: &mut Queue, a: SegmentIndex, b: SegmentIndex) {
    for instruction_to_replace in queue.iter_mut().map(|(_, _, instruction, _)| instruction) {
        let cmp = instruction_to_replace.segment_idx();
        if cmp == a {
            *instruction_to_replace = instruction_to_replace.with_replaced_sidx(b);
        } else if cmp == b {
            *instruction_to_replace = instruction_to_replace.with_replaced_sidx(a);
        }
    }
}

pub fn swap_commits_and_connections(graph: &mut PetGraph, a: SegmentIndex, b: SegmentIndex) {
    {
        let (a, b) = graph.index_twice_mut(a, b);
        std::mem::swap(&mut a.commits, &mut b.commits);
    }
    if graph.edges(a).next().is_some() || graph.edges(b).next().is_some() {
        todo!("swap connections of nodes as well")
    }
}

fn local_branches_by_id(
    refs_by_id: &RefsById,
    id: gix::ObjectId,
) -> Option<impl Iterator<Item = &gix::refs::FullName> + '_> {
    refs_by_id.get(&id).map(|refs| {
        refs.iter()
            .filter(|rn| rn.category() == Some(Category::LocalBranch))
    })
}

/// Split `src_sidx` into a new segment (to receive the commit at `info`) and connect it with the new segment
/// whose id will be returned, if…
///
/// * …there is exactly one eligible branch to name it.
/// * …it is a merge commit.

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Avoid code paths that reorder connected segments - restructure the walk so only edge-free segments are swapped
  2. Upgrade but-graph; track the upstream implementation of edge rewiring
  3. As a maintainer: implement the edge swap by collecting `EdgeReference`s for both indices and re-adding them with swapped endpoints via `graph.add_edge` before removing the old ones
Defensive patterns

Strategy: validation

Validate before calling

// Only swap edge-free segments
fn can_swap(graph: &PetGraph, a: SegmentIndex, b: SegmentIndex) -> bool {
    graph.edges(a).next().is_none() && graph.edges(b).next().is_none()
}
if can_swap(&graph, a, b) {
    swap_commits_and_connections(&mut graph, a, b);
} else {
    // pick a different rewrite strategy for connected segments
}

Prevention

When it happens

Trigger: A graph-rewriting pass (ordering/topology fixups during init) calls this with two segments where `graph.edges(a)` or `graph.edges(b)` yields at least one edge - i.e. either segment is connected to the rest of the graph rather than standing alone.

Common situations: Repositories whose ref graph produces connected segments that the walk wants to reorder; new graph-manipulation code paths added to init; changes to segment sorting rules.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20). Data as JSON: /api/errors/884a552bf59832a5. Report an issue: GitHub.