Cover Image for Ephemeral Branching Pattern

Ephemeral Branching Pattern

This is a branching pattern that I use at work and so far it's been pretty successful. It has pros and cons which I'll get into, but first I need to describe it and go through some examples.

In the ephemeral pattern there is the mainline which for me, and this example, is going to be master. The master branch represents the production state of the application.

All development work is done in feature or bugfix branches which are always cut from master, not develop.

As features or bugs are completed they are pushed to the remote repo in their own branch and then integrated together into a release candidate branch.

A release candidate is a combination of a semantic version and a candidate number. The version is determined by your own internal versioning scheme and the candidate is simply an auto-incrementing integer.

As an example if your next release version should be 1.2.0 then the first release candidate branch would be release-v1.2.0-rc1.

The release candidate must be cut from master and each feature branch is iteratively merged. I use a simple CLI gitrelease in order to manage the set of branches which compose a release.

If a feature branch is added, removed, or otherwise updated a new merge is done and the candidate number is incremented.

Since the command line tool iteratively merges the feature branches after each update the release branch is essential ephemeral. It can be created at will by running the CLI and merging all of the composite feature branches.

Let’s look at a simple example.


master is the mainline branch. This is currently deployed to production.

develop is used for CI/CD. Our automation tooling is watching for commits to this branch and will run all of the automated tests against this branch. If the tests pass, this branch is automatically deployed to the development environment for testing.

Our first developer Betty starts working on feature/GIT-1. After making three local commits she finishes the feature and pushes her branch to origin.

Our second developer Yolanda starts working on feature/GIT-2. She finishes her feature after two local commits and pushes to origin.

We use gitrelease to iteratively merge the two branches into a new branch release-v1.1.0-rc1. The release branch (release-v1.1.0-rc1) is force pushed over develop to start the CD/CI process.

While Yolanda and Betty are working on their features our third developer Ophelia start working on bugfix/GIT-3. It's a simple bug so she finishes after one local commit and pushes to origin.

Again; we use gitrelease to iteratively merge the branches into a new branch release-v1.1.0-rc2. This branch gets its candidate number incremented since it now contains three branches. The release branch (release-v1.1.0-rc2) is force pushed over develop to start the CD/CI process.

While the CI is running there is a bug discovered in feature/GIT-2. The team is under pressure to get that bug fix out so they pull the buggy feature from the release and test again.

The new branch gets a new candidate number since its constituent parts are different; release-v1.1.0-rc3.

This release (release-v1.1.0-rc3) passes CI and our manual QA on develop. It is deemed ready and pushed to the production environment. We merge to master and tag with a new version 1.1.0.


The best part about this strategy is the flexibility that you have when you get close to the planned release date. If your cadence is one release every two weeks then, when it gets closer to that release date you can modify the "release" any way you like.

The client may really want to jam in a last minute feature so you add the branch to the version and then roll a new release candidate.

If you find a bug at the last minute in one of your branches you can remove that branch from the release definition, roll a new candidate, and then release the rest of the “good” branches.

Another benefit of this pattern is that; since develop, and any other environment branches that you maintain, are ephemeral they can be re-created or shifted around at will.

You can create a new release definition, iteratively merge the branches, and force push the release branch onto an environment branch.

If your feature branch was cut from develop, as is tradition, you would have to ensure that the entire commit history is clean instead of just your history.

The example above illustrates that in the traditional GitFlow pattern; the features: GIT-1, GIT-2, GIT-3, and GIT-4 are inextricably linked for the release.

The client and you have committed to releasing these features together/next. You can not change course and decide to release GIT-3 before or without GIT-1.

Not only that but GIT-3 and GIT-4 contain the bug that was introduced with GIT-2. The bug in GIT-2 must be addressed before GIT-3 or GIT-4 can be released.


There are three downsides to this strategy.

1. Merge Conflicts

Since you are iteratively merging diverging branches you will inevitably run into merge conflicts. This can be mitigated with some tooling which is what I’ve done with gitrelease.

If you have a release with two coupled features or features which require changes in the same files then you are likely going to have to create an integration branch and then merge the integration branch instead of the separate feature branches.

2. Maintaining a release definition

What I’m calling the “release definition” is the set of branches which are merged together to create the release branch. Since every time that a branch is updated, added, or removed from the release a new candidate has to be made you will have to maintain that list of branches somewhere.

Again I’ve solved for this by adding some tooling around the git cli which maintains that list for me.

3. Testing

The biggest potential down side is testing. If you are constantly adding and removing branches from the release then the artifact that is going to be pushed to the production environment is also constantly changing.

You can, and should, test the feature branches independently of each other but at the end of the day they must be integrated and tested together.

If you have to add or remove a branch at the last minute before a deployment then the result of the merge, the release, will be fully tested. The feature branches themselves will have been tested by the combination of them all working together may not be (depending on whether or not you have time).

For many people this will be a deal breaker.

However; the flexibility that the pattern provides, especially under time pressure, may outweigh these downsides.