Git Tricks: Keeping branches even

Daniel Zanzini
Goiabada
Published in
4 min readAug 21, 2017

In one of our projects, we use git with 3 main branches: master, staging and production.

master has all the code that has already been reviewed and approved, staging has the code on our staging machine and production has what is currently running for the client. They all follow the same timeline: from master to staging, then to production, so we have something like this:

---------o---o---o->
P S M

Then when we need to merge master into staging or staging into production we simply need to go to the target branch and merge it with the branch that is ahead of it:

$ git checkout production
$ git merge staging

Using this approach, no merge commit will be created, because this merge is Fast Forward (FF). The commits from the ahead branch are just put after the last commit of the target branch. After that, we should have:

-------------o---o->
P,S M

But yesterday, one thing happened: staging diverged from master.
One of my coworkers merged a PR on master, that was not synced with remote, merged it to staging and pushed it. So we ended up with something like this:

--a-----b---d--> master
\------c----> staging

We just noticed it afterwards, when we tried to merge master into staging again and received a prompt to edit the message of the merge commit. An easy fix for it would be to just merge it, but if we did that, all our merges would stop being FF and would generate merge commits, something we don’t want.

Two options were available to solve this problem. I'll walk through both of them now.

Using reset and force push

This approach keeps history clean by rewriting it but you'll need to push to remote using the force option. This can cause some problems when working with other people. Be careful when using this approach.

First of all, cherry-pick the diverged commit from staging to master:

$ git checkout master 
$ git cherry-pick <commit-C-from-staging-branch>
$ git push origin master

Which leads us to:

--a-----b---d--c*-> master
\------c-------> staging

Notice that a new commit was created on master. This happened because cherry-pick creates a new commit with the same changes as the selected one.

Now we need to return staging to the point where it diverged. We can use reset here to remove the last commit from staging's history.

$ git checkout staging
$ git reset --hard HEAD~1

If you try to push staging now you won't be able to do it. At this point, your local staging has diverged from remote staging. Keep in mind that we're using --force. This will rewrite history for any person that is dealing with this branch, so sync with them before doing so.

$ git push --force origin staging

Which leads us to the same timeline:

--a-----b---d--c*-> master
|staging

At this time, staging doesn't have any different commits from master and is 3 commits behind. In this situation we can just run git merge master on staging. It will run smoothly, with FF and no merge commits, to the result of:

--a-----b---d--c*-> master, staging

Using merge commits to avoid force push

This approach avoids the use of the --force option, so it's safer. But it will add one merge commit to your history line. This is not a big deal but can pollute your history if used frequently.

Start this approach by going to master and merge staging into it.

$ git checkout master
$ git merge staging

This will create a merge commit, so you'll be prompted to confirm the commit message. Do it and push staging to origin, which will lead us to this, with a new merge commit:

--a-----b---d---m-> master
\------c----/--> staging

Notice that staging is still split from master. To fix this we need to merge master into staging. Since both branches are synced, the merge will be FF, so you can just push it after the merge.

$ git checkout staging
$ git merge master
$ git push origin staging

Now, our two branches will be synced and all our merges from now on will be FF, but notice that our history will stay polluted by the time staging diverged from master:

--a-----b---d---m-> master, staging
\------c----/

Git is extremely versatile and flexible so it usually offers different ways to accomplish the same goal. It's up to you to decide which one is best for your git flow. If you use another approach to keep your branches even, feel free to use the comments to share it with us.

--

--

Daniel Zanzini
Goiabada

Developer @ Guava Software. Passionate about Pão de Queijo. From Minas Gerais. Star Wars fan. Constantly happy, listening The Doors and drinking beer.