Switching to Git some years ago was a significant change to how I work. You’ve probably heard it all before so, yeah, branching is great, distributed, yada, yada — it’s just awesome, ok?
But sometimes people overcomplicate things. Take the much-linked-to “gitflow”. Look at all the arrows, it makes my brain hurt.
It doesn’t have to be that complex (and to be fair, gitflow looks much more complicated than it is), we use a much simpler strategy that can be summed up in the following bullet points:
- origin/master is deployed to production and origin/staging is deployed to staging
- work is done in topic branches, rebased before merged, and always merged with
I use is and not will be intentionally. origin/master is what is running in production. If you push something to origin/master you’re responsible for also deploying it. Don’t push to origin/master unless you will also deploy to production immediately. This is really important. If you don’t deploy someone else will merge in their stuff later, deploy, and if there is a problem with your code it will take them much longer to find it because they will think the problem is in their code.
The staging branch is treated a bit differently from master. When you want to run something in staging you force push to origin/staging and deploy. We don’t treat staging as a branch, but more like a movable tag. The staging branch should never contain code that isn’t also in a topic branch, because at any time it can be removed. Just as with origin/master, origin/staging is what is running in staging.
Work is (usually) done in topic branches, branched off of master. These topic branches can be (force) pushed to origin/staging for testing if needed, and finally merged into master.
Before the merge topic branches are rebased against origin/master so that conflicts are handled in the topic branch, not in the merge commit (and it straightens out the history, which makes for easier review). We always merge with
--no-ff, and for two reasons. Firstly because it visually groups together commits that are related when looking at the history in a tool like GitX or
git log --graph, and secondly because when things go wrong you can just revert the merge commit and see if the problem was introduced in that branch.
Since GitHub introduced intra-repository pull requests we’ve started using them to get some extra eyeballs on the code. Before everyone merged their topic branches themselves, but now we send pull requests instead, and make a colleague look through the code before we merge. It is not meant as formal code review, but as a way of making sure that more than one person has seen the code before it goes into production. That way we save debugging time if something goes wrong. Sometimes this process
Those that are extra meticulous squash and reword their commits before sending the pull request, making it easier to review. A commit history usually reflects the process, including all the mistakes that were made and undone, and can often hide the actual sum of the work that was done. GitHub’s tools also alleviate this problem, for example making it easier to see a diff of all the changes introduced by the branch.
I wrote “usually” when I said that work is done in topic branches. There are exceptions to this. One is when a project has just been started and isn’t yet in production. It’s not always meaningful to work with topic branches in this case, but as the code base grows larger topic branches start becoming useful. Also, for small repositories that are perhaps just for some simple tool, or something else that isn’t particulary complex topic branches usually don’t make sense. Topic branches are a tool, not a rule.
In short, we have three types of branches: master, staging and topic branches. There is only one master, one staging (if at all) and many topic branches. The first two represent the code that is currently running in production and staging, respectively, an the rest represent the work that is being done.
Finally, a tip for working effectively with topic branches: don’t forget to clean up when you’ve merged them into master. It’s easiest to do when you’ve just merged, but we always forget that. Instead, from time to time we go through a repository and delete old ones, and when we do
git branch -a --mergedis a huge timesaver. It lists all branches (local and remote) that have been merged into the branch you’re currently on, and can safely be deleted. This unfortunately does not list remote branches that have actually been merged, but were rebased first. For these, you can check if they’ve been merged by running this command:
git log --pretty=oneline | grep Merge | grep name-of-branch, it will show you if there is a merge commit that mentions that branch (but make sure there haven’t been two branches of that name).