Index: diffviewer/forms.py
===================================================================
--- diffviewer/forms.py	(revision 1137)
+++ diffviewer/forms.py	(working copy)
@@ -40,10 +40,10 @@
             raise EmptyDiffError(_("The diff file is empty"))
 
         # Check that we can actually get all these files.
-        if tool.get_diffs_use_absolute_paths():
+        if 'basedir' in self.fields:
+            basedir = smart_unicode(self.cleaned_data['basedir'])
+        else:
             basedir = ''
-        else:
-            basedir = smart_unicode(self.cleaned_data['basedir'])
 
         for f in files:
             f2, revision = tool.parse_diff_revision(f.origFile, f.origInfo)
Index: reviews/models.py
===================================================================
--- reviews/models.py	(revision 1137)
+++ reviews/models.py	(working copy)
@@ -2,6 +2,7 @@
 import re
 from datetime import datetime
 
+from django.conf import settings
 from django.contrib.auth.models import User
 from django.db import models
 from django.db.models import Q, permalink
@@ -16,6 +17,8 @@
 from reviewboard.scmtools.models import Repository
 from reviewboard.utils.fields import ModificationTimestampField
 from reviewboard.utils.templatetags.htmlutils import crop_image, thumbnail
+from reviewboard.reviews.email import mail_review, mail_review_request, \
+                                      mail_reply
 
 
 class InvalidChangeNumberError(Exception):
@@ -393,7 +396,13 @@
             self.visits.all().delete()
 
         super(ReviewRequest, self).save()
+        
+    def is_visible_to(self, user):
+        return self.public or self.is_mutable_by(user)
 
+    def is_mutable_by(self, user):
+        return self.submitter == user or user.is_staff
+
     class Admin:
         list_display = ('summary', 'submitter', 'status', 'public', \
                         'last_updated')
@@ -624,6 +633,9 @@
 
         request.save()
 
+        if settings.SEND_REVIEW_MAIL and changes:
+            mail_review_request(request.submitter, request, changes)
+
         return changes
 
     class Admin:
@@ -843,6 +855,12 @@
             comment.timetamp = self.timestamp
             comment.save()
 
+        if settings.SEND_REVIEW_MAIL:
+            if self.is_reply():
+                mail_reply(self.user, self)
+            else:
+                mail_review(self.user, self)
+
     def delete(self):
         """
         Deletes this review.
Index: reviews/views.py
===================================================================
--- reviews/views.py	(revision 1137)
+++ reviews/views.py	(working copy)
@@ -329,10 +329,11 @@
     """
     review_request = get_object_or_404(ReviewRequest, pk=review_request_id)
 
-    if review_request.submitter == request.user:
+    if review_request.is_mutable_by(request.user):
         if not review_request.target_groups and \
            not review_request.target_people:
-            pass # FIXME show an error
+            # FIXME error nicely
+            raise Exception('cannot publish with no reviewers set') 
 
         try:
             draft = review_request.reviewrequestdraft_set.get()
@@ -352,9 +353,6 @@
             review_request.public = True
             review_request.save()
 
-        if settings.SEND_REVIEW_MAIL:
-            mail_review_request(request.user, review_request)
-
         return HttpResponseRedirect(review_request.get_absolute_url())
     else:
         raise HttpResponseForbidden() # XXX Error out
Index: scmtools/svn.py
===================================================================
--- scmtools/svn.py	(revision 1137)
+++ scmtools/svn.py	(working copy)
@@ -7,7 +7,7 @@
 
 from reviewboard.diffviewer.parser import DiffParser
 from reviewboard.scmtools.core import \
-    SCMError, FileNotFoundError, SCMTool, HEAD, PRE_CREATION, UNKNOWN
+    ChangeSet, SCMError, FileNotFoundError, SCMTool, HEAD, PRE_CREATION, UNKNOWN
 
 class SVNTool(SCMTool):
     def __init__(self, repository):
@@ -19,6 +19,9 @@
 
         import pysvn
         self.client = pysvn.Client()
+        def callback_ssl(trust_dict):
+            return True, trust_dict['failures'], False
+        self.client.callback_ssl_server_trust_prompt = callback_ssl
         if repository.username:
             self.client.set_default_username(str(repository.username))
         if repository.password:
@@ -55,15 +58,23 @@
             stre = str(e)
             if 'File not found' in stre:
                 raise FileNotFoundError(path, revision, str(e))
-            elif 'callback_ssl_server_trust_prompt required' in stre:
-                raise SCMError(
-                    'HTTPS certificate not accepted.  Please ensure that ' +
-                    'the proper certificate exists in ~/.subversion/auth ' +
-                    'for the user that reviewboard is running as.')
             else:
                 raise SCMError(e)
 
 
+    def get_changeset(self, changesetid):
+        r = self.__normalize_revision(changesetid)
+        log = self.client.log(self.repopath, r, r, True)[0]
+        cs = ChangeSet()
+        cs.changenum = changesetid
+        cs.summary = log['message']
+        def pathinfo(path):
+            return path['action'] + ' ' + path['path']
+        cs.description = '\n'.join(pathinfo(path) for path in log['changed_paths'])
+        cs.username = log['author']
+        return cs
+        
+
     def parse_diff_revision(self, file_str, revision_str):
         if revision_str == "(working copy)":
             return file_str, HEAD
Index: webapi/json.py
===================================================================
--- webapi/json.py	(revision 1137)
+++ webapi/json.py	(working copy)
@@ -19,8 +19,6 @@
 from reviewboard.accounts.models import Profile
 from reviewboard.diffviewer.forms import UploadDiffForm, EmptyDiffError
 from reviewboard.diffviewer.models import FileDiff, DiffSet
-from reviewboard.reviews.email import mail_review, mail_review_request, \
-                                      mail_reply
 from reviewboard.reviews.forms import UploadScreenshotForm
 from reviewboard.reviews.models import ChangeNumberInUseError, \
                                        InvalidChangeNumberError, \
@@ -54,6 +52,7 @@
 LOGIN_FAILED              = JsonError(104, "The username or password was " +
                                            "not correct")
 INVALID_FORM_DATA         = JsonError(105, "One or more fields had errors")
+INVALID_USER              = JsonError(106, "User does not exist")
 
 UNSPECIFIED_DIFF_REVISION = JsonError(200, "Diff revision not specified")
 INVALID_DIFF_REVISION     = JsonError(201, "Invalid diff revision")
@@ -389,7 +388,17 @@
     try:
         repository_path = request.POST.get('repository_path',
                                            settings.DEFAULT_REPOSITORY_PATH)
-        repository_id = request.POST.get('repository_id', None)
+        repository_id = request.POST.get('repository_id')
+        submit_as = request.POST.get('submit_as')
+        if submit_as:
+            if not request.user.is_staff:
+                return JsonResponseError(request, PERMISSION_DENIED)
+            try:
+                user = User.objects.filter(username=submit_as)[0]
+            except IndexError:
+                return JsonResponseError(request, INVALID_USER)
+        else:
+            user = request.user
 
         if repository_path == None and repository_id == None:
             return JsonResponseError(request, MISSING_REPOSITORY)
@@ -399,7 +408,7 @@
             Q(mirror_path=repository_path))
 
         review_request = ReviewRequest.objects.create(
-            request.user, repository, request.POST.get('changenum', None))
+            user, repository, request.POST.get('changenum', None))
 
         return JsonResponse(request, {'review_request': review_request})
     except Repository.DoesNotExist, e:
@@ -419,7 +428,7 @@
     """
     review_request = get_object_or_404(ReviewRequest, pk=review_request_id)
 
-    if not review_request.public and review_request.submitter != request.user:
+    if not review_request.is_visible_to(request.user):
         return JsonResponseError(request, PERMISSION_DENIED)
 
     return JsonResponse(request, {'review_request': review_request})
@@ -431,8 +440,7 @@
         review_request = ReviewRequest.objects.get(changenum=changenum,
                                                    repository=repository_id)
 
-        if not review_request.public and \
-           review_request.submitter != request.user:
+        if not review_request.is_visible_to(request.user):
             return JsonResponseError(request, PERMISSION_DENIED)
 
         return JsonResponse(request, {'review_request': review_request})
@@ -574,7 +582,7 @@
     except ReviewRequestDraft.DoesNotExist:
         return JsonResponseError(request, DOES_NOT_EXIST)
 
-    if review_request.submitter != request.user:
+    if not review_request.is_mutable_by(request.user):
         return JsonResponseError(request, PERMISSION_DENIED)
 
     draft.delete()
@@ -591,15 +599,12 @@
     except ReviewRequestDraft.DoesNotExist:
         return JsonResponseError(request, DOES_NOT_EXIST)
 
-    if review_request.submitter != request.user:
+    if not review_request.is_mutable_by(request.user):
         return JsonResponseError(request, PERMISSION_DENIED)
 
-    changes = draft.save_draft()
+    draft.save_draft()
     draft.delete()
 
-    if settings.SEND_REVIEW_MAIL and changes:
-        mail_review_request(request.user, review_request, changes)
-
     return JsonResponse(request)
 
 
@@ -618,7 +623,7 @@
 
 
 def _prepare_draft(request, review_request):
-    if request.user != review_request.submitter:
+    if not review_request.is_mutable_by(request.user):
         return JsonResponseError(request, PERMISSION_DENIED)
     return ReviewRequestDraft.create(review_request)
 
@@ -778,9 +783,6 @@
     else:
         review.save()
 
-    if publish and settings.SEND_REVIEW_MAIL:
-        mail_review(request.user, review)
-
     return JsonResponse(request)
 
 
@@ -957,9 +959,6 @@
                                    user=request.user)
         reply.publish()
 
-        if settings.SEND_REVIEW_MAIL:
-            mail_reply(request.user, reply)
-
         return JsonResponse(request)
     except Review.DoesNotExist:
         return JsonResponseError(request, DOES_NOT_EXIST)
@@ -1008,11 +1007,14 @@
 def new_diff(request, review_request_id):
     review_request = get_object_or_404(ReviewRequest, pk=review_request_id)
 
-    if review_request.submitter != request.user:
+    if not review_request.is_mutable_by(request.user):
         return JsonResponseError(request, PERMISSION_DENIED)
 
     form_data = request.POST.copy()
     form = UploadDiffForm(review_request.repository, form_data, request.FILES)
+    # allow no basedir for diffs done against the repository root
+    if 'basedir' in form_data and not form_data['basedir']:
+        del(form.fields['basedir'])
 
     if not form.is_valid():
         return JsonResponseFormError(request, form)
@@ -1031,12 +1033,13 @@
                 'path': [str(e)]
             }
         })
-    except Exception, e:
+    except:
+        import sys, traceback
         # This could be very wrong, but at least they'll see the error.
         # We probably want a new error type for this.
         return JsonResponseError(request, INVALID_FORM_DATA, {
             'fields': {
-                'path': [str(e)]
+                'path': [traceback.format_exception(*sys.exc_info())]
             }
         })
 
@@ -1067,7 +1070,7 @@
 def new_screenshot(request, review_request_id):
     review_request = get_object_or_404(ReviewRequest, pk=review_request_id)
 
-    if review_request.submitter != request.user:
+    if not review_request.is_mutable_by(request.user):
         return JsonResponseError(request, PERMISSION_DENIED)
 
     form_data = request.POST.copy()
