diff --git a/reviewboard/diffviewer/diffutils.py b/reviewboard/diffviewer/diffutils.py
index 06e834f7391345b54e360435ed1fde85a2ec8572..fe9240aa8ce4afe17d4c16e1e2af432de8db3f2c 100644
--- a/reviewboard/diffviewer/diffutils.py
+++ b/reviewboard/diffviewer/diffutils.py
@@ -376,7 +376,10 @@ def get_original_file(filediff):
 
 
 def get_patched_file(buffer, filediff):
-    return patch(filediff.diff, buffer, filediff.dest_file)
+    tool = filediff.diffset.repository.get_scmtool()
+    diff = tool.normalize_patch(filediff.diff, filediff.source_file,
+                                filediff.source_revision)
+    return patch(diff, buffer, filediff.dest_file)
 
 
 def register_interesting_lines_for_filename(differ, filename):
diff --git a/reviewboard/scmtools/core.py b/reviewboard/scmtools/core.py
index 091546707a16e62c5c564356ac21e29cd64e4f0a..80236896b1513307f6c5819bfdd38b1bd239bba6 100644
--- a/reviewboard/scmtools/core.py
+++ b/reviewboard/scmtools/core.py
@@ -104,6 +104,13 @@ class SCMTool(object):
     def normalize_path_for_display(self, filename):
         return filename
 
+    def normalize_patch(self, patch, filename, revision):
+        """Adjust patch to apply in a given SCM.
+
+        Some SCMs need adjustments in the patch, such as contraction of
+        keywords for Subversion."""
+        return patch
+
     @classmethod
     def popen(cls, command, local_site_name=None):
         """Launches an application, capturing output.
diff --git a/reviewboard/scmtools/svn.py b/reviewboard/scmtools/svn.py
index ca5be5272c50c8ea61ddcf6764d2781174bef880..99f21aaad9e22565517f594d8e8c9f73c9a31286 100644
--- a/reviewboard/scmtools/svn.py
+++ b/reviewboard/scmtools/svn.py
@@ -51,6 +51,7 @@ class SVNTool(SCMTool):
     REVISION_KEYWORDS = ['Revision', 'LastChangedRevision', 'Rev']
     URL_KEYWORDS      = ['HeadURL', 'URL']
     ID_KEYWORDS       = ['Id']
+    HEADER_KEYWORDS   = ['Header']
 
     # Mapping of keywords to known aliases
     keywords = {
@@ -60,6 +61,7 @@ class SVNTool(SCMTool):
         'Revision':            REVISION_KEYWORDS,
         'HeadURL':             URL_KEYWORDS,
         'Id':                  ID_KEYWORDS,
+        'Header':              HEADER_KEYWORDS,
 
         # Aliases
         'LastChangedBy':       AUTHOR_KEYWORDS,
@@ -102,7 +104,7 @@ class SVNTool(SCMTool):
                                             # uses 'revision 0'
             """, re.VERBOSE)
 
-    def get_file(self, path, revision=HEAD):
+    def _do_on_path(self, cb, path, revision=HEAD):
         if not path:
             raise FileNotFoundError(path, revision)
 
@@ -121,20 +123,9 @@ class SVNTool(SCMTool):
                                                 urllib.quote(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])
+            normrev = self.__normalize_revision(revision)
+            return cb(normpath, normrev)
 
-            return data
         except ClientError, e:
             stre = str(e)
             if 'File not found' in stre or 'path not found' in stre:
@@ -150,6 +141,46 @@ class SVNTool(SCMTool):
             else:
                 raise SCMError(e)
 
+    def get_file(self, path, revision=HEAD):
+        def get_file_data(normpath, normrev):
+            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
+
+        return self._do_on_path(get_file_data, path, revision)
+
+    def get_keywords(self, path, revision=HEAD):
+        def get_file_keywords(normpath, normrev):
+            keywords = self.client.propget("svn:keywords", normpath, normrev,
+                                           recurse=True)
+            return keywords.get(normpath)
+
+        return self._do_on_path(get_file_keywords, path, revision)
+
+    def normalize_patch(self, patch, filename, revision=HEAD):
+        """
+	If using Subversion, we need not only contract keywords in file, but
+        also in the patch. Otherwise, if a file with expanded keyword somehow
+	ends up in the repository (e.g. by first checking in a file without
+	svn:keywords and then setting svn:keywords in the repository), RB
+	won't be able to apply a patch to such file.
+	"""
+        if revision != PRE_CREATION:
+            keywords = self.get_keywords(filename, revision)
+
+	    if keywords:
+                return self.collapse_keywords(patch, keywords)
+
+        return patch
+
     def collapse_keywords(self, data, keyword_str):
         """
         Collapse SVN keywords in string.
@@ -176,7 +207,7 @@ class SVNTool(SCMTool):
                     for name in keyword_str.split(" ")
                     for keyword in self.keywords.get(name, [])]
 
-        return re.sub(r"\$(%s):(:?)([^\$\n\r]+)\$" % '|'.join(keywords),
+        return re.sub(r"\$(%s):(:?)([^\$\n\r]*)\$" % '|'.join(keywords),
                       repl, data)
 
 
