Commit | Line | Data |
---|---|---|
b4bddc06 CM |
1 | __copyright__ = """ |
2 | Copyright (C) 2005, Catalin Marinas <catalin.marinas@gmail.com> | |
3 | ||
4 | This program is free software; you can redistribute it and/or modify | |
5 | it under the terms of the GNU General Public License version 2 as | |
6 | published by the Free Software Foundation. | |
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 | ||
13 | You should have received a copy of the GNU General Public License | |
14 | along with this program; if not, write to the Free Software | |
15 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
16 | """ | |
17 | ||
6cf5ec9b | 18 | import sys, os, re, time, datetime, socket, smtplib, getpass |
61eed152 | 19 | import email, email.Utils, email.Header |
b4bddc06 | 20 | from optparse import OptionParser, make_option |
b4bddc06 CM |
21 | |
22 | from stgit.commands.common import * | |
23 | from stgit.utils import * | |
5e888f30 | 24 | from stgit.out import * |
1f3bb017 | 25 | from stgit import stack, git, version, templates |
b4bddc06 CM |
26 | from stgit.config import config |
27 | ||
28 | ||
29 | help = 'send a patch or series of patches by e-mail' | |
6b1e0111 | 30 | usage = """%prog [options] [<patch1>] [<patch2>] [<patch3>..<patch4>] |
26aab5b0 | 31 | |
cec913c4 KH |
32 | Send a patch or a range of patches by e-mail using the SMTP server |
33 | specified by the 'stgit.smtpserver' configuration option, or the | |
34 | '--smtp-server' command line option. The From address and the e-mail | |
35 | format are generated from the template file passed as argument to | |
36 | '--template' (defaulting to '.git/patchmail.tmpl' or | |
79df2f0d | 37 | '~/.stgit/templates/patchmail.tmpl' or |
e5c32acf CM |
38 | '/usr/share/stgit/templates/patchmail.tmpl'). A patch can be sent as |
39 | attachment using the --attach option in which case the 'mailattch.tmpl' | |
40 | template will be used instead of 'patchmail.tmpl'. | |
79df2f0d CM |
41 | |
42 | The To/Cc/Bcc addresses can either be added to the template file or | |
43 | passed via the corresponding command line options. They can be e-mail | |
44 | addresses or aliases which are automatically expanded to the values | |
45 | stored in the [mail "alias"] section of GIT configuration files. | |
2bb96902 | 46 | |
0ba13ee9 KH |
47 | A preamble e-mail can be sent using the '--cover' and/or |
48 | '--edit-cover' options. The first allows the user to specify a file to | |
49 | be used as a template. The latter option will invoke the editor on the | |
50 | specified file (defaulting to '.git/covermail.tmpl' or | |
94d18868 YD |
51 | '~/.stgit/templates/covermail.tmpl' or |
52 | '/usr/share/stgit/templates/covermail.tmpl'). | |
e3e05587 CM |
53 | |
54 | All the subsequent e-mails appear as replies to the first e-mail sent | |
55 | (either the preamble or the first patch). E-mails can be seen as | |
56 | replies to a different e-mail by using the '--refid' option. | |
26aab5b0 CM |
57 | |
58 | SMTP authentication is also possible with '--smtp-user' and | |
59 | '--smtp-password' options, also available as configuration settings: | |
fc44c2ca PR |
60 | 'smtpuser' and 'smtppassword'. TLS encryption can be enabled by |
61 | '--smtp-tls' option and 'smtptls' setting. | |
26aab5b0 | 62 | |
27827959 KH |
63 | The following variables are accepted by both the preamble and the |
64 | patch e-mail templates: | |
26aab5b0 | 65 | |
26aab5b0 | 66 | %(diffstat)s - diff statistics |
27827959 | 67 | %(number)s - empty if only one patch is sent or ' patchnr/totalnr' |
26aab5b0 | 68 | %(patchnr)s - patch number |
27827959 | 69 | %(sender)s - 'sender' or 'authname <authemail>' as per the config file |
26aab5b0 | 70 | %(totalnr)s - total number of patches to be sent |
27827959 KH |
71 | %(version)s - ' version' string passed on the command line (or empty) |
72 | ||
73 | In addition to the common variables, the preamble e-mail template | |
74 | accepts the following: | |
75 | ||
76 | %(shortlog)s - first line of each patch description, listed by author | |
77 | ||
78 | In addition to the common variables, the patch e-mail template accepts | |
79 | the following: | |
80 | ||
26aab5b0 | 81 | %(authdate)s - patch creation date |
27827959 KH |
82 | %(authemail)s - author's email |
83 | %(authname)s - author's name | |
26aab5b0 | 84 | %(commemail)s - committer's e-mail |
27827959 KH |
85 | %(commname)s - committer's name |
86 | %(diff)s - unified diff of the patch | |
87 | %(fromauth)s - 'From: author\\n\\n' if different from sender | |
88 | %(longdescr)s - the rest of the patch description, after the first line | |
89 | %(patch)s - patch name | |
90 | %(prefix)s - 'prefix ' string passed on the command line | |
91 | %(shortdescr)s - the first line of the patch description""" | |
b4bddc06 | 92 | |
6dd8fafa | 93 | directory = DirectoryHasRepository() |
9a316368 CM |
94 | options = [make_option('-a', '--all', |
95 | help = 'e-mail all the applied patches', | |
96 | action = 'store_true'), | |
2bb96902 | 97 | make_option('--to', |
e83b3149 PO |
98 | help = 'add TO to the To: list', |
99 | action = 'append'), | |
2bb96902 | 100 | make_option('--cc', |
e83b3149 PO |
101 | help = 'add CC to the Cc: list', |
102 | action = 'append'), | |
2bb96902 | 103 | make_option('--bcc', |
e83b3149 PO |
104 | help = 'add BCC to the Bcc: list', |
105 | action = 'append'), | |
f8d1cf65 CM |
106 | make_option('--auto', |
107 | help = 'automatically cc the patch signers', | |
108 | action = 'store_true'), | |
d1ed3a12 CM |
109 | make_option('--noreply', |
110 | help = 'do not send subsequent messages as replies', | |
111 | action = 'store_true'), | |
c2a8af1d CM |
112 | make_option('--unrelated', |
113 | help = 'send patches without sequence numbering', | |
114 | action = 'store_true'), | |
e5c32acf CM |
115 | make_option('--attach', |
116 | help = 'send a patch as attachment', | |
117 | action = 'store_true'), | |
d0d139a3 CM |
118 | make_option('-v', '--version', metavar = 'VERSION', |
119 | help = 'add VERSION to the [PATCH ...] prefix'), | |
d323b5da RR |
120 | make_option('--prefix', metavar = 'PREFIX', |
121 | help = 'add PREFIX to the [... PATCH ...] prefix'), | |
9a316368 CM |
122 | make_option('-t', '--template', metavar = 'FILE', |
123 | help = 'use FILE as the message template'), | |
e3e05587 CM |
124 | make_option('-c', '--cover', metavar = 'FILE', |
125 | help = 'send FILE as the cover message'), | |
0ba13ee9 | 126 | make_option('-e', '--edit-cover', |
e3e05587 CM |
127 | help = 'edit the cover message before sending', |
128 | action = 'store_true'), | |
0ba13ee9 KH |
129 | make_option('-E', '--edit-patches', |
130 | help = 'edit each patch before sending', | |
131 | action = 'store_true'), | |
b4bddc06 CM |
132 | make_option('-s', '--sleep', type = 'int', metavar = 'SECONDS', |
133 | help = 'sleep for SECONDS between e-mails sending'), | |
134 | make_option('--refid', | |
d0d139a3 | 135 | help = 'use REFID as the reference id'), |
cec913c4 KH |
136 | make_option('--smtp-server', metavar = 'HOST[:PORT]', |
137 | help = 'SMTP server to use for sending mail'), | |
eb026d93 B |
138 | make_option('-u', '--smtp-user', metavar = 'USER', |
139 | help = 'username for SMTP authentication'), | |
140 | make_option('-p', '--smtp-password', metavar = 'PASSWORD', | |
2f7c8b0b | 141 | help = 'username for SMTP authentication'), |
fc44c2ca PR |
142 | make_option('-T', '--smtp-tls', |
143 | help = 'use SMTP with TLS encryption', | |
144 | action = 'store_true'), | |
2f7c8b0b | 145 | make_option('-b', '--branch', |
29f00589 | 146 | help = 'use BRANCH instead of the default one'), |
2ace36ab YD |
147 | make_option('-O', '--diff-opts', |
148 | help = 'options to pass to git-diff'), | |
29f00589 CM |
149 | make_option('-m', '--mbox', |
150 | help = 'generate an mbox file instead of sending', | |
151 | action = 'store_true')] | |
b4bddc06 CM |
152 | |
153 | ||
901288c2 | 154 | def __get_sender(): |
dae0f0be CM |
155 | """Return the 'authname <authemail>' string as read from the |
156 | configuration file | |
157 | """ | |
c73e63b7 YD |
158 | sender=config.get('stgit.sender') |
159 | if not sender: | |
9e3f506f KH |
160 | try: |
161 | sender = str(git.user()) | |
162 | except git.GitException: | |
163 | sender = str(git.author()) | |
164 | ||
165 | if not sender: | |
901288c2 | 166 | raise CmdException, 'unknown sender details' |
dae0f0be | 167 | |
79df2f0d | 168 | return address_or_alias(sender) |
9e3f506f | 169 | |
d650d6ed | 170 | def __parse_addresses(msg): |
b4bddc06 CM |
171 | """Return a two elements tuple: (from, [to]) |
172 | """ | |
d650d6ed CM |
173 | def __addr_list(msg, header): |
174 | return [name_addr[1] for name_addr in | |
175 | email.Utils.getaddresses(msg.get_all(header, []))] | |
b4bddc06 | 176 | |
d650d6ed | 177 | from_addr_list = __addr_list(msg, 'From') |
24aadb3f | 178 | if len(from_addr_list) == 0: |
b4bddc06 | 179 | raise CmdException, 'No "From" address' |
d650d6ed CM |
180 | |
181 | to_addr_list = __addr_list(msg, 'To') + __addr_list(msg, 'Cc') \ | |
182 | + __addr_list(msg, 'Bcc') | |
b4bddc06 CM |
183 | if len(to_addr_list) == 0: |
184 | raise CmdException, 'No "To/Cc/Bcc" addresses' | |
185 | ||
186 | return (from_addr_list[0], to_addr_list) | |
187 | ||
eb026d93 | 188 | def __send_message(smtpserver, from_addr, to_addr_list, msg, sleep, |
fc44c2ca | 189 | smtpuser, smtppassword, use_tls): |
b4bddc06 CM |
190 | """Send the message using the given SMTP server |
191 | """ | |
192 | try: | |
193 | s = smtplib.SMTP(smtpserver) | |
194 | except Exception, err: | |
195 | raise CmdException, str(err) | |
196 | ||
197 | s.set_debuglevel(0) | |
198 | try: | |
eb026d93 B |
199 | if smtpuser and smtppassword: |
200 | s.ehlo() | |
fc44c2ca PR |
201 | if use_tls: |
202 | if not hasattr(socket, 'ssl'): | |
203 | raise CmdException, "cannot use TLS - no SSL support in Python" | |
204 | s.starttls() | |
205 | s.ehlo() | |
eb026d93 B |
206 | s.login(smtpuser, smtppassword) |
207 | ||
0bc1343c YD |
208 | result = s.sendmail(from_addr, to_addr_list, msg) |
209 | if len(result): | |
210 | print "mail server refused delivery for the following recipients: %s" % result | |
b4bddc06 CM |
211 | # give recipients a chance of receiving patches in the correct order |
212 | time.sleep(sleep) | |
213 | except Exception, err: | |
214 | raise CmdException, str(err) | |
215 | ||
216 | s.quit() | |
217 | ||
61eed152 | 218 | def __build_address_headers(msg, options, extra_cc = []): |
f8d1cf65 CM |
219 | """Build the address headers and check existing headers in the |
220 | template. | |
221 | """ | |
61eed152 CM |
222 | def __replace_header(header, addr): |
223 | if addr: | |
224 | crt_addr = msg[header] | |
225 | del msg[header] | |
f8d1cf65 | 226 | |
61eed152 | 227 | if crt_addr: |
79df2f0d | 228 | msg[header] = address_or_alias(', '.join([crt_addr, addr])) |
61eed152 | 229 | else: |
79df2f0d | 230 | msg[header] = address_or_alias(addr) |
f8d1cf65 | 231 | |
f8d1cf65 CM |
232 | to_addr = '' |
233 | cc_addr = '' | |
234 | bcc_addr = '' | |
235 | ||
c73e63b7 | 236 | autobcc = config.get('stgit.autobcc') or '' |
d884c4d8 | 237 | |
e83b3149 | 238 | if options.to: |
61eed152 | 239 | to_addr = ', '.join(options.to) |
e83b3149 | 240 | if options.cc: |
61eed152 | 241 | cc_addr = ', '.join(options.cc + extra_cc) |
e5c32acf | 242 | cc_addr = ', '.join(options.cc + extra_cc) |
f8d1cf65 | 243 | elif extra_cc: |
61eed152 | 244 | cc_addr = ', '.join(extra_cc) |
e83b3149 | 245 | if options.bcc: |
61eed152 | 246 | bcc_addr = ', '.join(options.bcc + [autobcc]) |
d884c4d8 CM |
247 | elif autobcc: |
248 | bcc_addr = autobcc | |
f8d1cf65 | 249 | |
61eed152 CM |
250 | __replace_header('To', to_addr) |
251 | __replace_header('Cc', cc_addr) | |
252 | __replace_header('Bcc', bcc_addr) | |
f8d1cf65 CM |
253 | |
254 | def __get_signers_list(msg): | |
255 | """Return the address list generated from signed-off-by and | |
256 | acked-by lines in the message. | |
257 | """ | |
258 | addr_list = [] | |
259 | ||
769cd397 | 260 | r = re.compile('^(signed-off-by|acked-by|cc):\s+(.+)$', re.I) |
f8d1cf65 CM |
261 | for line in msg.split('\n'): |
262 | m = r.match(line) | |
263 | if m: | |
264 | addr_list.append(m.expand('\g<2>')) | |
265 | ||
266 | return addr_list | |
e83b3149 | 267 | |
61eed152 CM |
268 | def __build_extra_headers(msg, msg_id, ref_id = None): |
269 | """Build extra email headers and encoding | |
19a56fa1 | 270 | """ |
61eed152 CM |
271 | del msg['Date'] |
272 | msg['Date'] = email.Utils.formatdate(localtime = True) | |
273 | msg['Message-ID'] = msg_id | |
274 | if ref_id: | |
00375337 CM |
275 | # make sure the ref id has the angle brackets |
276 | ref_id = '<%s>' % ref_id.strip(' \t\n<>') | |
61eed152 CM |
277 | msg['In-Reply-To'] = ref_id |
278 | msg['References'] = ref_id | |
279 | msg['User-Agent'] = 'StGIT/%s' % version.version | |
280 | ||
281 | def __encode_message(msg): | |
282 | # 7 or 8 bit encoding | |
283 | charset = email.Charset.Charset('utf-8') | |
284 | charset.body_encoding = None | |
285 | ||
286 | # encode headers | |
287 | for header, value in msg.items(): | |
288 | words = [] | |
289 | for word in value.split(' '): | |
290 | try: | |
291 | uword = unicode(word, 'utf-8') | |
292 | except UnicodeDecodeError: | |
293 | # maybe we should try a different encoding or report | |
294 | # the error. At the moment, we just ignore it | |
295 | pass | |
296 | words.append(email.Header.Header(uword).encode()) | |
297 | new_val = ' '.join(words) | |
298 | msg.replace_header(header, new_val) | |
299 | ||
300 | # encode the body and set the MIME and encoding headers | |
e5c32acf CM |
301 | if msg.is_multipart(): |
302 | for p in msg.get_payload(): | |
303 | p.set_charset(charset) | |
304 | else: | |
305 | msg.set_charset(charset) | |
19a56fa1 | 306 | |
58c61f10 | 307 | def __edit_message(msg): |
0ba13ee9 KH |
308 | fname = '.stgitmail.txt' |
309 | ||
310 | # create the initial file | |
311 | f = file(fname, 'w') | |
312 | f.write(msg) | |
313 | f.close() | |
314 | ||
83bb4e4c | 315 | call_editor(fname) |
0ba13ee9 KH |
316 | |
317 | # read the message back | |
318 | f = file(fname) | |
319 | msg = f.read() | |
320 | f.close() | |
321 | ||
322 | return msg | |
323 | ||
99c4a4c5 | 324 | def __build_cover(tmpl, patches, msg_id, options): |
e3e05587 | 325 | """Build the cover message (series description) to be sent via SMTP |
b4bddc06 | 326 | """ |
901288c2 | 327 | sender = __get_sender() |
dae0f0be | 328 | |
d0d139a3 CM |
329 | if options.version: |
330 | version_str = ' %s' % options.version | |
ed5de0cc CM |
331 | else: |
332 | version_str = '' | |
d0d139a3 | 333 | |
d323b5da RR |
334 | if options.prefix: |
335 | prefix_str = options.prefix + ' ' | |
336 | else: | |
a7e0d4ee YD |
337 | confprefix = config.get('stgit.mail.prefix') |
338 | if confprefix: | |
339 | prefix_str = confprefix + ' ' | |
340 | else: | |
341 | prefix_str = '' | |
d323b5da | 342 | |
99c4a4c5 | 343 | total_nr_str = str(len(patches)) |
b8d258e5 | 344 | patch_nr_str = '0'.zfill(len(total_nr_str)) |
99c4a4c5 | 345 | if len(patches) > 1: |
b8d258e5 CM |
346 | number_str = ' %s/%s' % (patch_nr_str, total_nr_str) |
347 | else: | |
348 | number_str = '' | |
b4bddc06 | 349 | |
901288c2 CM |
350 | tmpl_dict = {'sender': sender, |
351 | # for backward template compatibility | |
352 | 'maintainer': sender, | |
61eed152 CM |
353 | # for backward template compatibility |
354 | 'endofheaders': '', | |
355 | # for backward template compatibility | |
356 | 'date': '', | |
d0d139a3 | 357 | 'version': version_str, |
d323b5da | 358 | 'prefix': prefix_str, |
b8d258e5 CM |
359 | 'patchnr': patch_nr_str, |
360 | 'totalnr': total_nr_str, | |
99c4a4c5 | 361 | 'number': number_str, |
27827959 KH |
362 | 'shortlog': stack.shortlog(crt_series.get_patch(p) |
363 | for p in patches), | |
99c4a4c5 | 364 | 'diffstat': git.diffstat( |
6972fd6b KH |
365 | rev1 = git_id(crt_series, '%s//bottom' % patches[0]), |
366 | rev2 = git_id(crt_series, '%s//top' % patches[-1]))} | |
b4bddc06 CM |
367 | |
368 | try: | |
61eed152 | 369 | msg_string = tmpl % tmpl_dict |
b4bddc06 CM |
370 | except KeyError, err: |
371 | raise CmdException, 'Unknown patch template variable: %s' \ | |
372 | % err | |
373 | except TypeError: | |
374 | raise CmdException, 'Only "%(name)s" variables are ' \ | |
375 | 'supported in the patch template' | |
376 | ||
58c61f10 CM |
377 | if options.edit_cover: |
378 | msg_string = __edit_message(msg_string) | |
379 | ||
61eed152 CM |
380 | # The Python email message |
381 | try: | |
382 | msg = email.message_from_string(msg_string) | |
383 | except Exception, ex: | |
384 | raise CmdException, 'template parsing error: %s' % str(ex) | |
385 | ||
386 | __build_address_headers(msg, options) | |
387 | __build_extra_headers(msg, msg_id, options.refid) | |
388 | __encode_message(msg) | |
389 | ||
d650d6ed | 390 | return msg |
b4bddc06 | 391 | |
2bb96902 | 392 | def __build_message(tmpl, patch, patch_nr, total_nr, msg_id, ref_id, options): |
b4bddc06 CM |
393 | """Build the message to be sent via SMTP |
394 | """ | |
395 | p = crt_series.get_patch(patch) | |
396 | ||
397 | descr = p.get_description().strip() | |
398 | descr_lines = descr.split('\n') | |
399 | ||
400 | short_descr = descr_lines[0].rstrip() | |
61eed152 | 401 | long_descr = '\n'.join(descr_lines[1:]).lstrip() |
b4bddc06 | 402 | |
1d1485c3 CM |
403 | authname = p.get_authname(); |
404 | authemail = p.get_authemail(); | |
405 | commname = p.get_commname(); | |
406 | commemail = p.get_commemail(); | |
407 | ||
901288c2 | 408 | sender = __get_sender() |
1d1485c3 CM |
409 | |
410 | fromauth = '%s <%s>' % (authname, authemail) | |
901288c2 | 411 | if fromauth != sender: |
1d1485c3 CM |
412 | fromauth = 'From: %s\n\n' % fromauth |
413 | else: | |
414 | fromauth = '' | |
dae0f0be | 415 | |
d0d139a3 CM |
416 | if options.version: |
417 | version_str = ' %s' % options.version | |
ed5de0cc CM |
418 | else: |
419 | version_str = '' | |
d0d139a3 | 420 | |
d323b5da RR |
421 | if options.prefix: |
422 | prefix_str = options.prefix + ' ' | |
423 | else: | |
a7e0d4ee YD |
424 | confprefix = config.get('stgit.mail.prefix') |
425 | if confprefix: | |
426 | prefix_str = confprefix + ' ' | |
427 | else: | |
428 | prefix_str = '' | |
d323b5da | 429 | |
2ace36ab YD |
430 | if options.diff_opts: |
431 | diff_flags = options.diff_opts.split() | |
0d219030 YD |
432 | else: |
433 | diff_flags = [] | |
434 | ||
b4bddc06 CM |
435 | total_nr_str = str(total_nr) |
436 | patch_nr_str = str(patch_nr).zfill(len(total_nr_str)) | |
c2a8af1d | 437 | if not options.unrelated and total_nr > 1: |
b8d258e5 CM |
438 | number_str = ' %s/%s' % (patch_nr_str, total_nr_str) |
439 | else: | |
440 | number_str = '' | |
b4bddc06 CM |
441 | |
442 | tmpl_dict = {'patch': patch, | |
901288c2 CM |
443 | 'sender': sender, |
444 | # for backward template compatibility | |
445 | 'maintainer': sender, | |
b4bddc06 CM |
446 | 'shortdescr': short_descr, |
447 | 'longdescr': long_descr, | |
61eed152 CM |
448 | # for backward template compatibility |
449 | 'endofheaders': '', | |
6972fd6b KH |
450 | 'diff': git.diff( |
451 | rev1 = git_id(crt_series, '%s//bottom' % patch), | |
452 | rev2 = git_id(crt_series, '%s//top' % patch), | |
453 | diff_flags = diff_flags), | |
454 | 'diffstat': git.diffstat( | |
455 | rev1 = git_id(crt_series, '%s//bottom'%patch), | |
456 | rev2 = git_id(crt_series, '%s//top' % patch)), | |
61eed152 CM |
457 | # for backward template compatibility |
458 | 'date': '', | |
d0d139a3 | 459 | 'version': version_str, |
d323b5da | 460 | 'prefix': prefix_str, |
b4bddc06 CM |
461 | 'patchnr': patch_nr_str, |
462 | 'totalnr': total_nr_str, | |
b8d258e5 | 463 | 'number': number_str, |
1d1485c3 CM |
464 | 'fromauth': fromauth, |
465 | 'authname': authname, | |
466 | 'authemail': authemail, | |
b4bddc06 | 467 | 'authdate': p.get_authdate(), |
1d1485c3 CM |
468 | 'commname': commname, |
469 | 'commemail': commemail} | |
61eed152 | 470 | # change None to '' |
b4bddc06 CM |
471 | for key in tmpl_dict: |
472 | if not tmpl_dict[key]: | |
473 | tmpl_dict[key] = '' | |
474 | ||
475 | try: | |
61eed152 | 476 | msg_string = tmpl % tmpl_dict |
b4bddc06 CM |
477 | except KeyError, err: |
478 | raise CmdException, 'Unknown patch template variable: %s' \ | |
479 | % err | |
480 | except TypeError: | |
481 | raise CmdException, 'Only "%(name)s" variables are ' \ | |
482 | 'supported in the patch template' | |
483 | ||
58c61f10 CM |
484 | if options.edit_patches: |
485 | msg_string = __edit_message(msg_string) | |
486 | ||
61eed152 CM |
487 | # The Python email message |
488 | try: | |
489 | msg = email.message_from_string(msg_string) | |
490 | except Exception, ex: | |
491 | raise CmdException, 'template parsing error: %s' % str(ex) | |
492 | ||
493 | if options.auto: | |
494 | extra_cc = __get_signers_list(descr) | |
495 | else: | |
496 | extra_cc = [] | |
497 | ||
498 | __build_address_headers(msg, options, extra_cc) | |
499 | __build_extra_headers(msg, msg_id, ref_id) | |
500 | __encode_message(msg) | |
501 | ||
d650d6ed | 502 | return msg |
b4bddc06 | 503 | |
b4bddc06 CM |
504 | def func(parser, options, args): |
505 | """Send the patches by e-mail using the patchmail.tmpl file as | |
506 | a template | |
507 | """ | |
cec913c4 | 508 | smtpserver = options.smtp_server or config.get('stgit.smtpserver') |
eb026d93 | 509 | |
b4bddc06 | 510 | applied = crt_series.get_applied() |
b4bddc06 | 511 | |
6b1e0111 CM |
512 | if options.all: |
513 | patches = applied | |
514 | elif len(args) >= 1: | |
b4f656f0 CM |
515 | unapplied = crt_series.get_unapplied() |
516 | patches = parse_patches(args, applied + unapplied, len(applied)) | |
b4bddc06 | 517 | else: |
9a316368 | 518 | raise CmdException, 'Incorrect options. Unknown patches to send' |
b4bddc06 | 519 | |
c73e63b7 YD |
520 | smtppassword = options.smtp_password or config.get('stgit.smtppassword') |
521 | smtpuser = options.smtp_user or config.get('stgit.smtpuser') | |
fc44c2ca | 522 | smtpusetls = options.smtp_tls or config.get('stgit.smtptls') == 'yes' |
eb026d93 B |
523 | |
524 | if (smtppassword and not smtpuser): | |
525 | raise CmdException, 'SMTP password supplied, username needed' | |
fc44c2ca PR |
526 | if (smtpusetls and not smtpuser): |
527 | raise CmdException, 'SMTP over TLS requested, username needed' | |
6cf5ec9b PR |
528 | if (smtpuser and not smtppassword): |
529 | smtppassword = getpass.getpass("Please enter SMTP password: ") | |
eb026d93 | 530 | |
b4bddc06 | 531 | total_nr = len(patches) |
9a316368 CM |
532 | if total_nr == 0: |
533 | raise CmdException, 'No patches to send' | |
b4bddc06 | 534 | |
c2a8af1d CM |
535 | if options.refid: |
536 | if options.noreply or options.unrelated: | |
537 | raise CmdException, \ | |
538 | '--refid option not allowed with --noreply or --unrelated' | |
d1ed3a12 | 539 | ref_id = options.refid |
c2a8af1d CM |
540 | else: |
541 | ref_id = None | |
b4bddc06 | 542 | |
c73e63b7 | 543 | sleep = options.sleep or config.getint('stgit.smtpdelay') |
b4bddc06 | 544 | |
e3e05587 | 545 | # send the cover message (if any) |
0ba13ee9 | 546 | if options.cover or options.edit_cover: |
c2a8af1d CM |
547 | if options.unrelated: |
548 | raise CmdException, 'cover sending not allowed with --unrelated' | |
549 | ||
e3e05587 CM |
550 | # find the template file |
551 | if options.cover: | |
16fee874 | 552 | tmpl = file(options.cover).read() |
e3e05587 | 553 | else: |
1f3bb017 CM |
554 | tmpl = templates.get_template('covermail.tmpl') |
555 | if not tmpl: | |
556 | raise CmdException, 'No cover message template file found' | |
b4bddc06 CM |
557 | |
558 | msg_id = email.Utils.make_msgid('stgit') | |
99c4a4c5 | 559 | msg = __build_cover(tmpl, patches, msg_id, options) |
2bb96902 | 560 | from_addr, to_addr_list = __parse_addresses(msg) |
b4bddc06 | 561 | |
d650d6ed CM |
562 | msg_string = msg.as_string(options.mbox) |
563 | ||
b4bddc06 | 564 | # subsequent e-mails are seen as replies to the first one |
d1ed3a12 CM |
565 | if not options.noreply: |
566 | ref_id = msg_id | |
b4bddc06 | 567 | |
29f00589 | 568 | if options.mbox: |
27ac2b7e | 569 | out.stdout_raw(msg_string + '\n') |
29f00589 | 570 | else: |
27ac2b7e | 571 | out.start('Sending the cover message') |
d650d6ed | 572 | __send_message(smtpserver, from_addr, to_addr_list, msg_string, |
fc44c2ca | 573 | sleep, smtpuser, smtppassword, smtpusetls) |
27ac2b7e | 574 | out.done() |
b4bddc06 CM |
575 | |
576 | # send the patches | |
577 | if options.template: | |
1f3bb017 | 578 | tmpl = file(options.template).read() |
b4bddc06 | 579 | else: |
e5c32acf CM |
580 | if options.attach: |
581 | tmpl = templates.get_template('mailattch.tmpl') | |
582 | else: | |
583 | tmpl = templates.get_template('patchmail.tmpl') | |
1f3bb017 CM |
584 | if not tmpl: |
585 | raise CmdException, 'No e-mail template file found' | |
b4bddc06 CM |
586 | |
587 | for (p, patch_nr) in zip(patches, range(1, len(patches) + 1)): | |
588 | msg_id = email.Utils.make_msgid('stgit') | |
2bb96902 CM |
589 | msg = __build_message(tmpl, p, patch_nr, total_nr, msg_id, ref_id, |
590 | options) | |
591 | from_addr, to_addr_list = __parse_addresses(msg) | |
592 | ||
d650d6ed CM |
593 | msg_string = msg.as_string(options.mbox) |
594 | ||
b4bddc06 | 595 | # subsequent e-mails are seen as replies to the first one |
c2a8af1d | 596 | if not options.noreply and not options.unrelated and not ref_id: |
b4bddc06 CM |
597 | ref_id = msg_id |
598 | ||
29f00589 | 599 | if options.mbox: |
27ac2b7e | 600 | out.stdout_raw(msg_string + '\n') |
29f00589 | 601 | else: |
27ac2b7e | 602 | out.start('Sending patch "%s"' % p) |
d650d6ed | 603 | __send_message(smtpserver, from_addr, to_addr_list, msg_string, |
fc44c2ca | 604 | sleep, smtpuser, smtppassword, smtpusetls) |
27ac2b7e | 605 | out.done() |