We are currently using a somewhat complicated deployment setup that involves a remote SVN server, 3 SVN branches for DEV, STAGE, and PROD, promoting code between them through pa
We don't use branches for staging web-related stuff; only for testing experimental things that will take a long time (read: more than a day) to merge back into trunk. The trunk, in 'continuous integration' style, represents a (hopefully) working, current state.
Thus, most changes get committed straight to trunk. A CruiseControl.NET server will automatically update on a machine that also runs IIS and has up-to-date copies of all the extra site's resources available, so the site can be fully, cleanly tested in-house. After testing, the files are uploaded to the public server.
I wouldn't say it's the perfect approach, but it's simple (and thus suitable for our relatively small staff) and relatively safe, and works just fine.