Merge lp:~spiv/bzr-pqm/non-local-submission into lp:bzr-pqm
- non-local-submission
- Merge into devel
Status: | Merged |
---|---|
Merged at revision: | not available |
Proposed branch: | lp:~spiv/bzr-pqm/non-local-submission |
Merge into: | lp:bzr-pqm |
Diff against target: |
214 lines (+120/-11) 3 files modified
__init__.py (+10/-5) pqm_submit.py (+45/-6) test_pqm_submit.py (+65/-0) |
To merge this branch: | bzr merge lp:~spiv/bzr-pqm/non-local-submission |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Robert Collins | Approve | ||
Jonathan Lange (community) | Needs Fixing | ||
Review via email: mp+13333@code.launchpad.net |
Commit message
Description of the change
Andrew Bennetts (spiv) wrote : | # |
Jonathan Lange (jml) wrote : | # |
Wow, what an excellent idea.
It's not that hackish. Bazaar's config APIs don't really let you do much better than you are now, and I can tolerate the imperative linear search, because I'm a tolerant person.
I'm afraid though that this needs to at least have a good excuse for a lack of tests before it lands.
Thanks for JFDIng this.
jml
Robert Collins (lifeless) wrote : | # |
I think this is:
- non core
- really really useful
- going stale
So we should land it without tests, but file a bug saying that we're accruing some debt here.
- 62. By Andrew Bennetts
-
Merge fix-tests.
- 63. By Andrew Bennetts
-
Add some tests.
Andrew Bennetts (spiv) wrote : | # |
> Wow, what an excellent idea.
Thanks!
> I'm afraid though that this needs to at least have a good excuse for a lack of
> tests before it lands.
Some basic tests added. They are a bit too blackboxy, but they're a start. I had to fix the existing failures first, so hopefully that gives me some brownie points ;)
Andrew Bennetts (spiv) wrote : | # |
Robert just rubberstamped the tests, and asked me to land it, so I am. Therefore further criticism is welcome in the form of bug reports :)
Preview Diff
1 | === modified file '__init__.py' | |||
2 | --- __init__.py 2009-02-06 17:20:48 +0000 | |||
3 | +++ __init__.py 2009-11-24 07:37:09 +0000 | |||
4 | @@ -78,10 +78,11 @@ | |||
5 | 78 | help='Use this url as the public location to the pqm.'), | 78 | help='Use this url as the public location to the pqm.'), |
6 | 79 | Option('submit-branch', type=str, | 79 | Option('submit-branch', type=str, |
7 | 80 | help='Use this url as the target submission branch.'), | 80 | help='Use this url as the target submission branch.'), |
8 | 81 | Option('ignore-local', help='Do not check the local branch or tree.'), | ||
9 | 81 | ] | 82 | ] |
10 | 82 | 83 | ||
11 | 83 | def run(self, location=None, message=None, public_location=None, | 84 | def run(self, location=None, message=None, public_location=None, |
13 | 84 | dry_run=False, submit_branch=None): | 85 | dry_run=False, submit_branch=None, ignore_local=False): |
14 | 85 | from bzrlib import errors, trace, bzrdir | 86 | from bzrlib import errors, trace, bzrdir |
15 | 86 | if __name__ != 'bzrlib.plugins.pqm': | 87 | if __name__ != 'bzrlib.plugins.pqm': |
16 | 87 | trace.warning('The bzr-pqm plugin needs to be called' | 88 | trace.warning('The bzr-pqm plugin needs to be called' |
17 | @@ -91,9 +92,13 @@ | |||
18 | 91 | return 1 | 92 | return 1 |
19 | 92 | from bzrlib.plugins.pqm.pqm_submit import submit | 93 | from bzrlib.plugins.pqm.pqm_submit import submit |
20 | 93 | 94 | ||
24 | 94 | if location is None: | 95 | if ignore_local: |
25 | 95 | location = '.' | 96 | tree, b, relpath = None, None, None |
26 | 96 | tree, b, relpath = bzrdir.BzrDir.open_containing_tree_or_branch(location) | 97 | else: |
27 | 98 | if location is None: | ||
28 | 99 | location = '.' | ||
29 | 100 | tree, b, relpath = bzrdir.BzrDir.open_containing_tree_or_branch( | ||
30 | 101 | location) | ||
31 | 97 | if relpath and not tree and location != '.': | 102 | if relpath and not tree and location != '.': |
32 | 98 | raise errors.BzrCommandError( | 103 | raise errors.BzrCommandError( |
33 | 99 | 'No working tree was found, but we were not given the ' | 104 | 'No working tree was found, but we were not given the ' |
34 | @@ -105,7 +110,7 @@ | |||
35 | 105 | submit(b, message=message, dry_run=dry_run, | 110 | submit(b, message=message, dry_run=dry_run, |
36 | 106 | public_location=public_location, | 111 | public_location=public_location, |
37 | 107 | submit_location=submit_branch, | 112 | submit_location=submit_branch, |
39 | 108 | tree=tree) | 113 | tree=tree, ignore_local=ignore_local) |
40 | 109 | 114 | ||
41 | 110 | 115 | ||
42 | 111 | register_command(cmd_pqm_submit) | 116 | register_command(cmd_pqm_submit) |
43 | 112 | 117 | ||
44 | === modified file 'pqm_submit.py' | |||
45 | --- pqm_submit.py 2008-04-21 15:53:51 +0000 | |||
46 | +++ pqm_submit.py 2009-11-24 07:37:09 +0000 | |||
47 | @@ -75,7 +75,7 @@ | |||
48 | 75 | If any of public_location, submit_location or message are | 75 | If any of public_location, submit_location or message are |
49 | 76 | omitted, they will be calculated from source_branch. | 76 | omitted, they will be calculated from source_branch. |
50 | 77 | """ | 77 | """ |
52 | 78 | if source_branch is None: | 78 | if source_branch is None and public_location is None: |
53 | 79 | raise errors.NoMergeSource() | 79 | raise errors.NoMergeSource() |
54 | 80 | self.source_branch = source_branch | 80 | self.source_branch = source_branch |
55 | 81 | self.tree = tree | 81 | self.tree = tree |
56 | @@ -100,6 +100,9 @@ | |||
57 | 100 | self.public_location = public_location | 100 | self.public_location = public_location |
58 | 101 | 101 | ||
59 | 102 | if submit_location is None: | 102 | if submit_location is None: |
60 | 103 | if self.source_branch is None: | ||
61 | 104 | raise errors.BzrError( | ||
62 | 105 | "Cannot determine submit location to use.") | ||
63 | 103 | config = self.source_branch.get_config() | 106 | config = self.source_branch.get_config() |
64 | 104 | # First check the deprecated pqm_branch config key: | 107 | # First check the deprecated pqm_branch config key: |
65 | 105 | submit_location = config.get_user_option('pqm_branch') | 108 | submit_location = config.get_user_option('pqm_branch') |
66 | @@ -166,7 +169,11 @@ | |||
67 | 166 | unsigned_text = ''.join(self.to_lines()) | 169 | unsigned_text = ''.join(self.to_lines()) |
68 | 167 | unsigned_text = unsigned_text.encode('ascii') #URLs should be ascii | 170 | unsigned_text = unsigned_text.encode('ascii') #URLs should be ascii |
69 | 168 | 171 | ||
71 | 169 | strategy = gpg.GPGStrategy(self.source_branch.get_config()) | 172 | if self.source_branch: |
72 | 173 | config = self.source_branch.get_config() | ||
73 | 174 | else: | ||
74 | 175 | config = _mod_config.GlobalConfig() | ||
75 | 176 | strategy = gpg.GPGStrategy(config) | ||
76 | 170 | return strategy.sign(unsigned_text) | 177 | return strategy.sign(unsigned_text) |
77 | 171 | 178 | ||
78 | 172 | def to_email(self, mail_from, mail_to, sign=True): | 179 | def to_email(self, mail_from, mail_to, sign=True): |
79 | @@ -185,10 +192,41 @@ | |||
80 | 185 | return message | 192 | return message |
81 | 186 | 193 | ||
82 | 187 | 194 | ||
83 | 195 | class StackedConfig(_mod_config.Config): | ||
84 | 196 | |||
85 | 197 | def __init__(self): | ||
86 | 198 | super(StackedConfig, self).__init__() | ||
87 | 199 | self._sources = [] | ||
88 | 200 | |||
89 | 201 | def add_source(self, source): | ||
90 | 202 | self._sources.append(source) | ||
91 | 203 | |||
92 | 204 | def _get_user_option(self, option_name): | ||
93 | 205 | """See Config._get_user_option.""" | ||
94 | 206 | for source in self._sources: | ||
95 | 207 | value = source._get_user_option(option_name) | ||
96 | 208 | if value is not None: | ||
97 | 209 | return value | ||
98 | 210 | return None | ||
99 | 211 | |||
100 | 212 | def _get_user_id(self): | ||
101 | 213 | for source in self._sources: | ||
102 | 214 | value = source._get_user_id() | ||
103 | 215 | if value is not None: | ||
104 | 216 | return value | ||
105 | 217 | return None | ||
106 | 218 | |||
107 | 219 | |||
108 | 188 | def submit(branch, message, dry_run=False, public_location=None, | 220 | def submit(branch, message, dry_run=False, public_location=None, |
110 | 189 | submit_location=None, tree=None): | 221 | submit_location=None, tree=None, ignore_local=False): |
111 | 190 | """Submit the given branch to the pqm.""" | 222 | """Submit the given branch to the pqm.""" |
113 | 191 | config = branch.get_config() | 223 | config = StackedConfig() |
114 | 224 | if branch: | ||
115 | 225 | config.add_source(branch.get_config()) | ||
116 | 226 | else: | ||
117 | 227 | if public_location: | ||
118 | 228 | config.add_source(_mod_config.LocationConfig(public_location)) | ||
119 | 229 | config.add_source(_mod_config.GlobalConfig()) | ||
120 | 192 | 230 | ||
121 | 193 | submission = PQMSubmission( | 231 | submission = PQMSubmission( |
122 | 194 | source_branch=branch, public_location=public_location, message=message, | 232 | source_branch=branch, public_location=public_location, message=message, |
123 | @@ -204,8 +242,9 @@ | |||
124 | 204 | raise NoPQMSubmissionAddress(branch) | 242 | raise NoPQMSubmissionAddress(branch) |
125 | 205 | mail_to = mail_to.encode('utf8') # same here | 243 | mail_to = mail_to.encode('utf8') # same here |
126 | 206 | 244 | ||
129 | 207 | submission.check_tree() | 245 | if not ignore_local: |
130 | 208 | submission.check_public_branch() | 246 | submission.check_tree() |
131 | 247 | submission.check_public_branch() | ||
132 | 209 | 248 | ||
133 | 210 | message = submission.to_email(mail_from, mail_to) | 249 | message = submission.to_email(mail_from, mail_to) |
134 | 211 | 250 | ||
135 | 212 | 251 | ||
136 | === modified file 'test_pqm_submit.py' | |||
137 | --- test_pqm_submit.py 2009-11-24 01:35:37 +0000 | |||
138 | +++ test_pqm_submit.py 2009-11-24 07:37:09 +0000 | |||
139 | @@ -127,6 +127,16 @@ | |||
140 | 127 | self.assertRaises(errors.NotBranchError, | 127 | self.assertRaises(errors.NotBranchError, |
141 | 128 | submission.check_public_branch) | 128 | submission.check_public_branch) |
142 | 129 | 129 | ||
143 | 130 | def test_ignore_local(self): | ||
144 | 131 | submission = pqm_submit.PQMSubmission( | ||
145 | 132 | source_branch=None, | ||
146 | 133 | public_location='public-location', | ||
147 | 134 | submit_location='submit-branch', | ||
148 | 135 | message='merge message') | ||
149 | 136 | message = submission.to_email('from@address', 'to@address', sign=False) | ||
150 | 137 | self.assertContainsRe( | ||
151 | 138 | message.as_string(), 'star-merge public-location submit-branch') | ||
152 | 139 | |||
153 | 130 | def test_to_lines(self): | 140 | def test_to_lines(self): |
154 | 131 | source_branch = self.make_branch('source') | 141 | source_branch = self.make_branch('source') |
155 | 132 | submission = pqm_submit.PQMSubmission( | 142 | submission = pqm_submit.PQMSubmission( |
156 | @@ -406,3 +416,58 @@ | |||
157 | 406 | self.assertEqual(('jrandom@example.com', ['pqm@example.com']), | 416 | self.assertEqual(('jrandom@example.com', ['pqm@example.com']), |
158 | 407 | call[1:3]) | 417 | call[1:3]) |
159 | 408 | self.assertContainsRe(call[3], EMAIL) | 418 | self.assertContainsRe(call[3], EMAIL) |
160 | 419 | |||
161 | 420 | def test_ignore_local(self): | ||
162 | 421 | """--ignore-local can submit a branch that isn't available locally. | ||
163 | 422 | |||
164 | 423 | It will use the location config of the public location to determine the | ||
165 | 424 | from and to email addresses. | ||
166 | 425 | """ | ||
167 | 426 | config = _mod_config.LocationConfig('http://example.com/') | ||
168 | 427 | config.set_user_option('pqm_email', 'PQM <pqm@example.com>') | ||
169 | 428 | config.set_user_option( | ||
170 | 429 | 'pqm_user_email', 'J. Random Hacker <jrandom@example.com>') | ||
171 | 430 | (out, err, connect_calls, | ||
172 | 431 | sendmail_calls) = self.run_bzr_fakemail( | ||
173 | 432 | ['pqm-submit', '-m', 'commit message', | ||
174 | 433 | '--submit-branch', 'http://example.com/submit/', | ||
175 | 434 | '--ignore-local', '--public-location', | ||
176 | 435 | 'http://example.com/public/' | ||
177 | 436 | ]) | ||
178 | 437 | self.assertEqual('', out) | ||
179 | 438 | self.assertEqual(1, len(connect_calls)) | ||
180 | 439 | call = connect_calls[0] | ||
181 | 440 | self.assertEqual(('localhost', 0), call[1:3]) | ||
182 | 441 | self.assertEqual(1, len(sendmail_calls)) | ||
183 | 442 | call = sendmail_calls[0] | ||
184 | 443 | self.assertEqual(('jrandom@example.com', ['pqm@example.com']), | ||
185 | 444 | call[1:3]) | ||
186 | 445 | self.assertContainsRe(call[3], EMAIL) | ||
187 | 446 | |||
188 | 447 | def test_ignore_local_global_config_fallback(self): | ||
189 | 448 | """--ignore-local can submit a branch that isn't available locally. | ||
190 | 449 | |||
191 | 450 | If there's no location config for the public location, it will | ||
192 | 451 | determine the from and to email addresses from the global config. | ||
193 | 452 | """ | ||
194 | 453 | config = _mod_config.GlobalConfig() | ||
195 | 454 | config.set_user_option('pqm_email', 'PQM <pqm@example.com>') | ||
196 | 455 | config.set_user_option( | ||
197 | 456 | 'pqm_user_email', 'J. Random Hacker <jrandom@example.com>') | ||
198 | 457 | (out, err, connect_calls, | ||
199 | 458 | sendmail_calls) = self.run_bzr_fakemail( | ||
200 | 459 | ['pqm-submit', '-m', 'commit message', | ||
201 | 460 | '--submit-branch', 'http://example.com/submit/', | ||
202 | 461 | '--ignore-local', '--public-location', | ||
203 | 462 | 'http://example.com/public/' | ||
204 | 463 | ]) | ||
205 | 464 | self.assertEqual('', out) | ||
206 | 465 | self.assertEqual(1, len(connect_calls)) | ||
207 | 466 | call = connect_calls[0] | ||
208 | 467 | self.assertEqual(('localhost', 0), call[1:3]) | ||
209 | 468 | self.assertEqual(1, len(sendmail_calls)) | ||
210 | 469 | call = sendmail_calls[0] | ||
211 | 470 | self.assertEqual(('jrandom@example.com', ['pqm@example.com']), | ||
212 | 471 | call[1:3]) | ||
213 | 472 | self.assertContainsRe(call[3], EMAIL) | ||
214 | 473 |
This could be better, especially in some of the error handling, but it allows me to pqm-submit branches without downloading them.
It adds a --ignore-local option which will skip opening the local branch/tree, and associated checks. In this case it will try looking up the config options (pqm_email etc) from the locations.conf for the public location (which must be given on the command line), and failing that will try the global conf. So e.g. I now have a [http:// bazaar. launchpad. net/~*/ bzr/] section in my locations.conf to specify the pqm_email.
Probably the StackedConfig class belongs in some form in bzrlib proper (at the moment BranchConfig does something similar but in a less reusable way).
Anyway, this is functional, despite the rough corners.