diff --git a/reviewboard/scmtools/svn/__init__.py b/reviewboard/scmtools/svn/__init__.py
index 98ea6d51230fbc894ef1e5048e6ff9f458623674..7f844f6e364fb95d8efe12a13bb10b89779887f5 100644
--- a/reviewboard/scmtools/svn/__init__.py
+++ b/reviewboard/scmtools/svn/__init__.py
@@ -202,9 +202,30 @@ class SVNTool(SCMTool):
 
         This returns a Commit object containing the details of the commit.
         """
-        cache_key = self.repository.get_commit_cache_key(revision)
+        revision = int(revision)
 
-        return self.client.get_change(revision, cache_key)
+        commits = self.client.get_log('/', start=revision, limit=2)
+
+        commit = commits[0]
+        message = commit['message'].decode('utf-8', 'replace')
+        author_name = commit['author'].decode('utf-8', 'replace')
+        date = commit['date'].isoformat()
+
+        if len(commits) > 1:
+            base_revision = commits[1]['revision']
+        else:
+            base_revision = 0
+
+        try:
+            diff = self.client.diff(base_revision, revision)
+        except Exception as e:
+            raise SCMError(e)
+
+        commit = Commit(author_name, six.text_type(revision), date,
+                        message, six.text_type(base_revision))
+        commit.diff = diff
+
+        return commit
 
     def normalize_patch(self, patch, filename, revision=HEAD):
         """
diff --git a/reviewboard/scmtools/svn/base.py b/reviewboard/scmtools/svn/base.py
index ca54bc5e864329e1639aa2ff1d1992927a486478..b6e4a797cb470800ed97ca33ce86e753795f576e 100644
--- a/reviewboard/scmtools/svn/base.py
+++ b/reviewboard/scmtools/svn/base.py
@@ -92,6 +92,16 @@ class Client(object):
         """
         raise NotImplementedError
 
+    def diff(self, revision1, revision2, path=None):
+        """Returns a diff between two revisions.
+
+        The diff will contain the differences between the two revisions,
+        and may optionally be limited to a specific path.
+
+        The returned diff will be returned as a Unicode object.
+        """
+        raise NotImplementedError
+
     def collapse_keywords(self, data, keyword_str):
         """
         Collapse SVN keywords in string.
diff --git a/reviewboard/scmtools/svn/pysvn.py b/reviewboard/scmtools/svn/pysvn.py
index cd01fdb041865ba0932dfd7e6fc1a9335acc4b25..55b337d03a438b10fdbf444a69691f21fc0be7ef 100644
--- a/reviewboard/scmtools/svn/pysvn.py
+++ b/reviewboard/scmtools/svn/pysvn.py
@@ -17,13 +17,12 @@ except ImportError:
     # the testsuite.
     has_svn_backend = False
 
-from django.core.cache import cache
 from django.utils import six
 from django.utils.datastructures import SortedDict
 from django.utils.six.moves.urllib.parse import (urlsplit, urlunsplit, quote)
 from django.utils.translation import ugettext as _
 
-from reviewboard.scmtools.core import Commit, HEAD, PRE_CREATION
+from reviewboard.scmtools.core import HEAD, PRE_CREATION
 from reviewboard.scmtools.errors import (AuthenticationError,
                                          FileNotFoundError,
                                          SCMError)
@@ -69,7 +68,7 @@ class Client(base.Client):
                                        quote(path),
                                        '', ''))
 
-            normrev = self.__normalize_revision(revision)
+            normrev = self._normalize_revision(revision)
             return cb(normpath, normrev)
 
         except ClientError as e:
@@ -89,54 +88,6 @@ class Client(base.Client):
             else:
                 raise SCMError(e)
 
-    def get_change(self, revision, cache_key):
-        """Get an individual change.
-
-        This returns a tuple with the commit message and the diff contents.
-        """
-        revision = int(revision)
-        head_revision = Revision(opt_revision_kind.number, revision)
-
-        commit = cache.get(cache_key)
-        if commit:
-            message = commit.message
-            author_name = commit.author_name
-            date = commit.date
-            base_revision = Revision(opt_revision_kind.number, commit.parent)
-        else:
-            commits = self.client.log(
-                self.repopath,
-                revision_start=head_revision,
-                limit=2)
-            commit = commits[0]
-            message = commit['message'].decode('utf-8', 'replace')
-            author_name = commit['author'].decode('utf-8', 'replace')
-            date = datetime.utcfromtimestamp(commit['date']).\
-                isoformat()
-
-            try:
-                commit = commits[1]
-                base_revision = commit['revision']
-            except IndexError:
-                base_revision = Revision(opt_revision_kind.number, 0)
-
-        tmpdir = mkdtemp(prefix='reviewboard-svn.')
-
-        diff = self.client.diff(
-            tmpdir,
-            self.repopath,
-            revision1=base_revision,
-            revision2=head_revision,
-            header_encoding='utf-8',
-            diff_options=['-u']).decode('utf-8')
-
-        rmtree(tmpdir)
-
-        commit = Commit(author_name, six.text_type(head_revision.number), date,
-                        message, six.text_type(base_revision.number))
-        commit.diff = diff
-        return commit
-
     def _get_file_data(self, normpath, normrev):
         data = self.client.cat(normpath, normrev)
 
@@ -165,7 +116,7 @@ class Client(base.Client):
 
     def get_filenames_in_revision(self, revision):
         """Returns a list of filenames associated with the revision."""
-        r = self.__normalize_revision(revision)
+        r = self._normalize_revision(revision)
         logs = self.client.log(self.repopath, r, r, True)
 
         if len(logs) == 0:
@@ -175,7 +126,7 @@ class Client(base.Client):
         else:
             assert False
 
-    def __normalize_revision(self, revision):
+    def _normalize_revision(self, revision):
         if revision == HEAD:
             r = Revision(opt_revision_kind.head)
         elif revision == PRE_CREATION:
@@ -296,3 +247,38 @@ class Client(base.Client):
             }
 
         return result
+
+    def diff(self, revision1, revision2, path=None):
+        """Returns a diff between two revisions.
+
+        The diff will contain the differences between the two revisions,
+        and may optionally be limited to a specific path.
+
+        The returned diff will be returned as a Unicode object.
+        """
+        if path:
+            path = self.normalize_path(path)
+        else:
+            path = self.repopath
+
+        tmpdir = mkdtemp(prefix='reviewboard-svn.')
+
+        try:
+            diff = self.client.diff(
+                tmpdir,
+                path,
+                revision1=self._normalize_revision(revision1),
+                revision2=self._normalize_revision(revision2),
+                header_encoding='utf-8',
+                diff_options=['-u']).decode('utf-8')
+        except Exception as e:
+            logging.error('Failed to generate diff using pysvn for revisions '
+                          '%s:%s for path %s: %s',
+                          revision1, revision2, path, e, exc_info=1)
+            raise SCMError(
+                _('Unable to get diff revisions %s through %s: %s')
+                % (revision1, revision2, e))
+        finally:
+            rmtree(tmpdir)
+
+        return diff
diff --git a/reviewboard/scmtools/svn/subvertpy.py b/reviewboard/scmtools/svn/subvertpy.py
index 662d88eae6c9a9ab6beb1ea50754d5add6cbca0d..0f8ea60de696b375f4dc159afd166f1843e241f8 100644
--- a/reviewboard/scmtools/svn/subvertpy.py
+++ b/reviewboard/scmtools/svn/subvertpy.py
@@ -16,20 +16,17 @@ except ImportError:
     # the testsuite.
     has_svn_backend = False
 
-from django.core.cache import cache
 from django.utils import six
 from django.utils.datastructures import SortedDict
+from django.utils.translation import ugettext as _
 
-from reviewboard.scmtools.core import Commit, Revision, HEAD, PRE_CREATION
+from reviewboard.scmtools.core import Revision, HEAD, PRE_CREATION
 from reviewboard.scmtools.errors import FileNotFoundError, SCMError
 from reviewboard.scmtools.svn import base
 
 B = six.binary_type
 DIFF_UNIFIED = [B('-u')]
-SVN_AUTHOR = B('svn:author')
-SVN_DATE = B('svn:date')
 SVN_KEYWORDS = B('svn:keywords')
-SVN_LOG = B('svn:log')
 
 
 class Client(base.Client):
@@ -69,52 +66,6 @@ class Client(base.Client):
     def set_ssl_server_trust_prompt(self, cb):
         self._ssl_trust_prompt_cb = cb
 
-    @property
-    def ra(self):
-        """Lazily creates the ``RemoteAccess`` object so
-        ``accept_ssl_certificate`` works properly.
-        """
-        if not hasattr(self, '_ra'):
-            self._ra = ra.RemoteAccess(self.repopath, auth=self.auth)
-        return self._ra
-
-    def get_change(self, revision, cache_key):
-        """Get an individual change.
-
-        This returns a tuple with the commit message and the diff contents.
-        """
-        revision = int(revision)
-
-        commit = cache.get(cache_key)
-        if commit:
-            message = commit.message
-            author_name = commit.author_name
-            date = commit.date
-            base_revision = commit.parent
-        else:
-            commits = list(self.ra.iter_log(None, revision, 0, limit=2))
-            rev, props = commits[0][1:3]
-            message = props[SVN_LOG].decode('utf-8', 'replace')
-            author_name = props[SVN_AUTHOR].decode('utf-8', 'replace')
-            date = props[SVN_DATE]
-
-            if len(commits) > 1:
-                base_revision = commits[1][1]
-            else:
-                base_revision = 0
-
-        try:
-            out, err = self.client.diff(int(base_revision), int(revision),
-                                        self.repopath, self.repopath,
-                                        diffopts=DIFF_UNIFIED)
-        except Exception as e:
-            raise SCMError(e)
-
-        commit = Commit(author_name, six.text_type(revision), date,
-                        message, six.text_type(base_revision))
-        commit.diff = out.read().decode('utf-8')
-        return commit
-
     def get_file(self, path, revision=HEAD):
         """Returns the contents of a given file at the given revision."""
         if not path:
@@ -146,11 +97,11 @@ class Client(base.Client):
         elif revision == PRE_CREATION:
             raise FileNotFoundError('', revision)
         elif isinstance(revision, Revision):
-            revnum = int(revision.name)
+            revision = int(revision.name)
         elif isinstance(revision, (B,) + six.string_types):
-            revnum = int(revision)
+            revision = int(revision)
 
-        return revnum
+        return revision
 
     def get_filenames_in_revision(self, revision):
         """Returns a list of filenames associated with the revision."""
@@ -338,3 +289,43 @@ class Client(base.Client):
                 }
 
         return result
+
+    def diff(self, revision1, revision2, path=None):
+        """Returns a diff between two revisions.
+
+        The diff will contain the differences between the two revisions,
+        and may optionally be limited to a specific path.
+
+        The returned diff will be returned as a Unicode object.
+        """
+        if path:
+            path = self.normalize_path(path)
+        else:
+            path = self.repopath
+
+        out = None
+        err = None
+
+        try:
+            out, err = self.client.diff(self._normalize_revision(revision1),
+                                        self._normalize_revision(revision2),
+                                        B(path),
+                                        B(path),
+                                        diffopts=DIFF_UNIFIED)
+
+            diff = out.read().decode('utf-8')
+        except Exception as e:
+            logging.error('Failed to generate diff using subvertpy for '
+                          'revisions %s:%s for path %s: %s',
+                          revision1, revision2, path, e, exc_info=1)
+            raise SCMError(
+                _('Unable to get diff revisions %s through %s: %s')
+                % (revision1, revision2, e))
+        finally:
+            if out:
+                out.close()
+
+            if err:
+                err.close()
+
+        return diff
