{"record":{"id":"37ffcdd4d960326f","repo":"gitbutlerapp/gitbutler","slug":"the-working-tree-differs-from-the-original-commit","errorCode":null,"errorMessage":"The working tree differs from the original commit. A forced abort is necessary.\\nIf you are seeing this message, please report it as a bug. The UI should have prevented this line getting hit.","messagePattern":"The working tree differs from the original commit\\. A forced abort is necessary\\.\\\\nIf you are seeing this message, please report it as a bug\\. The UI should have prevented this line getting hit\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/gitbutler-edit-mode/src/lib.rs","lineNumber":281,"sourceCode":"    for ref_name in [EDIT_BRANCH_REF, UNCOMMITTED_CHANGES_REF] {\n        // Tolerate an already-absent ref, but surface real lookup failures.\n        if let Some(reference) = repo.try_find_reference(ref_name)? {\n            reference\n                .delete()\n                .with_context(|| format!(\"Failed to delete reference {ref_name}\"))?;\n        }\n    }\n    delete_edit_mode_metadata(ctx)?;\n    Ok(())\n}\n\npub(crate) fn abort_and_return_to_workspace(\n    ctx: &Context,\n    force: bool,\n    perm: &mut RepoExclusive,\n) -> Result<()> {\n    if !force && !changes_from_initial(ctx, perm.read_permission())?.is_empty() {\n        bail!(\n            \"The working tree differs from the original commit. A forced abort is necessary.\\nIf you are seeing this message, please report it as a bug. The UI should have prevented this line getting hit.\"\n        );\n    }\n\n    #[expect(deprecated, reason = \"checkout/materialization boundary\")]\n    let repo = &*ctx.git2_repo.get()?;\n\n    // Checkout gitbutler workspace branch\n    repo.set_head(WORKSPACE_BRANCH_REF)\n        .context(\"Failed to set head reference\")?;\n\n    let uncommited_changes = get_uncommitted_changes(&*ctx.repo.get()?)?;\n    let uncommited_changes = repo.find_tree(uncommited_changes.to_git2())?;\n\n    repo.checkout_tree(\n        uncommited_changes.as_object(),\n        Some(CheckoutBuilder::new().force().remove_untracked(true)),\n    )?;","sourceCodeStart":263,"sourceCodeEnd":299,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/gitbutler-edit-mode/src/lib.rs#L263-L299","documentation":"Aborting edit mode via `abort_and_return_to_workspace` refuses to proceed without `force` when the working tree still differs from the commit edit mode started from (`changes_from_initial` returns entries). The GitButler UI is supposed to detect this divergence and get user confirmation before forcing, so the message itself says hitting it is a bug in whatever called the API unguarded.","triggerScenarios":"Calling `abort_and_return_to_workspace(ctx, force = false, ...)` while the working tree has modifications relative to the original edit-mode commit — edits made during edit mode that were never committed or reverted.","commonSituations":"Scripts or tests calling the edit-mode abort API directly; races where UI state says clean but the worktree is dirty; programmatic operating-mode switching that skips the confirmation step.","solutions":["If discarding the divergence is intended, pass `force: true` — exactly what the UI does after user confirmation","Otherwise commit or revert the working-tree changes first, then abort edit mode without force","If reached through the GitButler app UI, report it as a bug — the UI should have prevented it"],"exampleFix":"// before\nabort_and_return_to_workspace(&ctx, /* force */ false, &mut perm)?;\n\n// after: caller decides, like the UI does\nlet force = user_confirmed_discard();\nabort_and_return_to_workspace(&ctx, force, &mut perm)?;","handlingStrategy":"validation","validationCode":"// ask the edit-mode status API whether the worktree diverged before aborting\nif edit_mode_reports_changes(&ctx)? && !user_confirmed_discard {\n    return Err(anyhow::anyhow!(\"working tree diverged; confirm discard to force abort\"));\n}","typeGuard":null,"tryCatchPattern":"match abort_and_return_to_workspace(&ctx, force, &mut perm) {\n    Err(err) if err.to_string().contains(\"forced abort is necessary\") => {\n        // prompt the user, then retry with force = true\n    }\n    other => other,\n}","preventionTips":["Always show a confirmation step when aborting edit mode","Check edit-mode divergence status before calling abort in scripts"],"tags":["rust","gitbutler","edit-mode","abort","dirty-worktree"],"backgroundTag":"dirty-worktree-abort-blocked","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}