DEV Community

James Joyner
James Joyner

Posted on Originally published at moderngitacademy.com

Someone Force-Pushed Over main. Here Is the Reflog Nobody Knows They Have.

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
 ! [rejected]        main -> main (stale info)
error: failed to push some refs to '../remote.git'
Enter fullscreen mode Exit fullscreen mode

--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
Enter fullscreen mode Exit fullscreen mode

It succeeds. On the colleague's side:

cd ../colleague
git fetch origin
Enter fullscreen mode Exit fullscreen mode
 + b94d27d...44adf09 main       -> origin/main  (forced update)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
44adf09 refs/remotes/origin/main@{0}: fetch origin: forced-update
b94d27d refs/remotes/origin/main@{1}: update by push
Enter fullscreen mode Exit fullscreen mode

@{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
Enter fullscreen mode Exit fullscreen mode
 + 44adf09...b94d27d b94d27d -> main (forced update)
Enter fullscreen mode Exit fullscreen mode
b94d27d Colleague: fix typo in README
a848187 Feature 3
7c07d94 Feature 2
Enter fullscreen mode Exit fullscreen mode

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)"
Enter fullscreen mode Exit fullscreen mode
44adf09 HEAD@{0}: commit (amend): Feature 3 (renamed)
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)