Merge lp:~parthm/bzr/173274-export-hooks into lp:bzr

Proposed by Parth Malwankar
Status: Work in progress
Proposed branch: lp:~parthm/bzr/173274-export-hooks
Merge into: lp:bzr
Diff against target: 215 lines (+158/-3)
4 files modified
NEWS (+4/-0)
bzrlib/export/__init__.py (+93/-3)
bzrlib/hooks.py (+5/-0)
bzrlib/tests/test_export.py (+56/-0)
To merge this branch: bzr merge lp:~parthm/bzr/173274-export-hooks
Reviewer Review Type Date Requested Status
Andrew Bennetts Needs Fixing
Review via email: mp+35919@code.launchpad.net

Commit message

support for pre_export and post_export hooks.

Description of the change

=== Fixes Bug #173274 ===
This patch adds support for pre_export and post_export hooks.

The 'return _exporters[format](...)' call in bzrlib.export.export didn't really return a value. Trivially fixed that also.

To post a comment you must log in.
Revision history for this message
Andrew Bennetts (spiv) wrote :

144 +# FIXME: As export is a directory based module (as against a py file), our
145 +# lazy registering doesn't see 'hooks' in the first tuple. To work around
146 +# this directly list __init__ below. This can possibly be cleaner.
147 +known_hooks.register_lazy(('bzrlib.export.__init__', 'hooks'),
148 + 'bzrlib.export', 'ExportHooks')

I don't understand why this is necessary... oh, I see. There's a bug in _LazyObjectGetter. I've submitted <https://code.edge.launchpad.net/~spiv/bzr/hooks-refactoring/+merge/36101> to fix this, and also make known_hooks registration a little neater. Until that lands this is an ok workaround.

36 - return _exporters[format](tree, dest, root, subdir, filtered=filtered,
37 - per_file_timestamps=per_file_timestamps)
38 + for hook in hooks['pre_export']:
39 + hook(ExportHookParams(tree, dest, format, root, subdir,
40 + filtered, per_file_timestamps))
41 + _exporters[format](tree, dest, root, subdir, filtered=filtered,
42 + per_file_timestamps=per_file_timestamps)
43 + for hook in hooks['post_export']:
44 + hook(ExportHookParams(tree, dest, format, root, subdir,
45 + filtered, per_file_timestamps))

Is this actually sufficient to provide the functionality requested in bug 173274? Specifically, it requests a hook that allows modifying the tree to export, e.g. by running autoconf, and having the new, unversioned (and probably ignored) files that generates be included, and possibly include modifications to versioned files too. I don't think this implementation allows that, as I believe that regardless of the pre_export hook it will still export "the last committed revision", as "bzr help export" says.

Well, the pre-hook could commit a new revision with the changes, and the post-hook could uncommit it... that would be a really ugly hack, and a plugin could already wrap cmd_export to do that sort of hack.

So, two changes I'd like to see:

 * support for somehow allowing selected unversioned files in the tree to be included in the export. Perhaps by changing the Tree object that is being exported?
 * tests that demonstrate that support, so that we can be confident the use case in bug 173274 is satisfied :)

review: Needs Fixing
Revision history for this message
Parth Malwankar (parthm) wrote :

> * support for somehow allowing selected unversioned files in the tree to be
> included in the export. Perhaps by changing the Tree object that is being
> exported?
> * tests that demonstrate that support, so that we can be confident the use
> case in bug 173274 is satisfied :)

Taking bug #619478 into account, maybe a --all or --unversioned flag for 'bzr export'? I can create a patch for that before we try to land this in case one of the above seems like a good solution. Thoughts?

Thanks for the review.

lp:~parthm/bzr/173274-export-hooks updated
5436. By Parth Malwankar

merged in changes from trunk

Revision history for this message
Martin Pool (mbp) wrote :

On 22 September 2010 01:21, Parth Malwankar <email address hidden> wrote:
>
>>  * support for somehow allowing selected unversioned files in the tree to be
>> included in the export.  Perhaps by changing the Tree object that is being
>> exported?
>>  * tests that demonstrate that support, so that we can be confident the use
>> case in bug 173274 is satisfied :)
>
> Taking bug #619478 into account, maybe a --all or --unversioned flag for 'bzr export'? I can create a patch for that before we try to land this in case one of the above seems like a good solution. Thoughts?

That sounds good. I'd used --uncommitted, to be consistent with merge.

--
Martin

Revision history for this message
Vincent Ladeuil (vila) wrote :

>>>>> Martin Pool <email address hidden> writes:

<snip/>

    >> Taking bug #619478 into account, maybe a --all or --unversioned
    >> flag for 'bzr export'? I can create a patch for that before we
    >> try to land this in case one of the above seems like a good
    >> solution. Thoughts?

    > That sounds good. I'd used --uncommitted, to be consistent with merge.

+1

Handling 'junk' files may become possible in the near future and using
'--uncommitted' to encompass all the kinds of unversioned file seems to
be the best bet.

   Vincent

Revision history for this message
John A Meinel (jameinel) wrote :

Just mentioning that "--uncommitted" sounds to me like "give me the
content of the *versioned* files that hasn't been committed yet". Which
is how merge uses it. Specifically, you don't get junk files when you
'merge --uncomitted'.

Revision history for this message
Andrew Bennetts (spiv) wrote :

John A Meinel wrote:
> Just mentioning that "--uncommitted" sounds to me like "give me the
> content of the *versioned* files that hasn't been committed yet". Which
> is how merge uses it. Specifically, you don't get junk files when you
> 'merge --uncomitted'.

Yes, I think uncommitted-but-versioned isn't what the use case is asking for.
It is asking for a way to get some (all?) unversioned files included (and
probably uncommitted changes too).

I'm not sure exactly what would best suit the use case (bzr export of a tree
including 'autoconf' output). Typically you wouldn't want to export all ignored
files (e.g. usually you don't want to ship: tags, *.pyc, *.o, ...). Perhaps the
hypothetical 'precious' designation is good enough, but I'm not sure it's an
exact fit. So I suspect a complete, polished solution may involve some extra
complexity (perhaps just some config in bzrrules to define some files as
unversioned_exportable)...

But that's policy. In terms of mechanism, we need a hook or other API that
gives a plugin a way to:

  * cause selected unversioned files to be included in the export
  * cause uncommitted versions of versioned files to be included in the export

Then we should be able to support whatever exact behaviour is needed.

Revision history for this message
Vincent Ladeuil (vila) wrote :

ping, this branch is now a pre-requisite of https://code.edge.launchpad.net/~gz/bzr/transport_post_connect_hook/+merge/38431

What's the status here ?

Revision history for this message
Martin Packman (gz) wrote :

Ah, this one isn't a pre-requisite for me, I just used Parth as a reference and inspiration. :)

Unmerged revisions

5436. By Parth Malwankar

merged in changes from trunk

5435. By Parth Malwankar

added NEWS entry. fixed typo.

5434. By Parth Malwankar

added pre-export and post-export hooks + tests.

5433. By Parth Malwankar

removed unwanted return from export function

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'NEWS'
--- NEWS 2010-09-21 10:48:23 +0000
+++ NEWS 2010-09-21 16:04:46 +0000
@@ -16,6 +16,10 @@
16New Features16New Features
17************17************
1818
19* Added ``pre_export`` and ``post_export`` hooks. This allows plugins
20 to register custom handlers which will be invoked before/after the
21 tree is exported. (Parth Malwankar, #173274)
22
19Bug Fixes23Bug Fixes
20*********24*********
2125
2226
=== modified file 'bzrlib/export/__init__.py'
--- bzrlib/export/__init__.py 2010-03-25 09:39:03 +0000
+++ bzrlib/export/__init__.py 2010-09-21 16:04:46 +0000
@@ -20,7 +20,11 @@
20"""20"""
2121
22import os22import os
23import bzrlib.errors as errors23
24from bzrlib import (
25 errors,
26 hooks as _mod_hooks,
27 )
2428
25# Maps format name => export function29# Maps format name => export function
26_exporters = {}30_exporters = {}
@@ -103,8 +107,14 @@
103 raise errors.NoSuchExportFormat(format)107 raise errors.NoSuchExportFormat(format)
104 tree.lock_read()108 tree.lock_read()
105 try:109 try:
106 return _exporters[format](tree, dest, root, subdir, filtered=filtered,110 for hook in hooks['pre_export']:
107 per_file_timestamps=per_file_timestamps)111 hook(ExportHookParams(tree, dest, format, root, subdir,
112 filtered, per_file_timestamps))
113 _exporters[format](tree, dest, root, subdir, filtered=filtered,
114 per_file_timestamps=per_file_timestamps)
115 for hook in hooks['post_export']:
116 hook(ExportHookParams(tree, dest, format, root, subdir,
117 filtered, per_file_timestamps))
108 finally:118 finally:
109 tree.unlock()119 tree.unlock()
110120
@@ -176,6 +186,86 @@
176 yield entry186 yield entry
177187
178188
189class ExportHooks(_mod_hooks.Hooks):
190 """A dictionary mapping hook name to a list of callables for export hooks.
191
192 e.g. ['pre_export'] is the list of items to be called before bzr
193 starts exporting files.
194 """
195
196 def __init__(self):
197 """Create the default hooks.
198
199 These are all empty initially, because by default nothing should get
200 notified.
201 """
202 _mod_hooks.Hooks.__init__(self)
203 self.create_hook(_mod_hooks.HookPoint('pre_export',
204 "Called with argument ExportHookParams before Bazaar "
205 "exports the tree. ExportHookParams has the attributes "
206 "(tree, dest, format, root, subdir, filtered, "
207 "per_file_timestamps). The first argument is the tree to be "
208 "exported, other arguments correspond to command "
209 "line options specified by the user for the export command.",
210 (2, 3), None))
211 self.create_hook(_mod_hooks.HookPoint('post_export',
212 "Called with argument ExportHookParams after Bazaar has "
213 "exported the tree. ExportHookParams has the attributes "
214 "(tree, dest, format, root, subdir, filtered, "
215 "per_file_timestamps). The first argument is the tree to be "
216 "exported, other arguments correspond to command "
217 "line options specified by the user for the export command.",
218 (2, 3), None))
219
220
221class ExportHookParams(object):
222 """Object holding parameters passed to post_export hooks.
223
224 :ivar tree: The tree to export
225 :ivar dest: The destination where the files etc. should be put
226 :ivar format: The format (dir, zip, etc)
227 :ivar root: Name of the root directory inside the exported file
228 :ivar subdir: The subdirectory to export.
229 :ivar filtered: If True, content filtering is applied to the
230 files exported.
231 :ivar per_file_timestamps: If True, use the timestamp stored in the
232 tree rather than now().
233 """
234
235 def __init__(self, tree, dest, format=None, root=None, subdir=None,
236 filtered=False, per_file_timestamps=False):
237 """Create a group of post_export hook parameters.
238
239 :param tree: The tree to export
240 :param dest: The destination where the files etc. should be put
241 :param format: The format (dir, zip, etc)
242 :param root: Name of the root directory inside the exported file
243 :param subdir: The subdirectory to export.
244 :param filtered: If True, content filtering is applied to the
245 files exported.
246 :param per_file_timestamps: If True, use the timestamp stored in the
247 tree rather than now().
248 """
249 self.tree = tree
250 self.dest = dest
251 self.format = format
252 self.root = root
253 self.subdir = subdir
254 self.filtered = filtered
255 self.per_file_timestamps = per_file_timestamps
256
257 def __eq__(self, other):
258 return self.__dict__ == other.__dict__
259
260 def __repr__(self):
261 return "<%s(%s, %s, %s, %s, %s, %s, %s)>" % (self.__class__.__name__,
262 self.tree, self.dest, self.format, self.root,
263 self.subdir, self.filtered, self.per_file_timestamps)
264
265
266hooks = ExportHooks()
267
268
179register_lazy_exporter(None, [], 'bzrlib.export.dir_exporter', 'dir_exporter')269register_lazy_exporter(None, [], 'bzrlib.export.dir_exporter', 'dir_exporter')
180register_lazy_exporter('dir', [], 'bzrlib.export.dir_exporter', 'dir_exporter')270register_lazy_exporter('dir', [], 'bzrlib.export.dir_exporter', 'dir_exporter')
181register_lazy_exporter('tar', ['.tar'], 'bzrlib.export.tar_exporter', 'tar_exporter')271register_lazy_exporter('tar', ['.tar'], 'bzrlib.export.tar_exporter', 'tar_exporter')
182272
=== modified file 'bzrlib/hooks.py'
--- bzrlib/hooks.py 2010-08-28 06:13:48 +0000
+++ bzrlib/hooks.py 2010-09-21 16:04:46 +0000
@@ -40,6 +40,11 @@
40 'BzrDirHooks')40 'BzrDirHooks')
41known_hooks.register_lazy(('bzrlib.commands', 'Command.hooks'),41known_hooks.register_lazy(('bzrlib.commands', 'Command.hooks'),
42 'bzrlib.commands', 'CommandHooks')42 'bzrlib.commands', 'CommandHooks')
43# FIXME: As export is a directory based module (as against a py file), our
44# lazy registering doesn't see 'hooks' in the first tuple. To work around
45# this directly list __init__ below. This can possibly be cleaner.
46known_hooks.register_lazy(('bzrlib.export.__init__', 'hooks'),
47 'bzrlib.export', 'ExportHooks')
43known_hooks.register_lazy(('bzrlib.info', 'hooks'),48known_hooks.register_lazy(('bzrlib.info', 'hooks'),
44 'bzrlib.info', 'InfoHooks')49 'bzrlib.info', 'InfoHooks')
45known_hooks.register_lazy(('bzrlib.lock', 'Lock.hooks'), 'bzrlib.lock',50known_hooks.register_lazy(('bzrlib.lock', 'Lock.hooks'), 'bzrlib.lock',
4651
=== modified file 'bzrlib/tests/test_export.py'
--- bzrlib/tests/test_export.py 2010-04-14 00:11:32 +0000
+++ bzrlib/tests/test_export.py 2010-09-21 16:04:46 +0000
@@ -121,3 +121,59 @@
121 t = self.get_transport('target')121 t = self.get_transport('target')
122 self.assertEqual(a_time, t.stat('a').st_mtime)122 self.assertEqual(a_time, t.stat('a').st_mtime)
123 self.assertEqual(b_time, t.stat('b').st_mtime)123 self.assertEqual(b_time, t.stat('b').st_mtime)
124
125
126class TestHooks(tests.TestCaseWithTransport):
127
128 def build_tree_and_export(self):
129 """Build tree and run export."""
130 self.build_tree(['a/', 'a/b', 'a/c'])
131 wt = self.make_branch_and_tree('.')
132 wt.add(['a', 'a/b', 'a/c'])
133 export.export(wt, 'target', format="dir")
134
135 def assertIsExportHookParams(self, params):
136 """Ensure params is a valid ExportHookParams instance."""
137 self.assertIsInstance(params, export.ExportHookParams)
138 attrs = ['tree', 'dest', 'format', 'root', 'subdir',
139 'filtered', 'per_file_timestamps']
140 for a in attrs:
141 self.assertTrue(hasattr(params, a),
142 'Attribute "%s" not found in ExportHookParam' % a)
143
144 def test_constructor(self):
145 """Check that creating a ExportHooks instance has the right defaults.
146 """
147 hooks = export.ExportHooks()
148 self.assertTrue("pre_export" in hooks, "pre_export not in %s" % hooks)
149 self.assertTrue("post_export" in hooks, "post_export not in %s" % hooks)
150
151 def test_installed_hooks_are_ExportHooks(self):
152 """The installed hooks object should be a ExportHooks.
153 """
154 # The installed hooks are saved in self._preserved_hooks.
155 self.assertIsInstance(self._preserved_hooks[export.__init__][1],
156 export.__init__.ExportHooks)
157
158 def test_post_export_hook(self):
159 """Ensure that post_export hook is invoked with the right args.
160 """
161 calls = []
162 export.hooks.install_named_hook('post_export', calls.append, None)
163 self.assertLength(0, calls)
164 self.build_tree_and_export()
165 self.assertLength(1, calls)
166 params = calls[0]
167 self.assertIsExportHookParams(params)
168
169 def test_pre_export_hook(self):
170 """Ensure that pre_export hook is invoked with the right args.
171 """
172 calls = []
173 export.hooks.install_named_hook('pre_export', calls.append, None)
174 self.assertLength(0, calls)
175 self.build_tree_and_export()
176 self.assertLength(1, calls)
177 params = calls[0]
178 self.assertIsExportHookParams(params)
179