The problem is you are changing what the meaning of a branch is part way through the process.
Initially, the v1 dev branch is for development. All new features go there. At some point in the future, it becomes a maintenance branch for the v1 release branch. This is the crux of the problem.
Its not that the developers are sloppy, its that the permissions and roles of the branch are sloppy and subject to change.
What you need to do is establish what role each branch as, and maintain that role. If the role changes, branch.
For example:
developer
commits | | | | | | | |
v v v v v v v v
dev +--+---------------------+------------------->
| ^ ^ | ^ ^
| | | | | |
v1 +----+------+----+ | | |
prod patches | | |
| | |
| | |
v2 +-----+-----+----+
prod patches
In this model, developers always commit to dev. If you are building a patch, you check the patch into that release's branch (or better yet, branch the release branch for a patch and then merge it back into the release branch).
One article that you should read (and its probably an understatement for 'should') is Advanced SCM Branching Strategies by Stephen Vance (article copy at webarchive).
In this paper, I first define branching in a general sense. I then discuss various strategies for branching, starting with the obvious and moving up to several that are more appropriate for larger development efforts. Along the way, I discuss the pros and cons of each strategy, using them to motivate the changes that compose the more complex strategies...
In this article, he identifies five roles that branches may have. Sometimes a branch may fill two roles and roles do not necessarily need a new branch as long as the role policies do not change mid branch (you will occasionally see mention of "branch on incompatible policy").
These roles are:
- Mainline. This is where branches are made from. Always branching from the mainline makes merges easier since the two branches will have a common ancestor that isn't branch upon branch upon branches.
- Development. This is where developers check in code. One may have multiple development branches to isolate high risk changes from the ones that are routine and mundane.
- Maintenance. Bug fixes on an existing production environment.
- Accumulation. When merging two branches, one may not want to risk destabilizing the mainline. So branch the mainline, merge the branches into the accumulator and merge back to the mainline once things are settled.
- Packaging. Packaging a release happens in the packaging branches. This often becomes the release and serves to isolate the release effort from development. See How to deal with undesired commits that break long-running release builds? for an example of where the packaging conflicts with development.
In your example, you've got a cascading mainline (this is a problem - it makes merges more difficult - what happens if you want to merge a fix for v1 into v2 and v3?), a dev branch that becomes a maintenance branch (change of policy, this is a problem).
Ok, you say, thats great, but this was written for perforce which is a centralized VCS - I'm using DVCS.
Lets look at the git-flow model and see how it applies.

The master branch (blue) is the release branch - for tagging. It is not the mainline. The mainline is actually the develop branch (yellow). The release branches (green) are the packaging role. Low risk development happens in the mainline, high risk development happens in the feature branches (pink). In this model, accumulation is done in the develop branch. Maintenance are considered 'hot fixes' which are red.
While the role policies aren't exact match (each product has its own slightly different lifecycle), they are a match.
Doing this should simplify your branching policy and make it easier for everyone involved.