Skip to content
ExponaBLOG
GIT & GITHUB FOR PROFESSIONAL DEVELOPERS · DAY 070 / 100

Recovering from Git disasters for Git & GitHub developers

Calm steps for scary situations.

Expona Admin6 min read

Introduction

We've all been there: a misplaced commit, a branch that vanishes, or a force-push that overwrites a colleague's work. Recovering from Git disasters for Git & GitHub developers doesn't require panic—it requires knowing where Git keeps its safety net. Git is remarkably forgiving because it almost never truly deletes anything; understanding how to navigate the reflog, reset operations, and recovery workflows transforms scary moments into routine fixes.

This guide walks through four of the most common Git disasters we encounter in production teams and the calm, methodical steps to recover from each.

Deleted a branch — Find the commit in reflog and recreate the branch

Accidentally deleting a branch feels catastrophic until you remember that Git tracks every ref movement in the reflog. When you delete a branch locally with git branch -D branch-name or see it disappear after a force-push, the commits aren't gone—the pointer is.

Let's say your teammate deleted the feature/payment-integration branch:

bash
git branch -D feature/payment-integration

First, check your local reflog to find where that branch was pointing:

bash
git reflog

You'll see output like:

bash
a1b2c3d (HEAD -> main) commit: Merge pull request #487
f4e5d6c feature/payment-integration: Refactor checkout flow
e7d8c9b feature/payment-integration: Add Stripe webhook handler
d0c1b2a main: Update dependencies

Find the commit hash where feature/payment-integration was last pointing. In this example, that's f4e5d6c. Recreate the branch at that commit:

bash
git branch feature/payment-integration f4e5d6c

Push it back to the remote if needed:

bash
git push origin feature/payment-integration

If the branch was already deleted from the remote, you can still recover it locally from the reflog, but the remote won't have the history. This is why teams should adopt branch protection rules and avoid force-deleting branches without confirmation.

Real-world scenario: A developer runs a cleanup script that deletes all local branches merged into main, including one that was never pushed. Instead of hunting through GitHub's UI or asking for a backup, the reflog has the answer in seconds.

Bad rebase — git reset --hard ORIG_HEAD or use reflog

Rebasing is powerful but risky. A rebase conflict, an accidental abort, or a misunderstood merge strategy can leave your branch in a broken state or with commits vanished.

Imagine you're rebasing feature/analytics onto the latest main:

bash
git rebase main

Halfway through, you realize you made a mistake—perhaps you resolved conflicts incorrectly, or the rebase has tangled your commits. The simplest recovery is:

bash
git rebase --abort

This takes you back to the state before the rebase started. If you've already completed the rebase but regret it, Git sets ORIG_HEAD to point to the commit before the last dangerous operation:

bash
git reset --hard ORIG_HEAD

This works for rebases, merges, resets, and checkouts. It's your immediate undo button.

If you've already moved forward with other commands and ORIG_HEAD is stale, the reflog is your backup:

bash
git reflog

Find the commit hash from before the rebase:

bash
git reset --hard a1b2c3d

Real-world scenario: A developer rebases interactively to clean up commits, accidentally squashes the wrong commits together, and ends up with a broken feature. A single git reset --hard ORIG_HEAD restores the state before the rebase, and they can try again more carefully.

Committed to the wrong branch — Cherry-pick to the right branch, reset the wrong one

You're working in a feature branch, make several commits, and suddenly realize you've been committing to main or a different feature branch entirely. Don't panic; cherry-pick solves this cleanly.

Suppose you've committed directly to main when you meant to work in feature/dark-mode. First, check which commits are in the wrong place:

bash
git log --oneline -5 main

Output:

bash
a1b2c3d Dark mode: Add toggle component
f4e5d6c Dark mode: Update theme provider
e7d8c9b Dark mode: Add CSS variables
d0c1b2a Previous release

The last three commits belong in feature/dark-mode, not main. Check out the correct branch and cherry-pick those commits:

bash
git checkout feature/dark-mode
git cherry-pick d0c1b2a..a1b2c3d

Now go back to main and reset it to before those commits:

bash
git checkout main
git reset --hard d0c1b2a

If you've already pushed the wrong commits to the remote, coordinate with your team before force-pushing:

bash
git push origin main --force-with-lease

The --force-with-lease flag is safer than --force because it checks that the remote hasn't changed since your last fetch.

Real-world scenario: In a busy day, a developer forgets to create a new branch and codes directly on main for two hours. Instead of reverting and re-doing the work, cherry-pick moves those commits to the right place in seconds.

Force-pushed over work — Teammates' local copies or reflog can recover it

Force-pushing is the most dangerous operation in Git. When someone force-pushes to a shared branch, they can overwrite commits that teammates have based work on.

Let's say your teammate force-pushed to develop and overwrote three commits that you were building on:

bash
git push origin develop --force

If you have the branch checked out locally, your local copy still has those commits. First, check your local reflog:

bash
git reflog develop

You'll see the old commits before the force-push:

bash
a1b2c3d develop: Merge fixes for auth module
f4e5d6c develop: Update database schema

Create a new branch from the old commit to preserve it:

bash
git branch develop-backup a1b2c3d

Now fetch the new version from the remote and reset your local branch:

bash
git fetch origin
git reset --hard origin/develop

If you've already pushed your work on top of the old commits, you need to cherry-pick your commits onto the new develop:

bash
git log --oneline develop-backup..HEAD

This shows commits in your current branch not in the backup. Cherry-pick them back in:

bash
git cherry-pick <commit-hash>

If the force-push was a mistake and you need to recover the old version entirely, GitHub's reflog (when enabled) shows recent pushes. On GitHub Enterprise or with administrative access, the reflog can recover force-pushed commits for a limited time (typically 90 days).

Real-world scenario: A deployment pipeline auto-pushes to a shared branch and overwrites a developer's unmerged changes. The developer's local copy has the commits; they cherry-pick their work onto the new branch state and re-create their pull request.

Common mistakes

  • Assuming commits are lost: Git rarely deletes commits. Before panicking, check the reflog. If it's not in the reflog, it's likely already garbage-collected (which takes weeks), but it's worth checking first.
  • Using git reset --hard without understanding what you're resetting: Always verify which commit you're resetting to. A typo here can undo real work. Use git log to double-check the hash before running the reset.
  • Force-pushing without communication: Always let teammates know before force-pushing to a shared branch. Better yet, use --force-with-lease to add a safety check, and establish team practices that make force-pushing rare.
  • Not using branch protection rules: On GitHub, protect main and develop branches to require pull requests and block force-pushes. This prevents many disasters before they happen.
  • Forgetting that reflog is local: Your reflog doesn't recover commits that only existed on a teammate's machine or the remote. This is why local backups and communication matter.

Key takeaways

  • The reflog is Git's safety net; check it first with git reflog or git reflog <branch-name>.
  • Use git reset --hard ORIG_HEAD to undo the last dangerous operation (rebase, merge, reset, checkout).
  • Cherry-pick moves commits from the wrong branch to the right one without re-doing the work.
  • Always use --force-with-lease instead of --force to add a safety check when force-pushing.
  • Coordinate with teammates before dangerous operations, and use branch protection rules to prevent common disasters.
  • Local copies of branches preserve commits even after remote force-pushes; create a backup branch before resetting.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles