For productive work software teams need a version control system (VCS) flow. A set of rules, that describes how code changes will be integrated into the existing code and delivered to testing and production. Obviously the VCS itself is the first factor that affects the flow, what you can do with git may not work with svn. At the moment of writing git has become the industry standard, so the ideas are based on that premise.

There already exist frameworks like Gitflow (I suppose this is where the popularity of the word flow comes), but as with any framework, it brings a lot of tools you might never need in your project. So lets consider the leanest flow, that is still organized and effective.

Naming schema

What git has brought to the table in comparison to the previous generation of VSCs is the ease of working with branches. Since then, branching has become a tremendous tool in software development. The work on a project is done in branches, that are being merged together to form the final product. Just as with code style, naturally you need a convention for naming the branches you use. I have worked with different naming policies, but just the same way as with the code style, the style itself is not that important, as long as it is consistent throughout the project. A schema I can suggest is: "type/name". E.g. "release/1.2.3" or "testing/PROJ-10". More on that in sections dedicated to specific branches.

Commits in branches should also have a naming schema. It is again suggested to use a common prefix in the commit message, e.g. "PROJ-10: Added login form layout". Later, if you look through commits (or look for a bug) you can look up that ticket to understand what was the initial purpose. It may also be helpful to grep the commit list looking for commits that were a result of work on a specific ticket.

It will be our rule number 0.

  1. Have a naming convention for your branches and commits.

Master branch

When a developer starts working on a project, they need some code state to start with. In a repository there has to be a branch, that contains the latest stable state of the project. Normally it's the last released version. This is what's working in the production and is the status quo. In git this branch is usually the master branch. So the first rule of the flow is:

  1. Master branch contains the latest stable version (latest release)

Can you base your work on a different branch? Well, another branch may contain changes, that are not present in the master and will not ever be present, e.g. never go live due to any reason. Master is the most compatible branch, and thus it's least problematic to merge your changes later.

Feature branches

Every developer in the team can work on their task in their own branch, called feature branch. These branches are forked from master branch and the whole work on a task is done in that branch. In a feature branch, a developer has freedom to commit as often or infrequently as they wish.

Feature branches can be named like "feature/PROJ-10", where PROJ-10 is the issue tracker ticket number. Another option is "PROJ-10-implement-login-form". The idea is, it should be easy for anyone to find the branch just by an issue number. This is why having a standard prefix format is helpful. Your colleague may mention, that they're having a problem with their ticket, and you don't have to ask how is the branch named, you can easily find it yourself. Your command shell may also provide autocompletion, making it possible to checkout the code in question just in a couple of seconds.

I also find including a short name/description helpful. Later, when you clean up stale branches you will not need to look up issues/tickets by their number, it will be mostly obvious what part of work has been done there. It also helps if you establish a single format to write names, e.g. "kebab-case".

So we come to rule number two:

  1. Developer's work on a task is done in feature branches.

Integration branch

When work is done it is time to bring it to testing. I am not covering code reviews here, as it's not the part of the branching flow. Usually there is some testing/staging server where testers can try out new changes. We do not yet know if these changes are making it into the release or if they work as expected. It may so happen, that the feature gets cancelled altogether. So we need a branch, that temporarily holds the changes, so that they can be rolled out and tested.

For that purpose, integration branches are created. Feature branches are merged into integration branches without squashing. In git, squashing is creating one commit, that contains all of changes from a branch instead of taking them as they are. The idea is, if you need to add further improvements to your feature branch and subsequently integration branch, you may face merge conflicts if you have squashed your previous work.

Integration branch is permanently "dirty", they contain features, that may be broken, released later or never released. Many teams use develop branch for the same purpose. However, over time these branches (integration or develop) accumulated changes that will not be released, a multitude of merge commits and other temporary things. It is a little easier to dispose integration branches once a cycle, than to reset develop branch and make all developers checkout the new state.

Naming for integration branches can be as straightforward as: "integration/21.01", where 21.01 could be current iteration or next version number.

  1. Ready to test code is merged into an integration branch.

Release branch

Now we have some tested features we want to release. For this a release branch is created. All features planned for release are merged into corresponding release branch. Some teams merge directly into master branch, however here's why extra step can be useful. Once you've merged something, it's hard to unmerge it (reverts can be quite a pain). And again, you might discover a bug in the very last moment, or release of some feature gets blocked. It is usually far easier to remove and re-create a release branch if this happens.

When merging into release branch I like to squash commits. At this point it is much less likely that we will have follow-up commits. And nobody is really interested in seeing 50 commits with "Fixed tests" comments, so this helps to keep git history a little cleaner without much work. You can also do practically the same with rebasing, that is really up to you.

So a release branch has one (or two if we count merge commit) commits per feature and is usually called something like "release/21.01". Where the number is the same as in the integration branch.

  1. Tested code is merged (squashed) into release branch.

Releases

Ok, we got all the way through, now we want to release the changes. We assume, that at this moment release branch only contains the changes that we want to release. For a release you create a tag (a tag is a named commit), for example "v21.01". Depending on your processes, you might want to create a tag from a release branch and then merge it into master, or merge into master first and then create a tag based on a master branch. This makes no real difference. Tagging is a very easy and lightweight way of tracking released versions.

  1. Release branch is merged into master and a tag is created to mark the release.

Now master has been updated, and we have a new base state of the code. All of merged feature branches, integration branch and release branches can be disposed and new integration and release branches are created with the next version/iteration number.

  1. After release feature, integration and release branches are disposed and new ones are created.

Conclusion

So there we go, we have defined an organized and effective VCS flow in just 7 rules:

  1. Have a naming convention for your branches and commits.
  2. Master branch contains the latest stable version (latest release)
  3. Developer's work on a task is done in feature branches.
  4. Ready to test code is merged into an integration branch.
  5. Tested code is merged (squashed) into release branch.
  6. Release branch is merged into master and a tag is created to mark the release.
  7. After release feature, integration and release branches are disposed and new ones are created.