Q33 of 40 · Git

A teammate accidentally force-pushed to `main` and lost 3 commits that were never backed up. How do you recover them?

GitSeniorgitrecoveryforce-pushreflogbranch-protection

Short answer

Short answer: Check the teammate's local `git reflog` — the lost commits are still in their `.git/objects` for 30 days. Copy the SHAs, push the commits to a recovery branch, then reset `main` back to the correct state using `--force-with-lease`.

Detail

Force-pushing rewrites the remote's branch pointer but does not delete objects from any repository that has already fetched them. The objects are still in the .git/objects directory of every clone that pulled those commits, and they are visible in git reflog until git gc prunes them (default 90 days for reachable objects, 30 days for unreachable ones).

Recovery steps:

  1. Have the teammate run git reflog on their local clone to find the SHA of the last commit before the force-push.
  2. Verify the commit is still present: git cat-file -t <sha>.
  3. Push those commits to a recovery branch: git push origin <sha>:refs/heads/recovery/lost-commits.
  4. Inspect the recovery branch in GitHub/GitLab to confirm all three commits are there.
  5. Reset main to the correct commit: temporarily disable branch protection, then git push --force-with-lease origin <correct-sha>:refs/heads/main.
  6. Re-enable branch protection.

Preventing recurrence: enable branch protection rules that block force-pushes to main (GitHub: Settings → Branches → Add rule → uncheck "Allow force pushes"). In Git 2.28+, you can also protect branches server-side with receive.denyNonFastForwards.

If no local clone has the commits: check CI run artefacts or any server that checked out the branch. As a last resort, git fsck --lost-found recovers dangling objects into .git/lost-found/.

// EXAMPLE

# --- On the teammate's machine ---
git reflog
# HEAD@{0}: reset: moving to origin/main   ← the bad force-push
# HEAD@{1}: commit: fix: correct retry header    ← these 3 are lost on remote
# HEAD@{2}: commit: feat: add payment retry
# HEAD@{3}: commit: test: OrderRetryTest

LAST_GOOD_SHA=$(git rev-parse HEAD@{1})

# Verify the object is intact
git cat-file -t $LAST_GOOD_SHA  # → commit

# Push the lost commits to a recovery branch
git push origin $LAST_GOOD_SHA:refs/heads/recovery/lost-commits

# --- On the maintainer's machine ---
git fetch origin
git log origin/recovery/lost-commits --oneline -5

# Confirm the 3 commits are present, then restore main
# (temporarily disable branch protection in GitHub UI first)
git push --force-with-lease origin origin/recovery/lost-commits:refs/heads/main

# Clean up
git push origin --delete recovery/lost-commits

// WHAT INTERVIEWERS LOOK FOR

Knowing that force-push doesn't delete objects from other clones. Using reflog to find the lost SHAs. The recovery branch pattern (safe, reviewable). Knowing to disable/re-enable branch protection. Mentioning `receive.denyNonFastForwards` for prevention.