gitbutlerapp/gitbutler · info
anchor is always present in the order at this point
Error message
anchor is always present in the order at this point
What it means
Panic from `expect("anchor is always present in the order at this point")` in `insert_into_order` (but-workspace create_reference.rs:629). The function pushes `anchor` into `order` if missing, then `retain` removes only `new_ref`, so the subsequent `position()` lookup must succeed — the source comment documents exactly this reasoning. It is a locally proven invariant; a panic implies someone changed the push/retain logic so the anchor can be dropped.
Source
Thrown at crates/but-workspace/src/branch/create_reference.rs:629
/// - [`Position::Above`] takes the anchor's slot, pushing the anchor down; [`Position::Below`]
/// goes right after the anchor. This only affects *ordering* — unlike [`Anchor::AtSegment`],
/// it never changes which branch owns the commit.
fn insert_into_branch_stack_order(
mut order: Vec<gix::refs::FullName>,
anchor: &gix::refs::FullNameRef,
new_ref: &gix::refs::FullNameRef,
position: Position,
) -> Vec<gix::refs::FullName> {
if !order.iter().any(|branch| branch.as_ref() == anchor) {
order.push(anchor.to_owned());
}
order.retain(|branch| branch.as_ref() != new_ref);
// `anchor` was pushed above if it was missing, and `retain` only drops `new_ref`, so the
// anchor is guaranteed to still be present here.
let anchor_idx = order
.iter()
.position(|branch| branch.as_ref() == anchor)
.expect("anchor is always present in the order at this point");
let insert_idx = match position {
Position::Above => anchor_idx,
Position::Below => anchor_idx + 1,
};
order.insert(insert_idx, new_ref.to_owned());
order
}
fn is_not_a_directory_ref_edit_error(err: &gix::reference::edit::Error) -> bool {
matches!(
err,
gix::reference::edit::Error::FileTransactionPrepare(
gix::refs::file::transaction::prepare::Error::Io(io_err)
) if io_err.kind() == std::io::ErrorKind::NotADirectory
)
}
fn find_colliding_ref_ancestor(View on GitHub (pinned to caf1f223d3)
Solutions
- No caller-side action — the invariant holds in the shipped code
- For maintainers editing this function: keep the push-then-retain order, and assert `anchor != new_ref` upstream if renaming becomes possible
- Add a unit test covering anchor==new_ref if you touch this logic
Example fix
// before (fragile after edits)
order.retain(|branch| branch.as_ref() != new_ref);
let anchor_idx = order.iter().position(|b| b.as_ref() == anchor).expect("...");
// after: make the invariant explicit
if anchor == new_ref { return order; }
let Some(anchor_idx) = order.iter().position(|b| b.as_ref() == anchor) else {
bail!("anchor {anchor} disappeared from branch order");
}; Defensive patterns
Strategy: validation
Prevention
- No input can trigger this in shipped code
- Maintainers: keep push-anchor-then-retain order intact and test the anchor==new_ref case when editing insert_into_order
When it happens
Trigger: Calling branch-order insertion where `anchor == new_ref` combined with a code change that makes retain drop more than `new_ref`; refactors of `insert_into_order` that reorder the push/retain/position steps; only reachable through a modified implementation, not through any input to the shipped one.
Common situations: Contributors editing this helper (e.g. adding rename handling that also removes the old anchor name); downstream forks with custom order-filtering; none for stock GitButler users.
Related errors
- checked all selectors are present
- validated non-empty squash commit list
- AtSegment anchor always has oldest commit resolved
- a committed transaction always materializes a workspace
- if it was found before it will be found as id
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/a4e75ed6d5b8ac8a.
Report an issue: GitHub.