A git rebase followed by git push --force is two operations, not one: the
rebase rewrites local SHAs and the force push moves the remote ref, which
together orphan every commit that other people had already pulled. Recovery
is possible because the original commits are pinned by reflog entries, but
only until those entries expire and only if every teammate moves first.
← All break/fix scenarios in Git, CI/CD & GitOps
Bad rebase on shared history
Reported symptoms
- ●A branch that has been stable for a week suddenly has new commit SHAs that match no prior history
- ●Three teammates report their feature branches show "Your branch and origin have diverged" after a `git pull`
- ●Teammates cannot `git merge` or `git rebase` because the histories appear "unrelated"
- ●PRs opened against the rebased branch show conflicts that did not exist yesterday
- ●CI for one teammate's branch fails to find any commit reachable from `origin/feature/x`
- ●The rebased branch appears in the reflog of the original engineer with a chain of `rebase -i (pick)` and `rebase -i (finish)` entries
Evidence
- · `git log --oneline --all --graph` shows two parallel lines of commits sharing the same tree objects but with different commit SHAs
- · `git show <original-sha>` reports `commit <original-sha>... Author: ... Date: ...` and `git show <rebased-sha>` reports a different SHA for the same diff
- · `git fsck --unreachable` shows the original (pre-rebase) commits as dangling after the force push, while the post-rebase commits are reachable
- · The remote `refs/heads/feature/y` was moved from `<original-tip>` to `<rebased-tip>` per the GitHub/GitLab ref-change audit log
- · A teammate's `git log feature/x..origin/feature/x` shows three commits that exist only on their local branch
- · `git reflog` on each teammate's checkout shows the same chain of commit objects that are now dangling on the rebaser's machine
- · The engineer remembers running `git rebase -i main` followed by `git push --force`
Diagnosis and resolutionclick to reveal
Root cause
`git rebase` rewrites the local commit chain by replaying each commit on top of a new base, producing new commit SHAs even when the tree and the message are unchanged. Running `git push --force` (rather than `--force-with-lease`, or with no force push at all) then moves the remote ref to the rewritten chain, orphaning the original chain. Every ref that pointed at the original tip — including every teammate's local branch and every CI cache keyed by commit SHA — now points at history the remote considers never to have existed. The teammates' work is not deleted: the original commit objects are preserved by their local reflogs and by the rebaser's reflog until those entries expire, but the remote no longer considers the original chain reachable, so any teammate who already merged their work into the rebased branch has silently lost the merge base.
Remediation
The recovery has two halves, one for the rebased branch and one for every teammate's branch. First, the rebased branch itself: do not attempt to "un-rebase" the remote — restoring the original SHA over the rewritten one would orphan the engineer's work this time. Instead, push the original chain back as a separate ref (for example `feature/y-pre-recover`) using the SHA from `git reflog show feature/y@{1.day.ago}`, then cherry-pick or rebase the rewritten chain onto it. Second, each teammate: `git reflog` to find their last commit SHA before they pulled, `git rebase --onto origin/feature/y <their-pre-rebase-base>` to replay their commits onto the new tip. Coordinate via team chat before any push that rewrites a shared ref, because the second mistake is usually worse than the first.
Verification
Each teammate's branch, after rebase, contains their original commits in the original order, on top of the current `origin/feature/y`. `git log --oneline feature/x..origin/feature/x` is empty on every teammate's checkout — meaning no work is local-only. CI is green on every recovered branch. A `git fsck --unreachable` on the rebaser's clone reports no new dangling objects, meaning the recovery did not itself orphan anything else. The team's postmortem identifies the missing structural control — a branch protection rule that denies force pushes to non-personal branches, or a policy that bans rebasing any ref that has been pulled by anyone other than the author — and that control is in place before the next rebase.
Prevention
Never rewrite a ref that anyone other than the author has pulled. Enforce this structurally: branch protection rules with "Block force pushes" and "Require linear history" turned on for every shared branch, with an exception list that is empty by default. Configure the client to refuse `--force` entirely (alias `git push` to `git push --force-with-lease` in the team `~/.gitconfig` template), and add a `pre-push` hook that compares the local tip against the remote tracking branch and aborts if the push would orphan any commits that the remote currently exposes as reachable. Finally, make the policy human-readable: a shared "rebasing contract" document that names which branches may be rebased (typically only branches whose only commits are the author's own, and only before the branch is shared) and which may not.