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