Rev 3581: Add TipChangeRejected error so that pre_change_branch_tip hook in file:///home/pqm/archives/thelove/bzr/%2Btrunk/

Canonical.com Patch Queue Manager pqm at pqm.ubuntu.com
Fri Jul 25 16:19:56 BST 2008


At file:///home/pqm/archives/thelove/bzr/%2Btrunk/

------------------------------------------------------------
revno: 3581
revision-id:pqm at pqm.ubuntu.com-20080725151945-kdaru30ix1m8k0h6
parent: pqm at pqm.ubuntu.com-20080725144520-ir7276gij462t1te
parent: andrew.bennetts at canonical.com-20080725093926-xlyj4ahs24pu1a9c
committer: Canonical.com Patch Queue Manager <pqm at pqm.ubuntu.com>
branch nick: +trunk
timestamp: Fri 2008-07-25 16:19:45 +0100
message:
  Add TipChangeRejected error so that pre_change_branch_tip hook
  	functions can cleanly reject a change. (Andrew Bennetts)
modified:
  bzrlib/branch.py               branch.py-20050309040759-e4baf4e0d046576e
  bzrlib/errors.py               errors.py-20050309040759-20512168c4e14fbd
  bzrlib/remote.py               remote.py-20060720103555-yeeg2x51vn0rbtdp-1
  bzrlib/smart/branch.py         branch.py-20061124031907-mzh3pla28r83r97f-1
  bzrlib/tests/branch_implementations/test_hooks.py test_hooks.py-20070129154855-blhpwxmvjs07waei-1
  bzrlib/tests/test_errors.py    test_errors.py-20060210110251-41aba2deddf936a8
  bzrlib/tests/test_remote.py    test_remote.py-20060720103555-yeeg2x51vn0rbtdp-2
  bzrlib/tests/test_smart.py     test_smart.py-20061122024551-ol0l0o0oofsu9b3t-2
    ------------------------------------------------------------
    revno: 3577.1.3
    revision-id:andrew.bennetts at canonical.com-20080725093926-xlyj4ahs24pu1a9c
    parent: andrew.bennetts at canonical.com-20080725081420-aofimy5p9022c83r
    committer: Andrew Bennetts <andrew.bennetts at canonical.com>
    branch nick: tip-change-rejected-error
    timestamp: Fri 2008-07-25 19:39:26 +1000
    message:
      Fix test_trace failure: BzrError._format shouldn't call str() itself, it should leave that to __str__.
    modified:
      bzrlib/errors.py               errors.py-20050309040759-20512168c4e14fbd
    ------------------------------------------------------------
    revno: 3577.1.2
    revision-id:andrew.bennetts at canonical.com-20080725081420-aofimy5p9022c83r
    parent: andrew.bennetts at canonical.com-20080725064208-ui70gluukdypd4y9
    committer: Andrew Bennetts <andrew.bennetts at canonical.com>
    branch nick: tip-change-rejected-error
    timestamp: Fri 2008-07-25 18:14:20 +1000
    message:
      If there are no post_change_branch_tip hooks to run in set_revision_history, don't calculate last_revision_info().
    modified:
      bzrlib/branch.py               branch.py-20050309040759-e4baf4e0d046576e
    ------------------------------------------------------------
    revno: 3577.1.1
    revision-id:andrew.bennetts at canonical.com-20080725064208-ui70gluukdypd4y9
    parent: pqm at pqm.ubuntu.com-20080724061047-yrvo5cmeik38kibz
    committer: Andrew Bennetts <andrew.bennetts at canonical.com>
    branch nick: tip-change-rejected-error
    timestamp: Fri 2008-07-25 16:42:08 +1000
    message:
      Cherry-pick TipChangeRejected changes from pre-branch-tip-changed-hook loom.
    modified:
      bzrlib/branch.py               branch.py-20050309040759-e4baf4e0d046576e
      bzrlib/errors.py               errors.py-20050309040759-20512168c4e14fbd
      bzrlib/remote.py               remote.py-20060720103555-yeeg2x51vn0rbtdp-1
      bzrlib/smart/branch.py         branch.py-20061124031907-mzh3pla28r83r97f-1
      bzrlib/tests/branch_implementations/test_hooks.py test_hooks.py-20070129154855-blhpwxmvjs07waei-1
      bzrlib/tests/test_errors.py    test_errors.py-20060210110251-41aba2deddf936a8
      bzrlib/tests/test_remote.py    test_remote.py-20060720103555-yeeg2x51vn0rbtdp-2
      bzrlib/tests/test_smart.py     test_smart.py-20061122024551-ol0l0o0oofsu9b3t-2
=== modified file 'bzrlib/branch.py'
--- a/bzrlib/branch.py	2008-07-25 05:32:45 +0000
+++ b/bzrlib/branch.py	2008-07-25 15:19:45 +0000
@@ -15,6 +15,8 @@
 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
 
+import sys
+
 from bzrlib.lazy_import import lazy_import
 lazy_import(globals(), """
 from itertools import chain
@@ -533,8 +535,6 @@
         finally:
             other.unlock()
 
-
-
     def revision_id_to_revno(self, revision_id):
         """Given a revision id, return its revno"""
         if _mod_revision.is_null(revision_id):
@@ -899,7 +899,7 @@
         elif relation == 'a_descends_from_b':
             return False
         else:
-            raise AssertionError("invalid heads: %r" % heads)
+            raise AssertionError("invalid relation: %r" % (relation,))
 
     def _revision_relations(self, revision_a, revision_b, graph):
         """Determine the relationship between two revisions.
@@ -915,7 +915,7 @@
         elif heads == set([revision_a]):
             return 'a_descends_from_b'
         else:
-            raise AssertionError("invalid heads: %r" % heads)
+            raise AssertionError("invalid heads: %r" % (heads,))
 
 
 class BranchFormat(object):
@@ -1586,11 +1586,22 @@
         check_not_reserved_id = _mod_revision.check_not_reserved_id
         for rev_id in rev_history:
             check_not_reserved_id(rev_id)
+        if Branch.hooks['post_change_branch_tip']:
+            # Don't calculate the last_revision_info() if there are no hooks
+            # that will use it.
+            old_revno, old_revid = self.last_revision_info()
+        if len(rev_history) == 0:
+            revid = _mod_revision.NULL_REVISION
+        else:
+            revid = rev_history[-1]
+        self._run_pre_change_branch_tip_hooks(len(rev_history), revid)
         self._write_revision_history(rev_history)
         self._clear_cached_state()
         self._cache_revision_history(rev_history)
         for hook in Branch.hooks['set_rh']:
             hook(self, rev_history)
+        if Branch.hooks['post_change_branch_tip']:
+            self._run_post_change_branch_tip_hooks(old_revno, old_revid)
 
     def _run_pre_change_branch_tip_hooks(self, new_revno, new_revid):
         """Run the pre_change_branch_tip hooks."""
@@ -1601,7 +1612,15 @@
         params = ChangeBranchTipParams(
             self, old_revno, new_revno, old_revid, new_revid)
         for hook in hooks:
-            hook(params)
+            try:
+                hook(params)
+            except errors.TipChangeRejected:
+                raise
+            except Exception:
+                exc_info = sys.exc_info()
+                hook_name = Branch.hooks.get_hook_name(hook)
+                raise errors.HookFailed(
+                    'pre_change_branch_tip', hook_name, exc_info)
  
     def _run_post_change_branch_tip_hooks(self, old_revno, old_revid):
         """Run the post_change_branch_tip hooks."""
@@ -1627,16 +1646,13 @@
         be permitted.
         """
         revision_id = _mod_revision.ensure_null(revision_id)
-        old_revno, old_revid = self.last_revision_info()
         # this old format stores the full history, but this api doesn't
         # provide it, so we must generate, and might as well check it's
         # correct
         history = self._lefthand_history(revision_id)
         if len(history) != revno:
             raise AssertionError('%d != %d' % (len(history), revno))
-        self._run_pre_change_branch_tip_hooks(revno, revision_id)
         self.set_revision_history(history)
-        self._run_post_change_branch_tip_hooks(old_revno, old_revid)
 
     def _gen_revision_history(self):
         history = self._transport.get_bytes('revision-history').split('\n')

=== modified file 'bzrlib/errors.py'
--- a/bzrlib/errors.py	2008-07-23 08:10:03 +0000
+++ b/bzrlib/errors.py	2008-07-25 09:39:26 +0000
@@ -93,11 +93,11 @@
             for key, value in kwds.items():
                 setattr(self, key, value)
 
-    def __str__(self):
+    def _format(self):
         s = getattr(self, '_preformatted_string', None)
         if s is not None:
-            # contains a preformatted message; must be cast to plain str
-            return str(s)
+            # contains a preformatted message
+            return s
         try:
             fmt = self._get_format_string()
             if fmt:
@@ -108,8 +108,6 @@
                 s = fmt % d
                 # __str__() should always return a 'str' object
                 # never a 'unicode' object.
-                if isinstance(s, unicode):
-                    return s.encode('utf8')
                 return s
         except (AttributeError, TypeError, NameError, ValueError, KeyError), e:
             return 'Unprintable exception %s: dict=%r, fmt=%r, error=%r' \
@@ -118,6 +116,26 @@
                    getattr(self, '_fmt', None),
                    e)
 
+    def __unicode__(self):
+        u = self._format()
+        if isinstance(u, str):
+            # Try decoding the str using the default encoding.
+            u = unicode(u)
+        elif not isinstance(u, unicode):
+            # Try to make a unicode object from it, because __unicode__ must
+            # return a unicode object.
+            u = unicode(u)
+        return u
+    
+    def __str__(self):
+        s = self._format()
+        if isinstance(s, unicode):
+            s = s.encode('utf8')
+        else:
+            # __str__ must return a str.
+            s = str(s)
+        return s
+
     def _get_format_string(self):
         """Return format string for this exception or None"""
         fmt = getattr(self, '_fmt', None)
@@ -371,16 +389,6 @@
     # are not intended to be caught anyway.  UI code need not subclass
     # BzrCommandError, and non-UI code should not throw a subclass of
     # BzrCommandError.  ADHB 20051211
-    def __init__(self, msg):
-        # Object.__str__() must return a real string
-        # returning a Unicode string is a python error.
-        if isinstance(msg, unicode):
-            self.msg = msg.encode('utf8')
-        else:
-            self.msg = msg
-
-    def __str__(self):
-        return self.msg
 
 
 class NotWriteLocked(BzrError):
@@ -2794,3 +2802,34 @@
 
     def __init__(self, unknowns):
         BzrError.__init__(self, unknowns_str=", ".join(unknowns))
+
+
+class HookFailed(BzrError):
+    """Raised when a pre_change_branch_tip hook function fails anything other
+    than TipChangeRejected.
+    """
+
+    _fmt = ("Hook '%(hook_name)s' during %(hook_stage)s failed:\n"
+            "%(traceback_text)s%(exc_value)s")
+
+    def __init__(self, hook_stage, hook_name, exc_info):
+        import traceback
+        self.hook_stage = hook_stage
+        self.hook_name = hook_name
+        self.exc_info = exc_info
+        self.exc_type = exc_info[0]
+        self.exc_value = exc_info[1]
+        self.exc_tb = exc_info[2]
+        self.traceback_text = ''.join(traceback.format_tb(self.exc_tb))
+
+
+class TipChangeRejected(BzrError):
+    """A pre_change_branch_tip hook function may raise this to cleanly and
+    explicitly abort a change to a branch tip.
+    """
+    
+    _fmt = u"Tip change rejected: %(msg)s"
+
+    def __init__(self, msg):
+        self.msg = msg
+

=== modified file 'bzrlib/remote.py'
--- a/bzrlib/remote.py	2008-07-23 22:53:38 +0000
+++ b/bzrlib/remote.py	2008-07-25 06:42:08 +0000
@@ -1696,5 +1696,7 @@
         raise errors.TokenMismatch(find('token'), '(remote token)')
     elif err.error_verb == 'Diverged':
         raise errors.DivergedBranches(find('branch'), find('other_branch'))
+    elif err.error_verb == 'TipChangeRejected':
+        raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
     raise
 

=== modified file 'bzrlib/smart/branch.py'
--- a/bzrlib/smart/branch.py	2008-06-25 07:03:41 +0000
+++ b/bzrlib/smart/branch.py	2008-07-25 06:42:08 +0000
@@ -115,9 +115,24 @@
         return SuccessfulSmartServerResponse(('ok', str(revno), last_revision))
 
 
-class SmartServerBranchRequestSetLastRevision(SmartServerLockedBranchRequest):
+class SmartServerSetTipRequest(SmartServerLockedBranchRequest):
+    """Base class for handling common branch request logic for requests that
+    update the branch tip.
+    """
+
+    def do_with_locked_branch(self, branch, *args):
+        try:
+            return self.do_tip_change_with_locked_branch(branch, *args)
+        except errors.TipChangeRejected, e:
+            msg = e.msg
+            if isinstance(msg, unicode):
+                msg = msg.encode('utf-8')
+            return FailedSmartServerResponse(('TipChangeRejected', msg))
+
+
+class SmartServerBranchRequestSetLastRevision(SmartServerSetTipRequest):
     
-    def do_with_locked_branch(self, branch, new_last_revision_id):
+    def do_tip_change_with_locked_branch(self, branch, new_last_revision_id):
         if new_last_revision_id == 'null:':
             branch.set_revision_history([])
         else:
@@ -128,9 +143,9 @@
         return SuccessfulSmartServerResponse(('ok',))
 
 
-class SmartServerBranchRequestSetLastRevisionEx(SmartServerLockedBranchRequest):
+class SmartServerBranchRequestSetLastRevisionEx(SmartServerSetTipRequest):
     
-    def do_with_locked_branch(self, branch, new_last_revision_id,
+    def do_tip_change_with_locked_branch(self, branch, new_last_revision_id,
             allow_divergence, allow_overwrite_descendant):
         """Set the last revision of the branch.
 
@@ -176,15 +191,15 @@
             ('ok', new_revno, new_last_revision_id))
 
 
-class SmartServerBranchRequestSetLastRevisionInfo(
-    SmartServerLockedBranchRequest):
+class SmartServerBranchRequestSetLastRevisionInfo(SmartServerSetTipRequest):
     """Branch.set_last_revision_info.  Sets the revno and the revision ID of
     the specified branch.
 
     New in bzrlib 1.4.
     """
     
-    def do_with_locked_branch(self, branch, new_revno, new_last_revision_id):
+    def do_tip_change_with_locked_branch(self, branch, new_revno,
+            new_last_revision_id):
         try:
             branch.set_last_revision_info(int(new_revno), new_last_revision_id)
         except errors.NoSuchRevision:

=== modified file 'bzrlib/tests/branch_implementations/test_hooks.py'
--- a/bzrlib/tests/branch_implementations/test_hooks.py	2008-07-17 16:26:45 +0000
+++ b/bzrlib/tests/branch_implementations/test_hooks.py	2008-07-25 06:42:08 +0000
@@ -16,6 +16,7 @@
 
 """Tests that branch classes implement hook callouts correctly."""
 
+from bzrlib.errors import HookFailed, TipChangeRejected
 from bzrlib.branch import Branch, ChangeBranchTipParams
 from bzrlib.revision import NULL_REVISION
 from bzrlib.tests import TestCaseWithMemoryTransport
@@ -90,7 +91,7 @@
         """
         hook_calls = []
         Branch.hooks.install_named_hook(
-            'pre_change_branch_tip', hook_calls.append, None)
+            prefix + '_change_branch_tip', hook_calls.append, None)
         return hook_calls
 
     def make_branch_with_revision_ids(self, *revision_ids):
@@ -124,10 +125,10 @@
             'pre_change_branch_tip', assertBranchAtRevision1, None)
         branch.set_last_revision_info(0, NULL_REVISION)
 
-    def test_reject_by_hook(self):
+    def test_hook_failure_prevents_change(self):
         """If a hook raises an exception, the change does not take effect.
         
-        Also, the exception will be propogated.
+        Also, a HookFailed exception will be raised.
         """
         branch = self.make_branch_with_revision_ids(
             'one-\xc2\xb5', 'two-\xc2\xb5')
@@ -137,8 +138,9 @@
             raise PearShapedError()
         Branch.hooks.install_named_hook(
             'pre_change_branch_tip', hook_that_raises, None)
-        self.assertRaises(
-            PearShapedError, branch.set_last_revision_info, 0, NULL_REVISION)
+        hook_failed_exc = self.assertRaises(
+            HookFailed, branch.set_last_revision_info, 0, NULL_REVISION)
+        self.assertIsInstance(hook_failed_exc.exc_value, PearShapedError)
         # The revision info is unchanged.
         self.assertEqual((2, 'two-\xc2\xb5'), branch.last_revision_info())
         
@@ -183,6 +185,22 @@
         self.assertEqual(len(hook_calls_1), 1)
         self.assertEqual(len(hook_calls_2), 1)
 
+    def test_explicit_reject_by_hook(self):
+        """If a hook raises TipChangeRejected, the change does not take effect.
+        
+        TipChangeRejected exceptions are propagated, not wrapped in HookFailed.
+        """
+        branch = self.make_branch_with_revision_ids(
+            'one-\xc2\xb5', 'two-\xc2\xb5')
+        def hook_that_rejects(params):
+            raise TipChangeRejected('rejection message')
+        Branch.hooks.install_named_hook(
+            'pre_change_branch_tip', hook_that_rejects, None)
+        self.assertRaises(
+            TipChangeRejected, branch.set_last_revision_info, 0, NULL_REVISION)
+        # The revision info is unchanged.
+        self.assertEqual((2, 'two-\xc2\xb5'), branch.last_revision_info())
+        
 
 class TestPostChangeBranchTip(ChangeBranchTipTestCase):
     """Tests for post_change_branch_tip hook.
@@ -243,3 +261,57 @@
         # Both hooks are called.
         self.assertEqual(len(hook_calls_1), 1)
         self.assertEqual(len(hook_calls_2), 1)
+
+
+class TestAllMethodsThatChangeTipWillRunHooks(ChangeBranchTipTestCase):
+    """Every method of Branch that changes a branch tip will invoke the
+    pre/post_change_branch_tip hooks.
+    """
+
+    def setUp(self):
+        ChangeBranchTipTestCase.setUp(self)
+        self.installPreAndPostHooks()
+        
+    def installPreAndPostHooks(self):
+        self.pre_hook_calls = self.install_logging_hook('pre')
+        self.post_hook_calls = self.install_logging_hook('post')
+
+    def resetHookCalls(self):
+        del self.pre_hook_calls[:], self.post_hook_calls[:]
+
+    def assertPreAndPostHooksWereInvoked(self):
+        # Check for len == 1, because the hooks should only be be invoked once
+        # by an operation.
+        self.assertEqual(1, len(self.pre_hook_calls))
+        self.assertEqual(1, len(self.post_hook_calls))
+
+    def test_set_revision_history(self):
+        branch = self.make_branch('')
+        branch.set_revision_history([])
+        self.assertPreAndPostHooksWereInvoked()
+
+    def test_set_last_revision_info(self):
+        branch = self.make_branch('')
+        branch.set_last_revision_info(0, NULL_REVISION)
+        self.assertPreAndPostHooksWereInvoked()
+
+    def test_generate_revision_history(self):
+        branch = self.make_branch('')
+        branch.generate_revision_history(NULL_REVISION)
+        self.assertPreAndPostHooksWereInvoked()
+
+    def test_pull(self):
+        source_branch = self.make_branch_with_revision_ids('rev-1', 'rev-2')
+        self.resetHookCalls()
+        destination_branch = self.make_branch('destination')
+        destination_branch.pull(source_branch)
+        self.assertPreAndPostHooksWereInvoked()
+
+    def test_push(self):
+        source_branch = self.make_branch_with_revision_ids('rev-1', 'rev-2')
+        self.resetHookCalls()
+        destination_branch = self.make_branch('destination')
+        source_branch.push(destination_branch)
+        self.assertPreAndPostHooksWereInvoked()
+
+        

=== modified file 'bzrlib/tests/test_errors.py'
--- a/bzrlib/tests/test_errors.py	2008-07-14 07:47:45 +0000
+++ b/bzrlib/tests/test_errors.py	2008-07-25 06:42:08 +0000
@@ -18,6 +18,7 @@
 
 """Tests for the formatting and construction of errors."""
 
+import sys
 from bzrlib import (
     bzrdir,
     errors,
@@ -510,6 +511,27 @@
         err = errors.UnknownRules(['foo', 'bar'])
         self.assertEquals("Unknown rules detected: foo, bar.", str(err))
 
+    def test_hook_failed(self):
+        # Create an exc_info tuple by raising and catching an exception.
+        try:
+            1/0
+        except ZeroDivisionError:
+            exc_info = sys.exc_info()
+        err = errors.HookFailed('hook stage', 'hook name', exc_info)
+        self.assertStartsWith(
+            str(err), 'Hook \'hook name\' during hook stage failed:\n')
+        self.assertEndsWith(
+            str(err), 'integer division or modulo by zero')
+
+    def test_tip_change_rejected(self):
+        err = errors.TipChangeRejected(u'Unicode message\N{INTERROBANG}')
+        self.assertEquals(
+            u'Tip change rejected: Unicode message\N{INTERROBANG}',
+            unicode(err))
+        self.assertEquals(
+            'Tip change rejected: Unicode message\xe2\x80\xbd',
+            str(err))
+
 
 class PassThroughError(errors.BzrError):
     

=== modified file 'bzrlib/tests/test_remote.py'
--- a/bzrlib/tests/test_remote.py	2008-07-23 11:18:10 +0000
+++ b/bzrlib/tests/test_remote.py	2008-07-25 06:42:08 +0000
@@ -551,6 +551,40 @@
             errors.NoSuchRevision, branch.set_revision_history, ['rev-id'])
         branch.unlock()
 
+    def test_tip_change_rejected(self):
+        """TipChangeRejected responses cause a TipChangeRejected exception to
+        be raised.
+        """
+        transport = MemoryTransport()
+        transport.mkdir('branch')
+        transport = transport.clone('branch')
+        client = FakeClient(transport.base)
+        # lock_write
+        client.add_success_response('ok', 'branch token', 'repo token')
+        # set_last_revision
+        rejection_msg_unicode = u'rejection message\N{INTERROBANG}'
+        rejection_msg_utf8 = rejection_msg_unicode.encode('utf8')
+        client.add_error_response('TipChangeRejected', rejection_msg_utf8)
+        # unlock
+        client.add_success_response('ok')
+
+        bzrdir = RemoteBzrDir(transport, _client=False)
+        repo = RemoteRepository(bzrdir, None, _client=client)
+        branch = RemoteBranch(bzrdir, repo, _client=client)
+        branch._ensure_real = lambda: None
+        branch.lock_write()
+        self.addCleanup(branch.unlock)
+        client._calls = []
+
+        # The 'TipChangeRejected' error response triggered by calling
+        # set_revision_history causes a TipChangeRejected exception.
+        err = self.assertRaises(
+            errors.TipChangeRejected, branch.set_revision_history, ['rev-id'])
+        # The UTF-8 message from the response has been decoded into a unicode
+        # object.
+        self.assertIsInstance(err.msg, unicode)
+        self.assertEqual(rejection_msg_unicode, err.msg)
+
 
 class TestBranchSetLastRevisionInfo(tests.TestCase):
 
@@ -689,6 +723,39 @@
         self.assertEqual(('UnexpectedError',), err.error_tuple)
         branch.unlock()
 
+    def test_tip_change_rejected(self):
+        """TipChangeRejected responses cause a TipChangeRejected exception to
+        be raised.
+        """
+        transport = MemoryTransport()
+        transport.mkdir('branch')
+        transport = transport.clone('branch')
+        client = FakeClient(transport.base)
+        # lock_write
+        client.add_success_response('ok', 'branch token', 'repo token')
+        # set_last_revision
+        client.add_error_response('TipChangeRejected', 'rejection message')
+        # unlock
+        client.add_success_response('ok')
+
+        bzrdir = RemoteBzrDir(transport, _client=False)
+        repo = RemoteRepository(bzrdir, None, _client=client)
+        branch = RemoteBranch(bzrdir, repo, _client=client)
+        # This is a hack to work around the problem that RemoteBranch currently
+        # unnecessarily invokes _ensure_real upon a call to lock_write.
+        branch._ensure_real = lambda: None
+        # Lock the branch, reset the record of remote calls.
+        branch.lock_write()
+        self.addCleanup(branch.unlock)
+        client._calls = []
+
+        # The 'TipChangeRejected' error response triggered by calling
+        # set_last_revision_info causes a TipChangeRejected exception.
+        err = self.assertRaises(
+            errors.TipChangeRejected,
+            branch.set_last_revision_info, 123, 'revid')
+        self.assertEqual('rejection message', err.msg)
+
 
 class TestBranchControlGetBranchConf(tests.TestCaseWithMemoryTransport):
     """Getting the branch configuration should use an abstract method not vfs.

=== modified file 'bzrlib/tests/test_smart.py'
--- a/bzrlib/tests/test_smart.py	2008-07-07 05:45:40 +0000
+++ b/bzrlib/tests/test_smart.py	2008-07-25 06:42:08 +0000
@@ -36,7 +36,7 @@
     tests,
     urlutils,
     )
-from bzrlib.branch import BranchReferenceFormat
+from bzrlib.branch import Branch, BranchReferenceFormat
 import bzrlib.smart.branch
 import bzrlib.smart.bzrdir
 import bzrlib.smart.repository
@@ -480,6 +480,20 @@
         self.assertEqual(
             (1, rev_id_utf8), self.tree.branch.last_revision_info())
 
+    def test_TipChangeRejected(self):
+        """If a pre_change_branch_tip hook raises TipChangeRejected, the verb
+        returns TipChangeRejected.
+        """
+        rejection_message = u'rejection message\N{INTERROBANG}'
+        def hook_that_rejects(params):
+            raise errors.TipChangeRejected(rejection_message)
+        Branch.hooks.install_named_hook(
+            'pre_change_branch_tip', hook_that_rejects, None)
+        self.assertEqual(
+            FailedSmartServerResponse(
+                ('TipChangeRejected', rejection_message.encode('utf-8'))),
+            self.set_last_revision('null:', 0))
+
 
 class TestSmartServerBranchRequestSetLastRevision(
         SetLastRevisionTestBase, TestSetLastRevisionVerbMixin):




More information about the bazaar-commits mailing list