Before I dive into my main arguments, I do believe feature branches make
sense for open source development. Generally, developers will work on
branches dedicated to given features. This way, when they are done, they
can present the branches to the community, get feedback, and make
changes. Often times, they will not even have commit rights, so a
committer will have to take the branch and merge it in. This is much
harder if the work was a collection of commits and not a cohesive
feature branch. It is also common in open source for features to get
rejected, so it’s helpful if these commits don’t clutter up the git
history. Open source projects also tend to have more sporadic
development as opposed to lots of developers all working in the code
The problem is that most work done at companies do not have the same
flow. For example, at Braintree
Payments, we have about six developer
pairs all working on different stories, all committing at the same time,
and all with commit rights to our project. We trust that if a pair wrote
the code, it’s good and is going into the project. In 99% of cases, if
we are working on a feature, it is going to get released.
Given this kind of working environment, here is why I do not like
Feature branches make for nasty commit history
Feature branching as a practice encourages lots of small commits, often
before tests are passing or the code is in a useable state. Since these
commits are on a branch, most people think it’s ok as long as the final
merge commit back into master passes all of the tests.
The problem with this practice is that the commit history of the project
is now full of broken work and non-passing code. I often use git history
to get an idea of why a feature was done, or what other code was changed
at the same time. I will “git blame” a method name to see when it was
added, and what the code looked like at that point. With feature
branches, I can no longer trust that commit. It may have been done on a
branch, it may have broken half of the tests, and it may have been
completely scrapped before the code went into master.
Furthermore, If feature branches are deleted once they are merged into
master, where do bug fixes go? On a new feature branch or on master?
There will no longer be an easy way to find a list of the commits that
went into a feature. If feature branches are not deleted right away, the
repository will likely end up with a huge number of branches.
Feature branches generally have no builds
We use Jenkins to continually test our
application. We run numerous builds on every commit, including
integration builds that call out to external dependencies.
When we do work on feature branches, we no longer have these builds.
Developers will run the main suite of tests before they check in, but no
one is going to run the full suite of integration tests on every commit.
Furthermore, setting up new builds can be annoying, so we’re unlikely to
do it unless we are going to maintain a branch for a long time.
Lots of branches become cumbersome
The lack of builds is one aspect of where lots of branches become
cumbersome. Another is user testing. It is common to want the product
owner or someone else to take a look at the feature before it’s done to
get feedback. If you have a testing environment, they might want to
deploy your code there to take a look (rather than looking over your
shoulder on the workstation). But if you have feature branches, then
they have to deploy your branch, which means they can no longer test
other features at the same time. If they are in the middle of testing
another feature that requires specific data, they may not be able to
deploy your branch (e.g., if it has different database migrations that
would require the database to be rebuilt).
Feature branches make refactoring harder
Modern version control tools like git make branching and merging much
easier, but they are far from perfect. If we do large refactorings
(especially refactorings that take numerous commits), it can become
really hard to merge in feature branches. For example, if we rename
methods, shift code between classes, or even reorder the methods in a
class, the feature branch will likely get tons of conflicts on the
merge. Then, someone has to go through each conflict and figure out
where the code went. The longer the feature branch lives, the worse the
To take a specific example, say we want to switch our tests from one
factory pattern to another. With feature branching, we would create a
branch, and spend a few days porting over our tests. Then, when we merge
back, we are going to run into conflicts with any tests that were
changed by other feature branches. Even worse, while we are porting
tests, other developers are still writing tests on their feature
branches in the old style.
If we decide to make the test changes on master, but still have feature
branches, all of the currently running feature branches have to
continually merge from master in order to get the new tests. If they
forget, and start modifying tests that have not been ported yet, they
are still going to get conflicts when they merge back.
In contrast, if everyone is working in master, the people doing the
refactoring can pull, make the changes, and commit frequently. As long
as other developers also pull frequently (since everyone is making
small, frequent commits), they automatically get the changes.
Given that I don’t like feature branches, here is what I prefer:
All developers work in the master branch. When we are ready to release
code to production, we merge master into a release branch, which gets
some extra regression testing. We then tag from release and deploy the
We use descriptive commit messages to signify what feature we are
working on. For example, we use messages like “#card – message.” If we
need to see all of the commits for a given feature, we can grep the git
history for the card number. Likewise, if “git blame” a line of code,
you can get back to the original story card and see why the feature was
Commits only get pushed if they pass all of the tests. If a developer
wants to make temp commits, that’s fine as long as they get squashed
into a decent commit before pushing. Spike branches are fine for playing
around, but the real work should be done on master once the approach is
On my last few projects, we have used a tool called
rake_commit to do much of this
work for us. It will prompt for a pair, story, and commit message,
squash unpushed changes, run “git pull –rebase” (if working in git), run
tests, and then push if the tests pass. This tool ensures a clean,
linear git history.
For long running features, I prefer Branch by
feature switches. This way, the features can be developed in master
along side other development with the full suite of builds, but not
necessarily impact the end user until the work is complete. If we have
to release early for some reason (feature X just has to go out today),
then we can release without the new features in use.