Index: scmtools/svn.py
===================================================================
--- scmtools/svn.py	(revision 1190)
+++ scmtools/svn.py	(working copy)
@@ -9,7 +9,23 @@
 from reviewboard.scmtools.core import \
     SCMError, FileNotFoundError, SCMTool, HEAD, PRE_CREATION, UNKNOWN
 
+
 class SVNTool(SCMTool):
+    # Mapping of keywords to known aliases
+    keywords = {
+        # Standard keywords
+        'Date':                ['Date', 'LastChangedDate'],
+        'Revision':            ['Revision', 'LastChangedRevision', 'Rev'],
+        'Author':              ['Author', 'LastChangedBy'],
+        'HeadURL':             ['HeadURL', 'URL'],
+
+        # Aliases
+        'LastChangedDate':     ['LastChangedDate', 'Date'],
+        'LastChangedRevision': ['LastChangedRevision', 'Rev', 'Revision'],
+        'LastChangedBy':       ['LastChangedBy', 'Author'],
+        'URL':                 ['URL', 'HeadURL'],
+    }
+
     def __init__(self, repository):
         self.repopath = repository.path
         if self.repopath[-1] == '/':
@@ -49,8 +65,21 @@
             raise FileNotFoundError(path, revision)
 
         try:
-            return self.client.cat(self.__normalize_path(path),
-                                   self.__normalize_revision(revision))
+            normpath = self.__normalize_path(path)
+            normrev  = self.__normalize_revision(revision)
+
+            data = self.client.cat(normpath, normrev)
+
+            # Find out if this file has any keyword expansion set.
+            # If it does, collapse these keywords. This is because SVN
+            # will return the file expanded to us, which would break patching.
+            keywords = self.client.propget("svn:keywords", normpath, normrev,
+                                           recurse=True)
+
+            if normpath in keywords:
+                data = self.collapse_keywords(data, keywords[normpath])
+
+            return data
         except ClientError, e:
             stre = str(e)
             if 'File not found' in stre:
@@ -63,7 +92,36 @@
             else:
                 raise SCMError(e)
 
+    def collapse_keywords(self, data, keyword_str):
+        """
+        Collapse SVN keywords in string.
 
+        SVN allows for several keywords (such as $Id$ and $Revision$) to
+        be expanded, though these keywords are limited to a fixed set
+        (and associated aliases) and must be enabled per-file.
+
+        Keywords can take two forms: $Keyword$ and $Keyword::     $
+        The latter allows the field to take a fixed size when expanded.
+
+        When we cat a file on SVN, the keywords come back expanded, which
+        isn't good for us as we need to diff against the collapsed version.
+        This function makes that transformation.
+        """
+        def repl(m):
+            if m.group(2):
+                return "$%s::%s$" % (m.group(1), " " * len(m.group(3)))
+
+            return "$%s$" % m.group(1)
+
+        # Get any aliased keywords
+        keywords = [keyword
+                    for name in keyword_str.split(" ")
+                    for keyword in self.keywords.get(name, [])]
+
+        return re.sub(r"\$(%s):(:?)([^\$]+)\$" % '|'.join(keywords),
+                      repl, data)
+
+
     def parse_diff_revision(self, file_str, revision_str):
         if revision_str == "(working copy)":
             return file_str, HEAD
Index: scmtools/testdata/svn_repo/db/current
===================================================================
--- scmtools/testdata/svn_repo/db/current	(revision 1190)
+++ scmtools/testdata/svn_repo/db/current	(working copy)
@@ -1 +1 @@
-3 5 1
+4 5 1
Index: scmtools/testdata/svn_repo/db/revprops/4
===================================================================
--- scmtools/testdata/svn_repo/db/revprops/4	(revision 0)
+++ scmtools/testdata/svn_repo/db/revprops/4	(revision 0)
@@ -0,0 +1,14 @@
+K 10
+svn:author
+V 7
+chipx86
+K 8
+svn:date
+V 27
+2008-02-24T23:04:51.477951Z
+K 7
+svn:log
+V 66
+Add a couple keyword tests, and add Revision to the keyword list.
+
+END
Index: scmtools/testdata/svn_repo/db/revs/4
===================================================================

Property changes on: scmtools/testdata/svn_repo/db/revs/4
___________________________________________________________________
Name: svn:mime-type
   + application/octet-stream

Index: scmtools/tests.py
===================================================================
--- scmtools/tests.py	(revision 1190)
+++ scmtools/tests.py	(working copy)
@@ -249,16 +249,24 @@
         # 'svn cat' will expand special variables in svn:keywords,
         # but 'svn diff' doesn't expand anything.  This causes the
         # patch to fail if those variables appear in the patch context.
-        diff = "Index: Makefile\n==========================================" + \
-               "=========================\n--- Makefile    (revision 3)\n++" + \
-               "+ Makefile    (working copy)\n@@ -1,4 +1,5 @@\n # $Id$\n+# " + \
-               "foo\n include ../tools/Makefile.base-vars\n NAME = misc-doc" + \
-               "s\n OUTNAME = svn-misc-docs\n"
+        diff = "Index: Makefile\n" \
+               "===========================================================" \
+               "========\n" \
+               "--- Makefile    (revision 4)\n" \
+               "+++ Makefile    (working copy)\n" \
+               "@@ -1,6 +1,7 @@\n" \
+               " # $Id$\n" \
+               " # $Rev$\n" \
+               " # $Revision::     $\n" \
+               "+# foo\n" \
+               " include ../tools/Makefile.base-vars\n" \
+               " NAME = misc-docs\n" \
+               " OUTNAME = svn-misc-docs\n"
 
         filename = 'trunk/doc/misc-docs/Makefile'
-        rev = Revision('3')
+        rev = Revision('4')
         file = self.tool.get_file(filename, rev)
-        newfile = patch(diff, file, filename) # Throws an exception!
+        patch(diff, file, filename)
 
 
 class PerforceTests(unittest.TestCase):
