[MERGE] Add mechanism for pre_change_branch_tip hook to cleanly reject a change.

John Arbash Meinel john at arbash-meinel.com
Thu Jul 17 23:43:24 BST 2008


-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Andrew Bennetts wrote:
| Hi,
|
| This patch adds a TipChangeRejected error for pre_change_branch_tip
hooks to
| raise.  The message given to TipChangeRejected will be reported
directly to the
| user, even over the smart server.  This is important for a hook that
wants to
| explain to the user *why* a change is rejected (e.g. "foo.xml is not
valid XML:
| syntax error on line 1234").
|
| This patch is relative to my last patch for a smart server hooks
integration
| test, but doesn't really depend on it.
|
| -Andrew.
|
|

@@ -1522,11 +1524,18 @@
~         check_not_reserved_id = _mod_revision.check_not_reserved_id
~         for rev_id in rev_history:
~             check_not_reserved_id(rev_id)
+        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)

^- Why did you chose to move it into set_revision_history() ? Just in
case something else called it directly?

Should we have a test for that?

+
+
+class HookFailed(BzrError):
+
+    _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):
+
+    _fmt = "%(msg)s"
+
+    def __init__(self, msg):
+        self.msg = msg
+

^- A docstring for when these are used, and what they represent would be
nice.



+    def test_tip_change_rejected(self):
+        transport = MemoryTransport()
+        transport.mkdir('branch')
+        transport = transport.clone('branch')
+        # A response of 'NoSuchRevision' is translated into an exception.
+        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()
+        client._calls = []
+
+        err = self.assertRaises(
+            errors.TipChangeRejected, branch.set_revision_history,
['rev-id'])
+        self.assertIsInstance(err.msg, unicode)
+        self.assertEqual(rejection_msg_unicode, err.msg)
+        branch.unlock()
+

^- you should really be using try/finally or addCleanup rather than a
bare unlock.

Also, shouldn't you be asserting the last revision hasn't changed?



You should also have direct tests in test_errors() for the formatting of
these exceptions.

And I wonder if we would want to add:

_fmt = 'Tip change rejected: %(msg)s'

Have you tried it in 'reality' so that we know what it looks like when
it fails?


I really like the functionality, but it seems to need a bit more polish.

BB:resubmit.

John
=:->



-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (Cygwin)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iEYEARECAAYFAkh/ywwACgkQJdeBCYSNAAPJ1ACgmMjB01EkKPWO9LOcIVmvjxuH
pZIAnjjSOpMFv59Lv6zuVD7YxeE7fn+z
=b6AV
-----END PGP SIGNATURE-----



More information about the bazaar mailing list