A teammate force-pushed the branch you were on — how do you recover your commits?
answer
- do not merge two parallel histories
- you need the old upstream tip
- remote-tracking refs have a reflog too
- replay only your own commits
- a backup ref costs nothing
basics
~20 sFetch, then find the branch's old upstream tip in your reflog and replay only your own commits onto the new tip with git rebase --onto. Never merge or pull, because that reconciles two parallel histories and drags the discarded commits back.
solid answer
~40 sFetch first, and do not pull. Your local branch still descends from commits that no longer exist upstream, so a merge reconciles two parallel histories: you get duplicated changes and you reintroduce exactly the commits the rewrite removed. Instead identify three points — the new upstream tip (`origin/<branch>`), the old upstream tip you were based on (`git reflog show origin/<branch>` lists previous values, and `origin/<branch>@{1}` names the one before the last update), and your own commits on top of it. Then replay just yours: `git rebase --onto origin/<branch> <old-upstream-tip> <your-branch>`. If you had nothing unpushed, `git reset --hard origin/<branch>` is simpler and sufficient. Verify with `git log --oneline origin/<branch>..HEAD` that only your commits sit on top, and use `git range-diff` to confirm the rewrite preserved content.
code
bash · 4 linesgit fetch origin
git reflog show origin/feature/api # find the pre-rewrite tip
git log --oneline origin/feature/api@{1}..feature/api # your commits
git rebase --onto origin/feature/api origin/feature/api@{1} feature/apigo deeper
Recall that after an upstream rewrite you should not merge, and that your own unpushed commits are still safe locally until you deliberately discard them.
Explain why merging duplicates the series and resurrects removed commits, and show the transplant: old base, new tip, rebase --onto, then verify what sits above upstream.
Show the full drill under pressure — safety ref, reflog on the remote-tracking ref, targeted replay, verification — and the follow-up conversation about whether that branch should be rewritable at all.
Own the cost model: a shared-branch rewrite is N engineers times a manual recovery plus the risk that one of them merges instead. Decide which branches are protected and how that is enforced.
## Why the obvious move is the wrong one After an upstream rewrite, the remote branch contains new commits carrying the same changes with different SHAs. Your local branch still contains the originals. Git has no idea these are "the same" — to it they are two independent lines of development sharing an old ancestor. So `git pull` (a merge) produces a merge of both, which means every rewritten change appears twice and every commit the rewrite deliberately dropped comes back. That is how a purge gets silently undone, and it is the main thing an interviewer is listening for. ## Establish three reference points 1. **The new upstream tip.** `git fetch origin`, then `origin/<branch>` is where everyone else now is. 2. **The old upstream tip.** Your remote-tracking ref has a reflog: `git reflog show origin/<branch>` lists its previous values, and `origin/<branch>@{1}` refers to the value before the most recent update. That commit is the base your work was built on. 3. **Your own commits.** `git log --oneline <old-upstream-tip>..<your-branch>` lists exactly the commits you added on top of the old base — the ones worth saving. If you recently pushed or reset the branch yourself, `ORIG_HEAD` and `git reflog` on the local branch give further anchors. ## The three recovery paths **Nothing unpushed.** Point your branch at the new history: `git reset --hard origin/<branch>`. This throws away local commits, so confirm step 3 returned nothing first. **Your own commits on top.** Transplant them: `git rebase --onto origin/<branch> <old-upstream-tip> <your-branch>` This says: take the commits after `<old-upstream-tip>` on `<your-branch>` and replay them onto the new tip. Conflicts are resolved per commit as usual. Because you named the old base explicitly, none of the rewritten upstream commits are replayed — only yours. **Uncertain scope.** Create a safety branch at your current tip first (`git branch backup/pre-recovery`), so any mistake during recovery is one reset away from undone. Then recover, then delete the backup once satisfied. ## Verifying - `git log --oneline origin/<branch>..HEAD` should list exactly your commits and nothing else. Anything unexpected means the base you chose was wrong. - `git status` should report the branch ahead by that many commits and behind by zero. - `git range-diff` compares two versions of a commit series and shows whether the rewrite changed content or only SHAs — useful when you want assurance that the rewrite was a clean replay. ## Recovering work you thought was lost If you already merged and made a mess, nothing is gone: `git reflog` on your branch lists every position it held, so `git reset --hard <branch>@{n}` returns you to the state before the bad merge. Objects survive until they are unreachable and pruned, so acting promptly matters — but the reflog is why this situation is almost always recoverable on a machine that had the branch. ## The organisational half The recovery is mechanical; the cost is that every person on the branch pays it. That is why an upstream rewrite deserves an announcement with the exact commands, not a shrug. If the branch is long-lived and shared, the conclusion to draw out loud is that it should not have been rewritten at all — and that the receiving repository can be configured to refuse non-fast-forward updates to it so the question does not arise again.
- What exactly goes wrong if you just run git pull instead?Git treats the pre-rewrite and post-rewrite commits as unrelated lines sharing an old ancestor and merges them. Every rewritten change appears twice, and any commit the rewrite deliberately removed — a secret, a huge blob — is reachable again from the merge.
- How do you find the pre-rewrite upstream tip if you have already fetched?Remote-tracking refs keep a reflog: `git reflog show origin/<branch>` lists previous values and `origin/<branch>@{1}` names the value before the latest update. Your own branch's reflog and ORIG_HEAD give additional anchors if you had reset or pulled recently.
- When is git reset --hard origin/<branch> the right answer?When `git log --oneline <old-base>..HEAD` is empty — you had nothing of your own on top. Then your branch was only a copy of upstream and pointing it at the new tip loses nothing. Check first; the command discards local commits without asking.
saying these in an interview costs you the question
- Pulls or merges to resync after the rewrite
- Assumes the local commits are already lost
- Resets hard without checking for unpushed work
- Cannot locate the pre-rewrite upstream tip
- Thinks cherry-picking each commit by hand is the only option