How it works
The explanation, with examples you can run.
Set up a shared branch
Alice pushes topic. Bob adds a fix on top and pushes. Alice, who has not fetched since, amends her commit.
cd /tmp && mkdir demo && cd demogit init -q --bare -b main origin.gitgit clone -q origin.git alice 2>/dev/null && cd alicegit commit -q --allow-empty -m "First" && git push -q -u origin maingit switch -q -c topicgit commit -q --allow-empty -m "Topic work" && git push -q -u origin topiccd .. && git clone -q -b topic origin.git bob && cd bobgit commit -q --allow-empty -m "Bob's fix" && git push -qcd ../alicegit commit -q --amend --allow-empty -m "Topic work, reworded"git pushTo /tmp/demo/origin.git ! [rejected] topic -> topic (fetch first)error: failed to push some refs to '/tmp/demo/origin.git'hint: Updates were rejected because the remote contains work that you do nothint: have locally. This is usually caused by another repository pushing tohint: the same ref. If you want to integrate the remote changes, usehint: 'git pull' before pushing again.hint: See the 'Note about fast-forwards' in 'git push --help' for details.git push --force here would succeed, and Bob's fix would vanish from the server without a word. Use the lease instead:
git push --force-with-leaseTo /tmp/demo/origin.git ! [rejected] topic -> topic (stale info)error: failed to push some refs to '/tmp/demo/origin.git'Alice's origin/topic still points at the commit she pushed. The server's topic has moved on, so the lease fails and nothing is lost.
The gap the lease leaves
The lease is only as good as your origin/topic. If anything fetches (you, a script, or an editor that fetches in the background), origin/topic catches up with the server and the lease passes, even though you have never looked at Bob's commit. --force-if-includes (Git 2.30 and later) adds a second check: the remote-tracking tip must appear somewhere in your local branch's reflog, meaning you actually integrated it.
git fetch -qgit push --force-with-lease --force-if-includesTo /tmp/demo/origin.git ! [rejected] topic -> topic (remote ref updated since checkout)error: failed to push some refs to '/tmp/demo/origin.git'hint: Updates were rejected because the tip of the remote-tracking branch hashint: been updated since the last checkout. If you want to integrate thehint: remote changes, use 'git pull' before pushing again.hint: See the 'Note about fast-forwards' in 'git push --help' for details.Now Alice knows to look at git log topic..origin/topic, bring Bob's commit into her rewritten branch (for example with git rebase origin/topic or a cherry-pick), and push again.
Habits
- Never force without a lease. An alias makes it the easy path:
git config --global alias.pushf "push --force-with-lease --force-if-includes". - Rewrite only your own branches. Rebasing a feature branch only you push to is routine. Rewriting
mainor a release branch breaks every clone that has it. - Protect shared branches on the server. GitHub, GitLab and Bitbucket can block force-pushes and deletion on
mainregardless of what anyone types. - Rewriting does not unleak a secret. Removing a committed password with
git filter-repoand force-pushing does not remove it from clones, forks, caches or CI logs. Rotate the secret first. - The old commits are recoverable for a while. The reflog on the machine that pushed them still names them, as long as nothing has expired.
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.