Rev 71: Merge Ursula's improvements to 'bzr lp-land' in http://bazaar.launchpad.net/~bzr-pqm-devel/bzr-pqm/devel

John Arbash Meinel john at arbash-meinel.com
Fri Aug 6 17:53:06 BST 2010


At http://bazaar.launchpad.net/~bzr-pqm-devel/bzr-pqm/devel

------------------------------------------------------------
revno: 71 [merge]
revision-id: john at arbash-meinel.com-20100806165230-o5afk5bidrn2534o
parent: jelmer at samba.org-20100802200119-dh7wi5xhgr2spfgu
parent: ursinha at canonical.com-20100803215803-62k04w0bsshiu8u9
committer: John Arbash Meinel <john at arbash-meinel.com>
branch nick: devel
timestamp: Fri 2010-08-06 11:52:30 -0500
message:
  Merge Ursula's improvements to 'bzr lp-land'
added:
  tests/                         tests-20100730213203-epbos0c30teloz15-1
  tests/__init__.py              __init__.py-20100730213203-epbos0c30teloz15-2
  tests/fakemethod.py            fakemethod.py-20100803151442-de9oz0iiatmvii03-1
renamed:
  test_lpland.py => tests/test_lpland.py test_lpland.py-20100120062010-i3jdqimllf2sw7ya-1
  test_pqm_submit.py => tests/test_pqm_submit.py test_pqm_submit.py-20060221060137-fb48d47216aa0077
modified:
  __init__.py                    __init__.py-20060221052551-30932fa7d369a24b
  lpland.py                      lpland.py-20100120060107-dv7gwwei4u3ducuh-1
  tests/test_lpland.py           test_lpland.py-20100120062010-i3jdqimllf2sw7ya-1
  tests/test_pqm_submit.py       test_pqm_submit.py-20060221060137-fb48d47216aa0077
-------------- next part --------------
=== modified file '__init__.py'
--- a/__init__.py	2010-04-14 10:48:03 +0000
+++ b/__init__.py	2010-08-06 16:52:30 +0000
@@ -1,4 +1,4 @@
-# Copyright (C) 2005-2008 by Canonical Ltd
+# Copyright (C) 2006-2010 by Canonical Ltd
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -18,6 +18,7 @@
 
 from bzrlib.commands import Command, register_command
 from bzrlib.option import Option
+from bzrlib.errors import BzrCommandError
 
 
 version_info = (1, 4, 0, 'dev', 0)
@@ -83,7 +84,7 @@
 
     def run(self, location=None, message=None, public_location=None,
             dry_run=False, submit_branch=None, ignore_local=False):
-        from bzrlib import errors, trace, bzrdir
+        from bzrlib import trace, bzrdir
         if __name__ != 'bzrlib.plugins.pqm':
             trace.warning('The bzr-pqm plugin needs to be called'
                           ' "bzrlib.plugins.pqm" not "%s"\n'
@@ -103,12 +104,12 @@
                 b.lock_read()
                 self.add_cleanup(b.unlock)
         if relpath and not tree and location != '.':
-            raise errors.BzrCommandError(
+            raise BzrCommandError(
                 'No working tree was found, but we were not given the '
                 'exact path to the branch.\n'
                 'We found a branch at: %s' % (b.base,))
         if message is None:
-            raise errors.BzrCommandError(
+            raise BzrCommandError(
                 'You must supply a commit message for the pqm to use.')
         submit(b, message=message, dry_run=dry_run,
                public_location=public_location,
@@ -125,18 +126,53 @@
 
     takes_args = ['location?']
 
-    takes_options = [Option(
-        'dry-run', help='Display the PQM message instead of sending.')]
+    takes_options = [
+        Option('dry-run', help='Display the PQM message instead of sending.'),
+        Option(
+            'testfix',
+            help="This is a testfix (tags commit with [testfix])."),
+        Option(
+            'no-qa',
+            help="Does not require QA (tags commit with [no-qa])."),
+        Option(
+            'incremental',
+            help="Incremental to other bug fix (tags commit with [incr])."),
+        ]
 
-    def run(self, location=None, dry_run=False):
+    def run(self, location=None, dry_run=False, testfix=False, 
+            no_qa=False, incremental=False):
         from bzrlib.plugins.pqm.lpland import Submitter
         from bzrlib import branch as _mod_branch
+        from bzrlib.plugins.pqm.lpland import (
+            MissingReviewError, MissingBugsError, MissingBugsIncrementalError)
+
+
+        if no_qa and incremental:
+            raise BzrCommandError(
+                "--no-qa and --incremental cannot be given at the same time.")
+
         branch = _mod_branch.Branch.open_containing('.')[0]
         if dry_run:
             outf = self.outf
         else:
             outf = None
-        submitter = Submitter(branch, location).run(outf)
+        try:
+            submitter = Submitter(branch, location, testfix, no_qa, incremental
+                ).run(outf)
+        except MissingReviewError:
+            raise BzrCommandError(
+                "Cannot land branches that haven't got approved code "
+                "reviews. Get an 'Approved' vote so we can fill in the "
+                "[r=REVIEWER] section.")
+        except MissingBugsError:
+            raise BzrCommandError(
+                "Branch doesn't have linked bugs and doesn't have no-qa "
+                "option set. Use --no-qa, or link the related bugs to the "
+                "branch.")
+        except MissingBugsIncrementalError:
+            raise BzrCommandError(
+                "--incremental option requires bugs linked to the branch. "
+                "Link the bugs or remove the --incremental option.")
 
 
 register_command(cmd_pqm_submit)
@@ -147,8 +183,7 @@
     from bzrlib.tests import TestLoader
     from unittest import TestSuite
 
-    import test_lpland
-    import test_pqm_submit
+    from tests import test_lpland, test_pqm_submit
 
     loader = TestLoader()
     return TestSuite([

=== modified file 'lpland.py'
--- a/lpland.py	2010-04-07 09:47:52 +0000
+++ b/lpland.py	2010-08-03 21:58:03 +0000
@@ -33,6 +33,14 @@
     """Raised when we try to get a review message without enough reviewers."""
 
 
+class MissingBugsError(Exception):
+    """Merge proposal has no linked bugs and no [no-qa] tag."""
+
+
+class MissingBugsIncrementalError(Exception):
+    """Merge proposal has the [incr] tag but no linked bugs."""
+
+
 class LaunchpadBranchLander:
 
     name = 'launchpad-branch-lander'
@@ -171,25 +179,31 @@
         # XXX: JonathanLange 2009-09-24: No unit tests.
         return branch.composePublicURL(scheme="bzr+ssh")
 
-    def get_commit_message(self, commit_text, testfix=False):
+    def get_commit_message(self, commit_text, testfix=False, no_qa=False,
+                           incremental=False):
         """Get the Launchpad-style commit message for a merge proposal."""
         reviews = self.get_reviews()
         bugs = self.get_bugs()
-        if testfix:
-            testfix = '[testfix]'
-        else:
-            testfix = ''
-        return '%s%s%s %s' % (
-            testfix,
+
+        tags = ''.join([
+            get_testfix_clause(testfix),
             get_reviewer_clause(reviews),
             get_bugs_clause(bugs),
-            commit_text)
+            get_qa_clause(bugs, no_qa,
+                incremental),
+            ])
+
+        return '%s %s' % (tags, commit_text)
 
 
 class Submitter(object):
 
-    def __init__(self, branch, location):
+    def __init__(self, branch, location, testfix=False, no_qa=False,
+                 incremental=False):
         self.branch = branch
+        self.testfix = testfix
+        self.no_qa = no_qa
+        self.incremental = incremental
         self.config = self.branch.get_config()
         self.mail_to = self.config.get_user_option('pqm_email')
         if not self.mail_to:
@@ -211,10 +225,11 @@
         submission.check_public_branch()
 
     @staticmethod
-    def set_message(submission, mp):
+    def set_message(submission, mp, testfix, no_qa, incremental):
         pqm_command = ''.join(submission.to_lines())
         commit_message = mp.commit_message or ''
-        start_message = mp.get_commit_message(commit_message)
+        start_message = mp.get_commit_message(commit_message, testfix, no_qa,
+            incremental)
         message = msgeditor.edit_commit_message(
             'pqm command:\n%s' % pqm_command,
             start_message=start_message).rstrip('\n')
@@ -227,7 +242,8 @@
             mp = self.lander.load_merge_proposal(self.location)
         submission = self.submission(mp)
         self.check_submission(submission)
-        self.set_message(submission, mp)
+        self.set_message(submission, mp, self.testfix, self.no_qa,
+            self.incremental)
         email = submission.to_email(self.mail_from, self.mail_to)
         if outf is not None:
             outf.write(email.as_string())
@@ -256,6 +272,39 @@
     return '[bug=%s]' % ','.join(str(bug.id) for bug in bugs)
 
 
+def get_testfix_clause(testfix=False):
+    """Get the testfix clause."""
+    if testfix:
+        testfix_clause = '[testfix]'
+    else:
+        testfix_clause = ''
+    return testfix_clause
+
+
+def get_qa_clause(bugs, no_qa=False, incremental=False):
+    """Check the no-qa and incremental options, getting the qa clause.
+
+    The qa clause will always be or no-qa, or incremental or no tags, never
+    both at the same time.
+    """
+    qa_clause = ""
+
+    if not bugs and not no_qa and not incremental:
+        raise MissingBugsError
+
+    if incremental and not bugs:
+        raise MissingBugsIncrementalError
+
+    if incremental:
+        qa_clause = '[incr]'
+    elif no_qa:
+        qa_clause = '[no-qa]'
+    else:
+        qa_clause = ''
+
+    return qa_clause
+
+
 def get_reviewer_handle(reviewer):
     """Get the handle for 'reviewer'.
 

=== added directory 'tests'
=== added file 'tests/__init__.py'
=== added file 'tests/fakemethod.py'
--- a/tests/fakemethod.py	1970-01-01 00:00:00 +0000
+++ b/tests/fakemethod.py	2010-08-03 15:15:07 +0000
@@ -0,0 +1,21 @@
+__metaclass__ = type
+
+class FakeMethod:
+    """Catch any function or method call, and record the fact."""
+
+    def __init__(self, result=None, failure=None):
+        """Set up a fake function or method.
+
+        :param result: Value to return.
+        :param failure: Exception to raise.
+        """
+        self.result = result
+        self.failure = failure
+
+    def __call__(self, *args, **kwargs):
+        """Catch an invocation to the method."""
+
+        if self.failure is None:
+            return self.result
+        else:
+            raise self.failure

=== renamed file 'test_lpland.py' => 'tests/test_lpland.py'
--- a/test_lpland.py	2010-03-31 14:41:33 +0000
+++ b/tests/test_lpland.py	2010-08-06 16:52:30 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009 Canonical Ltd.  This software is licensed under the
+# Copyright 2010 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Tests for automatic landing thing."""
@@ -13,7 +13,11 @@
 
 from bzrlib.plugins.pqm.lpland import (
     get_bazaar_host, get_bugs_clause, get_reviewer_clause,
-    get_reviewer_handle, MissingReviewError)
+    get_reviewer_handle, get_testfix_clause, get_qa_clause,
+    MissingReviewError, MissingBugsError, MissingBugsIncrementalError, 
+    MergeProposal)
+
+from fakemethod import FakeMethod
 
 
 class FakeBug:
@@ -48,6 +52,16 @@
         self.network = network
 
 
+class FakeLPMergeProposal:
+    """Fake launchpadlib MergeProposal object.
+
+    Only used for the purposes of testing.
+    """
+
+    def __init__(self, root=None):
+        self._root = root
+
+
 class TestBugsClaused(unittest.TestCase):
     """Tests for `get_bugs_clause`."""
 
@@ -70,6 +84,64 @@
         self.assertEqual('[bug=20,45]', bugs_clause)
 
 
+class TestGetTestfixClause(unittest.TestCase):
+    """Tests for `get_testfix_clause`"""
+
+    def test_no_testfix(self):
+        testfix = False
+        self.assertEqual('', get_testfix_clause(testfix))
+
+    def test_is_testfix(self):
+        testfix = True
+        self.assertEqual('[testfix]', get_testfix_clause(testfix))
+
+
+class TestGetQaClause(unittest.TestCase):
+    """Tests for `get_qa_clause`"""
+
+    def test_no_bugs_no_option_given(self):
+        bugs = None
+        no_qa = False
+        incr = False
+        self.assertRaises(MissingBugsError, get_qa_clause, bugs, no_qa,
+            incr)
+
+    def test_bugs_noqa_option_given(self):
+        bug1 = FakeBug(20)
+        no_qa = True
+        incr = False
+        self.assertEqual('[no-qa]',
+            get_qa_clause([bug1], no_qa, incr))
+
+    def test_no_bugs_noqa_option_given(self):
+        bugs = None
+        no_qa = True
+        incr = False
+        self.assertEqual('[no-qa]',
+            get_qa_clause(bugs, no_qa, incr))
+
+    def test_bugs_no_option_given(self):
+        bug1 = FakeBug(20)
+        no_qa = False
+        incr = False
+        self.assertEqual('',
+            get_qa_clause([bug1], no_qa, incr))
+
+    def test_bugs_incr_option_given(self):
+        bug1 = FakeBug(20)
+        no_qa = False
+        incr = True
+        self.assertEqual('[incr]',
+            get_qa_clause([bug1], no_qa, incr))
+
+    def test_no_bugs_incr_option_given(self):
+        bugs = None
+        no_qa = False
+        incr = True
+        self.assertRaises(MissingBugsIncrementalError,
+            get_qa_clause, bugs, no_qa, incr)
+
+
 class TestGetReviewerHandle(unittest.TestCase):
     """Tests for `get_reviewer_handle`."""
 
@@ -96,6 +168,91 @@
         self.assertEqual('foo', get_reviewer_handle(person))
 
 
+class TestGetCommitMessage(unittest.TestCase):
+
+    def setUp(self):
+        self.mp = MergeProposal(FakeLPMergeProposal())
+        self.fake_bug = FakeBug(20)
+        self.fake_person = self.makePerson('foo')
+
+    def makePerson(self, name):
+        return FakePerson(name, [])
+
+    def test_commit_with_bugs(self):
+        incr = False
+        no_qa = False
+        testfix = False
+
+        self.mp.get_bugs = FakeMethod([self.fake_bug])
+        self.mp.get_reviews = FakeMethod({None : [self.fake_person]})
+
+        self.assertEqual("[r=foo][ui=none][bug=20] Foobaring the sbrubble.",
+            self.mp.get_commit_message("Foobaring the sbrubble.",
+                testfix, no_qa, incr))
+
+    def test_commit_no_bugs_no_noqa(self):
+        incr = False
+        no_qa = False
+        testfix = False
+
+        self.mp.get_bugs = FakeMethod([])
+        self.mp.get_reviews = FakeMethod({None : [self.fake_person]})
+
+        self.assertRaises(MissingBugsError, self.mp.get_commit_message,
+            testfix, no_qa, incr)
+
+    def test_commit_no_bugs_with_noqa(self):
+        incr = False
+        no_qa = True
+        testfix = False
+
+        self.mp.get_bugs = FakeMethod([])
+        self.mp.get_reviews = FakeMethod({None : [self.fake_person]})
+
+        self.assertEqual("[r=foo][ui=none][no-qa] Foobaring the sbrubble.",
+            self.mp.get_commit_message("Foobaring the sbrubble.",
+                testfix, no_qa, incr))
+
+    def test_commit_bugs_with_noqa(self):
+        incr = False
+        no_qa = True
+        testfix = False
+
+        self.mp.get_bugs = FakeMethod([self.fake_bug])
+        self.mp.get_reviews = FakeMethod({None : [self.fake_person]})
+
+        self.assertEqual(
+            "[r=foo][ui=none][bug=20][no-qa] Foobaring the sbrubble.",
+            self.mp.get_commit_message("Foobaring the sbrubble.",
+                testfix, no_qa, incr))
+
+    def test_commit_bugs_with_incr(self):
+        incr = True
+        no_qa = False
+        testfix = False
+
+        self.mp.get_bugs = FakeMethod([self.fake_bug])
+        self.mp.get_reviews = FakeMethod({None : [self.fake_person]})
+
+        self.assertEqual(
+            "[r=foo][ui=none][bug=20][incr] Foobaring the sbrubble.",
+            self.mp.get_commit_message("Foobaring the sbrubble.",
+                testfix, no_qa, incr))
+
+    def test_commit_no_bugs_with_incr(self):
+        incr = True
+        no_qa = False
+        testfix = False
+
+        self.mp.get_bugs = FakeMethod([self.fake_bug])
+        self.mp.get_reviews = FakeMethod({None : [self.fake_person]})
+
+        self.assertEqual(
+            "[r=foo][ui=none][bug=20][incr] Foobaring the sbrubble.",
+            self.mp.get_commit_message("Foobaring the sbrubble.",
+                testfix, no_qa, incr))
+
+
 class TestGetReviewerClause(unittest.TestCase):
     """Tests for `get_reviewer_clause`."""
 

=== renamed file 'test_pqm_submit.py' => 'tests/test_pqm_submit.py'
--- a/test_pqm_submit.py	2009-11-24 07:35:29 +0000
+++ b/tests/test_pqm_submit.py	2010-08-06 16:52:30 +0000
@@ -1,4 +1,4 @@
-# Copyright (C) 2005-2008 by Canonical Ltd
+# Copyright (C) 2006-2010 by Canonical Ltd
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by



More information about the bazaar-commits mailing list