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