{"record":{"id":"2e812ac93344efc1","repo":"gitbutlerapp/gitbutler","slug":"gitforcepushprotection","errorCode":"GitForcePushProtection","errorMessage":"The force push was blocked because the remote branch contains commits that would be overwritten.\n\n{e}","messagePattern":"The force push was blocked because the remote branch contains commits that would be overwritten\\.\n\n(.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/gitbutler-git/src/context.rs","lineNumber":290,"sourceCode":"    })\n    .join()\n    .map_err(|panic| {\n        let reason = if let Some(message) = panic.downcast_ref::<String>() {\n            message.clone()\n        } else if let Some(message) = panic.downcast_ref::<&'static str>() {\n            (*message).to_owned()\n        } else {\n            \"unknown panic payload\".to_owned()\n        };\n\n        anyhow!(\"git push worker thread panicked: {reason}\").context(\n            but_error::Context::new(\"git push failed unexpectedly\").with_code(Code::Unknown),\n        )\n    })??;\n    match result {\n            Ok(stderr) => Ok(stderr),\n            Err(err) => match err {\n                crate::Error::ForcePushProtection(e) => Err(anyhow!(\n                    \"The force push was blocked because the remote branch contains commits that would be overwritten.\\n\\n{e}\"\n                )\n                .context(Code::GitForcePushProtection)),\n                crate::Error::GerritNoNewChanges(_) => {\n                    // Treat \"no new changes\" as success for Gerrit.\n                    Ok(String::new())\n                }\n                crate::Error::NonFastForward(_) => Err(err).context(Code::GitNonFastForward),\n                _ => Err(map_needs_authorization(err)),\n            },\n        }\n}\n\nfn serialize_branch_to_remote<S>(\n    branch_to_remote: &[(String, gix::refs::FullName, String)],\n    serializer: S,\n) -> std::result::Result<S::Ok, S::Error>\nwhere","sourceCodeStart":272,"sourceCodeEnd":308,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/gitbutler-git/src/context.rs#L272-L308","documentation":"Returned from push_with_askpass when the push was initiated with force_push_protection enabled and gix reported ForcePushProtection: the remote ref points at commits that the local push would overwrite (the remote branch contains commits not present locally, i.e. the update is not a strict fast-forward of what was expected). It carries Code::GitForcePushProtection so the UI/API can offer an explicit 'force anyway' action. GerritNoNewChanges and NonFastForward are handled as separate cases right below it.","triggerScenarios":"Pushing a virtual branch/workspace over refs/remotes origin state that has moved ahead (someone else pushed); force_push_protection=true with stale local remote-tracking refs because no fetch ran recently; rebasing/rewriting a stack and pushing with force while protection is on; concurrent pushes from another machine or CI.","commonSituations":"Two developers or a developer plus CI pushing to the same branch; pushing after history rewrite (rebase, amend) with the safety check enabled; the app's remote-tracking ref is stale relative to the actual server state.","solutions":["Fetch and integrate the remote commits first (rebase/merge your stack onto the updated origin branch), then push again","If overwriting is intended, push with the force flag while consciously disabling force_push_protection for that call (the UI's 'force push' confirmation does exactly this)","Verify what would be lost: `git log --oneline <local>..<remote-branch>` before forcing","If CI noise causes this, coordinate branch ownership or enable push --force-with-lease semantics"],"exampleFix":"// before\nctx.push(head, target_ref, /* with_force */ true, /* force_push_protection */ true, None, askpass, vec![])?;\n\n// after: integrate remote state first, then the protected push succeeds\nctx.fetch(&remote_name, None)?;\n// ... rebase the stack onto the updated origin branch ...\nctx.push(head, target_ref, true, /* protection now passes */ true, None, askpass, vec![])?;","handlingStrategy":"validation","validationCode":"// pre-check divergence before a protected force push\nctx.fetch(&remote_name, None)?;\nlet remote_ref = format!(\"refs/remotes/{remote_name}/{branch}\");\nlet remote_id = repo.find_reference(&remote_ref)?.peel_to_commit()?.id;\nlet base = repo.merge_base(remote_id, local_head)?;\nif base != Some(remote_id) {\n    // remote has commits local does not contain: the protection WILL fire\n    anyhow::bail!(\"remote {remote_ref} moved ahead — rebase/merge first or force without protection\");\n}","typeGuard":null,"tryCatchPattern":"use but_error::Code;\nmatch ctx.push(head, target, true, true, None, askpass, vec![]) {\n    Ok(stderr) => Ok(stderr),\n    Err(err) if err.downcast_ref::<Code>() == Some(&Code::GitForcePushProtection)\n        || err.to_string().contains(\"force push was blocked\") =>\n    {\n        // surface to the user: integrate remote changes, or retry with protection off\n        offer_force_push_confirmation()\n    }\n    Err(err) => Err(err),\n}","preventionTips":["Fetch before every push so protection decisions use current remote state","Rebase/amend locally and integrate others' commits before pushing rewritten history","Reserve force_push_protection=false for explicit user-confirmed force pushes only","Check `git log --oneline local..remote` to see exactly what a force would discard"],"tags":["git","push","force-push","non-fast-forward","gitbutler"],"backgroundTag":"force-push-protection","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}