gitbutlerapp/gitbutler · error

Moving a non-empty branch in single-branch mode is not yet s

Error message

Moving a non-empty branch in single-branch mode is not yet supported

What it means

In an ad-hoc (single-branch mode) workspace, moving a branch that owns commits across stacks would change commit ownership and requires a real rebase, which this code path deliberately does not perform. The guard fires only when the subject segment is non-empty and source and destination are different stacks. Reordering within the same stack remains allowed.

Source

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

    /// The reordered chain is returned in [`Outcome::branch_stack_order`] for the caller to persist
    /// (via [`RefMetadata::set_branch_stack_order`]) rather than being written here, so callers can
    /// skip persistence for dry-run previews.
    fn move_branch_in_single_branch_mode<'ws, 'meta, M: RefMetadata>(
        mut successful_rebase: SuccessfulRebase<'ws, 'meta, M>,
        workspace: but_graph::Workspace,
        source: WorkspaceSegmentContext,
        destination: WorkspaceSegmentContext,
        subject_branch_name: &FullNameRef,
        target_branch_name: &FullNameRef,
    ) -> anyhow::Result<Outcome<'ws, 'meta, M>> {
        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

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Switch the project to a managed workspace (workspace commit) where cross-stack moves are supported.
  2. Move the commits out of the subject branch first (or integrate them), then move the now-empty branch.
  3. Keep the move within the same stack if only reordering was intended.
Defensive patterns

Strategy: validation

Validate before calling

let (source_stack, subject_segment) = source;
let (destination_stack, _) = destination;
if !subject_segment.commits.is_empty()
    && !same_stack(source_stack, destination_stack)
{
    // cross-stack move of a commit-owning branch: unsupported in single-branch mode
    return show_mode_notice();
}

Try / catch

match move_branch(editor, &subject, &target) {
    Err(err) if err.to_string().contains("non-empty branch in single-branch mode") => {
        ui::info("Switch to a managed workspace to move branches across stacks");
        Ok(default_outcome())
    }
    other => other,
}

Prevention

When it happens

Trigger: Calling move_branch on an ad-hoc workspace where subject_segment.commits is non-empty and same_stack(source_stack, destination_stack) is false — i.e., dragging a commit-owning virtual branch onto a branch in another stack.

Common situations: Users of the single-branch (Lite/ad-hoc) mode trying to reorganize branches across stacks in the UI; tests exercising cross-stack moves against ad-hoc fixtures.

Related errors


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