$ Git Game

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.

play the bot now

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.