Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""
-import sys, os, re, time, datetime, smtplib, email.Utils
+import sys, os, re, time, datetime, smtplib
+import email, email.Utils, email.Header
from optparse import OptionParser, make_option
from stgit.commands.common import *
can either be added to the template file or passed via the
corresponding command line options.
-A preamble e-mail can be sent using the '--cover' and/or '--edit'
-options. The first allows the user to specify a file to be used as a
-template. The latter option will invoke the editor on the specified
-file (defaulting to '.git/covermail.tmpl' or
+A preamble e-mail can be sent using the '--cover' and/or
+'--edit-cover' options. The first allows the user to specify a file to
+be used as a template. The latter option will invoke the editor on the
+specified file (defaulting to '.git/covermail.tmpl' or
'~/.stgit/templates/covermail.tmpl' or
'/usr/share/stgit/templates/covermail.tmpl').
'--smtp-password' options, also available as configuration settings:
'smtpuser' and 'smtppassword'.
-The template e-mail headers and body must be separated by
-'%(endofheaders)s' variable, which is replaced by StGIT with
-additional headers and a blank line. The patch e-mail template accepts
-the following variables:
+The patch e-mail template accepts the following variables:
%(patch)s - patch name
%(maintainer)s - 'authname <authemail>' as read from the config file
%(shortdescr)s - the first line of the patch description
%(longdescr)s - the rest of the patch description, after the first line
- %(endofheaders)s - delimiter between e-mail headers and body
%(diff)s - unified diff of the patch
%(diffstat)s - diff statistics
- %(date)s - current date/time
%(version)s - ' version' string passed on the command line (or empty)
%(prefix)s - 'prefix ' string passed on the command line
%(patchnr)s - patch number
%(commname)s - committer's name
%(commemail)s - committer's e-mail
-For the preamble e-mail template, only the %(maintainer)s, %(date)s,
-%(endofheaders)s, %(version)s, %(patchnr)s, %(totalnr)s and %(number)s
-variables are supported."""
+For the preamble e-mail template, only the %(maintainer)s,
+%(version)s, %(patchnr)s, %(totalnr)s and %(number)s variables are
+supported."""
options = [make_option('-a', '--all',
help = 'e-mail all the applied patches',
help = 'use FILE as the message template'),
make_option('-c', '--cover', metavar = 'FILE',
help = 'send FILE as the cover message'),
- make_option('-e', '--edit',
+ make_option('-e', '--edit-cover',
help = 'edit the cover message before sending',
action = 'store_true'),
+ make_option('-E', '--edit-patches',
+ help = 'edit each patch before sending',
+ action = 'store_true'),
make_option('-s', '--sleep', type = 'int', metavar = 'SECONDS',
help = 'sleep for SECONDS between e-mails sending'),
make_option('--refid',
s.quit()
-def __write_mbox(from_addr, msg):
- """Write an mbox like file to the standard output
- """
- r = re.compile('^From ', re.M)
- msg = r.sub('>\g<0>', msg)
-
- print 'From %s %s' % (from_addr, datetime.datetime.today().ctime())
- print msg
- print
-
-def __build_address_headers(tmpl, options, extra_cc = []):
+def __build_address_headers(msg, options, extra_cc = []):
"""Build the address headers and check existing headers in the
template.
"""
- def csv(lst):
- s = ''
- for i in lst:
- if not i:
- continue
- if s:
- s += ', ' + i
- else:
- s = i
- return s
-
- def replace_header(header, addr, tmpl):
- r = re.compile('^' + header + ':\s+.+$', re.I | re.M)
- if r.search(tmpl):
- tmpl = r.sub('\g<0>, ' + addr, tmpl, 1)
- h = ''
- else:
- h = header + ': ' + addr
+ def __replace_header(header, addr):
+ if addr:
+ crt_addr = msg[header]
+ del msg[header]
- return tmpl, h
+ if crt_addr:
+ msg[header] = ', '.join([crt_addr, addr])
+ else:
+ msg[header] = addr
- headers = ''
to_addr = ''
cc_addr = ''
bcc_addr = ''
autobcc = ''
if options.to:
- to_addr = csv(options.to)
+ to_addr = ', '.join(options.to)
if options.cc:
- cc_addr = csv(options.cc + extra_cc)
+ cc_addr = ', '.join(options.cc + extra_cc)
elif extra_cc:
- cc_addr = csv(extra_cc)
+ cc_addr = ', '.join(extra_cc)
if options.bcc:
- bcc_addr = csv(options.bcc + [autobcc])
+ bcc_addr = ', '.join(options.bcc + [autobcc])
elif autobcc:
bcc_addr = autobcc
- # replace existing headers
- if to_addr:
- tmpl, h = replace_header('To', to_addr, tmpl)
- if h:
- headers += h + '\n'
- if cc_addr:
- tmpl, h = replace_header('Cc', cc_addr, tmpl)
- if h:
- headers += h + '\n'
- if bcc_addr:
- tmpl, h = replace_header('Bcc', bcc_addr, tmpl)
- if h:
- headers += h + '\n'
-
- return tmpl, headers
+ __replace_header('To', to_addr)
+ __replace_header('Cc', cc_addr)
+ __replace_header('Bcc', bcc_addr)
def __get_signers_list(msg):
"""Return the address list generated from signed-off-by and
return addr_list
-def __build_extra_headers():
- """Build extra headers like content-type etc.
+def __build_extra_headers(msg, msg_id, ref_id = None):
+ """Build extra email headers and encoding
"""
- headers = 'Content-Type: text/plain; charset=utf-8; format=fixed\n'
- headers += 'Content-Transfer-Encoding: 8bit\n'
- headers += 'User-Agent: StGIT/%s\n' % version.version
+ del msg['Date']
+ msg['Date'] = email.Utils.formatdate(localtime = True)
+ msg['Message-ID'] = msg_id
+ if ref_id:
+ msg['In-Reply-To'] = ref_id
+ msg['References'] = ref_id
+ msg['User-Agent'] = 'StGIT/%s' % version.version
+
+def __encode_message(msg):
+ # 7 or 8 bit encoding
+ charset = email.Charset.Charset('utf-8')
+ charset.body_encoding = None
+
+ # encode headers
+ for header, value in msg.items():
+ words = []
+ for word in value.split(' '):
+ try:
+ uword = unicode(word, 'utf-8')
+ except UnicodeDecodeError:
+ # maybe we should try a different encoding or report
+ # the error. At the moment, we just ignore it
+ pass
+ words.append(email.Header.Header(uword).encode())
+ new_val = ' '.join(words)
+ msg.replace_header(header, new_val)
+
+ # encode the body and set the MIME and encoding headers
+ msg.set_charset(charset)
+
+def edit_message(msg):
+ fname = '.stgitmail.txt'
+
+ # create the initial file
+ f = file(fname, 'w')
+ f.write(msg)
+ f.close()
+
+ # the editor
+ if config.has_option('stgit', 'editor'):
+ editor = config.get('stgit', 'editor')
+ elif 'EDITOR' in os.environ:
+ editor = os.environ['EDITOR']
+ else:
+ editor = 'vi'
+ editor += ' %s' % fname
+
+ print 'Invoking the editor: "%s"...' % editor,
+ sys.stdout.flush()
+ print 'done (exit code: %d)' % os.system(editor)
- return headers
+ # read the message back
+ f = file(fname)
+ msg = f.read()
+ f.close()
+
+ return msg
def __build_cover(tmpl, total_nr, msg_id, options):
"""Build the cover message (series description) to be sent via SMTP
if not maintainer:
maintainer = ''
- tmpl, headers_end = __build_address_headers(tmpl, options)
- headers_end += 'Message-Id: %s\n' % msg_id
- if options.refid:
- headers_end += "In-Reply-To: %s\n" % options.refid
- headers_end += "References: %s\n" % options.refid
- headers_end += __build_extra_headers()
-
if options.version:
version_str = ' %s' % options.version
else:
number_str = ''
tmpl_dict = {'maintainer': maintainer,
- 'endofheaders': headers_end,
- 'date': email.Utils.formatdate(localtime = True),
+ # for backward template compatibility
+ 'endofheaders': '',
+ # for backward template compatibility
+ 'date': '',
'version': version_str,
'prefix': prefix_str,
'patchnr': patch_nr_str,
'number': number_str}
try:
- msg = tmpl % tmpl_dict
+ msg_string = tmpl % tmpl_dict
except KeyError, err:
raise CmdException, 'Unknown patch template variable: %s' \
% err
raise CmdException, 'Only "%(name)s" variables are ' \
'supported in the patch template'
- if options.edit:
- fname = '.stgitmail.txt'
-
- # create the initial file
- f = file(fname, 'w+')
- f.write(msg)
- f.close()
+ # The Python email message
+ try:
+ msg = email.message_from_string(msg_string)
+ except Exception, ex:
+ raise CmdException, 'template parsing error: %s' % str(ex)
- # the editor
- if config.has_option('stgit', 'editor'):
- editor = config.get('stgit', 'editor')
- elif 'EDITOR' in os.environ:
- editor = os.environ['EDITOR']
- else:
- editor = 'vi'
- editor += ' %s' % fname
+ __build_address_headers(msg, options)
+ __build_extra_headers(msg, msg_id, options.refid)
+ __encode_message(msg)
- print 'Invoking the editor: "%s"...' % editor,
- sys.stdout.flush()
- print 'done (exit code: %d)' % os.system(editor)
+ msg_string = msg.as_string(options.mbox)
- # read the message back
- f = file(fname)
- msg = f.read()
- f.close()
+ if options.edit_cover:
+ msg_string = edit_message(msg_string)
- return msg.strip('\n')
+ return msg_string.strip('\n')
def __build_message(tmpl, patch, patch_nr, total_nr, msg_id, ref_id, options):
"""Build the message to be sent via SMTP
descr_lines = descr.split('\n')
short_descr = descr_lines[0].rstrip()
- long_descr = reduce(lambda x, y: x + '\n' + y,
- descr_lines[1:], '').lstrip()
+ long_descr = '\n'.join(descr_lines[1:]).lstrip()
maintainer = __get_maintainer()
if not maintainer:
maintainer = '%s <%s>' % (p.get_commname(), p.get_commemail())
- if options.auto:
- extra_cc = __get_signers_list(descr)
- else:
- extra_cc = []
-
- tmpl, headers_end = __build_address_headers(tmpl, options, extra_cc)
- headers_end += 'Message-Id: %s\n' % msg_id
- if ref_id:
- headers_end += "In-Reply-To: %s\n" % ref_id
- headers_end += "References: %s\n" % ref_id
- headers_end += __build_extra_headers()
-
if options.version:
version_str = ' %s' % options.version
else:
'maintainer': maintainer,
'shortdescr': short_descr,
'longdescr': long_descr,
- 'endofheaders': headers_end,
+ # for backward template compatibility
+ 'endofheaders': '',
'diff': git.diff(rev1 = git_id('%s//bottom' % patch),
rev2 = git_id('%s//top' % patch)),
'diffstat': git.diffstat(rev1 = git_id('%s//bottom'%patch),
rev2 = git_id('%s//top' % patch)),
- 'date': email.Utils.formatdate(localtime = True),
+ # for backward template compatibility
+ 'date': '',
'version': version_str,
'prefix': prefix_str,
'patchnr': patch_nr_str,
'authdate': p.get_authdate(),
'commname': p.get_commname(),
'commemail': p.get_commemail()}
+ # change None to ''
for key in tmpl_dict:
if not tmpl_dict[key]:
tmpl_dict[key] = ''
try:
- msg = tmpl % tmpl_dict
+ msg_string = tmpl % tmpl_dict
except KeyError, err:
raise CmdException, 'Unknown patch template variable: %s' \
% err
raise CmdException, 'Only "%(name)s" variables are ' \
'supported in the patch template'
- return msg.strip('\n')
+ # The Python email message
+ try:
+ msg = email.message_from_string(msg_string)
+ except Exception, ex:
+ raise CmdException, 'template parsing error: %s' % str(ex)
+
+ if options.auto:
+ extra_cc = __get_signers_list(descr)
+ else:
+ extra_cc = []
+
+ __build_address_headers(msg, options, extra_cc)
+ __build_extra_headers(msg, msg_id, ref_id)
+ __encode_message(msg)
+
+ msg_string = msg.as_string(options.mbox)
+
+ if options.edit_patches:
+ msg_string = edit_message(msg_string)
+
+ return msg_string.strip('\n')
def func(parser, options, args):
"""Send the patches by e-mail using the patchmail.tmpl file as
sleep = config.getint('stgit', 'smtpdelay')
# send the cover message (if any)
- if options.cover or options.edit:
+ if options.cover or options.edit_cover:
# find the template file
if options.cover:
tmpl = file(options.cover).read()
ref_id = msg_id
if options.mbox:
- __write_mbox(from_addr, msg)
+ print msg
+ print
else:
print 'Sending the cover message...',
sys.stdout.flush()
ref_id = msg_id
if options.mbox:
- __write_mbox(from_addr, msg)
+ print msg
+ print
else:
print 'Sending patch "%s"...' % p,
sys.stdout.flush()