Merge lp:~abentley/bzr/send-hookage into lp:~bzr/bzr/trunk-old

Proposed by Aaron Bentley
Status: Merged
Merged at revision: not available
Proposed branch: lp:~abentley/bzr/send-hookage
Merge into: lp:~bzr/bzr/trunk-old
Diff against target: 289 lines
To merge this branch: bzr merge lp:~abentley/bzr/send-hookage
Reviewer Review Type Date Requested Status
Robert Collins (community) Approve
Review via email: mp+6436@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Aaron Bentley (abentley) wrote :

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

Hi all,

This patch adds a new MergeDirective hook to calculate the message body.
 This helps projects with a specific review template, like Launchpad, to
ensure the template is considered for all reviews.

It does this by moving the mail_client-specific functionality of
cmd_send into the new MergeDirective.compose_merge_request method.
There is a similarly-named method on MailClient, but this receives a
MergeDirective that has already been serialized, and for the hook, live
objects are more useful.

As well as the Branch and directive, this method also takes an optional
Tree parameter. This will typically be the WorkingTree that the user
ran 'bzr send' against. This may be useful for hooks that want to
examine the working tree to generate the body. Launchpad, for example,
runs 'lint' against files which have changed.

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

iEYEARECAAYFAkoIh8cACgkQ0F+nu1YWqI1KPACeNrcy/bMN+CqdgEjOnA5rNJKl
eGQAn1hIvbNAj7PUUgZRGqMM8NJlMPB0
=biQr
-----END PGP SIGNATURE-----

Revision history for this message
Robert Collins (lifeless) wrote :

 review approve

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'NEWS'
--- NEWS 2009-05-11 06:44:30 +0000
+++ NEWS 2009-05-11 20:35:15 +0000
@@ -150,6 +150,9 @@
150 broken. This is mainly for testing that lock/unlock are150 broken. This is mainly for testing that lock/unlock are
151 balanced in tests. (Vincent Ladeuil)151 balanced in tests. (Vincent Ladeuil)
152152
153* New MergeDirective hook 'merge_request_body' allows hooks to supply or
154 alter a body for the message produced by ``bzr send``.
155
153* New smart server verb ``BzrDir.initialize_ex`` which implements a156* New smart server verb ``BzrDir.initialize_ex`` which implements a
154 refactoring to the core of clone allowing less round trips on new157 refactoring to the core of clone allowing less round trips on new
155 branches. (Robert Collins)158 branches. (Robert Collins)
156159
=== modified file 'bzrlib/builtins.py'
--- bzrlib/builtins.py 2009-05-07 05:08:46 +0000
+++ bzrlib/builtins.py 2009-05-11 20:35:15 +0000
@@ -4866,13 +4866,7 @@
4866 def _run(self, submit_branch, revision, public_branch, remember, format,4866 def _run(self, submit_branch, revision, public_branch, remember, format,
4867 no_bundle, no_patch, output, from_, mail_to, message, body):4867 no_bundle, no_patch, output, from_, mail_to, message, body):
4868 from bzrlib.revision import NULL_REVISION4868 from bzrlib.revision import NULL_REVISION
4869 branch = Branch.open_containing(from_)[0]4869 tree, branch = bzrdir.BzrDir.open_containing_tree_or_branch(from_)[:2]
4870 if output is None:
4871 outfile = cStringIO.StringIO()
4872 elif output == '-':
4873 outfile = self.outf
4874 else:
4875 outfile = open(output, 'wb')
4876 # we may need to write data into branch's repository to calculate4870 # we may need to write data into branch's repository to calculate
4877 # the data to send.4871 # the data to send.
4878 branch.lock_write()4872 branch.lock_write()
@@ -4959,21 +4953,20 @@
4959 public_branch=public_branch, patch_type=patch_type,4953 public_branch=public_branch, patch_type=patch_type,
4960 message=message)4954 message=message)
49614955
4962 outfile.writelines(directive.to_lines())
4963 if output is None:4956 if output is None:
4964 subject = '[MERGE] '4957 directive.compose_merge_request(mail_client, mail_to, body,
4965 if message is not None:4958 branch, tree)
4966 subject += message4959 else:
4960 if output == '-':
4961 outfile = self.outf
4967 else:4962 else:
4968 revision = branch.repository.get_revision(revision_id)4963 outfile = open(output, 'wb')
4969 subject += revision.get_summary()4964 try:
4970 basename = directive.get_disk_name(branch)4965 outfile.writelines(directive.to_lines())
4971 mail_client.compose_merge_request(mail_to, subject,4966 finally:
4972 outfile.getvalue(),4967 if outfile is not self.outf:
4973 basename, body)4968 outfile.close()
4974 finally:4969 finally:
4975 if output != '-':
4976 outfile.close()
4977 branch.unlock()4970 branch.unlock()
49784971
49794972
49804973
=== modified file 'bzrlib/hooks.py'
--- bzrlib/hooks.py 2009-04-29 15:01:40 +0000
+++ bzrlib/hooks.py 2009-05-11 20:35:15 +0000
@@ -56,6 +56,9 @@
56known_hooks.register_lazy(56known_hooks.register_lazy(
57 ('bzrlib.version_info_formats.format_rio', 'RioVersionInfoBuilder.hooks'),57 ('bzrlib.version_info_formats.format_rio', 'RioVersionInfoBuilder.hooks'),
58 'bzrlib.version_info_formats.format_rio', 'RioVersionInfoBuilderHooks')58 'bzrlib.version_info_formats.format_rio', 'RioVersionInfoBuilderHooks')
59known_hooks.register_lazy(
60 ('bzrlib.merge_directive', '_BaseMergeDirective.hooks'),
61 'bzrlib.merge_directive', 'MergeDirectiveHooks')
5962
6063
61def known_hooks_key_to_object((module_name, member_name)):64def known_hooks_key_to_object((module_name, member_name)):
@@ -178,8 +181,8 @@
178 :ivar introduced: A version tuple specifying what version the hook was181 :ivar introduced: A version tuple specifying what version the hook was
179 introduced in. None indicates an unknown version.182 introduced in. None indicates an unknown version.
180 :ivar deprecated: A version tuple specifying what version the hook was183 :ivar deprecated: A version tuple specifying what version the hook was
181 deprecated or superceded in. None indicates that the hook is not184 deprecated or superseded in. None indicates that the hook is not
182 superceded or deprecated. If the hook is superceded then the doc185 superseded or deprecated. If the hook is superseded then the doc
183 should describe the recommended replacement hook to register for.186 should describe the recommended replacement hook to register for.
184 :ivar doc: The docs for using the hook.187 :ivar doc: The docs for using the hook.
185 """188 """
186189
=== modified file 'bzrlib/merge_directive.py'
--- bzrlib/merge_directive.py 2009-05-06 05:36:28 +0000
+++ bzrlib/merge_directive.py 2009-05-11 20:35:15 +0000
@@ -23,11 +23,13 @@
23 diff,23 diff,
24 errors,24 errors,
25 gpg,25 gpg,
26 hooks,
26 registry,27 registry,
27 revision as _mod_revision,28 revision as _mod_revision,
28 rio,29 rio,
29 testament,30 testament,
30 timestamp,31 timestamp,
32 trace,
31 )33 )
32from bzrlib.bundle import (34from bzrlib.bundle import (
33 serializer as bundle_serializer,35 serializer as bundle_serializer,
@@ -35,8 +37,37 @@
35from bzrlib.email_message import EmailMessage37from bzrlib.email_message import EmailMessage
3638
3739
40class MergeRequestBodyParams(object):
41 """Parameter object for the merge_request_body hook."""
42
43 def __init__(self, body, orig_body, directive, to, basename, subject,
44 branch, tree=None):
45 self.body = body
46 self.orig_body = orig_body
47 self.directive = directive
48 self.branch = branch
49 self.tree = tree
50 self.to = to
51 self.basename = basename
52 self.subject = subject
53
54
55class MergeDirectiveHooks(hooks.Hooks):
56 """Hooks for MergeDirective classes."""
57
58 def __init__(self):
59 hooks.Hooks.__init__(self)
60 self.create_hook(hooks.HookPoint('merge_request_body',
61 "Called with a MergeRequestBodyParams when a body is needed for"
62 " a merge request. Callbacks must return a body. If more"
63 " than one callback is registered, the output of one callback is"
64 " provided to the next.", (1, 15, 0), False))
65
66
38class _BaseMergeDirective(object):67class _BaseMergeDirective(object):
3968
69 hooks = MergeDirectiveHooks()
70
40 def __init__(self, revision_id, testament_sha1, time, timezone,71 def __init__(self, revision_id, testament_sha1, time, timezone,
41 target_branch, patch=None, source_branch=None, message=None,72 target_branch, patch=None, source_branch=None, message=None,
42 bundle=None):73 bundle=None):
@@ -240,6 +271,37 @@
240 target_repo.fetch(source_branch.repository, self.revision_id)271 target_repo.fetch(source_branch.repository, self.revision_id)
241 return self.revision_id272 return self.revision_id
242273
274 def compose_merge_request(self, mail_client, to, body, branch, tree=None):
275 """Compose a request to merge this directive.
276
277 :param mail_client: The mail client to use for composing this request.
278 :param to: The address to compose the request to.
279 :param branch: The Branch that was used to produce this directive.
280 :param tree: The Tree (if any) for the Branch used to produce this
281 directive.
282 """
283 basename = self.get_disk_name(branch)
284 subject = '[MERGE] '
285 if self.message is not None:
286 subject += self.message
287 else:
288 revision = branch.repository.get_revision(self.revision_id)
289 subject += revision.get_summary()
290 if getattr(mail_client, 'supports_body', False):
291 orig_body = body
292 for hook in self.hooks['merge_request_body']:
293 params = MergeRequestBodyParams(body, orig_body, self,
294 to, basename, subject, branch,
295 tree)
296 body = hook(params)
297 elif len(self.hooks['merge_request_body']) > 0:
298 trace.warning('Cannot run merge_request_body hooks because mail'
299 ' client %s does not support message bodies.',
300 mail_client.__class__.__name__)
301 mail_client.compose_merge_request(to, subject,
302 ''.join(self.to_lines()),
303 basename, body)
304
243305
244class MergeDirective(_BaseMergeDirective):306class MergeDirective(_BaseMergeDirective):
245307
246308
=== modified file 'bzrlib/tests/test_merge_directive.py'
--- bzrlib/tests/test_merge_directive.py 2009-03-23 14:59:43 +0000
+++ bzrlib/tests/test_merge_directive.py 2009-05-11 20:35:15 +0000
@@ -19,8 +19,10 @@
19from bzrlib import (19from bzrlib import (
20 errors,20 errors,
21 gpg,21 gpg,
22 mail_client,
22 merge_directive,23 merge_directive,
23 tests,24 tests,
25 trace,
24 )26 )
2527
2628
@@ -697,3 +699,89 @@
697 self.assertEqual('booga', md.patch)699 self.assertEqual('booga', md.patch)
698 self.assertEqual('diff', md.patch_type)700 self.assertEqual('diff', md.patch_type)
699 self.assertEqual('Hi mom!', md.message)701 self.assertEqual('Hi mom!', md.message)
702
703
704class TestHook(object):
705 """Hook callback for test purposes."""
706
707 def __init__(self, result=None):
708 self.calls = []
709 self.result = result
710
711 def __call__(self, params):
712 self.calls.append(params)
713 return self.result
714
715
716class HookMailClient(mail_client.MailClient):
717 """Mail client for testing hooks."""
718
719 def __init__(self, config):
720 self.body = None
721 self.config = config
722
723 def compose(self, prompt, to, subject, attachment, mime_subtype,
724 extension, basename=None, body=None):
725 self.body = body
726
727
728class TestBodyHook(tests.TestCaseWithTransport):
729
730 def compose_with_hooks(self, test_hooks, supports_body=True):
731 client = HookMailClient({})
732 client.supports_body = supports_body
733 for test_hook in test_hooks:
734 merge_directive.MergeDirective.hooks.install_named_hook(
735 'merge_request_body', test_hook, 'test')
736 tree = self.make_branch_and_tree('foo')
737 tree.commit('foo')
738 directive = merge_directive.MergeDirective2(
739 tree.branch.last_revision(), 'sha', 0, 0, 'sha',
740 source_branch=tree.branch.base,
741 base_revision_id=tree.branch.last_revision(),
742 message='This code rox')
743 directive.compose_merge_request(client, 'jrandom@example.com',
744 None, tree.branch)
745 return client, directive
746
747 def test_no_supports_body(self):
748 test_hook = TestHook('foo')
749 old_warn = trace.warning
750 warnings = []
751 def warn(*args):
752 warnings.append(args)
753 trace.warning = warn
754 try:
755 client, directive = self.compose_with_hooks([test_hook],
756 supports_body=False)
757 finally:
758 trace.warning = old_warn
759 self.assertEqual(0, len(test_hook.calls))
760 self.assertEqual(('Cannot run merge_request_body hooks because mail'
761 ' client %s does not support message bodies.',
762 'HookMailClient'), warnings[0])
763
764 def test_body_hook(self):
765 test_hook = TestHook('foo')
766 client, directive = self.compose_with_hooks([test_hook])
767 self.assertEqual(1, len(test_hook.calls))
768 self.assertEqual('foo', client.body)
769 params = test_hook.calls[0]
770 self.assertIsInstance(params,
771 merge_directive.MergeRequestBodyParams)
772 self.assertIs(None, params.body)
773 self.assertIs(None, params.orig_body)
774 self.assertEqual('jrandom@example.com', params.to)
775 self.assertEqual('[MERGE] This code rox', params.subject)
776 self.assertEqual(directive, params.directive)
777 self.assertEqual('foo-1', params.basename)
778
779 def test_body_hook_chaining(self):
780 test_hook1 = TestHook('foo')
781 test_hook2 = TestHook('bar')
782 client = self.compose_with_hooks([test_hook1, test_hook2])[0]
783 self.assertEqual(None, test_hook1.calls[0].body)
784 self.assertEqual(None, test_hook1.calls[0].orig_body)
785 self.assertEqual('foo', test_hook2.calls[0].body)
786 self.assertEqual(None, test_hook2.calls[0].orig_body)
787 self.assertEqual('bar', client.body)