There is a simpler, albeit slightly limiting, variant of the preceding. In it, the branch developers freeze while the trunk merges, and then the trunk developers create an entirely new branch, which replaces the old one. The branch developers move onto that branch and continue working. The cycle continues until there is no more need for branch development. It goes something like this (in shorthand – we'll assume jrandom@floss has the trunk and qsmith@paste has the branch, as usual):
floss$ cvs tag -b BRANCH-1 paste$ cvs checkout -r BRANCH-1 myproj
Trunk and branch both start working; eventually, the developers confer and decide it's time to merge the branch into the trunk:
paste$ cvs ci -m "committing all uncommitted changes" floss$ cvs update -j BRANCH-1
All the changes from the branch merge in; the branch developers stop working while the trunk developers resolve any conflicts, commit, tag, and create a new branch:
floss$ cvs ci -m "merged from BRANCH-1" floss$ cvs tag merged-from-BRANCH-1 floss$ cvs tag -b BRANCH-2
Now the branch developers switch their working copies over to the new branch; they know they won't lose any uncommitted changes by doing so, because they were up-to-date when the merge happened, and the new branch is coming out of a trunk that has incorporated the changes from the old branch:
paste$ cvs update -r BRANCH-2
And the cycle continues in that way, indefinitely; just substitute BRANCH-2 for BRANCH-1 and BRANCH-3 for BRANCH-2.
I call this the Flying Fish technique, because the branch is constantly emerging from the trunk, traveling a short distance, then rejoining it. The advantages of this approach are that it's simple (the trunk always merges in all the changes from a given branch) and the branch developers never need to resolve conflicts (they're simply handed a new, clean branch on which to work each time). The disadvantage, of course, is that the branch people must sit idle while the trunk is undergoing merge (which can take an arbitrary amount of time, depending on how many conflicts need to be resolved). Another minor disadvantage is that it results in many little, unused branches laying around instead of many unused non-branch tags. However, if having millions of tiny, obsolete branches doesn't bother you, and you anticipate fairly trouble-free merges, Flying Fish may be the easiest way to go in terms of mental bookkeeping.
Whichever way you do it, you should try to keep the separations as short as possible. If the branch and the trunk go too long without merging, they could easily begin to suffer not just from textual drift, but semantic drift as well. Changes that conflict textually are the easiest ones to resolve. Changes that conflict conceptually, but not textually, often prove hardest to find and fix. The isolation of a branch, so freeing to the developers, is dangerous precisely because it shields each side from the effects of others' changes...for a time. When you use branches, communication becomes more vital than ever: Everyone needs to make extra sure to review each others' plans and code to ensure that they're all staying on the same track.