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 *
'--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
+ %(sender)s - 'sender' or 'authname <authemail>' as per 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
%(totalnr)s - total number of patches to be sent
%(number)s - empty if only one patch is sent or ' patchnr/totalnr'
+ %(fromauth)s - 'From: author\\n\\n' if different from sender
%(authname)s - author's name
%(authemail)s - author's email
%(authdate)s - patch creation date
%(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 %(sender)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',
action = 'store_true')]
-def __get_maintainer():
+def __get_sender():
"""Return the 'authname <authemail>' string as read from the
configuration file
"""
- if config.has_option('stgit', 'authname') \
- and config.has_option('stgit', 'authemail'):
- return '%s <%s>' % (config.get('stgit', 'authname'),
- config.get('stgit', 'authemail'))
+ if config.has_option('stgit', 'sender'):
+ sender = config.get('stgit', 'sender')
else:
- return None
+ try:
+ sender = str(git.user())
+ except git.GitException:
+ sender = str(git.author())
+
+ if not sender:
+ raise CmdException, 'unknown sender details'
+
+ return sender
def __parse_addresses(addresses):
"""Return a two elements tuple: (from, [to])
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
-
- return headers
-
-def edit_message(msg):
+ 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
def __build_cover(tmpl, total_nr, msg_id, options):
"""Build the cover message (series description) to be sent via SMTP
"""
- maintainer = __get_maintainer()
- 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()
+ sender = __get_sender()
if options.version:
version_str = ' %s' % options.version
else:
number_str = ''
- tmpl_dict = {'maintainer': maintainer,
- 'endofheaders': headers_end,
- 'date': email.Utils.formatdate(localtime = True),
+ tmpl_dict = {'sender': sender,
+ # for backward template compatibility
+ 'maintainer': sender,
+ # 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
'supported in the patch template'
if options.edit_cover:
- msg = edit_message(msg)
+ msg_string = __edit_message(msg_string)
+
+ # The Python email message
+ try:
+ msg = email.message_from_string(msg_string)
+ except Exception, ex:
+ raise CmdException, 'template parsing error: %s' % str(ex)
+
+ __build_address_headers(msg, options)
+ __build_extra_headers(msg, msg_id, options.refid)
+ __encode_message(msg)
+
+ msg_string = msg.as_string(options.mbox)
- 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())
+ authname = p.get_authname();
+ authemail = p.get_authemail();
+ commname = p.get_commname();
+ commemail = p.get_commemail();
- if options.auto:
- extra_cc = __get_signers_list(descr)
- else:
- extra_cc = []
+ sender = __get_sender()
- 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()
+ fromauth = '%s <%s>' % (authname, authemail)
+ if fromauth != sender:
+ fromauth = 'From: %s\n\n' % fromauth
+ else:
+ fromauth = ''
if options.version:
version_str = ' %s' % options.version
number_str = ''
tmpl_dict = {'patch': patch,
- 'maintainer': maintainer,
+ 'sender': sender,
+ # for backward template compatibility
+ 'maintainer': sender,
'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,
'totalnr': total_nr_str,
'number': number_str,
- 'authname': p.get_authname(),
- 'authemail': p.get_authemail(),
+ 'fromauth': fromauth,
+ 'authname': authname,
+ 'authemail': authemail,
'authdate': p.get_authdate(),
- 'commname': p.get_commname(),
- 'commemail': p.get_commemail()}
+ 'commname': commname,
+ 'commemail': 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
'supported in the patch template'
if options.edit_patches:
- msg = edit_message(msg)
+ msg_string = __edit_message(msg_string)
+
+ # 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)
- return msg.strip('\n')
+ return msg_string.strip('\n')
def func(parser, options, args):
"""Send the patches by e-mail using the patchmail.tmpl file as
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()