diff --git a/rbtools/postreview.py b/rbtools/postreview.py
--- a/rbtools/postreview.py
+++ b/rbtools/postreview.py
@@ -3,11 +3,13 @@ import base64
 import cookielib
 import difflib
 import getpass
+import io
 import marshal
 import mimetools
 import ntpath
 import os
 import re
+import shutil
 import socket
 import stat
 import subprocess
@@ -15,6 +17,7 @@ import sys
 import tempfile
 import urllib
 import urllib2
+import xml.parsers.expat
 from optparse import OptionParser
 from tempfile import mkstemp
 from urlparse import urljoin, urlparse
@@ -2246,6 +2249,112 @@ class PerforceClient(SCMClient):
             # XXX: This breaks on filenames with spaces.
             return where_output[-1]['data'].split(' ')[2].strip()
 
+class DarcsClient(SCMClient):
+    """
+    Fetch info for and generate diffs againts Darcs repositories.
+    """
+    def get_repository_info(self):
+        try:
+            info_str = execute(["darcs", "show", "repo"])
+            self.remote_path = ""
+            for l in info_str.splitlines():
+                key, value = l.split(":", 1)
+                if key.strip() == "Default Remote":
+                    self.remote_path = value.strip()
+            return RepositoryInfo(path = self.remote_path)
+        except:
+            return None
+
+    def _mktemp_name(self):
+        tmph, tmpn = tempfile.mkstemp()
+        os.close(tmph)
+        os.remove(tmpn)
+        return tmpn
+
+    def diff(self, args):
+        """
+        Returns the generated diff and optional parent diff for this
+        repository.
+
+        The returned tuple is (diff_string, parent_diff_string)
+        """
+        patches = self._get_sendable_patches([])
+        dpatch_tmp = self._make_dpatch([])
+        diff = self._get_diff(dpatch_tmp, patches)
+        dpatch = self._get_dpatch(dpatch_tmp)
+
+        self.guess_summary(patches[0])
+        self.guess_description(patches[0], patches[-1])
+        return (dpatch + "\n" + diff, None)
+
+    def _make_dpatch(self, match):
+        dpatch_tmp = self._mktemp_name()
+        execute(["darcs", "send", "-a", "--no-edit-description",
+                 "-o", dpatch_tmp] + match + [self.remote_path])
+        return dpatch_tmp
+
+    def _get_dpatch(self, dpatch_tmp):
+        dpatch_h = io.open(dpatch_tmp)
+        dpatch = dpatch_h.read()
+        dpatch_h.close()
+        os.remove(dpatch_tmp)
+        return dpatch
+
+    def _get_diff(self, dpatch_tmp, patches):
+        repo_tmp = self._mktemp_name()
+        execute(["darcs", "get", "--lazy", "./", repo_tmp])
+        execute(["darcs", "unpull", "-a", "--repo", repo_tmp, 
+                 "--matches", " || ".join(patches)])
+        execute(["darcs", "apply", "--repo", repo_tmp, dpatch_tmp])
+        diff = execute(["darcs", "diff", "-u", "--repo", repo_tmp,
+                        "--from-match", patches[0]])
+        shutil.rmtree(repo_tmp)
+        return diff
+
+    def _get_sendable_patches(self, match):
+        changes_xml = execute(["darcs", "send", "--xml-output", "--dry-run"]
+                               + match + [self.remote_path],
+                              split_lines = True)
+        string = ""
+        if changes_xml[1].startswith("No recorded local changes"):
+            die("No recorded local changes to send!")
+        for l in changes_xml[1:]:
+            string += l
+        return self._read_hashes(string)
+
+    def _read_hashes(self, string):
+        self._patches = []
+        def element_start(name, attrs):
+            if name == "patch":
+                self._patches.append("hash " + attrs["hash"])
+        p = xml.parsers.expat.ParserCreate()
+        p.StartElementHandler = element_start
+        p.Parse(string)
+        return self._patches
+
+    def guess_summary(self, rev):
+        """
+        Extracts the first line from the description of the given changeset.
+        """
+        if options.guess_summary and not options.summary:
+            changelog = execute(["darcs", "changes", "--match", rev])
+            lines = changelog.splitlines()
+            for l in lines:
+                if l.strip().startswith("*"):
+                    options.summary = l.strip(" *")
+
+    def guess_description(self, rev1, rev2):
+        """
+        Extracts all descriptions in the given revision range. 
+        """
+        if options.guess_description and not options.description:
+            options.description = execute(["darcs", "changes",
+                                           "--from-match", rev1,
+                                           "--to-match", rev2])
+
+    def diff_between_revisions(self, revision_range, args, repository_info):
+        raise NotImplementedError
+
 
 class MercurialClient(SCMClient):
     """
@@ -3153,6 +3262,7 @@ SCMCLIENTS = (
     SVNClient(),
     CVSClient(),
     GitClient(),
+    DarcsClient(),
     MercurialClient(),
     PerforceClient(),
     ClearCaseClient(),
@@ -3278,7 +3388,7 @@ def die(msg=None):
             pass
 
     if msg:
-        print msg
+        print >> sys.stderr, msg
 
     sys.exit(1)
 
@@ -3468,12 +3578,12 @@ def parse_options(args):
                       dest="guess_summary", action="store_true",
                       default=False,
                       help="guess summary from the latest commit (git/"
-                           "hg/hgsubversion only)")
+                           "hg/hgsubversion/darcs only)")
     parser.add_option("--guess-description",
                       dest="guess_description", action="store_true",
                       default=False,
                       help="guess description based on commits on this branch "
-                           "(git/hg/hgsubversion only)")
+                           "(git/hg/hgsubversion/darcs only)")
     parser.add_option("--testing-done",
                       dest="testing_done", default=None,
                       help="details of testing done ")
