Add an optional prefix to the PATCH subject when mailing.
[stgit] / stgit / commands / mail.py
index fff069c..e4ee399 100644 (file)
@@ -65,6 +65,7 @@ the following variables:
   %(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'
@@ -90,8 +91,16 @@ options = [make_option('-a', '--all',
            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('--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('-c', '--cover', metavar = 'FILE',
@@ -182,24 +191,66 @@ def __write_mbox(from_addr, msg):
     print msg
     print
 
-def __build_address_headers(options):
-    headers_end = ''
+def __build_address_headers(tmpl, options, extra_cc = []):
+    """Build the address headers and check existing headers in the
+    template.
+    """
+    def csv(lst):
+        return reduce(lambda x, y: x + ', ' + y, lst)
+
+    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
+
+        return tmpl, h
+
+    headers = ''
+    to_addr = ''
+    cc_addr = ''
+    bcc_addr = ''
+
     if options.to:
-        headers_end += 'To: '
-        for to in options.to:
-            headers_end += '%s, ' % to
-        headers_end = headers_end[:-2] + '\n'
+        to_addr = csv(options.to)
     if options.cc:
-        headers_end += 'Cc: '
-        for cc in options.cc:
-            headers_end += '%s, ' % cc
-        headers_end = headers_end[:-2] + '\n'
+        cc_addr = csv(options.cc + extra_cc)
+    elif extra_cc:
+        cc_addr = csv(extra_cc)
     if options.bcc:
-        headers_end += 'Bcc: '
-        for bcc in options.bcc:
-            headers_end += '%s, ' % bcc
-        headers_end = headers_end[:-2] + '\n'
-    return headers_end
+        bcc_addr = csv(options.bcc)
+
+    # 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
+
+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():
     """Build extra headers like content-type etc.
@@ -217,7 +268,7 @@ def __build_cover(tmpl, total_nr, msg_id, options):
     if not maintainer:
         maintainer = ''
 
-    headers_end = __build_address_headers(options)
+    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
@@ -229,6 +280,11 @@ def __build_cover(tmpl, total_nr, msg_id, options):
     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:
@@ -240,6 +296,7 @@ def __build_cover(tmpl, total_nr, msg_id, options):
                  'endofheaders': headers_end,
                  'date':         email.Utils.formatdate(localtime = True),
                  'version':      version_str,
+                 'prefix':      prefix_str,
                  'patchnr':      patch_nr_str,
                  'totalnr':      total_nr_str,
                  'number':       number_str}
@@ -297,7 +354,12 @@ def __build_message(tmpl, patch, patch_nr, total_nr, msg_id, ref_id, options):
     if not maintainer:
         maintainer = '%s <%s>' % (p.get_commname(), p.get_commemail())
 
-    headers_end = __build_address_headers(options)
+    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
@@ -309,6 +371,11 @@ def __build_message(tmpl, patch, patch_nr, total_nr, msg_id, ref_id, options):
     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:
@@ -327,6 +394,7 @@ def __build_message(tmpl, patch, patch_nr, total_nr, msg_id, ref_id, options):
                                               rev2 = git_id('%s//top' % patch)),
                  'date':         email.Utils.formatdate(localtime = True),
                  'version':      version_str,
+                 'prefix':       prefix_str,
                  'patchnr':      patch_nr_str,
                  'totalnr':      total_nr_str,
                  'number':       number_str,
@@ -387,7 +455,10 @@ def func(parser, options, args):
     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
@@ -398,7 +469,7 @@ def func(parser, options, args):
     if options.cover or options.edit:
         # find the template file
         if options.cover:
-            tmpl = file(options.template).read()
+            tmpl = file(options.cover).read()
         else:
             tmpl = templates.get_template('covermail.tmpl')
             if not tmpl:
@@ -409,7 +480,8 @@ def func(parser, options, args):
         from_addr, to_addr_list = __parse_addresses(msg)
 
         # subsequent e-mails are seen as replies to the first one
-        ref_id = msg_id
+        if not options.noreply:
+            ref_id = msg_id
 
         if options.mbox:
             __write_mbox(from_addr, msg)
@@ -435,7 +507,7 @@ def func(parser, options, args):
         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
 
         if options.mbox: