The Dark Side of the Force Push
Let’s talk about force pushing in Git. We’ll cover when you should do it, when you shouldn’t, and how to do it safely.
When you push changes to a remote server, Git prevents you from pushing your changes if the remote has changes you’re missing. This feature is designed to prevent data loss. If you run into this, you’ll likely see a message similar to this:
1 2 3 4 5 6 7 8
99% of the time, the appropriate solution to this message is to
git pull to fetch changes from the server,
git push origin my-branch to push your updated branch to the server.
The only real exception to the “always pull, then push” rule, is rebasing. When you rebase, you’re creating a copy of your commit history. Git treats this new copy of history as a completely different set of commits (because it is).
I won’t go into deep detail about rebasing, because it’s been covered many times before. GitHub has a good tutorial on rebasing. If you want some high-level concept slides on rebasing, I wrote some for a team presentation.
Point is, when you rebase a branch that you’ve pushed to a remote server, your copy of the branch and the server’s copy will be considered completely distinct.
This means when you try to push, the remote server will reject your changes because they’re not a fast-forward.
However, if you
git pull, you’ll end up with two copies of the branch which are then merged with a merge commit.
In addition to muddling your commit history, this will re-introduce whatever you were trying to change with your rebase.
In this very specific case, the correct solution might be to
git push origin my-branch -f.
Force Push Basics
So you have a branch that you’ve rebased, and you want to force push it to the remote server.
First, make sure nobody’s pulled down your branch and done local work off of it. If they have, your force push may make it difficult for them to reconcile their changes with yours.
If you’re working on a team and need to rebase a shared branch, here are the steps:
- Make sure your team has committed and pushed any pending changes
- Ask your team to pause work on that branch temporarily
- Make sure you have the latest changes for that branch (git pull)
- Rebase, then
git push origin <yourbranch> -f
- Have your team fix up their local branches with
git checkout <yourbranch>,
git reset --hard origin/<yourbranch>
As you can see, this is a complicated process. It requires team coordination, and potentially blocks team members from doing work while you rebase. As a general rule, avoid rebasing or otherwise modifying history on pushed branches unless you’ve accidentally published sensitive data.
If you’re working alone, the process is a little simpler.
Simply rebase and then
git push origin <yourbranch> -f.
Keep in mind, however, that you may still cause headaches for other people if you rebase on a public GitHub (Bitbucket, etc.) repository.
Aside: Understanding Remote Tracking Branches
Git uses the concept of remote tracking branches (or branch upstreams) to help you associate your local branches with the branches on remote servers.
If you’ve ever seen this message, that’s what it’s talking about:
Branch my-branch set up to track remote branch my-branch origin.
Additionally, if you’ve seen this message when you ran
git status, it means your branch is tracking a remote branch:
1 2 3
Finally, when you run
git pull or
git push, Git can use the remote tracking branch to know which remote branch to interact with.
This keeps you from having to type
git pull origin my-branch every time.
There’s a blog post with more info on remote tracking branches and upstreams.
Force Push Pitfalls
When you start force pushing, there are a few pitfalls you should be aware of.
Before you force push a branch, double check that nobody has pushed new changes to that branch. If they have, and you don’t pull them in, you’ll overwrite them when you force push.
With online source control, you can just browse the repository and check the commit history for your branch.
If you don’t have a web UI, you can also check directly in the source.
git fetchto pull down the latest changes to your machine
git log origin/<yourbranch>to remote branch’s log
- If you don’t see any new commits, it’s probably safe to proceed (ignoring developer race conditions)
- If you see new changes, resolve them into your local branch before you force push
As with software, you can run into a race condition.
If somebody pushes changes between when you fetch and when you push, you may still overwrite them.
To avoid this, communicate with your team.
A simple “don’t push anything on
Overwriting Other Branches
When you push and pull, Git can use remote tracking branches (see above) to infer which remote branch you want to push/pull from. This is generally ok for normal push/pull activity, but when you force push, it can be dangerous.
By default, Git responds to
git push by pushing all branches that exist on both your local machine and the remote server.
This means, if you have commits on master and my-feature-branch, and you run
git push, it will push your changes from both branches at once.
In Git 2.0, this behavior was changed to only push the current branch, but older versions still have this unexpected behavior. To fix it, run the following command:
git config --global push.default simple
Now if you run
git push, it will only try to push your current branch.
The default push behavior becomes especially dangerous when you force push.
If, for example, you run
git push -f, and your master branch is behind the remote server’s master,
you may accidentally remove commits from master.
For this reason, I recommend two things:
- Configure push.default with the safer
- Always explicitly state the remote and branch name when you force push:
git push -f origin my-feature-branch
Hopefully this gives you a good overview of the how/why/when of force pushing.
If you haven’t already, be sure to read the section on force push pitfalls. They’re important to understand, and they’re the main reason I wrote this post.