maciej łebkowski
Maciej Łebkowski

Git workflow, branch naming and pull requests

in Professional


Git is a very powerful tool and there’s always a lot of ways do achieve same goal. We chose this kind of workflow but of course YMMV. This is the basic workflow for git at Docplanner. It’s loosely based on gitflow, but hasn’t got that much in common.

We used Github from the start and it’s our main and only fronted. It’s also the origin for our repo. We have tried Bitbucket and Gitlab but with no luck. Every project, public or private, goes to Github.

As for the clients — it’s mainly PhpStorm and some command line for more advanced tasks (or for more proficient users). Some tool for displaying the history in a graphical way is often useful (gitk or gitx are good choices).

We’re focusing our development on branches and using pull requests to merge larger pieces of code into master. From there, a continuous deployment is set up to publish the application to staging and production environments.

Branch structure

  • master — every stable feature or fix goes to master. master gets deployed to staging environment continuously. This is known as the develop branch in gitflow.
  • env/production (actually just prod) — tests are ran on the master branch, and if they pass the env/production branch is updated and deployed to production. aka release or master branch in gitflow. So no one actually pushes to this branch unless they wish to rollback some changes. The CI won’t ever force push into this branch, so committing there is a way of blocking automatic deployments.
  • feature/eglebegle — you’re working on a feature eglebegle, so this is your stable branch. The prefix is important — it designates the branch purpose.
  • feature/eglebegle/xyzzy — if your feature is large enough to split into several smaller branches, keep them in this hierarchy. Merge them as soon as they are ready to your main feature branch. In this scenario, the main branch shouldn’t have any non-merge commits.
  • refactor/feature — we’re not adding anything, just refactoring.
  • fix/eglebegle — just a little change to existing feature (hotfix branches in gitflow)
  • rfc/feature — this is an unstable branch and will be rebased in the future. It’s only pushed for code review.

In addition, you can combine them together:

  • feature/eglebegle/refactor/xyzzy — using /refactor/ just adds semantics
  • feature/xyzzy/rfc/xyzzy-2.0 — just to review code, then refactor / fix issues, rebase and merge to feature branch

We decided to use hyphen-notation for the names.

Usage of forks

Since Github allows us unlimited forks of private repos, and they do not count to the private repo limit (they are basically free), we use private forks as the RFC branches. Small features, implemented by one or two people, are pushed there not to pollute the main repo.

Merging back to main branch

Whether it’s the feature branch or the master, all large features need to be merged back using a pull request.

  1. Clean up your code. Remove comments, reformat code to comply with the codings standard and do every other thing you would otherwise notice during the code review
  2. If you can — rebase the branch. Rebase can do two things:
    • rebase -i main-branch --onto $(git merge-base main-branch) — squash multiple commits into one, without moving the branch (this way you won’t have to resolve conflicts after every squashed commit)
    • rebase main-branch — and move your branch to the latest commit on the main branch. This results in a cleaner history and and up to date code (without conflicts).
  3. Choose your branch name, then push and create a pull request. Make sure to define the correct branch to merge to (Github always defaults to master), and give it a nice description. It’s always good to point out what has changed, what was the reason for those changes and is there something tricky in particular about this diff. Use "@" to mention the devs you want specifically to review your code.

Remember that you should never rebase a public history (pushed commits). We make an exception for that for rfc/ branches. Just make sure that everyone know not to fetch them and base their work of them.

Fixing code after review

So your teammates are now beating the living shit out of your code and you comply and fix your shitty feature. And depending on your skills it may take one, two, or more rounds like this. After every set of comments, fix them in a commit and push to the same branch. The pull request will refresh automatically.

After the nightmare is done, you have three options:

  • Just merge the damn branch as is
  • Make one last rebase to squash all those fixes together, delete branch and cherry pick directly into the main branch.
  • Rebase & squash and create a new — clean — pull request. This time it should merge cleanly without further comments.

A poor man’s rebase

Sometimes you’re stuck on a feature branch with lots of commits and lots of merges from master. I don’t know a way to rebase those merge commits, but there’s a trick to squash it all to one commit:

git reset --soft main-branch
git commit -am 'my new feature rebased'
git push origin HEAD:refs/heads/feature/bite-my-shiny-metal-ass

This way all your files stay the same, but the HEAD is moved and git sees all your changes made on your branch as uncommitted.

Skipping the pull request

If you ever want to skip the pull request, make sure not to make a fast-forward merge. This is done by git merge --no-ff …. If you skip this flag, the history could become fucked up.

* [03d2ab0] (master) (HEAD)
* [4163f27] merge master into feature/hal-9000 [parents: 32a0828, 798b456]
| \
|  *  [798b456] this commit was on master before   
*  |  [32a0828] implement AI in feature/hal-9000
|  *  [92b8700] some other stuff

Doing git log --first-parent will show commits from the feature branch instead of the master branch. This is because of the "merge master…" on your feature branch just before reintegrating your feature. Instead, with --no--ff:

* [7006fcf] (master) (HEAD)
* [9344222] merge feature/hal-9000 into master [parents: 798b456, 4163f27]
| \
|  * [4163f27] merge master into feature/hal-9000 [parents: 32a0828, 798b456]
*  | [798b456] this commit was on master before   
|  * [32a0828] implement AI in feature/hal-9000
*  | [92b8700] some other stuff

And everything is nice and clear (notice the order of parents in merge commit in HEAD~).

That’s it

One last rule — you should never merge your own branches. Make someone else do it. This way there are at least two people responsible for that code being on master (the committer/author and the developer that merged it in).

Delete your branches after use. You’re done.

Was this interesting?

About the author

My name is Maciej Łebkowski. I’m a full stack software engineer, a writer, a leader, and I play board games in my spare time. I’m currently in charge of the technical side of a new project called Docplanner Phone.

Creative Commons License
This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. This means that you may use it for commercial purposes, adapt upon it, but you need to release it under the same license. In any case you must give credit to the original author of the work (Maciej Łebkowski), including a URI to the work.