DevOps
Someone Force-Pushed Over main. Here Is the Reflog Nobody Knows They Have.
James Joyner DEV Community
3 views
A colleague pushed a fix to main. Ten minutes later you amended a commit message and ran git push --force. Their commit is no longer reachable from any branch on the server.
This is the one Git command that can destroy other people's work as they see it. It is also almost always recoverable, from a place most people do not know exists. Here is the whole thing, reproduced in a disposable repository so you can run it yourself — every command below was run and the output is real.
Set the scene
A bare repository stands in for the server, plus two clones: yours and the colleague's.
mkdir -p /tmp/lab-fp && cd /tmp/lab-fp
git init -q --bare -b main remote.git
git init -q -b main you && cd you
git config user.email "lab@example.com" && git config user.name "Lab User"
git remote add origin ../remote.git
echo "# App" > README.md && git add . && git commit -q -m "Initial commit"
for i in 1 2 3; do echo "feature $i" >> app.txt; git add . && git commit -q -m "Feature $i"; done
git push -q -u origin main
cd .. && git clone -q remote.git colleague && cd colleague
git config user.email "c@example.com" && git config user.name "Colleague"
echo "typo fixed" >> README.md && git commit -qam "Colleague: fix typo in README"
git push -q origin main
The colleague's commit is on the remote. You have not fetched it.
The safe force refuses
Back in your clone, amend without fetching first — the realistic setup — and try the lease:
cd /tmp/lab-fp/you
git commit --amend -qm "Feature 3 (renamed)"
git push --force-with-lease origin main
! [rejected] main -> main (stale info)
error: failed to push some refs to '../remote.git'
--force-with-lease is a compare-and-swap: "overwrite main only if it still points where I last saw it." Your origin/main is stale because the colleague pushed since, so it refuses. That is the check working.
The plain force does not
git push --force origin main
It succeeds. On the colleague's side:
cd ../colleague
git fetch origin
+ b94d27d...44adf09 main -> origin/main (forced update)
The + and (forced update) are Git telling them history moved non-linearly. git log --oneline origin/main no longer shows their commit.
The recovery tool nobody knows they have
The colleague's clone kept a record of every value origin/main has held — the reflog of the remote-tracking branch:
git reflog show origin/main
44adf09 refs/remotes/origin/main@{0}: fetch origin: forced-update
b94d27d refs/remotes/origin/main@{1}: update by push
@{1} is the tip that was overwritten. (Their own main still points there too — but the remote-tracking reflog works even when the local branch has moved on.) Push it back, with a lease so this push cannot itself clobber something newer:
OLD=$(git reflog show origin/main --format=%h | sed -n '2p')
git push --force-with-lease=main:origin/main origin "$OLD:main"
git fetch -q origin && git log --oneline origin/main | head -3
+ 44adf09...b94d27d b94d27d -> main (forced update)
b94d27d Colleague: fix typo in README
a848187 Feature 3
7c07d94 Feature 2
Restored. And your renamed commit is not lost either — it is in your reflog:
cd ../you
git fetch origin
git reflog | grep "Feature 3 (renamed)"
44adf09 HEAD@{0}: commit (amend): Feature 3 (renamed)
Now do it properly: rebase the rename onto the restored main, or redo the amend after pulling.
Why this works, and when it does not
A force push replaces a ref on the server. It does not delete commits from clones. Any clone that fetched before the force push has the overwritten tip in git reflog show origin/<branch>. The only truly lost case is a commit that existed only on the server — which, in a normal workflow, is never.
The lease has a trap. If you git fetch right before pushing, your origin/main is refreshed and the lease passes — the fetch is what defeats it. Fetch before you rewrite, not before you push. And add --force-if-includes, which additionally requires the remote's tip to be in your local history:
git config --global alias.pushf 'push --force-with-lease --force-if-includes'
Then never type --force for a shared branch again.
On GitHub, one more layer: a ruleset or branch protection with "block force pushes" makes the mistake impossible in the first place.
Clean up: cd /tmp && rm -rf lab-fp.
This is Lab 19 of twenty hands-on Git and GitHub labs, each in a disposable repository with hints, the full solution and the explanation: Recover from a force push. The lab before it recovers a deleted branch from the local reflog — same idea, one level down.
Read original: https://dev.to/jjoyneriv/someone-force-pushed-over-main-here-is-the-reflog-nobody-knows-they-have-1d6d
← Previous
gh api --paginate --slurp --jq 'length' Counts Pages, Not Issues
Next →
You Installed Git. Here Are the Next Ten Minutes.
Related
From VibeConnect to CrowdWide: Building My Own Social Platform
DevOps
2
Dev.to (EN Zone)
Building Self-Hosted Developer Tools with TrueCharts: Deploying Private Services Like AdGuard and V2Ray on Kubernetes
DevOps
4
Dev.to (EN Zone)
You Installed Git. Here Are the Next Ten Minutes.
DevOps
4
DEV Community
Your First AWS Production Architecture: From a Simple Application to a Scalable System
DevOps
2
DEV Community
Comments0
No comments yet — be the first