|
84
by Vincent Ladeuil
(2, 5, 0, 'beta', 5) < (2, 5, 0, 'dev', 5) so (2,5) should be used. People can only encounter the issue if they upgrade bzr to > 2.5b5 at which point they need to upgrade the plugin too. |
1 |
# Copyright (C) 2006-2012 by Canonical Ltd
|
|
24
by John Arbash Meinel
Add GPL copyright |
2 |
#
|
3 |
# This program is free software; you can redistribute it and/or modify
|
|
4 |
# it under the terms of the GNU General Public License as published by
|
|
5 |
# the Free Software Foundation; either version 2 of the License, or
|
|
6 |
# (at your option) any later version.
|
|
7 |
#
|
|
8 |
# This program is distributed in the hope that it will be useful,
|
|
9 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
10 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
11 |
# GNU General Public License for more details.
|
|
12 |
#
|
|
|
57
by James Henstridge
* Bump version number to 1.3.0 final. |
13 |
# You should have received a copy of the GNU General Public License along
|
14 |
# with this program; if not, write to the Free Software Foundation, Inc.,
|
|
15 |
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
2
by John Arbash Meinel
Adding basic work for a test suite. |
16 |
"""Submit an email to a Patch Queue Manager"""
|
17 |
||
|
18.2.1
by Aaron Bentley
Better diagnostics when out-of-sync |
18 |
from bzrlib import ( |
|
37.1.3
by James Henstridge
Try and share more configuration from merge directive code: |
19 |
config as _mod_config, |
|
18.2.1
by Aaron Bentley
Better diagnostics when out-of-sync |
20 |
errors, |
|
37.1.3
by James Henstridge
Try and share more configuration from merge directive code: |
21 |
gpg, |
22 |
osutils, |
|
23 |
urlutils, |
|
|
79.1.1
by Jelmer Vernooij
Fix compatibility with newer versions of bzr, which use a new config method. |
24 |
version_info as bzrlib_version, |
|
18.2.1
by Aaron Bentley
Better diagnostics when out-of-sync |
25 |
)
|
|
11
by John Arbash Meinel
[patch] Aaron Bentley: Check the public branch has the expected revision. |
26 |
from bzrlib.branch import Branch |
|
37.1.2
by James Henstridge
Kill the custom smtplib code, and use bzr's email+smtp infrastructure. |
27 |
from bzrlib.email_message import EmailMessage |
28 |
from bzrlib.smtp_connection import SMTPConnection |
|
|
80.2.2
by Jelmer Vernooij
Register the bzr-pqm configuration options. |
29 |
from bzrlib.trace import note |
|
2
by John Arbash Meinel
Adding basic work for a test suite. |
30 |
|
31 |
||
|
34
by Robert Collins
Fix the test_suite method to actually return the correct tests and add a test for commit messages with newlines, fixing bug #110137. (Robert Collins) |
32 |
class BadCommitMessage(errors.BzrError): |
|
39
by John Arbash Meinel
Update copyright and version information. |
33 |
|
|
34
by Robert Collins
Fix the test_suite method to actually return the correct tests and add a test for commit messages with newlines, fixing bug #110137. (Robert Collins) |
34 |
_fmt = "The commit message %(msg)r cannot be used by pqm." |
35 |
||
|
76
by Vincent Ladeuil
Fix bzr test failure, 'message' shouldn'y be used as an exception attribute (arguably the test should test that instead of the __init__ arguments). |
36 |
def __init__(self, msg): |
|
34
by Robert Collins
Fix the test_suite method to actually return the correct tests and add a test for commit messages with newlines, fixing bug #110137. (Robert Collins) |
37 |
errors.BzrError.__init__(self) |
|
76
by Vincent Ladeuil
Fix bzr test failure, 'message' shouldn'y be used as an exception attribute (arguably the test should test that instead of the __init__ arguments). |
38 |
self.msg = msg |
|
34
by Robert Collins
Fix the test_suite method to actually return the correct tests and add a test for commit messages with newlines, fixing bug #110137. (Robert Collins) |
39 |
|
40 |
||
|
54
by James Henstridge
* Use the standard NoPublicBranch exception to complain about a missing |
41 |
class NoPQMSubmissionAddress(errors.BzrError): |
42 |
||
43 |
_fmt = "No PQM submission email address specified for %(branch_url)s." |
|
44 |
||
45 |
def __init__(self, branch): |
|
|
63.2.1
by Martin Pool
More robust NoPQMSubmissionAddress message |
46 |
if (branch is None) or (branch.base is None): |
47 |
branch_url = '(none)' |
|
48 |
else: |
|
49 |
branch_url = urlutils.unescape_for_display(branch.base, 'ascii') |
|
|
54
by James Henstridge
* Use the standard NoPublicBranch exception to complain about a missing |
50 |
errors.BzrError.__init__(self, branch_url=branch_url) |
51 |
||
52 |
||
|
45
by John Arbash Meinel
Fix bug #160530, don't use proper email subject lines, as the PQM bot doesn't understand them. |
53 |
class PQMEmailMessage(EmailMessage): |
54 |
"""PQM doesn't support proper email subjects, so we hack around it."""
|
|
55 |
||
56 |
def __init__(self, from_address, to_address, subject, body=None): |
|
57 |
EmailMessage.__init__(self, from_address=from_address, |
|
58 |
to_address=to_address, subject=subject, |
|
59 |
body=body) |
|
60 |
# Now override self.Subject to use raw utf-8
|
|
61 |
self._headers['Subject'] = osutils.safe_unicode(subject).encode('UTF-8') |
|
62 |
||
63 |
||
|
37.1.4
by James Henstridge
* Move the PQM submission logic into a PQMSubmission object. |
64 |
class PQMSubmission(object): |
65 |
"""A request to perform a PQM merge into a branch."""
|
|
66 |
||
|
37.1.5
by James Henstridge
Simplify PQMSubmission object, fixing up locations in the constructor. |
67 |
def __init__(self, source_branch, public_location=None, |
|
42
by John Arbash Meinel
Fix bug #141026, check that the WT is clean before submitting. |
68 |
submit_location=None, message=None, |
69 |
tree=None): |
|
|
37.1.4
by James Henstridge
* Move the PQM submission logic into a PQMSubmission object. |
70 |
"""Create a PQMSubmission object.
|
71 |
||
72 |
:param source_branch: the source branch for the merge
|
|
|
37.1.5
by James Henstridge
Simplify PQMSubmission object, fixing up locations in the constructor. |
73 |
:param public_location: the public location of the source branch
|
74 |
:param submit_location: the location of the target branch
|
|
|
37.1.4
by James Henstridge
* Move the PQM submission logic into a PQMSubmission object. |
75 |
:param message: The message to use when committing this merge
|
|
42
by John Arbash Meinel
Fix bug #141026, check that the WT is clean before submitting. |
76 |
:param tree: A WorkingTree or None. If not None the WT will be checked
|
77 |
for uncommitted changes.
|
|
|
37.1.5
by James Henstridge
Simplify PQMSubmission object, fixing up locations in the constructor. |
78 |
|
79 |
If any of public_location, submit_location or message are
|
|
80 |
omitted, they will be calculated from source_branch.
|
|
|
37.1.4
by James Henstridge
* Move the PQM submission logic into a PQMSubmission object. |
81 |
"""
|
|
61
by Andrew Bennetts
Slightly hackish way to allow submitting branches I don't have locally. |
82 |
if source_branch is None and public_location is None: |
|
37.1.5
by James Henstridge
Simplify PQMSubmission object, fixing up locations in the constructor. |
83 |
raise errors.NoMergeSource() |
|
37.1.4
by James Henstridge
* Move the PQM submission logic into a PQMSubmission object. |
84 |
self.source_branch = source_branch |
|
42
by John Arbash Meinel
Fix bug #141026, check that the WT is clean before submitting. |
85 |
self.tree = tree |
|
37.1.5
by James Henstridge
Simplify PQMSubmission object, fixing up locations in the constructor. |
86 |
|
87 |
if public_location is None: |
|
88 |
public_location = self.source_branch.get_public_branch() |
|
89 |
if public_location is None: |
|
|
54
by James Henstridge
* Use the standard NoPublicBranch exception to complain about a missing |
90 |
raise errors.NoPublicBranch(self.source_branch) |
|
37.1.5
by James Henstridge
Simplify PQMSubmission object, fixing up locations in the constructor. |
91 |
self.public_location = public_location |
92 |
||
93 |
if submit_location is None: |
|
|
61
by Andrew Bennetts
Slightly hackish way to allow submitting branches I don't have locally. |
94 |
if self.source_branch is None: |
95 |
raise errors.BzrError( |
|
96 |
"Cannot determine submit location to use.") |
|
|
37.1.5
by James Henstridge
Simplify PQMSubmission object, fixing up locations in the constructor. |
97 |
config = self.source_branch.get_config() |
|
80.2.1
by Jelmer Vernooij
Drop support for the long deprecated `pqm_branch` and `public_repository` options. |
98 |
submit_location = self.source_branch.get_submit_branch() |
|
37.1.5
by James Henstridge
Simplify PQMSubmission object, fixing up locations in the constructor. |
99 |
|
100 |
if submit_location is None: |
|
101 |
raise errors.NoSubmitBranch(self.source_branch) |
|
|
46
by John Arbash Meinel
Now that we are using submit_branch instead of pqm_branch, |
102 |
# See if the submit_location has a public branch
|
103 |
try: |
|
104 |
submit_branch = Branch.open(submit_location) |
|
105 |
except errors.NotBranchError: |
|
106 |
pass
|
|
107 |
else: |
|
108 |
submit_public_location = submit_branch.get_public_branch() |
|
109 |
if submit_public_location is not None: |
|
110 |
submit_location = submit_public_location |
|
|
37.1.5
by James Henstridge
Simplify PQMSubmission object, fixing up locations in the constructor. |
111 |
self.submit_location = submit_location |
|
37.1.4
by James Henstridge
* Move the PQM submission logic into a PQMSubmission object. |
112 |
|
113 |
# Check that the message is okay to pass to PQM
|
|
|
52.1.1
by John Arbash Meinel
Require using the --message flag. |
114 |
assert message is not None |
|
37.1.4
by James Henstridge
* Move the PQM submission logic into a PQMSubmission object. |
115 |
self.message = message.encode('utf8') |
116 |
if '\n' in self.message: |
|
117 |
raise BadCommitMessage(self.message) |
|
118 |
||
|
42
by John Arbash Meinel
Fix bug #141026, check that the WT is clean before submitting. |
119 |
def check_tree(self): |
120 |
"""Check that the working tree has no uncommitted changes."""
|
|
121 |
if self.tree is None: |
|
122 |
return
|
|
123 |
note('Checking the working tree is clean ...') |
|
124 |
self.tree.lock_read() |
|
125 |
try: |
|
126 |
basis_tree = self.tree.basis_tree() |
|
127 |
basis_tree.lock_read() |
|
128 |
try: |
|
|
52
by Aaron Bentley
Update to use public iter_changes |
129 |
for change in self.tree.iter_changes(basis_tree): |
|
42
by John Arbash Meinel
Fix bug #141026, check that the WT is clean before submitting. |
130 |
# If we have any changes, the tree is not clean
|
131 |
raise errors.UncommittedChanges(self.tree) |
|
132 |
finally: |
|
133 |
basis_tree.unlock() |
|
134 |
finally: |
|
135 |
self.tree.unlock() |
|
136 |
||
|
37.1.4
by James Henstridge
* Move the PQM submission logic into a PQMSubmission object. |
137 |
def check_public_branch(self): |
138 |
"""Check that the public branch is up to date with the local copy."""
|
|
|
59
by John Arbash Meinel
Display the public location when we check it for accuracy. |
139 |
note('Checking that the public branch is up to date at\n %s', |
140 |
urlutils.unescape_for_display(self.public_location, 'utf-8')) |
|
|
37.1.4
by James Henstridge
* Move the PQM submission logic into a PQMSubmission object. |
141 |
local_revision = self.source_branch.last_revision() |
|
37.1.5
by James Henstridge
Simplify PQMSubmission object, fixing up locations in the constructor. |
142 |
public_revision = Branch.open(self.public_location).last_revision() |
|
37.1.4
by James Henstridge
* Move the PQM submission logic into a PQMSubmission object. |
143 |
if local_revision != public_revision: |
144 |
raise errors.PublicBranchOutOfDate( |
|
|
37.1.5
by James Henstridge
Simplify PQMSubmission object, fixing up locations in the constructor. |
145 |
self.public_location, local_revision) |
|
37.1.4
by James Henstridge
* Move the PQM submission logic into a PQMSubmission object. |
146 |
|
147 |
def to_lines(self): |
|
148 |
"""Serialise as a list of lines."""
|
|
|
37.1.5
by James Henstridge
Simplify PQMSubmission object, fixing up locations in the constructor. |
149 |
return ['star-merge %s %s\n' % (self.public_location, self.submit_location)] |
|
37.1.4
by James Henstridge
* Move the PQM submission logic into a PQMSubmission object. |
150 |
|
151 |
def to_signed(self): |
|
152 |
"""Serialize as a signed string."""
|
|
153 |
unsigned_text = ''.join(self.to_lines()) |
|
154 |
unsigned_text = unsigned_text.encode('ascii') #URLs should be ascii |
|
155 |
||
|
84
by Vincent Ladeuil
(2, 5, 0, 'beta', 5) < (2, 5, 0, 'dev', 5) so (2,5) should be used. People can only encounter the issue if they upgrade bzr to > 2.5b5 at which point they need to upgrade the plugin too. |
156 |
if bzrlib_version < (2, 5): |
|
79.1.1
by Jelmer Vernooij
Fix compatibility with newer versions of bzr, which use a new config method. |
157 |
if self.source_branch: |
158 |
config = self.source_branch.get_config() |
|
159 |
else: |
|
160 |
config = _mod_config.GlobalConfig() |
|
|
61
by Andrew Bennetts
Slightly hackish way to allow submitting branches I don't have locally. |
161 |
else: |
|
79.1.1
by Jelmer Vernooij
Fix compatibility with newer versions of bzr, which use a new config method. |
162 |
if self.source_branch: |
163 |
config = self.source_branch.get_config_stack() |
|
164 |
else: |
|
165 |
config = _mod_config.GlobalStack() |
|
|
61
by Andrew Bennetts
Slightly hackish way to allow submitting branches I don't have locally. |
166 |
strategy = gpg.GPGStrategy(config) |
|
37.1.4
by James Henstridge
* Move the PQM submission logic into a PQMSubmission object. |
167 |
return strategy.sign(unsigned_text) |
168 |
||
169 |
def to_email(self, mail_from, mail_to, sign=True): |
|
170 |
"""Serialize as an email message.
|
|
171 |
||
172 |
:param mail_from: The from address for the message
|
|
173 |
:param mail_to: The address to send the message to
|
|
174 |
:param sign: If True, gpg-sign the email
|
|
175 |
:return: an email message
|
|
176 |
"""
|
|
177 |
if sign: |
|
178 |
body = self.to_signed() |
|
179 |
else: |
|
180 |
body = ''.join(self.to_lines()) |
|
|
45
by John Arbash Meinel
Fix bug #160530, don't use proper email subject lines, as the PQM bot doesn't understand them. |
181 |
message = PQMEmailMessage(mail_from, mail_to, self.message, body) |
|
37.1.4
by James Henstridge
* Move the PQM submission logic into a PQMSubmission object. |
182 |
return message |
|
37.1.3
by James Henstridge
Try and share more configuration from merge directive code: |
183 |
|
184 |
||
|
61
by Andrew Bennetts
Slightly hackish way to allow submitting branches I don't have locally. |
185 |
class StackedConfig(_mod_config.Config): |
186 |
||
187 |
def __init__(self): |
|
188 |
super(StackedConfig, self).__init__() |
|
189 |
self._sources = [] |
|
190 |
||
191 |
def add_source(self, source): |
|
192 |
self._sources.append(source) |
|
193 |
||
194 |
def _get_user_option(self, option_name): |
|
195 |
"""See Config._get_user_option."""
|
|
196 |
for source in self._sources: |
|
197 |
value = source._get_user_option(option_name) |
|
198 |
if value is not None: |
|
199 |
return value |
|
200 |
return None |
|
201 |
||
|
84.1.2
by Aaron Bentley
Fix email detection |
202 |
def get(self, option_name): |
|
84.1.5
by Aaron Bentley
Cleanup. |
203 |
"""Return an option exactly as bzrlib.config.Stack would.
|
204 |
||
205 |
Since Stack allows the environment to override 'email', this uses the
|
|
206 |
same logic.
|
|
207 |
"""
|
|
|
84.1.2
by Aaron Bentley
Fix email detection |
208 |
if option_name == 'email': |
209 |
return self.username() |
|
210 |
else: |
|
211 |
return self._get_user_option(option_name) |
|
|
82.1.1
by Jelmer Vernooij
Fix test suite when run against bzr 2.5. |
212 |
|
|
61
by Andrew Bennetts
Slightly hackish way to allow submitting branches I don't have locally. |
213 |
def _get_user_id(self): |
214 |
for source in self._sources: |
|
215 |
value = source._get_user_id() |
|
216 |
if value is not None: |
|
217 |
return value |
|
218 |
return None |
|
219 |
||
|
90
by Aaron Bentley
Fix config use for bzr 2.3-2.6dev |
220 |
def set(self, name, value): |
221 |
self._sources[0].set_user_option(name, value) |
|
222 |
||
|
61
by Andrew Bennetts
Slightly hackish way to allow submitting branches I don't have locally. |
223 |
|
|
80.1.1
by Jelmer Vernooij
Support child_pqm_email in lp-land command. |
224 |
def pqm_email(local_config, submit_location): |
225 |
"""Determine the PQM email address.
|
|
226 |
||
227 |
:param local_config: Config object for local branch
|
|
228 |
:param submit_location: Location of submit branch
|
|
229 |
"""
|
|
|
82.1.1
by Jelmer Vernooij
Fix test suite when run against bzr 2.5. |
230 |
mail_to = local_config.get('pqm_email') |
|
80.1.1
by Jelmer Vernooij
Support child_pqm_email in lp-land command. |
231 |
if not mail_to: |
232 |
submit_branch = Branch.open(submit_location) |
|
|
90
by Aaron Bentley
Fix config use for bzr 2.3-2.6dev |
233 |
submit_branch_config = get_stacked_config(submit_branch) |
|
82.1.1
by Jelmer Vernooij
Fix test suite when run against bzr 2.5. |
234 |
mail_to = submit_branch_config.get('child_pqm_email') |
|
80.1.1
by Jelmer Vernooij
Support child_pqm_email in lp-land command. |
235 |
if mail_to is None: |
236 |
return None |
|
237 |
return mail_to.encode('utf8') # same here |
|
238 |
||
239 |
||
|
84.1.5
by Aaron Bentley
Cleanup. |
240 |
def get_stacked_config(branch=None, public_location=None): |
241 |
"""Return the relevant stacked config.
|
|
242 |
||
243 |
If the branch is supplied, a branch stacked config is returned.
|
|
244 |
Otherwise, if the public location is supplied, a stacked location config
|
|
245 |
is returned.
|
|
246 |
Otherwise, a global config is returned.
|
|
247 |
||
248 |
For bzr versions earlier than 2.5, pqm_submit.StackedConfig is used. For
|
|
249 |
later versions, the standard stacked config is used.
|
|
250 |
||
251 |
:param branch: The branch to retrieve the config for.
|
|
252 |
:param public_location: The public location to retrieve the config for.
|
|
253 |
"""
|
|
|
84
by Vincent Ladeuil
(2, 5, 0, 'beta', 5) < (2, 5, 0, 'dev', 5) so (2,5) should be used. People can only encounter the issue if they upgrade bzr to > 2.5b5 at which point they need to upgrade the plugin too. |
254 |
if bzrlib_version < (2, 5): |
|
82.1.1
by Jelmer Vernooij
Fix test suite when run against bzr 2.5. |
255 |
config = StackedConfig() |
256 |
if branch: |
|
257 |
config.add_source(branch.get_config()) |
|
258 |
else: |
|
259 |
if public_location: |
|
260 |
config.add_source(_mod_config.LocationConfig(public_location)) |
|
261 |
config.add_source(_mod_config.GlobalConfig()) |
|
|
61
by Andrew Bennetts
Slightly hackish way to allow submitting branches I don't have locally. |
262 |
else: |
|
82.1.1
by Jelmer Vernooij
Fix test suite when run against bzr 2.5. |
263 |
if branch: |
|
82.1.2
by Jelmer Vernooij
Simplify config stack handling. |
264 |
config = branch.get_config_stack() |
265 |
elif public_location: |
|
266 |
config = _mod_config.LocationStack(public_location) |
|
267 |
else: |
|
268 |
config = _mod_config.GlobalStack() |
|
|
84.1.2
by Aaron Bentley
Fix email detection |
269 |
return config |
270 |
||
271 |
||
272 |
def get_mail_from(config): |
|
|
84.1.5
by Aaron Bentley
Cleanup. |
273 |
"""Return the email id that the email is from.
|
274 |
||
275 |
:param config: The config to use for determining the from address.
|
|
276 |
"""
|
|
|
84.1.2
by Aaron Bentley
Fix email detection |
277 |
mail_from = config.get('pqm_user_email') |
278 |
if not mail_from: |
|
279 |
mail_from = config.get('email') |
|
280 |
mail_from = mail_from.encode('utf8') # Make sure this isn't unicode |
|
281 |
return mail_from |
|
282 |
||
283 |
||
284 |
def submit(branch, message, dry_run=False, public_location=None, |
|
285 |
submit_location=None, tree=None, ignore_local=False): |
|
286 |
"""Submit the given branch to the pqm."""
|
|
287 |
config = get_stacked_config(branch, public_location) |
|
|
37.1.4
by James Henstridge
* Move the PQM submission logic into a PQMSubmission object. |
288 |
submission = PQMSubmission( |
|
42
by John Arbash Meinel
Fix bug #141026, check that the WT is clean before submitting. |
289 |
source_branch=branch, public_location=public_location, message=message, |
|
43
by John Arbash Meinel
Fix bug #110100, Add a --submit-location option. |
290 |
submit_location=submit_location, |
|
42
by John Arbash Meinel
Fix bug #141026, check that the WT is clean before submitting. |
291 |
tree=tree) |
|
3
by John Arbash Meinel
Everything is hooked up. |
292 |
|
|
84.1.2
by Aaron Bentley
Fix email detection |
293 |
mail_from = get_mail_from(config) |
|
80.1.1
by Jelmer Vernooij
Support child_pqm_email in lp-land command. |
294 |
mail_to = pqm_email(config, submit_location) |
|
37.1.4
by James Henstridge
* Move the PQM submission logic into a PQMSubmission object. |
295 |
if not mail_to: |
|
80.1.1
by Jelmer Vernooij
Support child_pqm_email in lp-land command. |
296 |
raise NoPQMSubmissionAddress(branch) |
|
37.1.4
by James Henstridge
* Move the PQM submission logic into a PQMSubmission object. |
297 |
|
|
61
by Andrew Bennetts
Slightly hackish way to allow submitting branches I don't have locally. |
298 |
if not ignore_local: |
299 |
submission.check_tree() |
|
300 |
submission.check_public_branch() |
|
|
37.1.4
by James Henstridge
* Move the PQM submission logic into a PQMSubmission object. |
301 |
|
302 |
message = submission.to_email(mail_from, mail_to) |
|
|
3
by John Arbash Meinel
Everything is hooked up. |
303 |
|
|
82.1.1
by Jelmer Vernooij
Fix test suite when run against bzr 2.5. |
304 |
mail_bcc = config.get('pqm_bcc') |
|
65.2.1
by Jelmer Vernooij
Add 'pqm_bcc' option. |
305 |
if mail_bcc is not None: |
306 |
message["Bcc"] = mail_bcc |
|
307 |
||
|
10
by John Arbash Meinel
[patch] Aaron Bentley: add --dry-run |
308 |
if dry_run: |
|
37.1.4
by James Henstridge
* Move the PQM submission logic into a PQMSubmission object. |
309 |
print message.as_string() |
|
37.1.2
by James Henstridge
Kill the custom smtplib code, and use bzr's email+smtp infrastructure. |
310 |
return
|
311 |
||
|
37.1.4
by James Henstridge
* Move the PQM submission logic into a PQMSubmission object. |
312 |
SMTPConnection(config).send_email(message) |