While at a company I used to work for, I spent a few years building one of their main products. It was a Web facing application, and as I only had front-end skills when I joined, I started off by working exclusively on the front end. Curiosity eventually got the better of me and I slowly moved on to working on the back end of the application too.
It was a great feeling to be able to build new features and debug existing ones without needing help from the API team. Don’t get me wrong; I enjoy teamwork – one of the best projects I’d worked on had me in a team of three. However, I’d often feel awkward about disrupting someone else’s state of flow, asking them to context-switch to help me with something that would typically seem trivial. Being able to work full stack also allowed me to be more productive as there were fewer moments where I would simply wait for someone to help me.
The more I worked on the API, the better I understood its architecture. The company offered two main types of data as products. Each type came in different flavours, matching the company’s different sub-products. While the API was monolithic, new flavours could be added without too much effort. However, the platform only supported one of the two main data types. Over time, I became one of the people that others would come to if they wanted help with the system.
One day, a new team was formed. It had two members to begin with, but it was enough: its goal was to rethink the overall system architecture and build a proof-of-concept to provide the missing data type. To achieve this, they decided to build a new standalone microservice to serve the data, and to hook it into the existing platform. As the main product already had a UI, it seemed most cost-effective to expand it to display the new data type.
The project seemed like an interesting challenge. As someone who knew that area of the product, I was eager to take it on. While management loosely agreed for me to do this cross-team collaboration, its priorities were somewhat mixed. I was eventually told that I should work on it, but only when there wasn’t (more important) work from within my own team.
While not ideal, I accepted this compromise. As it involved a significant architectural change in the UI, I created a new branch to build it on. In the various scraps of time between sprints I switched to that branch, adding new commits from time to time. To avoid a massive merge conflict at the end of development, I regularly updated the branch with the latest code. I chose to do this by rebasing the branch to avoid introducing merge commits. As a long-term project, I pushed the branch regularly for backup purposes.
When Push turns to Force Push
Pushing a branch for the first time usually goes smoothly. After a rebase, trying to push it typically results in an error with a message similar to the following:
To github.com:MyOrganisation/MyRepository.git
! [rejected] branch-name -> branch-name (non-fast-forward)
error: failed to push some refs to 'github.com:MyOrganisation/MyRepository.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.
To understand why this happens, let’s briefly remind ourselves what happens when we rebase.
A Quick Rebase Refresher
Rebasing a branch takes all commits that are exclusive to that branch and reapplies them at a new starting point. It’s a bit like resetting your code to that same point, and then re-doing all the work again at lightning speed. While the contents of the commits will be the same (unless we have resolved a merge conflict), the branch is now different from how it was before the rebase – it is now rooted at a different point on the branch that we rebased it onto.
Bypassing the Safety Mechanism…
The error is a safety mechanism built into Git to prevent accidental data loss. As the change (i.e. the rebase) was intentional, we can decide to override it if it is an older version of our branch, rather than a different branch with the same name. Assuming our remote is called origin
and our branch is named feature
, we can push with the command:
git push origin feature –-force
This will completely overwrite the remote branch with our local version.
…But Not Completely Overriding the Safety
It’s usually quite safe to force push a branch after rebasing if:
- It is our own branch, and
- No one else is working on it.
As it’s usually not recommended to rebase a shared branch, these two conditions will typically be true. However, if we want some safeguards, we can use:
git push origin feature --force-with-lease
From the Git documentation at time of writing,
--force-with-lease alone, without specifying the details, will protect all remote refs that are going to be updated by requiring their current value to be the same as the remote-tracking branch we have for them.
In other words, this command will prevent data loss by failing if:
- Any of our teammates have pushed additional commits to the branch, or
- The branch has changed for whatever reason.
Summary
When working on a feature branch, there are times when you might want to rebase it to get the latest code updates without introducing merge commits. Once rebased, a normal push of your feature branch will fail. This is because the branch has changed, and Git has a safety mechanism built in to prevent accidental data loss.
If the feature branch is your own and nobody else is using it, you can override the safety by using the command:
git push origin feature --force
This will completely overwrite the remote branch with your local version. If you want to make sure that you don’t overwrite someone else’s work, a safer option is:
git push origin feature --force-with-lease
By understanding these options, you now have full control over how you handle your branches. Force-pushing can be dangerous. But now that you understand when and when not to do so, there’s nothing stopping you from managing your repositories in the way that’s best for your project.
P.S. A few months later, my branch of approximately 60 commits was successfully merged into the main codebase.
Thanks for reading!
Level up your developer skills! Sign up for free for more articles like this, delivered straight to your inbox (link goes to Substack).
Top comments (0)