git push --force: forced update
The push went through, and that is the problem. A plus sign where a rejected push shows !, three
dots where there were two: main on the remote no longer continues from where it was, and whatever
was there is gone from the branch.
$ git push --force
To ../origin.git
+ 7a20dd4...864afd7 main -> main (forced update)
What Git did
A plain push refuses to move main anywhere but forward (the
non-fast-forward page). --force tells the remote to move it
to your commit regardless. Git marks the move with a + and prints old...new with
three dots: the two tips aren't on one line. Two dots would mean new follows from
old; three mean the two only share an ancestor.
Everything after that ancestor on the old main, bot's commit here, is no longer reachable from
the branch. It isn't deleted: the remote keeps the object for a while, and the clone it was pushed from still
has it. But anyone fetching main won't get it, and a build of main won't include it.
What the others see
Their next fetch tells them, in the same shape:
$ git fetch From ../origin + 7a20dd4...864afd7 main -> origin/main (forced update) $ git status On branch main Your branch and 'origin/main' have diverged, and have 1 and 1 different commits each, respectively. (use "git pull" if you want to integrate the remote branch with yours) nothing to commit, working tree clean
The + and the three dots again, this time on origin/main. Their own
main still has the commit, so git status says the two have diverged: one commit each
that the other lacks. Their way back is a page of its own:
someone force-pushed over my commit.
--force-with-lease: the safer force
A forced push is sometimes right: your own branch, after a rebase, where nobody else has pushed. The risk is
erasing a commit you haven't seen. --force-with-lease refuses exactly that: it goes through only
if the remote's main is still where your clone last saw it. Here ana hasn't fetched since bot
pushed, so her lease is stale. Once she fetches, and can see what she is about to erase, it goes through:
$ git push --force-with-lease To ../origin.git ! [rejected] main -> main (stale info) error: failed to push some refs to '../origin.git' $ git push --force-with-lease To ../origin.git + 7a20dd4...864afd7 main -> main (forced update)
The second push still erases bot's commit. The lease protects you from erasing what you haven't seen, not from
erasing. On a shared branch the other protection is the host's: a branch rule that refuses forced pushes to
main altogether.
Why the game does this to you
push --force is a card in Git Game. Played, it moves main back to your pointer and
puts your commits on top; every commit after your pointer is erased and goes back to its owner's local branch,
to be pushed again. You take a sin token for it, and the day log prints Git's line. One card answers it: a
reflog armed in your pack fires inside the forcer's own pack and puts your erased commits back on
top of main.
Play a day where this happens
One tap starts a game against the bot. It holds the card too, and says why when it plays it.
Output captured from Git 2.50.1 (Apple Git-155) by scripts/git-output.sh, in a scratch repository with two clones, ana's and bot's, and fixed dates, so the hashes are the same on every run. It is captured again at every Git release.