StackDevLife
Cover image for: We Ran git rebase on a Shared Branch and Lost Three Days of Work
Back to Blog

We Ran git rebase on a Shared Branch and Lost Three Days of Work

It was a Thursday afternoon. One git rebase followed by git push --force wiped three days of work across four developers. Here's exactly what happened, how we recovered every commit, and the rules we put in place so it never happens again.

SB

Sandeep Bansod

April 12, 20268 min read
Share:

It was a Thursday afternoon. Developer 1 had been working on a feature for four days. Clean commits, good code, reviewed and approved. He was about to open the final PR when he ran one command.

Bash
git rebase main

Then git push --force.

By the time anyone realized what had happened, four developers had lost their local branches. Two PRs were silently broken. One developer's two days of work had simply vanished from the remote. The CI pipeline was green — because it was running against old commits.

We spent three days untangling it. Here's exactly what happened, why it happens, and how we recovered — so you don't have to learn this the same way we did.

What git rebase Actually Does to History

Most explanations of rebase start with diagrams. Let's start with the thing that matters: rebase rewrites commits. It doesn't move them. It creates brand new commits with new hashes that contain the same changes.

Before rebase, your feature branch's commits have specific SHA hashes. After you rebase onto main, those same commits become completely different hashes. Different objects in Git's database. As far as Git is concerned, those are completely different commits.

Bash
# Before rebase
git log --oneline feature/payments
# d4e5f6  Add payment validation
# a1b2c3  Add payment model
# 9g8h7i  Initial setup (base commit on main)

# After git rebase main
git log --oneline feature/payments
# m1n2o3  Add payment validation   ← new hash, same change
# x7y8z9  Add payment model        ← new hash, same change
# 3k4l5m  Latest commit on main

This is fine when the branch only exists on your machine. The problem starts the moment that branch lives on the remote and someone else has based work on it.

How the Disaster Unfolded

Here's what our team's branch situation looked like that Thursday:

Bash
main
└── feature/payments Developer 1's branch, pushed to remote 4 days ago
    └── feature/payments-ui  ← Developer 2's branch, based off Developer 1's

Developer 2 had been building the UI layer on top of Developer 1's branch for two days. Her local branch pointed to Developer 1's old commits. Then Developer 1 rebased and force-pushed.

The remote feature/payments now had completely different commits. Developer 2's branch still pointed to the old ones — which now existed nowhere except her local machine and Git's reflog.

When Developer 2 ran git pull, Git saw her branch had diverged from remote. She assumed it was a normal conflict. She merged. Git dutifully merged two versions of the same code — the original and the rebased copy — creating a mess of duplicate changes, phantom conflicts, and commits that referenced parents that no longer existed on the remote.

The Error That Should Have Stopped It

The actual signal was there. When Developer 1 tried to push after rebasing, Git rejected it:

Bash
git push origin feature/payments
# error: failed to push some refs to 'origin/github.com/team/repo'
# hint: Updates were rejected because the tip of your current branch is behind
# hint: its remote counterpart.

Git was saying: the remote has commits you don't have. Your histories have diverged. Stop.

Instead of reading this as a warning, Developer 1 added --force to make it go away.

Bash
git push --force origin feature/payments
# Everything up to date.  ← the most dangerous success message in Git

How We Recovered

Recovery took three steps. The order matters — don't skip step one.

Step 1: Stop everyone from pulling or pushing immediately

The moment you realize a shared branch has been force-pushed, message the team. Anyone who pulls will compound the damage. Anyone who pushes will overwrite the recovery.

Bash
# Message the team immediately:
# "Do NOT pull feature/payments. Do NOT push anything to it.
#  Stay on your current branch. We're recovering."

Step 2: Find the lost commits with git reflog

git reflog is Git's black box recorder. It tracks every position HEAD has been at, including commits that are no longer reachable from any branch. On Developer 1's machine — the original commits still existed in reflog.

Bash
git reflog
# m1n2o3 HEAD@{0}: rebase finished: returning to refs/heads/feature/payments
# x7y8z9 HEAD@{1}: rebase: Add payment model
# d4e5f6 HEAD@{2}: commit: Add payment validation   ← original commit
# a1b2c3 HEAD@{3}: commit: Add payment model        ← original commit
# 9g8h7i HEAD@{4}: checkout: moving from main to feature/payments

# Create a recovery branch at the last good state
git checkout -b feature/payments-recovered d4e5f6

Step 3: Restore the remote branch from the recovery point

Bash
# Force-push the recovered branch back — using --force-with-lease, not --force
git push --force-with-lease origin feature/payments-recovered:feature/payments

# Each affected developer resets their local branch
git fetch origin
git checkout feature/payments
git reset --hard origin/feature/payments

# For Developer 2's branch that was derived from the broken state:
git rebase --onto origin/feature/payments <bad-merge-commit> feature/payments-ui

The Actual Fix: Never Let This Happen Again

The root cause wasn't Developer 1 making a mistake. The root cause was that the repo allowed it.

Fix 1: Use --force-with-lease instead of --force — always

Bash
# Never — overwrites remote with no checks
git push --force origin feature/branch

# Always — checks if remote changed since your last fetch
git push --force-with-lease origin feature/branch

Fix 2: Enable branch protection on shared branches

In GitHub, go to Settings → Branches → Branch protection rules. For any branch that more than one developer works from, enable: Require pull request reviews, Require status checks, and — the one that prevents this entire incident — Restrict force pushes.

Fix 3: The golden rule of rebase

Bash
# The rule:
# Only rebase branches that exist ONLY on your machine.
# Once a branch is pushed and someone else might have it — merge, don't rebase.

# If you want a clean linear history, use squash merge when closing the PR.
# That gives you one clean commit on main without rewriting anyone's history.

Fix 4: Enable git rerere for teams doing a lot of rebasing

Bash
# rerere = Reuse Recorded Resolution
# Caches how you resolved a conflict — auto-resolves the same conflict next time
git config --global rerere.enabled true

The Takeaway

git rebase is not dangerous. Force-pushing a rebased branch to a remote that others are working from is dangerous. The commands are the same. The context is what changes everything.

The rule is simple: if your branch exists only locally, rebase freely. The moment it's pushed - especially if someone else might have pulled it - treat it as immutable. Merge onto it. Squash when you close the PR.

And if it's already happened: breathe, message the team to stop, open reflog, and work backwards. The commits are almost certainly still there.

Git doesn't delete things. It just stops showing them to you.

Was this article helpful?

Let me know if this was useful — it helps me write more content like this.

Found this useful? Share it.

XLinkedInHN
SB

Sandeep Bansod

I'm a Front-End Developer located in India focused on making websites look great, work fast and perform well with a seamless user experience. Over the years I've worked across different areas of digital design, web development, email design, app UI/UX and development.

Related Articles

You might also enjoy these

Stay in the loop

Get articles on technology, health, and lifestyle delivered to your inbox.No spam — unsubscribe anytime.