gitbutlerapp/gitbutler · error

cannot restore a snapshot without checkout identity outside

Error message

cannot restore a snapshot without checkout identity outside the workspace branch

What it means

Legacy (unmanaged) snapshots record no checkout identity, only a workspace commit. Restoring them is only safe while HEAD sits on the GitButler workspace branch — otherwise GitButler would overwrite the checkout of an unrelated branch. This bail is that guard: it fires when an identity-less snapshot is restored while HEAD is on some other branch (detached HEAD fails earlier with a different context error).

Source

Thrown at crates/gitbutler-oplog/src/oplog.rs:1043

        {
            bail!("snapshot checkout and workspace commits disagree");
        }
    }
    // Managed snapshots already identify their checkout through the workspace commit entry.
    let restored_checkout = restored_checkout.or_else(|| {
        restored_workspace_commit.map(|commit_id| SnapshotCheckout {
            ref_name: workspace_ref.to_owned(),
            commit_id,
        })
    });

    let head = repo.head()?;
    let head_ref = head
        .referent_name()
        .context("We will not change a worktree in detached HEAD state")?;
    // Snapshots with neither checkout identity nor a workspace commit retain the old guard.
    if restored_checkout.is_none() && head_ref != workspace_ref {
        bail!("cannot restore a snapshot without checkout identity outside the workspace branch");
    }

    let gix_repo = ctx.clone_repo_for_merging()?;
    let workdir_tree_id = get_workdir_tree(None, snapshot_commit_id, &gix_repo)?;

    // Check out the snapshot's worktree while HEAD still points at the pre-restore commit:
    // safe_checkout diffs from `before_restore_snapshot_workdir_tree_id`, so
    // the workspace ref is repointed only afterwards (below).
    but_core::worktree::safe_checkout_from_head(
        workdir_tree_id,
        &gix_repo,
        but_core::worktree::checkout::Options {
            // `workdir_tree_id` is the restored snapshot's full workdir tree, so it already
            // contains the uncommitted changes captured at that point. `safe_checkout_from_head`
            // otherwise re-applies the current uncommitted changes on top via a 3-way merge whose
            // base is `HEAD^{tree}`, which makes those changes collide with the identical ones
            // already in the destination. Using the pre-restore workdir tree as the merge base
            // means the current uncommitted changes equal the base and cancel out, so the restored

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Check out the GitButler workspace branch first, then retry the restore
  2. Or restore a newer (managed) snapshot that carries checkout identity
  3. Avoid checking out non-workspace branches directly in GitButler-managed repositories

Example fix

# before: HEAD is on 'main' while restoring an old snapshot -> bail
git checkout gitbutler/workspace
# after: restore succeeds from the workspace branch
# (retry the restore in GitButler)
Defensive patterns

Strategy: validation

Validate before calling

let head_ref = repo
    .head()?
    .referent_name()
    .context("detached HEAD")?;
if snapshot_checkout(&snapshot_tree, &repo)?.is_none() && head_ref != *workspace_ref {
    return Err(anyhow::anyhow!("switch to the workspace branch before restoring this legacy snapshot"));
}

Type guard

fn can_restore_without_checkout_identity(snapshot_has_checkout: bool, head_ref: Option<&gix::refs::FullName>, workspace_ref: &gix::refs::FullName) -> bool {
    snapshot_has_checkout || head_ref == Some(workspace_ref)
}

Try / catch

match oplog.restore(snapshot_id) {
    Err(err) if err.to_string().contains("without checkout identity") => {
        // check out the workspace branch, then retry
    }
    other => other,
}

Prevention

When it happens

Trigger: Restoring an old snapshot with no `checkout/ref` record while repository HEAD points at any branch other than the workspace ref — e.g. the user checked out a real branch with plain git.

Common situations: Long undo histories spanning the snapshot format change; users checking out regular branches in the underlying repo and then using GitButler undo.

Related errors


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