Status: | Merged |
---|---|
Approved by: | Andrew Bennetts |
Approved revision: | no longer in the source branch. |
Merged at revision: | not available |
Proposed branch: | lp:~lifeless/bzr/commands |
Merge into: | lp:bzr |
Diff against target: |
265 lines (+60/-23) 13 files modified
NEWS (+6/-0) bzrlib/commands.py (+38/-6) bzrlib/tests/commands/test_branch.py (+3/-3) bzrlib/tests/commands/test_cat.py (+1/-1) bzrlib/tests/commands/test_checkout.py (+2/-2) bzrlib/tests/commands/test_commit.py (+1/-1) bzrlib/tests/commands/test_init.py (+1/-1) bzrlib/tests/commands/test_init_repository.py (+1/-1) bzrlib/tests/commands/test_merge.py (+1/-2) bzrlib/tests/commands/test_missing.py (+1/-1) bzrlib/tests/commands/test_pull.py (+2/-2) bzrlib/tests/commands/test_push.py (+2/-2) bzrlib/tests/commands/test_update.py (+1/-1) |
To merge this branch: | bzr merge lp:~lifeless/bzr/commands |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Andrew Bennetts | Approve | ||
Review via email: mp+23068@code.launchpad.net |
Commit message
(robertc) Make Command.run safe to call directly. (Robert Collins)
Description of the change
This branch removes(deprecates) the helper function added in 2.1, Command.run_direct. run_direct is confusingly named (it is what was used to run with cleanups), and had the undesirable effect of making a previously safe to call function (command.run) unsafe to call. By decorating run, it is always safe to call run directly, and run stays both public for subclassing and public for calling.
Doing this fixes a number of bugs in bzrtools, loom and other plugins where the run method is currently called. We may wish to port this to 2.1 too, but I don't know if its considered 'minimal' enough.
Andrew Bennetts (spiv) wrote : | # |
Andrew Bennetts (spiv) wrote : | # |
52 + This is called by __init__ to make the Command be able to be run
53 + by just calling run(), as it could be before cleanups were added.
This part of the docstring is a bit unclear. People using the Command API shouldn't need to know about the history of the API to be able to use it.
Perhaps it should say something like this instead:
This is called by __init__ to make it possible to call cmd.run()
directly.
I'm still a bit uncomfortable with being so tricky, but it does seem like the best compromise that preserves the APIs we like (that subclasses can override run; callers can invoke run directly; cleanups added during run will be executed).
Robert Collins (lifeless) wrote : | # |
Thanks, I've tweaked the doc string with that insight, and its on the way now.
Martin Pool (mbp) wrote : | # |
On 9 April 2010 14:10, Robert Collins <email address hidden> wrote:
> Robert Collins has proposed merging lp:~lifeless/bzr/commands into lp:bzr.
>
> Requested reviews:
> bzr-core (bzr-core)
>
>
> This branch removes(deprecates) the helper function added in 2.1, Command.run_direct. run_direct is confusingly named (it is what was used to run with cleanups), and had the undesirable effect of making a previously safe to call function (command.run) unsafe to call. By decorating run, it is always safe to call run directly, and run stays both public for subclassing and public for calling.
>
> Doing this fixes a number of bugs in bzrtools, loom and other plugins where the run method is currently called. We may wish to port this to 2.1 too, but I don't know if its considered 'minimal' enough.
We shouldn't make any deprecations in stable series unless there is a
really strong reason, eg that it is the only feasible way to fix a
serious bug. I don't know if the other parts of this would make sense
or be worth backporting without that change?
--
Martin <http://
Robert Collins (lifeless) wrote : | # |
On Mon, 2010-04-12 at 04:12 +0000, Martin Pool wrote:
> On 9 April 2010 14:10, Robert Collins <email address hidden> wrote:
> > Robert Collins has proposed merging lp:~lifeless/bzr/commands into lp:bzr.
> >
> > Requested reviews:
> > bzr-core (bzr-core)
> >
> >
> > This branch removes(deprecates) the helper function added in 2.1, Command.run_direct. run_direct is confusingly named (it is what was used to run with cleanups), and had the undesirable effect of making a previously safe to call function (command.run) unsafe to call. By decorating run, it is always safe to call run directly, and run stays both public for subclassing and public for calling.
> >
> > Doing this fixes a number of bugs in bzrtools, loom and other plugins where the run method is currently called. We may wish to port this to 2.1 too, but I don't know if its considered 'minimal' enough.
>
> We shouldn't make any deprecations in stable series unless there is a
> really strong reason, eg that it is the only feasible way to fix a
> serious bug. I don't know if the other parts of this would make sense
> or be worth backporting without that change?
I think having people change code to call a new function we've removed
immediately after isn't worth a deprecation-
change the docstring to say 'deprecated in 2.2' and have them use run();
and yes I think taking the other changes than the deprecation would be
sensible for 2.1.
-Rob
Preview Diff
1 | === modified file 'NEWS' | |||
2 | --- NEWS 2010-04-08 08:48:59 +0000 | |||
3 | +++ NEWS 2010-04-09 04:10:34 +0000 | |||
4 | @@ -57,6 +57,12 @@ | |||
5 | 57 | Internals | 57 | Internals |
6 | 58 | ********* | 58 | ********* |
7 | 59 | 59 | ||
8 | 60 | * ``bzrlib.commands.Command.run_direct`` is no longer needed - the pre | ||
9 | 61 | 2.1 method of calling run() to perform testing or direct use via the API | ||
10 | 62 | is now possible again. As part of this, the _operation attribute on | ||
11 | 63 | Command is now transient and only exists for the duration of ``run()``. | ||
12 | 64 | (Robert Collins) | ||
13 | 65 | |||
14 | 60 | Testing | 66 | Testing |
15 | 61 | ******* | 67 | ******* |
16 | 62 | 68 | ||
17 | 63 | 69 | ||
18 | === modified file 'bzrlib/commands.py' | |||
19 | --- bzrlib/commands.py 2010-04-01 03:52:41 +0000 | |||
20 | +++ bzrlib/commands.py 2010-04-09 04:10:34 +0000 | |||
21 | @@ -411,7 +411,7 @@ | |||
22 | 411 | warn("No help message set for %r" % self) | 411 | warn("No help message set for %r" % self) |
23 | 412 | # List of standard options directly supported | 412 | # List of standard options directly supported |
24 | 413 | self.supported_std_options = [] | 413 | self.supported_std_options = [] |
26 | 414 | self._operation = cleanup.OperationWithCleanups(self.run) | 414 | self._setup_run() |
27 | 415 | 415 | ||
28 | 416 | def add_cleanup(self, cleanup_func, *args, **kwargs): | 416 | def add_cleanup(self, cleanup_func, *args, **kwargs): |
29 | 417 | """Register a function to call after self.run returns or raises. | 417 | """Register a function to call after self.run returns or raises. |
30 | @@ -429,7 +429,9 @@ | |||
31 | 429 | 429 | ||
32 | 430 | This is useful for releasing expensive or contentious resources (such | 430 | This is useful for releasing expensive or contentious resources (such |
33 | 431 | as write locks) before doing further work that does not require those | 431 | as write locks) before doing further work that does not require those |
35 | 432 | resources (such as writing results to self.outf). | 432 | resources (such as writing results to self.outf). Note though, that |
36 | 433 | as it releases all resources, this may release locks that the command | ||
37 | 434 | wants to hold, so use should be done with care. | ||
38 | 433 | """ | 435 | """ |
39 | 434 | self._operation.cleanup_now() | 436 | self._operation.cleanup_now() |
40 | 435 | 437 | ||
41 | @@ -680,11 +682,30 @@ | |||
42 | 680 | 682 | ||
43 | 681 | self._setup_outf() | 683 | self._setup_outf() |
44 | 682 | 684 | ||
47 | 683 | return self.run_direct(**all_cmd_args) | 685 | return self.run(**all_cmd_args) |
48 | 684 | 686 | ||
49 | 687 | def _setup_run(self): | ||
50 | 688 | """Wrap the defined run method on self with a cleanup. | ||
51 | 689 | |||
52 | 690 | This is called by __init__ to make the Command be able to be run | ||
53 | 691 | by just calling run(), as it could be before cleanups were added. | ||
54 | 692 | |||
55 | 693 | If a different form of cleanups are in use by your Command subclass, | ||
56 | 694 | you can override this method. | ||
57 | 695 | """ | ||
58 | 696 | class_run = self.run | ||
59 | 697 | def run(*args, **kwargs): | ||
60 | 698 | self._operation = cleanup.OperationWithCleanups(class_run) | ||
61 | 699 | try: | ||
62 | 700 | return self._operation.run_simple(*args, **kwargs) | ||
63 | 701 | finally: | ||
64 | 702 | del self._operation | ||
65 | 703 | self.run = run | ||
66 | 704 | |||
67 | 705 | @deprecated_method(deprecated_in((2, 2, 0))) | ||
68 | 685 | def run_direct(self, *args, **kwargs): | 706 | def run_direct(self, *args, **kwargs): |
71 | 686 | """Call run directly with objects (without parsing an argv list).""" | 707 | """Deprecated thunk from bzrlib 2.1.""" |
72 | 687 | return self._operation.run_simple(*args, **kwargs) | 708 | return self.run(*args, **kwargs) |
73 | 688 | 709 | ||
74 | 689 | def run(self): | 710 | def run(self): |
75 | 690 | """Actually run the command. | 711 | """Actually run the command. |
76 | @@ -695,6 +716,17 @@ | |||
77 | 695 | Return 0 or None if the command was successful, or a non-zero | 716 | Return 0 or None if the command was successful, or a non-zero |
78 | 696 | shell error code if not. It's OK for this method to allow | 717 | shell error code if not. It's OK for this method to allow |
79 | 697 | an exception to raise up. | 718 | an exception to raise up. |
80 | 719 | |||
81 | 720 | This method is automatically wrapped by Command.__init__ with a | ||
82 | 721 | cleanup operation, stored as self._operation. This can be used | ||
83 | 722 | via self.add_cleanup to perform automatic cleanups at the end of | ||
84 | 723 | run(). | ||
85 | 724 | |||
86 | 725 | The argument for run are assembled by introspection. So for instance, | ||
87 | 726 | if your command takes an argument files, you would declare:: | ||
88 | 727 | |||
89 | 728 | def run(self, files=None): | ||
90 | 729 | pass | ||
91 | 698 | """ | 730 | """ |
92 | 699 | raise NotImplementedError('no implementation of command %r' | 731 | raise NotImplementedError('no implementation of command %r' |
93 | 700 | % self.name()) | 732 | % self.name()) |
94 | 701 | 733 | ||
95 | === modified file 'bzrlib/tests/commands/test_branch.py' | |||
96 | --- bzrlib/tests/commands/test_branch.py 2010-02-17 17:11:16 +0000 | |||
97 | +++ bzrlib/tests/commands/test_branch.py 2010-04-09 04:10:34 +0000 | |||
98 | @@ -28,16 +28,16 @@ | |||
99 | 28 | 28 | ||
100 | 29 | def test_branch_remote_local(self): | 29 | def test_branch_remote_local(self): |
101 | 30 | cmd = cmd_branch() | 30 | cmd = cmd_branch() |
103 | 31 | cmd.run_direct(self.get_url('branch'), 'local') | 31 | cmd.run(self.get_url('branch'), 'local') |
104 | 32 | self.assertEquals(1, len(self.connections)) | 32 | self.assertEquals(1, len(self.connections)) |
105 | 33 | 33 | ||
106 | 34 | def test_branch_local_remote(self): | 34 | def test_branch_local_remote(self): |
107 | 35 | cmd = cmd_branch() | 35 | cmd = cmd_branch() |
109 | 36 | cmd.run_direct('branch', self.get_url('remote')) | 36 | cmd.run('branch', self.get_url('remote')) |
110 | 37 | self.assertEquals(1, len(self.connections)) | 37 | self.assertEquals(1, len(self.connections)) |
111 | 38 | 38 | ||
112 | 39 | def test_branch_remote_remote(self): | 39 | def test_branch_remote_remote(self): |
113 | 40 | cmd = cmd_branch() | 40 | cmd = cmd_branch() |
115 | 41 | cmd.run_direct(self.get_url('branch'), self.get_url('remote')) | 41 | cmd.run(self.get_url('branch'), self.get_url('remote')) |
116 | 42 | self.assertEquals(2, len(self.connections)) | 42 | self.assertEquals(2, len(self.connections)) |
117 | 43 | 43 | ||
118 | 44 | 44 | ||
119 | === modified file 'bzrlib/tests/commands/test_cat.py' | |||
120 | --- bzrlib/tests/commands/test_cat.py 2010-02-23 07:43:11 +0000 | |||
121 | +++ bzrlib/tests/commands/test_cat.py 2010-04-09 04:10:34 +0000 | |||
122 | @@ -44,7 +44,7 @@ | |||
123 | 44 | self.start_logging_connections() | 44 | self.start_logging_connections() |
124 | 45 | 45 | ||
125 | 46 | cmd = cmd_cat() | 46 | cmd = cmd_cat() |
127 | 47 | cmd.run_direct(self.get_url('branch/foo')) | 47 | cmd.run(self.get_url('branch/foo')) |
128 | 48 | self.assertEquals(1, len(self.connections)) | 48 | self.assertEquals(1, len(self.connections)) |
129 | 49 | self.assertEquals('foo', self.outf.getvalue()) | 49 | self.assertEquals('foo', self.outf.getvalue()) |
130 | 50 | 50 | ||
131 | 51 | 51 | ||
132 | === modified file 'bzrlib/tests/commands/test_checkout.py' | |||
133 | --- bzrlib/tests/commands/test_checkout.py 2010-02-17 17:11:16 +0000 | |||
134 | +++ bzrlib/tests/commands/test_checkout.py 2010-04-09 04:10:34 +0000 | |||
135 | @@ -27,7 +27,7 @@ | |||
136 | 27 | self.start_logging_connections() | 27 | self.start_logging_connections() |
137 | 28 | 28 | ||
138 | 29 | cmd = cmd_checkout() | 29 | cmd = cmd_checkout() |
140 | 30 | cmd.run_direct(self.get_url('branch1'), 'local') | 30 | cmd.run(self.get_url('branch1'), 'local') |
141 | 31 | self.assertEquals(1, len(self.connections)) | 31 | self.assertEquals(1, len(self.connections)) |
142 | 32 | 32 | ||
143 | 33 | def test_checkout_lightweight(self): | 33 | def test_checkout_lightweight(self): |
144 | @@ -36,6 +36,6 @@ | |||
145 | 36 | self.start_logging_connections() | 36 | self.start_logging_connections() |
146 | 37 | 37 | ||
147 | 38 | cmd = cmd_checkout() | 38 | cmd = cmd_checkout() |
149 | 39 | cmd.run_direct(self.get_url('branch1'), 'local', lightweight=True) | 39 | cmd.run(self.get_url('branch1'), 'local', lightweight=True) |
150 | 40 | self.assertEquals(1, len(self.connections)) | 40 | self.assertEquals(1, len(self.connections)) |
151 | 41 | 41 | ||
152 | 42 | 42 | ||
153 | === modified file 'bzrlib/tests/commands/test_commit.py' | |||
154 | --- bzrlib/tests/commands/test_commit.py 2010-03-01 19:53:13 +0000 | |||
155 | +++ bzrlib/tests/commands/test_commit.py 2010-04-09 04:10:34 +0000 | |||
156 | @@ -42,7 +42,7 @@ | |||
157 | 42 | # commit do not provide a directory parameter, we have to change dir | 42 | # commit do not provide a directory parameter, we have to change dir |
158 | 43 | # manually | 43 | # manually |
159 | 44 | os.chdir('local') | 44 | os.chdir('local') |
161 | 45 | commit.run_direct(message=u'empty commit', unchanged=True) | 45 | commit.run(message=u'empty commit', unchanged=True) |
162 | 46 | self.assertEquals(1, len(self.connections)) | 46 | self.assertEquals(1, len(self.connections)) |
163 | 47 | 47 | ||
164 | 48 | def test_commit_both_modified(self): | 48 | def test_commit_both_modified(self): |
165 | 49 | 49 | ||
166 | === modified file 'bzrlib/tests/commands/test_init.py' | |||
167 | --- bzrlib/tests/commands/test_init.py 2010-02-17 17:11:16 +0000 | |||
168 | +++ bzrlib/tests/commands/test_init.py 2010-04-09 04:10:34 +0000 | |||
169 | @@ -30,6 +30,6 @@ | |||
170 | 30 | cmd = cmd_init() | 30 | cmd = cmd_init() |
171 | 31 | # We don't care about the ouput but 'outf' should be defined | 31 | # We don't care about the ouput but 'outf' should be defined |
172 | 32 | cmd.outf = tests.StringIOWrapper() | 32 | cmd.outf = tests.StringIOWrapper() |
174 | 33 | cmd.run_direct(self.get_url()) | 33 | cmd.run(self.get_url()) |
175 | 34 | self.assertEquals(1, len(self.connections)) | 34 | self.assertEquals(1, len(self.connections)) |
176 | 35 | 35 | ||
177 | 36 | 36 | ||
178 | === modified file 'bzrlib/tests/commands/test_init_repository.py' | |||
179 | --- bzrlib/tests/commands/test_init_repository.py 2010-02-17 17:11:16 +0000 | |||
180 | +++ bzrlib/tests/commands/test_init_repository.py 2010-04-09 04:10:34 +0000 | |||
181 | @@ -30,6 +30,6 @@ | |||
182 | 30 | cmd = cmd_init_repository() | 30 | cmd = cmd_init_repository() |
183 | 31 | # We don't care about the ouput but 'outf' should be defined | 31 | # We don't care about the ouput but 'outf' should be defined |
184 | 32 | cmd.outf = tests.StringIOWrapper() | 32 | cmd.outf = tests.StringIOWrapper() |
186 | 33 | cmd.run_direct(self.get_url()) | 33 | cmd.run(self.get_url()) |
187 | 34 | self.assertEquals(1, len(self.connections)) | 34 | self.assertEquals(1, len(self.connections)) |
188 | 35 | 35 | ||
189 | 36 | 36 | ||
190 | === modified file 'bzrlib/tests/commands/test_merge.py' | |||
191 | --- bzrlib/tests/commands/test_merge.py 2010-02-17 17:11:16 +0000 | |||
192 | +++ bzrlib/tests/commands/test_merge.py 2010-04-09 04:10:34 +0000 | |||
193 | @@ -34,6 +34,5 @@ | |||
194 | 34 | cmd = cmd_merge() | 34 | cmd = cmd_merge() |
195 | 35 | # We don't care about the ouput but 'outf' should be defined | 35 | # We don't care about the ouput but 'outf' should be defined |
196 | 36 | cmd.outf = StringIOWrapper() | 36 | cmd.outf = StringIOWrapper() |
198 | 37 | cmd.run_direct(self.get_url('branch1'), directory='branch2') | 37 | cmd.run(self.get_url('branch1'), directory='branch2') |
199 | 38 | self.assertEquals(1, len(self.connections)) | 38 | self.assertEquals(1, len(self.connections)) |
200 | 39 | |||
201 | 40 | 39 | ||
202 | === modified file 'bzrlib/tests/commands/test_missing.py' | |||
203 | --- bzrlib/tests/commands/test_missing.py 2010-02-17 17:11:16 +0000 | |||
204 | +++ bzrlib/tests/commands/test_missing.py 2010-04-09 04:10:34 +0000 | |||
205 | @@ -33,6 +33,6 @@ | |||
206 | 33 | cmd = cmd_missing() | 33 | cmd = cmd_missing() |
207 | 34 | # We don't care about the ouput but 'outf' should be defined | 34 | # We don't care about the ouput but 'outf' should be defined |
208 | 35 | cmd.outf = self.make_utf8_encoded_stringio() | 35 | cmd.outf = self.make_utf8_encoded_stringio() |
210 | 36 | cmd.run_direct(self.get_url('branch2')) | 36 | cmd.run(self.get_url('branch2')) |
211 | 37 | self.assertEquals(1, len(self.connections)) | 37 | self.assertEquals(1, len(self.connections)) |
212 | 38 | 38 | ||
213 | 39 | 39 | ||
214 | === modified file 'bzrlib/tests/commands/test_pull.py' | |||
215 | --- bzrlib/tests/commands/test_pull.py 2010-02-17 17:11:16 +0000 | |||
216 | +++ bzrlib/tests/commands/test_pull.py 2010-04-09 04:10:34 +0000 | |||
217 | @@ -35,7 +35,7 @@ | |||
218 | 35 | cmd = builtins.cmd_pull() | 35 | cmd = builtins.cmd_pull() |
219 | 36 | # We don't care about the ouput but 'outf' should be defined | 36 | # We don't care about the ouput but 'outf' should be defined |
220 | 37 | cmd.outf = tests.StringIOWrapper() | 37 | cmd.outf = tests.StringIOWrapper() |
222 | 38 | cmd.run_direct(self.get_url('branch1'), directory='branch2') | 38 | cmd.run(self.get_url('branch1'), directory='branch2') |
223 | 39 | self.assertEquals(1, len(self.connections)) | 39 | self.assertEquals(1, len(self.connections)) |
224 | 40 | 40 | ||
225 | 41 | def test_pull_with_bound_branch(self): | 41 | def test_pull_with_bound_branch(self): |
226 | @@ -53,6 +53,6 @@ | |||
227 | 53 | pull = builtins.cmd_pull() | 53 | pull = builtins.cmd_pull() |
228 | 54 | # We don't care about the ouput but 'outf' should be defined | 54 | # We don't care about the ouput but 'outf' should be defined |
229 | 55 | pull.outf = tests.StringIOWrapper() | 55 | pull.outf = tests.StringIOWrapper() |
231 | 56 | pull.run_direct(self.get_url('remote'), directory='local') | 56 | pull.run(self.get_url('remote'), directory='local') |
232 | 57 | self.assertEquals(1, len(self.connections)) | 57 | self.assertEquals(1, len(self.connections)) |
233 | 58 | 58 | ||
234 | 59 | 59 | ||
235 | === modified file 'bzrlib/tests/commands/test_push.py' | |||
236 | --- bzrlib/tests/commands/test_push.py 2010-02-17 17:11:16 +0000 | |||
237 | +++ bzrlib/tests/commands/test_push.py 2010-04-09 04:10:34 +0000 | |||
238 | @@ -30,7 +30,7 @@ | |||
239 | 30 | cmd = cmd_push() | 30 | cmd = cmd_push() |
240 | 31 | # We don't care about the ouput but 'outf' should be defined | 31 | # We don't care about the ouput but 'outf' should be defined |
241 | 32 | cmd.outf = tests.StringIOWrapper() | 32 | cmd.outf = tests.StringIOWrapper() |
243 | 33 | cmd.run_direct(self.get_url('remote'), directory='branch') | 33 | cmd.run(self.get_url('remote'), directory='branch') |
244 | 34 | self.assertEquals(1, len(self.connections)) | 34 | self.assertEquals(1, len(self.connections)) |
245 | 35 | 35 | ||
246 | 36 | def test_push_onto_stacked(self): | 36 | def test_push_onto_stacked(self): |
247 | @@ -41,6 +41,6 @@ | |||
248 | 41 | 41 | ||
249 | 42 | cmd = cmd_push() | 42 | cmd = cmd_push() |
250 | 43 | cmd.outf = tests.StringIOWrapper() | 43 | cmd.outf = tests.StringIOWrapper() |
252 | 44 | cmd.run_direct(self.get_url('remote'), directory='source', | 44 | cmd.run(self.get_url('remote'), directory='source', |
253 | 45 | stacked_on=self.get_url('base')) | 45 | stacked_on=self.get_url('base')) |
254 | 46 | self.assertEqual(1, len(self.connections)) | 46 | self.assertEqual(1, len(self.connections)) |
255 | 47 | 47 | ||
256 | === modified file 'bzrlib/tests/commands/test_update.py' | |||
257 | --- bzrlib/tests/commands/test_update.py 2010-02-17 17:11:16 +0000 | |||
258 | +++ bzrlib/tests/commands/test_update.py 2010-04-09 04:10:34 +0000 | |||
259 | @@ -40,6 +40,6 @@ | |||
260 | 40 | # update needs the encoding from outf to print URLs | 40 | # update needs the encoding from outf to print URLs |
261 | 41 | update.outf = tests.StringIOWrapper() | 41 | update.outf = tests.StringIOWrapper() |
262 | 42 | # update calls it 'dir' where other commands calls it 'directory' | 42 | # update calls it 'dir' where other commands calls it 'directory' |
264 | 43 | update.run_direct(dir='local') | 43 | update.run(dir='local') |
265 | 44 | self.assertEquals(1, len(self.connections)) | 44 | self.assertEquals(1, len(self.connections)) |
266 | 45 | 45 |
I think I like it, but I'm going to sleep on it over the weekend.