Force-Push Recovery

Course 3 · Ch 9 · Scenario
Force-Push Recovery Across a Team
When a history rewrite goes wrong with several people already pulled in — coordinating recovery, not just running one command

Course 2, Chapter 9 introduced why force-push is dangerous; Course 3, Chapter 3 covered recovering your own lost commits via the reflog. This chapter combines both into the genuinely hard version: a history rewrite has already gone out to a shared branch, multiple people have already pulled the bad version, and recovery now needs coordination, not just a local fix.

The Scenario

You Rebase a shared feature branch to clean up history, then force-push — not realizing two teammates have it checked out with their own local commits on top.
Teammate A Pulls normally. Git reports a confusing divergence — their local commits don't match anything on the remote anymore, since the rebase gave everything new hashes (Chapter 2).
Teammate B Already had uncommitted work in progress on top of the old history; their next push is rejected outright.
Result Three different local states, all technically diverged from the rewritten remote, and growing confusion about which version is "correct."

Step 1: Stop — Don't Let Anyone Push Yet

The single most important first action is communication, not commands. A quick message — "I just rewrote history on feature/checkout, please don't push anything to it until I say it's safe" — prevents the situation from getting worse while you sort out the recovery plan.

Don't let anyone force-push "to fix it" in a panic
The most common way this incident compounds is someone else force-pushing their own version in an attempt to "fix" the divergence, without coordinating first — now there are two competing rewritten histories instead of one, and untangling that is genuinely harder than the original problem.

Step 2: Establish What Everyone Actually Has

1
Ask everyone NOT to do anything yet, just report their local state
git log --oneline -10 from each person, shared in a team channel — establishes the full picture before any commands run.
2
Identify whose local commits genuinely don't exist anywhere else
Anyone with uncommitted or unpushed local work on top of the OLD history has the only copy of that work — their local repo is now the most valuable thing in this recovery, more so than your reflog.
3
Decide: keep the rewritten history, or restore the original?
If the rebase genuinely improved things and only a few people are affected, recovering everyone onto the NEW history is usually less disruptive than reverting your work and starting the rebase over later.

Step 3: Recovering Each Person onto the New History

Assuming the decision is to keep your rewritten history and bring everyone else's unpushed work onto it — for each affected teammate, individually:

$ # Teammate identifies which of their local commits are genuinely new (not in the old shared history)
$ git log --oneline origin/feature/checkout..HEAD
4f3e2d1 My genuinely new work, not yet pushed anywhere

$ # Fetch your rewritten history
$ git fetch origin

$ # Cherry-pick just their genuinely new commits onto the new history (Course 2, Ch5)
$ git switch -c feature/checkout-recovered origin/feature/checkout
$ git cherry-pick 4f3e2d1

$ # Verify everything looks right, then this becomes their new feature/checkout
Cherry-pick is the right tool here specifically because it doesn't care about shared ancestry
Course 2, Chapter 5 introduced cherry-pick for backporting a single commit between branches with different histories — exactly the situation here. Each teammate's genuinely new work gets individually replayed onto the new, correct base, regardless of how different the rest of the history now looks.

Step 4: If You Need to Recover the OLD History Instead

If the team decides the rewrite itself was the mistake and should be undone entirely — your own reflog (Chapter 3) almost certainly still has the pre-rewrite state, since your local repo's reflog isn't affected by what you pushed.

$ git reflog
b2c1d0e HEAD@{6}: commit: Last commit before the rebase

$ # Restore the branch to its pre-rewrite state...
$ git reset --hard b2c1d0e
$ # ...and push it back, undoing your own force-push
$ git push --force-with-lease origin feature/checkout
If you don't have the old history in your reflog either, check teammates' local copies
Anyone who pulled the old history before your force-push, and hasn't reset or rebased locally since, still has that old history sitting in their own repo, completely intact — even if your own reflog has already expired or been pruned. Their local branch is a full, valid backup of the state you're trying to restore.

Preventing the Next One

  • Branch protection's "block force pushes" (Course 2/3, Chapter 9/6) on genuinely shared branches removes this entire category of incident as a possibility.
  • Reserve interactive rebase and history rewriting (Chapter 2) for branches you're confident are still personal, even if technically pushed somewhere.
  • If a shared branch genuinely needs cleanup, communicate first, every time — the actual technical recovery in this chapter is straightforward; the damage comes from skipping the coordination step.

Chapter 9 Quick Reference

  • First action: communicate, don't compute — stop anyone else from pushing or "fixing it" before you have a plan
  • Establish what everyone has before running any recovery commands — unpushed local work is the most valuable thing in play
  • Cherry-pick each person's genuinely new commits onto the corrected history — works regardless of how different the rest of the history now looks
  • To undo the rewrite entirely: your own reflog (Ch3), or any teammate's untouched local copy if yours has expired
  • git push --force-with-lease (not plain --force) when pushing the restored history back
  • Prevention: branch protection blocking force pushes, reserving rewrites for genuinely personal branches, communicating before any history-rewriting operation on shared work
  • Next chapter (final, Scenario): full repo migration — splitting/merging repos while preserving history, moving hosts, handling LFS/submodules