Update 2019:
These days, the question would be seen in a context using Git, and 10 years of using that distributed development workflow (collaborating mainly through GitHub) shows the general best practices:
master
is the branch ready to be deployed into production at any time: the next release, with a selected set of feature branches merged in master
.
dev
(or integration branch, or 'next
') is the one where the feature branch selected for the next release are tested together
maintenance
(or hot-fix
) branch is the one for the current release evolution/bug fixes, with possible merges back to dev
and or master
That kind of workflow (where you don't merge dev
to master
, but where you merge only feature branch to dev
, then if selected, to master
, in order to be able to drop easily feature branches not ready for the next release) is implemented in the Git repo itself, with the gitworkflow (one word, illustrated here).
See more at rocketraman/gitworkflow
. The history of doing this vs Trunk-Based-Development is noted in the comments and discussions of this article by Adam Dymitruk.
(source: Gitworkflow: A Task-Oriented Primer)
Note: in that distributed workflow, you can commit whenever you want and push to a personal branch some WIP (Work In Progress) without issue: you will be able to reorganize (git rebase) your commits before making them part of a feature branch.
Original answer (Oct. 2008, 10+ years ago)
It all depends of the sequential nature of your release management
First, is everything in your trunk really for the next release?
You might find out that some of the currently developed functions are:
- too complicated and still need to be refined
- not ready in time
- interesting but not for this next release
In this case, trunk should contain any current development efforts, but a release branch defined early before the next release can serve as consolidation branch in which only the appropriate code (validated for the next release) is merged, then fixed during the homologation phase, and finally frozen as it goes into production.
When it comes to production code, you also need to manage your patch branches, while keeping in mind that:
- the first set of patches might actually begin before to first release into production (meaning you know you will go into production with some bugs you can not fix in time, but you can initiate work for those bugs in a separate branch)
- the other patch branches will have the luxury to start from a well-defined production label
When it comes to dev branch, you can have one trunk, unless you have other development efforts you need to make in parallel like:
- massive refactoring
- testing of a new technical library which might change the way you calls things in other classes
- beginning of a new release cycle where important architectural changes need to be incorporated.
Now, if your development-release cycle is very sequential, you can just go as the other answers suggest: one trunk and several release branches. That works for small projects where all the development is sure to go into the next release, and can just be frozen and serve as a starting point for release branch, where patches can take place. That is the nominal process, but as soon as you have a more complex project... it is not enough anymore.
To answer Ville M.'s comment:
- keep in mind that dev branch does not mean 'one branch per developer' (which would trigger 'merge madness', in that each developer would have to merge the work of other to see/get their work), but one dev branch per development effort.
- When those efforts need to be merged back into trunk (or any other "main" or release branch you define), this is the work of the developer, not - I repeat, NOT - the SC Manager (who would not know how to solve any conflicting merge). The project leader may supervise the merge, meaning make sure it starts/finish on time.
- whoever you choose for actually doing the merge, the most important is:
- to have unit tests and/or assembly environment in which you can deploy/test the result of the merge.
- to have defined a tag before the beginning of the merge in order to be able to get back to previous state if said merge proves itself too complex or rather long to resolve.