We use the standard approach to creating releases:
New code is added to the trunk.
When it's time to create a new major release, we tag the trunk. The major release is always compiled directly from the trunk. Major releases have numbers such as 1.0, 2.0 etc.
Bug fix releases are numbered 1.1, 1.2 etc and are released from bug fix branches.
Due to the nature of our customers, our bug fix branches tend to be very long lived. IT organizations are generally conservative in upgrading their equipment. If the system works, all they want are bug fixes. If your product is aimed at individuals or SOHO rather than IT departments, bug fix branches are not likely to be as long lived. It may well be possible to persuade your customers to accept new features along with bug fixes. As previously mentioned, tracking the state of a bug fix in multiple branches is still a problem for us.
The main things I have learned are:
If you have the money and depending on the complexity of your product and type of customers, you should consider an industrial-strength product management suite (such as that from Rational - now IBM) right from day one. Although such tools are expensive (several thousand dollars per seat), ultimately you will wind up paying this amount one way or another. In my company we were fortunate to have talented developers who could customize open source tools such as CVS and GNATS. However, the cost of making these modifications in terms of developer time has been substantial.
Tight integration of bug tracking with source code management is highly desirable. By using loosely integrated tools, release managers and developers have also had to spend a considerable amount of time tracking down the state of various features and bug fixes. On a number of occasions, this high degree of manual labor has hampered our ability to get a release out in a timely manner.