gitbutlerapp/gitbutler · error

Cannot reorder '{subject_branch_name}' in single-branch mode

Error message

Cannot reorder '{subject_branch_name}' in single-branch mode without branch order metadata

What it means

In single-branch mode, reordering empty branches is persisted through branch-stack-order metadata. The guard fires when the RefMetadata implementation reports can_persist_branch_stack_order() == false, which for the legacy backend means the metadata database handle is absent or opened read-only. Without a writable order store the reorder would be silently lost, so the operation is refused.

Source

Thrown at crates/but-workspace/src/branch/move_branch.rs:255

        let (source_stack, subject_segment) = &source;
        let (destination_stack, _) = &destination;
        let entrypoint = workspace.ref_name().map(ToOwned::to_owned);
        // A branch that owns commits can only be reordered within its current stack in
        // single-branch mode. Moving it across stacks would change commit ownership and needs a
        // real rebase.
        if !subject_segment.commits.is_empty() && !same_stack(source_stack, destination_stack) {
            bail!("Moving a non-empty branch in single-branch mode is not yet supported");
        }
        // Reordering same-target empty refs only changes which empty segment is displayed first.
        // If their targets differ, however, the subject crosses commit-owning segments and its ref
        // must move with it or those commits would be projected as belonging to the empty branch.
        let move_requires_graph_update = !subject_segment.commits.is_empty()
            || successful_rebase.reference_target(subject_branch_name)?
                != successful_rebase.reference_target(target_branch_name)?;
        let existing_order = {
            let (_repo, meta) = successful_rebase.repo_and_meta_mut();
            if !meta.can_persist_branch_stack_order() {
                bail!(
                    "Cannot reorder '{subject_branch_name}' in single-branch mode without branch order metadata"
                );
            }
            // Reorder against the existing chain. A movable subject is always part of `branch_order`
            // (that's what makes it a projected segment), so the first lookup normally succeeds. The
            // target and entrypoint lookups are defensive fallbacks so that, should the projection ever
            // surface a segment that isn't tracked yet, we extend the real chain instead of clobbering
            // it down to just the moved refs.
            match meta.branch_stack_order(subject_branch_name)? {
                Some(order) => order,
                None => match meta.branch_stack_order(target_branch_name)? {
                    Some(order) => order,
                    None => entrypoint
                        .as_ref()
                        .map(|entrypoint| meta.branch_stack_order(entrypoint.as_ref()))
                        .transpose()?
                        .flatten()
                        .unwrap_or_else(|| stack_branch_order(source_stack)),

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Run the operation with a writable metadata backend (open the project database normally, not read-only).
  2. If you implement RefMetadata yourself, implement the branch_stack_order family and return true from can_persist_branch_stack_order().
  3. Skip reorder UI actions when the metadata handle is read-only.

Example fix

// before
let outcome = move_branch(editor, &subject, &target)?;

// after
let (_, meta) = successful_rebase.repo_and_meta_mut();
ensure!(meta.can_persist_branch_stack_order(), "metadata read-only; reorder disabled");
let outcome = move_branch(editor, &subject, &target)?;
Defensive patterns

Strategy: validation

Validate before calling

let (_repo, meta) = successful_rebase.repo_and_meta_mut();
if !meta.can_persist_branch_stack_order() {
    // read-only or missing metadata backend: skip reorder actions
    return Ok(disabled_outcome());
}

Try / catch

match move_branch(editor, &subject, &target) {
    Err(err) if err.to_string().contains("without branch order metadata") => {
        Err(anyhow!("reorder requires a writable metadata store; reopen the project database"))
    }
    other => other,
}

Prevention

When it happens

Trigger: Calling the ad-hoc reorder path while meta is a read-only or database-less RefMetadata (but-meta legacy: db.is_none() || read_only), e.g. inspection tools, dry-run traversals, or tests with in-memory metadata.

Common situations: Opening a project in a read-only/preview context and then attempting a reorder; custom RefMetadata implementations that never implemented order persistence; unit tests using stub metadata hitting production code paths.

Related errors


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