diff --git a/reviewboard/scmtools/svn/base.py b/reviewboard/scmtools/svn/base.py
index 138cdfd53cce4efe2ea029ec3c5844f79b85c273..ec0b6d6ab8abe4c3f695b15bb8bc585f14fa3e59 100644
--- a/reviewboard/scmtools/svn/base.py
+++ b/reviewboard/scmtools/svn/base.py
@@ -16,6 +16,9 @@ class Client(object):
     ID_KEYWORDS = ['Id']
     HEADER_KEYWORDS = ['Header']
 
+    LOG_DEFAULT_START = 'HEAD'
+    LOG_DEFAULT_END = '1'
+
     # Mapping of keywords to known aliases
     keywords = {
         # Standard keywords
@@ -66,6 +69,21 @@ class Client(object):
         """Returns a list of SVN keywords for a given path."""
         raise NotImplementedError
 
+    def get_log(self, path, start=None, end=None, limit=None,
+                discover_changed_paths=False, limit_to_path=False):
+        """Returns log entries at the specified path.
+
+        The log entries will appear ordered from most recent to least,
+        with 'start' being the most recent commit in the range.
+
+        If 'start' is not specified, then it will default to 'HEAD'. If
+        'end' is not specified, it will default to '1'.
+
+        To limit the commits to the given path, not factoring in history
+        from any branch operations, set 'limit_to_path' to True.
+        """
+        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 99a52e0d9a2d72b9f0cc9747e2a9a82a697ead69..7e67e1dae8fc03611ee46616ed0b0bd0f85ec71e 100644
--- a/reviewboard/scmtools/svn/pysvn.py
+++ b/reviewboard/scmtools/svn/pysvn.py
@@ -326,3 +326,38 @@ class Client(base.Client):
                 on_failure(e, path, cert)
 
         return cert
+
+    def get_log(self, path, start=None, end=None, limit=None,
+                discover_changed_paths=False, limit_to_path=False):
+        """Returns log entries at the specified path.
+
+        The log entries will appear ordered from most recent to least,
+        with 'start' being the most recent commit in the range.
+
+        If 'start' is not specified, then it will default to 'HEAD'. If
+        'end' is not specified, it will default to '1'.
+
+        To limit the commits to the given path, not factoring in history
+        from any branch operations, set 'limit_to_path' to True.
+        """
+        if start is None:
+            start = self.LOG_DEFAULT_START
+
+        if end is None:
+            end = self.LOG_DEFAULT_END
+
+        commits = self.client.log(
+            self.normalize_path(path),
+            limit=limit,
+            revision_start=Revision(opt_revision_kind.number, start),
+            revision_end=Revision(opt_revision_kind.number, end),
+            discover_changed_paths=discover_changed_paths,
+            strict_node_history=limit_to_path)
+
+        for commit in commits:
+            commit['revision'] = six.text_type(commit['revision'].number)
+
+            if 'date' in commit:
+                commit['date'] = datetime.utcfromtimestamp(commit['date'])
+
+        return commits
diff --git a/reviewboard/scmtools/svn/subvertpy.py b/reviewboard/scmtools/svn/subvertpy.py
index 88d62d3e9844aad7d6bb056560e2f87494ba43ac..afd60e80ef92143d01656f92d8c28c9c198823f9 100644
--- a/reviewboard/scmtools/svn/subvertpy.py
+++ b/reviewboard/scmtools/svn/subvertpy.py
@@ -3,6 +3,7 @@ from __future__ import absolute_import, unicode_literals
 
 import logging
 import os
+from datetime import datetime
 
 try:
     from subvertpy import ra, SubversionException, __version__
@@ -205,7 +206,9 @@ class Client(base.Client):
         return self.client.propget(SVN_KEYWORDS, path, None, revnum).get(path)
 
     def _normalize_revision(self, revision, negatives_allowed=True):
-        if revision == HEAD:
+        if revision is None:
+            return None
+        elif revision == HEAD:
             return B('HEAD')
         elif revision == PRE_CREATION:
             raise FileNotFoundError('', revision)
@@ -213,6 +216,7 @@ class Client(base.Client):
             revnum = int(revision.name)
         elif isinstance(revision, (B,) + six.string_types):
             revnum = int(revision)
+
         return revnum
 
     def get_filenames_in_revision(self, revision):
@@ -326,3 +330,50 @@ class Client(base.Client):
                 on_failure(e, path, cert)
 
         return cert
+
+    def get_log(self, path, start=None, end=None, limit=None,
+                discover_changed_paths=False, limit_to_path=False):
+        """Returns log entries at the specified path.
+
+        The log entries will appear ordered from most recent to least,
+        with 'start' being the most recent commit in the range.
+
+        If 'start' is not specified, then it will default to 'HEAD'. If
+        'end' is not specified, it will default to '1'.
+
+        To limit the commits to the given path, not factoring in history
+        from any branch operations, set 'limit_to_path' to True.
+        """
+        def log_cb(changed_paths, revision, props, has_children):
+            commit = {
+                'revision': six.text_type(revision),
+            }
+
+            if 'svn:date' in props:
+                commit['date'] = datetime.strptime(props['svn:date'],
+                                                   '%Y-%m-%dT%H:%M:%S.%fZ')
+
+            if 'svn:author' in props:
+                commit['author'] = props['svn:author']
+
+            if 'svn:log' in props:
+                commit['message'] = props['svn:log']
+
+            commits.append(commit)
+
+        if start is None:
+            start = self.LOG_DEFAULT_START
+
+        if end is None:
+            end = self.LOG_DEFAULT_END
+
+        commits = []
+        self.client.log(log_cb,
+                        paths=B(self.normalize_path(path)),
+                        start_rev=self._normalize_revision(start),
+                        end_rev=self._normalize_revision(end),
+                        limit=limit,
+                        discover_changed_paths=discover_changed_paths,
+                        strict_node_history=limit_to_path)
+
+        return commits
