Git path · Working with others · Topic 9 of 13

Rewriting history and force-pushing safely

Not started

Rewriting history means replacing commits: git commit --amend, git rebase, an interactive rebase that squashes fixups, or tools like git filter-repo that strip a leaked secret. Locally that is harmless. Once the old commits are on a server, a normal push is rejected because it is not a fast-forward, and you have to force it. --force replaces the remote branch whatever it holds; --force-with-lease replaces it only if it still holds what you last saw.

Why it matters: A careless git push --force can silently erase commits someone else pushed a minute ago. Two flags and one habit make it safe.

  1. 1Rewriting makes new commits

    amend, rebase, squash and filter tools replace commits with new ones, so the remote branch is no longer an ancestor of yours.

  2. 2--force overwrites blindly

    It sets the remote branch to your commit, whatever is there now, including commits you've never seen.

  3. 3--force-with-lease checks first

    It only overwrites if the remote branch is still where your origin/<branch> says it is.

  4. 4--force-if-includes closes the gap

    It also refuses if you fetched new commits but never looked at them, which a background fetch can cause.

1

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 push
To /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-lease
To /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-includes
To /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 main or 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 main regardless of what anyone types.
  • Rewriting does not unleak a secret. Removing a committed password with git filter-repo and 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.
2

Check yourself

3 questions. Get them all right to finish this topic.

  1. 1.Why does a normal git push fail after you amend a commit you already pushed?
  2. 2.What does --force-with-lease compare before overwriting?
  3. 3.Your editor fetches in the background, so origin/topic already includes a teammate's commit you never looked at. What protects you?

0 of 3 right so far. Get all three to finish this topic.