Advent Day 20: Force with Lease

December 20, 2018

This is day 20 of my Git Tips and Tricks Advent Calendar. If you want to see the whole list of tips as they're published, see the index.

On one of my projects, we usually use a workflow where we create short-lived topic branches and try to keep a reasonably clean history. This means that we often rebase our topic branches onto the master branch so that you'll have a nice history when we merge the pull requests.

Merge History Example

This all sounds nice, but there's a bit of a "gotcha" when it comes to this workflow: if you push a branch to the server and you later rebase that branch, the server will think that you're not up-to-date, since you don't have the latest commits on that branch:

To github.com:ethomson/repo.git
 ! [rejected]        master -> master (non-fast-forward)
error: failed to push some refs to 'git@github.com:ethomson/repo.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

You'll now need to actually force push to the remote, basically removing the existing commits and pushing up your new, rewritten branch in their place. And this is a bit scary, since there's really no checking. Unless, of course, you use the --force-with-lease option.

The "force with lease" option basically does an atomic compare-and-swap on the branch that you're pushing to, based on the last information you fetched. In other words, it ensures that you're force pushing to remove what you think that you're force pushing to remove.

Assuming that you're up-to-date: you've just done a git fetch on the remote, when you run git push --force-with-lease, you'll overwrite the remote:

To github.com:ethomson/repo.git
 + d008110...3653e05 master -> master (forced update)

However, if you're not up-to-date... maybe somebody snuck in and pushed to your branch while you weren't paying attention. If you were only to git push --force then you would overwrite their commits, losing them. But if you git push --force-with-lease then you will not overwrite the branch and lose their changes.

To github.com:ethomson/repo.git
 ! [rejected]        master -> master (stale info)
error: failed to push some refs to 'git@github.com:ethomson/repo.git'

This makes git push --force-with-lease a good default, and I suggest you get in the muscle memory of using it (or set an alias if you prefer). You can always do a "true" force if you really need to, but this helps avoid any accidental mistakes.