Only create a 'From: author' line if needed
[stgit] / stgit / commands / mail.py
index a53f3af..307a129 100644 (file)
@@ -15,69 +15,70 @@ along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 """
 
 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 """
 
-import sys, os, re, time, smtplib, email.Utils
+import sys, os, re, time, datetime, smtplib
+import email, email.Utils, email.Header
 from optparse import OptionParser, make_option
 from optparse import OptionParser, make_option
-from time import gmtime, strftime
 
 from stgit.commands.common import *
 from stgit.utils import *
 
 from stgit.commands.common import *
 from stgit.utils import *
-from stgit import stack, git
+from stgit import stack, git, version, templates
 from stgit.config import config
 
 
 help = 'send a patch or series of patches by e-mail'
 from stgit.config import config
 
 
 help = 'send a patch or series of patches by e-mail'
-usage = """%prog [options] [<patch> [<patch2...]]
-
-Send a patch or a range of patches (defaulting to the applied patches)
-by e-mail using the 'smtpserver' configuration option. The From
-address and the e-mail format are generated from the template file
-passed as argument to '--template' (defaulting to .git/patchmail.tmpl
-or /usr/share/stgit/templates/patchmail.tmpl). The To/Cc/Bcc addresses
+usage = """%prog [options] [<patch1>] [<patch2>] [<patch3>..<patch4>]
+
+Send a patch or a range of patches by e-mail using the 'smtpserver'
+configuration option. The From address and the e-mail format are
+generated from the template file passed as argument to '--template'
+(defaulting to '.git/patchmail.tmpl' or
+'~/.stgit/templates/patchmail.tmpl' or or
+'/usr/share/stgit/templates/patchmail.tmpl'). The To/Cc/Bcc addresses
 can either be added to the template file or passed via the
 corresponding command line options.
 
 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 '--first' option. All the
-subsequent e-mails appear as replies to the first e-mail sent (either
-the preamble or the first patch). E-mails can be seen as replies to a
-different e-mail by using the '--refid' option.
+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').
+
+All the subsequent e-mails appear as replies to the first e-mail sent
+(either the preamble or the first patch). E-mails can be seen as
+replies to a different e-mail by using the '--refid' option.
 
 SMTP authentication is also possible with '--smtp-user' and
 '--smtp-password' options, also available as configuration settings:
 'smtpuser' and 'smtppassword'.
 
 
 SMTP authentication is also possible with '--smtp-user' and
 '--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
 
   %(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
   %(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)
   %(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'
   %(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 maintainer
   %(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
 
   %(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 %(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',
                        action = 'store_true'),
 
 options = [make_option('-a', '--all',
                        help = 'e-mail all the applied patches',
                        action = 'store_true'),
-           make_option('-r', '--range',
-                       metavar = '[PATCH1][:[PATCH2]]',
-                       help = 'e-mail patches between PATCH1 and PATCH2'),
            make_option('--to',
                        help = 'add TO to the To: list',
                        action = 'append'),
            make_option('--to',
                        help = 'add TO to the To: list',
                        action = 'append'),
@@ -87,12 +88,26 @@ options = [make_option('-a', '--all',
            make_option('--bcc',
                        help = 'add BCC to the Bcc: list',
                        action = 'append'),
            make_option('--bcc',
                        help = 'add BCC to the Bcc: list',
                        action = 'append'),
+           make_option('--auto',
+                       help = 'automatically cc the patch signers',
+                       action = 'store_true'),
+           make_option('--noreply',
+                       help = 'do not send subsequent messages as replies',
+                       action = 'store_true'),
            make_option('-v', '--version', metavar = 'VERSION',
                        help = 'add VERSION to the [PATCH ...] prefix'),
            make_option('-v', '--version', metavar = 'VERSION',
                        help = 'add VERSION to the [PATCH ...] prefix'),
+           make_option('--prefix', metavar = 'PREFIX',
+                       help = 'add PREFIX to the [... PATCH ...] prefix'),
            make_option('-t', '--template', metavar = 'FILE',
                        help = 'use FILE as the message template'),
            make_option('-t', '--template', metavar = 'FILE',
                        help = 'use FILE as the message template'),
-           make_option('-f', '--first', metavar = 'FILE',
-                       help = 'send FILE as the first message'),
+           make_option('-c', '--cover', metavar = 'FILE',
+                       help = 'send FILE as the cover message'),
+           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',
            make_option('-s', '--sleep', type = 'int', metavar = 'SECONDS',
                        help = 'sleep for SECONDS between e-mails sending'),
            make_option('--refid',
@@ -102,7 +117,10 @@ options = [make_option('-a', '--all',
            make_option('-p', '--smtp-password', metavar = 'PASSWORD',
                        help = 'username for SMTP authentication'),
            make_option('-b', '--branch',
            make_option('-p', '--smtp-password', metavar = 'PASSWORD',
                        help = 'username for SMTP authentication'),
            make_option('-b', '--branch',
-                       help = 'use BRANCH instead of the default one')]
+                       help = 'use BRANCH instead of the default one'),
+           make_option('-m', '--mbox',
+                       help = 'generate an mbox file instead of sending',
+                       action = 'store_true')]
 
 
 def __get_maintainer():
 
 
 def __get_maintainer():
@@ -116,18 +134,18 @@ def __get_maintainer():
     else:
         return None
 
     else:
         return None
 
-def __parse_addresses(string):
+def __parse_addresses(addresses):
     """Return a two elements tuple: (from, [to])
     """
     """Return a two elements tuple: (from, [to])
     """
-    def __addr_list(string):
-       m = re.search('[^@\s<,]+@[^>\s,]+', string);
-       if (m == None):
-               return []
-       return [ m.group() ] + __addr_list(string[m.end():])
+    def __addr_list(addrs):
+        m = re.search('[^@\s<,]+@[^>\s,]+', addrs);
+        if (m == None):
+            return []
+        return [ m.group() ] + __addr_list(addrs[m.end():])
 
     from_addr_list = []
     to_addr_list = []
 
     from_addr_list = []
     to_addr_list = []
-    for line in string.split('\n'):
+    for line in addresses.split('\n'):
         if re.match('from:\s+', line, re.I):
             from_addr_list += __addr_list(line)
         elif re.match('(to|cc|bcc):\s+', line, re.I):
         if re.match('from:\s+', line, re.I):
             from_addr_list += __addr_list(line)
         elif re.match('(to|cc|bcc):\s+', line, re.I):
@@ -163,40 +181,136 @@ def __send_message(smtpserver, from_addr, to_addr_list, msg, sleep,
 
     s.quit()
 
 
     s.quit()
 
-def __build_address_headers(options):
-    headers_end = ''
+def __build_address_headers(msg, options, extra_cc = []):
+    """Build the address headers and check existing headers in the
+    template.
+    """
+    def __replace_header(header, addr):
+        if addr:
+            crt_addr = msg[header]
+            del msg[header]
+
+            if crt_addr:
+                msg[header] = ', '.join([crt_addr, addr])
+            else:
+                msg[header] = addr
+
+    to_addr = ''
+    cc_addr = ''
+    bcc_addr = ''
+
+    if config.has_option('stgit', 'autobcc'):
+        autobcc = config.get('stgit', 'autobcc')
+    else:
+        autobcc = ''
+
     if options.to:
     if options.to:
-       headers_end += 'To: '
-       for to in options.to:
-               headers_end += '%s,' % to
-       headers_end = headers_end[:-1] + '\n'
+        to_addr = ', '.join(options.to)
     if options.cc:
     if options.cc:
-       headers_end += 'Cc: '
-       for cc in options.cc:
-               headers_end += '%s,' % cc
-       headers_end = headers_end[:-1] + '\n'
+        cc_addr = ', '.join(options.cc + extra_cc)
+    elif extra_cc:
+        cc_addr = ', '.join(extra_cc)
     if options.bcc:
     if options.bcc:
-       headers_end += 'Bcc: '
-       for bcc in options.bcc:
-               headers_end += '%s,' % bcc
-       headers_end = headers_end[:-1] + '\n'
-    return headers_end
-
-def __build_first(tmpl, total_nr, msg_id, options):
-    """Build the first message (series description) to be sent via SMTP
+        bcc_addr = ', '.join(options.bcc + [autobcc])
+    elif autobcc:
+        bcc_addr = autobcc
+
+    __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
+    acked-by lines in the message.
+    """
+    addr_list = []
+
+    r = re.compile('^(signed-off-by|acked-by):\s+(.+)$', re.I)
+    for line in msg.split('\n'):
+        m = r.match(line)
+        if m:
+            addr_list.append(m.expand('\g<2>'))
+
+    return addr_list
+
+def __build_extra_headers(msg, msg_id, ref_id = None):
+    """Build extra email headers and encoding
+    """
+    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)
+
+    # 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
     """
     maintainer = __get_maintainer()
     if not maintainer:
         maintainer = ''
 
     """
     maintainer = __get_maintainer()
     if not maintainer:
         maintainer = ''
 
-    headers_end = __build_address_headers(options)
-    headers_end += 'Message-Id: %s\n' % msg_id
-
     if options.version:
         version_str = ' %s' % options.version
     else:
         version_str = ''
 
     if options.version:
         version_str = ' %s' % options.version
     else:
         version_str = ''
 
+    if options.prefix:
+        prefix_str = options.prefix + ' '
+    else:
+        prefix_str = ''
+        
     total_nr_str = str(total_nr)
     patch_nr_str = '0'.zfill(len(total_nr_str))
     if total_nr > 1:
     total_nr_str = str(total_nr)
     patch_nr_str = '0'.zfill(len(total_nr_str))
     if total_nr > 1:
@@ -205,15 +319,18 @@ def __build_first(tmpl, total_nr, msg_id, options):
         number_str = ''
 
     tmpl_dict = {'maintainer':   maintainer,
         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,
                  'version':      version_str,
+                 'prefix':      prefix_str,
                  'patchnr':      patch_nr_str,
                  'totalnr':      total_nr_str,
                  'number':       number_str}
 
     try:
                  'patchnr':      patch_nr_str,
                  'totalnr':      total_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
     except KeyError, err:
         raise CmdException, 'Unknown patch template variable: %s' \
               % err
@@ -221,7 +338,22 @@ def __build_first(tmpl, total_nr, msg_id, options):
         raise CmdException, 'Only "%(name)s" variables are ' \
               'supported in the patch template'
 
         raise CmdException, 'Only "%(name)s" variables are ' \
               'supported in the patch template'
 
-    return msg
+    if options.edit_cover:
+        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_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
 
 def __build_message(tmpl, patch, patch_nr, total_nr, msg_id, ref_id, options):
     """Build the message to be sent via SMTP
@@ -232,24 +364,33 @@ def __build_message(tmpl, patch, patch_nr, total_nr, msg_id, ref_id, options):
     descr_lines = descr.split('\n')
 
     short_descr = descr_lines[0].rstrip()
     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()
+
+    authname = p.get_authname();
+    authemail = p.get_authemail();
+    commname = p.get_commname();
+    commemail = p.get_commemail();
 
     maintainer = __get_maintainer()
     if not maintainer:
 
     maintainer = __get_maintainer()
     if not maintainer:
-        maintainer = '%s <%s>' % (p.get_commname(), p.get_commemail())
+        maintainer = '%s <%s>' % (commname, commemail)
 
 
-    headers_end = __build_address_headers(options)
-    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
+    fromauth = '%s <%s>' % (authname, authemail)
+    if fromauth != maintainer:
+        fromauth = 'From: %s\n\n' % fromauth
+    else:
+        fromauth = ''
 
     if options.version:
         version_str = ' %s' % options.version
     else:
         version_str = ''
 
 
     if options.version:
         version_str = ' %s' % options.version
     else:
         version_str = ''
 
+    if options.prefix:
+        prefix_str = options.prefix + ' '
+    else:
+        prefix_str = ''
+        
     total_nr_str = str(total_nr)
     patch_nr_str = str(patch_nr).zfill(len(total_nr_str))
     if total_nr > 1:
     total_nr_str = str(total_nr)
     patch_nr_str = str(patch_nr).zfill(len(total_nr_str))
     if total_nr > 1:
@@ -261,27 +402,32 @@ def __build_message(tmpl, patch, patch_nr, total_nr, msg_id, ref_id, options):
                  'maintainer':   maintainer,
                  'shortdescr':   short_descr,
                  'longdescr':    long_descr,
                  'maintainer':   maintainer,
                  'shortdescr':   short_descr,
                  'longdescr':    long_descr,
-                 'endofheaders': headers_end,
-                 '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
+                 '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)),
+                 # for backward template compatibility
+                 'date':         '',
                  'version':      version_str,
                  'version':      version_str,
+                 'prefix':       prefix_str,
                  'patchnr':      patch_nr_str,
                  'totalnr':      total_nr_str,
                  'number':       number_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(),
                  '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:
     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
     except KeyError, err:
         raise CmdException, 'Unknown patch template variable: %s' \
               % err
@@ -289,14 +435,32 @@ def __build_message(tmpl, patch, patch_nr, total_nr, msg_id, ref_id, options):
         raise CmdException, 'Only "%(name)s" variables are ' \
               'supported in the patch template'
 
         raise CmdException, 'Only "%(name)s" variables are ' \
               'supported in the patch template'
 
-    return msg
+    if options.edit_patches:
+        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_string.strip('\n')
 
 def func(parser, options, args):
     """Send the patches by e-mail using the patchmail.tmpl file as
     a template
     """
 
 def func(parser, options, args):
     """Send the patches by e-mail using the patchmail.tmpl file as
     a template
     """
-    if not config.has_option('stgit', 'smtpserver'):
-        raise CmdException, 'smtpserver not defined'
     smtpserver = config.get('stgit', 'smtpserver')
 
     smtpuser = None
     smtpserver = config.get('stgit', 'smtpserver')
 
     smtpuser = None
@@ -308,43 +472,10 @@ def func(parser, options, args):
 
     applied = crt_series.get_applied()
 
 
     applied = crt_series.get_applied()
 
-    if len(args) >= 1:
-        for patch in args:
-            if not patch in applied:
-                raise CmdException, 'Patch "%s" not applied' % patch
-            patches = args
-    elif options.all:
+    if options.all:
         patches = applied
         patches = applied
-    elif options.range:
-        boundaries = options.range.split(':')
-        if len(boundaries) == 1:
-            start = boundaries[0]
-            stop = boundaries[0]
-        elif len(boundaries) == 2:
-            if boundaries[0] == '':
-                start = applied[0]
-            else:
-                start = boundaries[0]
-            if boundaries[1] == '':
-                stop = applied[-1]
-            else:
-                stop = boundaries[1]
-        else:
-            raise CmdException, 'incorrect parameters to "--range"'
-
-        if start in applied:
-            start_idx = applied.index(start)
-        else:
-            raise CmdException, 'Patch "%s" not applied' % start
-        if stop in applied:
-            stop_idx = applied.index(stop) + 1
-        else:
-            raise CmdException, 'Patch "%s" not applied' % stop
-
-        if start_idx >= stop_idx:
-            raise CmdException, 'Incorrect patch range order'
-
-        patches = applied[start_idx:stop_idx]
+    elif len(args) >= 1:
+        patches = parse_patches(args, applied)
     else:
         raise CmdException, 'Incorrect options. Unknown patches to send'
 
     else:
         raise CmdException, 'Incorrect options. Unknown patches to send'
 
@@ -363,49 +494,51 @@ def func(parser, options, args):
     if total_nr == 0:
         raise CmdException, 'No patches to send'
 
     if total_nr == 0:
         raise CmdException, 'No patches to send'
 
-    ref_id = options.refid
+    if options.noreply:
+        ref_id = None
+    else:
+        ref_id = options.refid
 
     if options.sleep != None:
         sleep = options.sleep
     else:
 
     if options.sleep != None:
         sleep = options.sleep
     else:
-        sleep = 2
+        sleep = config.getint('stgit', 'smtpdelay')
 
 
-    # send the first message (if any)
-    if options.first:
-        tmpl = file(options.first).read()
+    # send the cover message (if any)
+    if options.cover or options.edit_cover:
+        # find the template file
+        if options.cover:
+            tmpl = file(options.cover).read()
+        else:
+            tmpl = templates.get_template('covermail.tmpl')
+            if not tmpl:
+                raise CmdException, 'No cover message template file found'
 
         msg_id = email.Utils.make_msgid('stgit')
 
         msg_id = email.Utils.make_msgid('stgit')
-        msg = __build_first(tmpl, total_nr, msg_id, options)
+        msg = __build_cover(tmpl, total_nr, msg_id, options)
         from_addr, to_addr_list = __parse_addresses(msg)
 
         # subsequent e-mails are seen as replies to the first one
         from_addr, to_addr_list = __parse_addresses(msg)
 
         # subsequent e-mails are seen as replies to the first one
-        ref_id = msg_id
-
-        print 'Sending file "%s"...' % options.first,
-        sys.stdout.flush()
-
-        __send_message(smtpserver, from_addr, to_addr_list, msg, sleep,
-                       smtpuser, smtppassword)
+        if not options.noreply:
+            ref_id = msg_id
 
 
-        print 'done'
+        if options.mbox:
+            print msg
+            print
+        else:
+            print 'Sending the cover message...',
+            sys.stdout.flush()
+            __send_message(smtpserver, from_addr, to_addr_list, msg, sleep,
+                           smtpuser, smtppassword)
+            print 'done'
 
     # send the patches
     if options.template:
 
     # send the patches
     if options.template:
-        tfile_list = [options.template]
+        tmpl = file(options.template).read()
     else:
     else:
-        tfile_list = []
-
-    tfile_list += [os.path.join(git.base_dir, 'patchmail.tmpl'),
-                   os.path.join(sys.prefix,
-                                'share/stgit/templates/patchmail.tmpl')]
-    tmpl = None
-    for tfile in tfile_list:
-        if os.path.isfile(tfile):
-            tmpl = file(tfile).read()
-            break
-    if not tmpl:
-        raise CmdException, 'No e-mail template file: %s or %s' \
-              % (tfile_list[-1], tfile_list[-2])
+        tmpl = templates.get_template('patchmail.tmpl')
+        if not tmpl:
+            raise CmdException, 'No e-mail template file found'
 
     for (p, patch_nr) in zip(patches, range(1, len(patches) + 1)):
         msg_id = email.Utils.make_msgid('stgit')
 
     for (p, patch_nr) in zip(patches, range(1, len(patches) + 1)):
         msg_id = email.Utils.make_msgid('stgit')
@@ -414,13 +547,15 @@ def func(parser, options, args):
         from_addr, to_addr_list = __parse_addresses(msg)
 
         # subsequent e-mails are seen as replies to the first one
         from_addr, to_addr_list = __parse_addresses(msg)
 
         # subsequent e-mails are seen as replies to the first one
-        if not ref_id:
+        if not options.noreply and not ref_id:
             ref_id = msg_id
 
             ref_id = msg_id
 
-        print 'Sending patch "%s"...' % p,
-        sys.stdout.flush()
-
-        __send_message(smtpserver, from_addr, to_addr_list, msg, sleep,
-                       smtpuser, smtppassword)
-
-        print 'done'
+        if options.mbox:
+            print msg
+            print
+        else:
+            print 'Sending patch "%s"...' % p,
+            sys.stdout.flush()
+            __send_message(smtpserver, from_addr, to_addr_list, msg, sleep,
+                           smtpuser, smtppassword)
+            print 'done'