Recovering from an accidental force-push Jump to heading
It usually happens with a stale local branch: someone runs git push --force to update their feature branch and, because of a misconfigured push default or a typo, overwrites main with a version from last week. Every commit merged since then vanishes from the remote. The good news is that commits are almost never truly lost. They still exist in the reflogs of the people who had them, in CI checkouts, in the forge’s own records, and often in the remote repository’s object store until garbage collection runs. Recovery is a matter of finding the right tip quickly and putting it back without making things worse. This page gives the order to search in and the safe way to restore, within history rewriting and recovery.
When to use this approach Jump to heading
- A shared branch’s history suddenly lost commits after someone’s push.
- CI reports “branch was force-pushed” or pull requests show unexpected diffs.
git pulltells you your branch and the remote have diverged when you have not changed anything.- For a deliberate rewrite that went wrong, the same steps apply, along with the backup advice in rewriting history with git filter-repo.
Step 1 — Stop further pushes and fetches that would spread the damage Jump to heading
The first priority is to stop anyone pulling the bad state and rebasing their work onto it, or force-pushing again. Announce it, and if you can, temporarily lock the branch.
# Lock the branch on GitHub (classic protection shown) while you recover
gh api -X PUT "repos/$OWNER/$REPO/branches/main/protection" --input - <<'EOF'
{ "required_status_checks": null, "enforce_admins": true,
"required_pull_request_reviews": null, "restrictions": { "users": [], "teams": [] },
"lock_branch": true }
EOF People who have not fetched since the force-push are holding the good history. Ask them not to fetch until you have restored the branch.
Step 2 — Find the lost tip Jump to heading
Search the places most likely to have it, starting with the cheapest. Any one hit is enough.
# 1. Your own remote-tracking reflog — the tip of origin/main before your last fetch
git reflog show --date=iso origin/main | head -5
# 2. The forge records every push with before and after commits
gh api "repos/$OWNER/$REPO/activity?ref=refs/heads/main&activity_type=force_push" \
--jq '.[0] | {before, after, actor: .actor.login, timestamp}' # 3. Any teammate who has not fetched since
git rev-parse origin/main # run on their machine; send you the SHA
# Verification: the candidate contains the commits that went missing
git fetch origin <candidate-sha> 2>/dev/null || true
git log --oneline <candidate-sha> -10 Fetching a commit by hash works if it still exists on the server, which it usually does for a while after a force-push.
Step 3 — Compare and decide what the branch should be Jump to heading
Do not blindly restore the old tip. Someone may have pushed legitimate commits after the bad force-push, and those need to survive too.
good=<candidate-sha>
bad=$(git rev-parse origin/main)
git log --oneline "$bad..$good" | wc -l # commits the force-push removed
git log --oneline "$good..$bad" # commits only on the current tip — anything legitimate? Step 4 — Restore with --force-with-lease Jump to heading
Restoring is itself a force-push, so do it in the safest form. --force-with-lease with an explicit expected value refuses if the branch has moved since you looked, which prevents overwriting a push that arrived during the recovery.
# Simple case: put the old tip back
git push --force-with-lease=main:"$bad" origin "$good":refs/heads/main
# With legitimate commits made on top of the bad state
git switch -c restore "$good"
git cherry-pick <legit-sha-1> <legit-sha-2>
git push --force-with-lease=main:"$bad" origin restore:refs/heads/main ⚠️ SAFETY WARNING: Restoring a branch is another history change for anyone who fetched the bad state. People who pulled in the meantime must reset their local branch to the restored tip —
git fetch && git reset --keep origin/main— and anyone who based new work on the bad state should rebase it onto the restored branch rather than merging, or the bad history comes back.
# Verification: the remote now has the full history again
git fetch origin && git log --oneline origin/main | head -5
git merge-base --is-ancestor "$good" origin/main && echo "old tip restored" Step 5 — Prevent the next one Jump to heading
Most accidental force-pushes to shared branches are preventable with configuration that costs nothing.
# Server side: forbid force-pushes to protected branches (and unlock the branch)
gh api -X PUT "repos/$OWNER/$REPO/branches/main/protection" --input protection.json # allow_force_pushes: false
# Client side: safer defaults for everyone
git config --global push.default simple # push only the current branch to its upstream
git config --global alias.pushf 'push --force-with-lease --force-if-includes' --force-if-includes (Git 2.30+) adds a second check: the force-push is refused unless your local branch has incorporated the remote tip you are about to overwrite. It catches the “stale branch” case that --force-with-lease alone can miss when a background fetch updated the lease. Server-side rejection is covered in blocking force-pushes with a pre-receive hook.
Validation checklist Jump to heading
Frequently Asked Questions Jump to heading
How long do force-pushed commits stay on the server? Jump to heading
Until the server garbage-collects unreachable objects, which hosted forges do on their own schedule — typically days to weeks. Do not rely on it; recover as soon as you notice, and if the server copy is gone, a clone or CI workspace will still have it.
What if nobody has the old commits anywhere? Jump to heading
Check CI caches, deployment artefacts with embedded commit IDs, and any mirrors. Hosted forges can sometimes restore from their own backups through support. A preserving mirror, as in mirroring third-party repositories for resilience, makes this question irrelevant for repositories you depend on.
Should the person who force-pushed do the recovery? Jump to heading
Whoever is calmest and has the right access. The person who made the mistake usually has the best idea of what happened and is a good second pair of eyes, but recovery under pressure benefits from someone who is not also processing the mistake.
Related Jump to heading
- History Rewriting & Recovery — the parent topic.
- Recovering Lost Commits with git reflog — the local-reflog techniques used in step 2.
- Recovering a Deleted Branch — the closely related case of a branch removed entirely.
- Designing Branch Protection Rulesets for an Org — blocking force-pushes everywhere at once.