diff --git a/rbtools/postreview.py b/rbtools/postreview.py
--- a/rbtools/postreview.py
+++ b/rbtools/postreview.py
@@ -2177,6 +2177,154 @@ class MercurialClient(SCMClient):
         return server_url
 
 
+class BazaarClient(SCMClient):
+    """
+    Bazaar client wrapper that fetches repository information and generates
+    compatible diffs.
+    
+    The :class:`RepositoryInfo` object reports whether the repository supports
+    parent diffs (every branch with a parent supports them).
+    
+    """
+    
+    BRANCH_REGEX = r'\w*(repository branch|branch root): (?P<branch_path>.+)$'
+    """
+    Regular expression that matches the path to the current branch.
+    
+    For branches with shared repositories, Bazaar reports
+    "repository branch: /foo", but for standalone branches it reports
+    "branch root: /foo".
+    
+    """
+    
+    def get_repository_info(self):
+        """
+        Find out information about the current Bazaar branch (if any) and return
+        it.
+        
+        """
+        if not check_install("bzr help"):
+            return None
+        
+        bzr_info = execute(["bzr", "info"], ignore_errors=True)
+        
+        if "ERROR: Not a branch:" in bzr_info:
+            # This is not a branch:
+            repository_info = None
+        else:
+            # This is a branch, let's get its attributes:
+            branch_match = re.search(self.BRANCH_REGEX, bzr_info, re.MULTILINE)
+            
+            path = branch_match.group("branch_path")
+            if path == ".":
+                path = os.getcwd()
+            
+            repository_info = RepositoryInfo(
+                path=path,
+                base_path="/",    # Diffs are always relative to the root.
+                supports_parent_diffs=True,
+                )
+        
+        return repository_info
+
+    def diff(self, files):
+        """
+        Return the diff of this branch with respect to its parent and set
+        the summary and description is required.
+        
+        """
+        files = files or []
+        
+        if options.parent_branch:
+            revision_range = "ancestor:%s.." % options.parent_branch
+        else:
+            revision_range = "submit:.."
+        
+        # Getting the diff for the changes in the current branch:
+        diff = self._get_range_diff(revision_range, files)
+        self._set_summary("-1")
+        self._set_description(revision_range)
+        
+        return (diff, None)
+
+    def diff_between_revisions(self, revision_range, files, repository_info):
+        """
+        Return the diff for the two revisions in ``revision_range`` and set
+        the summary and description is required.
+        
+        """
+        diff = self._get_range_diff(revision_range, files)
+        
+        # Revision ranges in Bazaar and separated with dots, not colons:
+        last_revision = revision_range.split("..")[1]
+        self._set_summary(last_revision)
+        self._set_description(revision_range)
+        
+        return diff
+
+    def _get_range_diff(self, revision_range, files):
+        """
+        Return the diff for the two revisions in ``revision_range``.
+        
+        """
+        diff_cmd = ["bzr", "diff", "-q", "-r", revision_range]
+        diff = execute(
+            diff_cmd + files,
+            ignore_errors=True,
+            )
+        diff = diff or None
+        
+        return diff
+    
+    def _set_summary(self, revision):
+        """
+        Set the summary to the message of ``revision`` if asked to guess it.
+        
+        """
+        if options.guess_summary and not options.summary:
+            options.summary = self._extract_summary(revision)
+    
+    def _set_description(self, revision_range=None):
+        """
+        Set the description to the changelog of ``revision_range`` if asked to
+        guess it.
+        
+        """
+        if options.guess_description and not options.description:
+            options.description = self._extract_description(revision_range)
+    
+    def _extract_summary(self, revision):
+        """Return the commit message for ``revision``."""
+        # `bzr log --line' returns the log in the format:
+        #   {revision-number}: {committer-name} {commit-date} {commit-message}
+        # So we should ignore everything after the date (YYYY-MM-DD).
+        log_message = execute(["bzr", "log", "-r", revision, "--line"]).rstrip()
+        log_message_match = re.search(r"\d{4}-\d{2}-\d{2}", log_message)
+        truncated_characters = log_message_match.end() + 1
+        
+        summary = log_message[truncated_characters:]
+        
+        return summary
+    
+    def _extract_description(self, revision_range=None):
+        command = ["bzr"]
+        
+        # If there is no revision range specified, that means we need the logs
+        # of all the outgoing changes:
+        if revision_range:
+            command.extend(["log", "-r", revision_range])
+        else:
+            command.extend(["missing", "-q", "--mine-only"])
+        
+        # We want to use the "short" output format, where all the logs are
+        # separated with hyphens:
+        command.append("--short")
+        
+        changelog = execute(command, ignore_errors=True).rstrip()
+        
+        return changelog
+
+
 class GitClient(SCMClient):
     """
     A wrapper around git that fetches repository information and generates
@@ -2463,6 +2611,7 @@ SCMCLIENTS = (
     SVNClient(),
     CVSClient(),
     GitClient(),
+    BazaarClient(),
     MercurialClient(),
     PerforceClient(),
     ClearCaseClient(),
@@ -2769,13 +2918,13 @@ def parse_options(args):
     parser.add_option("--guess-summary",
                       dest="guess_summary", action="store_true",
                       default=False,
-                      help="guess summary from the latest commit (git/"
+                      help="guess summary from the latest commit (bzr/git/"
                            "hg/hgsubversion 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)")
+                           "(bzr/git/hg/hgsubversion only)")
     parser.add_option("--testing-done",
                       dest="testing_done", default=None,
                       help="details of testing done ")
diff --git a/rbtools/tests.py b/rbtools/tests.py
--- a/rbtools/tests.py
+++ b/rbtools/tests.py
@@ -23,7 +23,7 @@ import nose
 
 from rbtools.postreview import execute, load_config_file
 from rbtools.postreview import APIError, GitClient, MercurialClient, \
-                               RepositoryInfo, ReviewBoardServer
+                               BazaarClient, RepositoryInfo, ReviewBoardServer
 import rbtools.postreview
 
 
@@ -373,6 +373,222 @@ class GitClientTests(unittest.TestCase):
         self.assertEqual(self.client.diff(None), (diff, None))
 
 
+class BazaarClientTests(unittest.TestCase):
+    
+    def _bzr_cmd(self, command, *args, **kwargs):
+        
+        full_command = ["bzr"] + command
+
+        return execute(full_command, *args, **kwargs)
+
+    def _bzr_add_file_commit(self, file, data, msg):
+        """
+        Add a file to a Bazaar repository with the content of data and commit
+        with msg.
+        
+        """
+        foo = open(file, "w")
+        foo.write(data)
+        foo.close()
+        self._bzr_cmd(["add", file])
+        self._bzr_cmd(["commit", "-m", msg])
+    
+    def _compare_diffs(self, filename, full_diff, expected_diff):
+        """
+        Test that the full_diff for ``filename`` matches the ``expected_diff``.
+        
+        """
+        diff_lines = full_diff.splitlines()
+        
+        self.assertEqual("=== modified file %r" % filename, diff_lines[0])
+        self.assert_(diff_lines[1].startswith("--- %s\t" % filename))
+        self.assert_(diff_lines[2].startswith("+++ %s\t" % filename))
+        
+        diff_body = "\n".join(diff_lines[3:])
+        self.assertEqual(diff_body, expected_diff)
+    
+    def setUp(self):
+        if not is_exe_in_path("bzr"):
+            raise nose.SkipTest("bzr not found in path")
+
+        self.orig_dir = os.getcwd()
+
+        self.original_branch = _get_tmpdir()
+        os.chdir(self.original_branch)
+        self._bzr_cmd(["init", "."])
+        self._bzr_add_file_commit("foo.txt", FOO, "initial commit")
+
+        self.child_branch = _get_tmpdir()
+        os.rmdir(self.child_branch)
+        self._bzr_cmd(["branch", self.original_branch, self.child_branch])
+        self.client = BazaarClient()
+        os.chdir(self.orig_dir)
+
+        rbtools.postreview.user_config = load_config_file("")
+        rbtools.postreview.options = OptionsStub()
+        rbtools.postreview.options.parent_branch = None
+        rbtools.postreview.options.summary = None
+        rbtools.postreview.options.description = None
+
+    def tearDown(self):
+        os.chdir(self.orig_dir)
+        shutil.rmtree(self.original_branch)
+        shutil.rmtree(self.child_branch)
+
+    def test_get_repository_info_original_branch(self):
+        """Test BazaarClient get_repository_info with original branch"""
+        os.chdir(self.original_branch)
+        ri = self.client.get_repository_info()
+        
+        self.assert_(isinstance(ri, RepositoryInfo))
+        self.assertEqual(ri.path, self.original_branch)
+        self.assertTrue(ri.supports_parent_diffs)
+        
+        self.assertEqual(ri.base_path, "/")
+        self.assertFalse(ri.supports_changesets)
+
+    def test_get_repository_info_child_branch(self):
+        """Test BazaarClient get_repository_info with child branch"""
+        os.chdir(self.child_branch)
+        ri = self.client.get_repository_info()
+        
+        self.assert_(isinstance(ri, RepositoryInfo))
+        self.assertEqual(ri.path, self.child_branch)
+        self.assertTrue(ri.supports_parent_diffs)
+        
+        self.assertEqual(ri.base_path, "/")
+        self.assertFalse(ri.supports_changesets)
+
+    def test_get_repository_info_no_branch(self):
+        """Test BazaarClient get_repository_info, no branch"""
+        regular_directory = _get_tmpdir()
+        os.chdir(regular_directory)
+        ri = self.client.get_repository_info()
+        self.assertEqual(ri, None)
+
+    def test_diff_simple(self):
+        """Test BazaarClient simple diff case"""
+        os.chdir(self.child_branch)
+
+        self._bzr_add_file_commit("foo.txt", FOO1, "delete and modify stuff")
+        
+        outgoing_diff, parent_diff = self.client.diff(None)
+        
+        self._compare_diffs("foo.txt", outgoing_diff, EXPECTED_BZR_DIFF_0)
+        self.assertEqual(parent_diff, None)
+
+    def test_diff_specific_files(self):
+        """Test BazaarClient diff with specific files"""
+        os.chdir(self.child_branch)
+
+        self._bzr_add_file_commit("foo.txt", FOO1, "delete and modify stuff")
+        self._bzr_add_file_commit("bar.txt", "baz", "added bar")
+        
+        outgoing_diff, parent_diff = self.client.diff(["foo.txt"])
+        
+        self._compare_diffs("foo.txt", outgoing_diff, EXPECTED_BZR_DIFF_0)
+        self.assertEqual(parent_diff, None)
+    
+    def test_diff_simple_multiple(self):
+        """Test BazaarClient simple diff with multiple commits case"""
+        os.chdir(self.child_branch)
+
+        self._bzr_add_file_commit("foo.txt", FOO1, "commit 1")
+        self._bzr_add_file_commit("foo.txt", FOO2, "commit 2")
+        self._bzr_add_file_commit("foo.txt", FOO3, "commit 3")
+        
+        outgoing_diff, parent_diff = self.client.diff(None)
+        
+        self._compare_diffs("foo.txt", outgoing_diff, EXPECTED_BZR_DIFF_1)
+        self.assertEqual(parent_diff, None)
+
+    def test_diff_parent(self):
+        """Test BazaarClient diff with changes only in the parent branch"""
+        os.chdir(self.child_branch)
+        self._bzr_add_file_commit("foo.txt", FOO1, "delete and modify stuff")
+        
+        grand_child_branch = _get_tmpdir()
+        os.rmdir(grand_child_branch)
+        self._bzr_cmd(["branch", self.child_branch, grand_child_branch])
+        os.chdir(grand_child_branch)
+        
+        outgoing_diff, parent_diff = self.client.diff(None)
+        
+        self.assertEqual(outgoing_diff, None)
+        self.assertEqual(parent_diff, None)
+
+    def test_diff_grand_parent(self):
+        """Test BazaarClient diff with changes between a 2nd level descendant"""
+        os.chdir(self.child_branch)
+        self._bzr_add_file_commit("foo.txt", FOO1, "delete and modify stuff")
+        
+        grand_child_branch = _get_tmpdir()
+        os.rmdir(grand_child_branch)
+        self._bzr_cmd(["branch", self.child_branch, grand_child_branch])
+        os.chdir(grand_child_branch)
+        
+        # Requesting the diff between the grand child branch and its grand
+        # parent:
+        rbtools.postreview.options.parent_branch = self.original_branch
+        
+        outgoing_diff, parent_diff = self.client.diff(None)
+        
+        self._compare_diffs("foo.txt", outgoing_diff, EXPECTED_BZR_DIFF_0)
+        self.assertEqual(parent_diff, None)
+    
+    def test_guessed_summary_and_description_in_diff(self):
+        """Test BazaarClient diff with summary and description guessed"""
+        os.chdir(self.child_branch)
+        
+        self._bzr_add_file_commit("foo.txt", FOO1, "commit 1")
+        self._bzr_add_file_commit("foo.txt", FOO2, "commit 2")
+        self._bzr_add_file_commit("foo.txt", FOO3, "commit 3")
+        
+        rbtools.postreview.options.guess_summary = True
+        rbtools.postreview.options.guess_description = True
+        self.client.diff(None)
+        
+        self.assertEquals("commit 3", rbtools.postreview.options.summary)
+        
+        description = rbtools.postreview.options.description
+        self.assert_("commit 1" in description, description)
+        self.assert_("commit 2" in description, description)
+        self.assert_("commit 3" in description, description)
+    
+    def test_guessed_summary_and_description_in_grand_parent_branch_diff(self):
+        """
+        Test BazaarClient diff with summary and description guessed for
+        grand parent branch.
+        
+        """
+        os.chdir(self.child_branch)
+        
+        self._bzr_add_file_commit("foo.txt", FOO1, "commit 1")
+        self._bzr_add_file_commit("foo.txt", FOO2, "commit 2")
+        self._bzr_add_file_commit("foo.txt", FOO3, "commit 3")
+        
+        rbtools.postreview.options.guess_summary = True
+        rbtools.postreview.options.guess_description = True
+        
+        grand_child_branch = _get_tmpdir()
+        os.rmdir(grand_child_branch)
+        self._bzr_cmd(["branch", self.child_branch, grand_child_branch])
+        os.chdir(grand_child_branch)
+        
+        # Requesting the diff between the grand child branch and its grand
+        # parent:
+        rbtools.postreview.options.parent_branch = self.original_branch
+        
+        self.client.diff(None)
+        
+        self.assertEquals("commit 3", rbtools.postreview.options.summary)
+        
+        description = rbtools.postreview.options.description
+        self.assert_("commit 1" in description, description)
+        self.assert_("commit 2" in description, description)
+        self.assert_("commit 3" in description, description)
+
+
 class MercurialTestBase(unittest.TestCase):
 
     def setUp(self):
@@ -911,6 +1127,38 @@ moenia Romae. Musa, mihi causas memora, quo numine laeso,
 
 """
 
+# Partial diff for Bazaar, excluding the initial comments because they contain
+# the time when it was generated:
+EXPECTED_BZR_DIFF_0 = """\
+@@ -6,7 +6,4 @@
+ inferretque deos Latio, genus unde Latinum,
+ Albanique patres, atque altae moenia Romae.
+ Musa, mihi causas memora, quo numine laeso,
+-quidve dolens, regina deum tot volvere casus
+-insignem pietate virum, tot adire labores
+-impulerit. Tantaene animis caelestibus irae?
+ 
+"""
+
+EXPECTED_BZR_DIFF_1 = """\
+@@ -1,12 +1,11 @@
+ ARMA virumque cano, Troiae qui primus ab oris
++ARMA virumque cano, Troiae qui primus ab oris
+ Italiam, fato profugus, Laviniaque venit
+ litora, multum ille et terris iactatus et alto
+ vi superum saevae memorem Iunonis ob iram;
+-multa quoque et bello passus, dum conderet urbem,
++dum conderet urbem,
+ inferretque deos Latio, genus unde Latinum,
+ Albanique patres, atque altae moenia Romae.
++Albanique patres, atque altae moenia Romae.
+ Musa, mihi causas memora, quo numine laeso,
+-quidve dolens, regina deum tot volvere casus
+-insignem pietate virum, tot adire labores
+-impulerit. Tantaene animis caelestibus irae?
+ 
+"""
+
 EXPECTED_HG_DIFF_0 = """\
 diff --git a/foo.txt b/foo.txt
 --- a/foo.txt
