diff --git a/reviewboard/diffviewer/diffutils.py b/reviewboard/diffviewer/diffutils.py
--- a/reviewboard/diffviewer/diffutils.py
+++ b/reviewboard/diffviewer/diffutils.py
@@ -1026,6 +1026,11 @@ def get_diff_files(diffset, filediff=None, interdiffset=None,
             basepath = ""
             basename = filediff.source_file
 
+        # Elide absurdly long revision strings. Necessary for Darcs, which
+        # requires large patch contexts.
+        if len(source_revision) > 80:
+            source_revision = source_revision[:76] + "..."
+
         file = {
             'depot_filename': filediff.source_file,
             'basename': basename,
diff --git a/reviewboard/scmtools/darcs.py b/reviewboard/scmtools/darcs.py
--- /dev/null
+++ b/reviewboard/scmtools/darcs.py
@@ -0,0 +1,143 @@
+import logging
+import os
+import subprocess
+import xml.parsers.expat
+
+from reviewboard.diffviewer.parser import DiffParser
+from reviewboard.scmtools.core import SCMTool, HEAD, PRE_CREATION, UNKNOWN
+from reviewboard.scmtools.errors import SCMError
+
+
+class DarcsTool(SCMTool):
+    name = 'Darcs'
+    dependencies = {
+        'executables': ['darcs']
+    }
+
+    def __init__(self, repository):
+        self.repopath = repository.path
+
+        SCMTool.__init__(self, repository)
+
+        self.client = DarcsClient(self.repopath)
+
+    def get_diffs_use_absolute_paths(self):
+        return True
+
+    def get_file(self, path, rev = None):
+        return self.client.get_file(path, rev)
+
+    def parse_diff_revision(self, file_str, revision_str):
+        return file_str, revision_str
+
+    def get_fields(self):
+        return ['diff_path']
+
+    def get_parser(self, data):
+        return DarcsDiffParser(self.client, data)
+
+
+class DarcsDiffParser(DiffParser):
+    def __init__(self, client, data):
+        self.client = client
+        super(DarcsDiffParser, self).__init__(data)
+
+    def _strip_leading(self, name):
+        return name[(name.find("/") + 1):]
+
+    def _parse_patch(self, i):
+        if self.lines[i].startswith("["):
+            name = self.lines[i][1:]
+            author, timestamp = self.lines[i + 1].split("**", 1)
+            return ["(exact \"%s\" && date %s && author \"%s\")"
+                    % (self._escape(name), timestamp.strip(" ]"), author)]
+        else:
+            return []
+
+    def _escape(self, string):
+        return string.replace("\"", "\\\"")
+
+    def _skip_to_new_patches(self, i):
+        while i < len(self.lines):
+            if self._starts_new_patches(i):
+                break
+            i += 1
+        return i
+
+    def _starts_new_patches(self, i):
+        return self.lines[i].startswith("New patches:")
+
+    def _starts_context(self, i):
+        return self.lines[i].startswith("Context:")
+
+    def _starts_hash(self, i):
+        return self.lines[i].startswith("Patch bundle hash:")
+
+    def parse(self):
+        self.patches = []
+        self.context_patches = []
+        i = self._skip_to_new_patches(0)
+        while i < len(self.lines):
+            self.patches += self._parse_patch(i)
+            if self._starts_context(i):
+                break
+            i += 1
+        while i < len(self.lines):
+            self.context_patches += self._parse_patch(i)
+            if self._starts_hash(i):
+                break
+            i += 1
+        origHashes = self.client.patches_to_hashes(self.context_patches)
+        newId = " || ".join(self.patches)
+        origId = " || ".join(origHashes)
+        files = super(DarcsDiffParser, self).parse()
+        i = 0
+        while i < len(files):
+            files[i].origFile = self._strip_leading(files[i].origFile)
+            files[i].newFile = self._strip_leading(files[i].newFile)
+            files[i].origInfo = origId
+            files[i].origChangesetId = origId
+            files[i].newInfo = newId 
+            i += 1
+        return files
+
+class DarcsClient:
+    def __init__(self, path):
+        self.repodir = path
+
+    def _darcs(self, args, input_str = None):
+        command = ["darcs"] + args + ["--repo", self.repodir]
+        logging.debug("%s" % command)
+        d = subprocess.Popen(command,
+                             stdin = subprocess.PIPE,
+                             stderr = subprocess.STDOUT,
+                             stdout = subprocess.PIPE)
+        output_str, error_str = d.communicate(input_str)
+        if d.returncode != 0:
+            raise SCMError("Darcs: %s" % (output_str))
+        else:
+            return output_str
+
+    def patches_to_hashes(self, patches):
+        assert len(patches) > 0
+        self.hashes = []
+        def start_element(name, attrs):
+            if name == "patch":
+                self.hashes.append("hash " + attrs["hash"])
+        x = xml.parsers.expat.ParserCreate()
+        x.StartElementHandler = start_element
+        clog = self._darcs(["changes", "--xml-output",
+                            "--matches", " || ".join(patches)])
+        x.Parse(clog)
+        return self.hashes
+
+    def get_file(self, path, rev = HEAD):
+        if rev == HEAD or len(rev) == 0:
+            matches = []
+        elif rev == UNKNOWN:
+            raise NotImplementedError
+        elif rev == PRE_CREATION:
+            return ""
+        else:
+            matches = ["--match", rev]
+        return self._darcs(["show", "contents", path] + matches)
