Use --refid option even when sending a cover mail
[stgit] / stgit / commands / mail.py
index 9f4b3d1..975f8c9 100644 (file)
@@ -36,10 +36,15 @@ 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'
+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
+/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:
 
 SMTP authentication is also possible with '--smtp-user' and
 '--smtp-password' options, also available as configuration settings:
@@ -79,17 +84,23 @@ options = [make_option('-a', '--all',
                        metavar = '[PATCH1][:[PATCH2]]',
                        help = 'e-mail patches between PATCH1 and PATCH2'),
            make_option('--to',
                        metavar = '[PATCH1][:[PATCH2]]',
                        help = 'e-mail patches between PATCH1 and PATCH2'),
            make_option('--to',
-                       help = 'add TO to the To: list'),
+                       help = 'add TO to the To: list',
+                       action = 'append'),
            make_option('--cc',
            make_option('--cc',
-                       help = 'add CC to the Cc: list'),
+                       help = 'add CC to the Cc: list',
+                       action = 'append'),
            make_option('--bcc',
            make_option('--bcc',
-                       help = 'add BCC to the Bcc: list'),
+                       help = 'add BCC to the Bcc: list',
+                       action = 'append'),
            make_option('-v', '--version', metavar = 'VERSION',
                        help = 'add VERSION to the [PATCH ...] prefix'),
            make_option('-t', '--template', metavar = 'FILE',
                        help = 'use FILE as the message template'),
            make_option('-v', '--version', metavar = 'VERSION',
                        help = 'add VERSION to the [PATCH ...] prefix'),
            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',
+                       help = 'edit the cover message 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',
@@ -113,15 +124,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):
-        return re.split('.*?([\w\.]+@[\w\.]+)', string)[1:-1:2]
+    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):
@@ -157,24 +171,42 @@ def __send_message(smtpserver, from_addr, to_addr_list, msg, sleep,
 
     s.quit()
 
 
     s.quit()
 
-def __build_first(tmpl, total_nr, msg_id, options):
-    """Build the first message (series description) to be sent via SMTP
+def __build_address_headers(options):
+    headers_end = ''
+    if options.to:
+        headers_end += 'To: '
+        for to in options.to:
+            headers_end += '%s, ' % to
+        headers_end = headers_end[:-2] + '\n'
+    if options.cc:
+        headers_end += 'Cc: '
+        for cc in options.cc:
+            headers_end += '%s, ' % cc
+        headers_end = headers_end[:-2] + '\n'
+    if options.bcc:
+        headers_end += 'Bcc: '
+        for bcc in options.bcc:
+            headers_end += '%s, ' % bcc
+        headers_end = headers_end[:-2] + '\n'
+    return headers_end
+
+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 = ''
-    if options.to:
-        headers_end += 'To: %s\n' % options.to
-    if options.cc:
-        headers_end += 'Cc: %s\n' % options.cc
-    if options.bcc:
-        headers_end += 'Bcc: %s\n' % options.bcc
+    headers_end = __build_address_headers(options)
     headers_end += 'Message-Id: %s\n' % msg_id
     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
 
     if options.version:
         version_str = ' %s' % options.version
 
     if options.version:
         version_str = ' %s' % options.version
+    else:
+        version_str = ''
 
     total_nr_str = str(total_nr)
     patch_nr_str = '0'.zfill(len(total_nr_str))
 
     total_nr_str = str(total_nr)
     patch_nr_str = '0'.zfill(len(total_nr_str))
@@ -200,6 +232,32 @@ 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'
 
+    if options.edit:
+        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_message(tmpl, patch, patch_nr, total_nr, msg_id, ref_id, options):
     return msg
 
 def __build_message(tmpl, patch, patch_nr, total_nr, msg_id, ref_id, options):
@@ -218,13 +276,7 @@ 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())
 
     if not maintainer:
         maintainer = '%s <%s>' % (p.get_commname(), p.get_commemail())
 
-    headers_end = ''
-    if options.to:
-        headers_end += 'To: %s\n' % options.to
-    if options.cc:
-        headers_end += 'Cc: %s\n' % options.cc
-    if options.bcc:
-        headers_end += 'Bcc: %s\n' % options.bcc
+    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 += 'Message-Id: %s\n' % msg_id
     if ref_id:
         headers_end += "In-Reply-To: %s\n" % ref_id
@@ -232,6 +284,8 @@ def __build_message(tmpl, patch, patch_nr, total_nr, msg_id, ref_id, options):
 
     if options.version:
         version_str = ' %s' % options.version
 
     if options.version:
         version_str = ' %s' % options.version
+    else:
+        version_str = ''
 
     total_nr_str = str(total_nr)
     patch_nr_str = str(patch_nr).zfill(len(total_nr_str))
 
     total_nr_str = str(total_nr)
     patch_nr_str = str(patch_nr).zfill(len(total_nr_str))
@@ -290,12 +344,15 @@ def func(parser, options, args):
         smtppassword = config.get('stgit', 'smtppassword')
 
     applied = crt_series.get_applied()
         smtppassword = config.get('stgit', 'smtppassword')
 
     applied = crt_series.get_applied()
+    unapplied = crt_series.get_unapplied()
 
     if len(args) >= 1:
         for patch in args:
 
     if len(args) >= 1:
         for patch in args:
-            if not patch in applied:
+            if patch in unapplied:
                 raise CmdException, 'Patch "%s" not applied' % patch
                 raise CmdException, 'Patch "%s" not applied' % patch
-            patches = args
+            if not patch in applied:
+                raise CmdException, 'Patch "%s" does not exist' % patch
+        patches = args
     elif options.all:
         patches = applied
     elif options.range:
     elif options.all:
         patches = applied
     elif options.range:
@@ -318,11 +375,17 @@ def func(parser, options, args):
         if start in applied:
             start_idx = applied.index(start)
         else:
         if start in applied:
             start_idx = applied.index(start)
         else:
-            raise CmdException, 'Patch "%s" not applied' % start
+            if start in unapplied:
+                raise CmdException, 'Patch "%s" not applied' % start
+            else:
+                raise CmdException, 'Patch "%s" does not exist' % start
         if stop in applied:
             stop_idx = applied.index(stop) + 1
         else:
         if stop in applied:
             stop_idx = applied.index(stop) + 1
         else:
-            raise CmdException, 'Patch "%s" not applied' % stop
+            if stop in unapplied:
+                raise CmdException, 'Patch "%s" not applied' % stop
+            else:
+                raise CmdException, 'Patch "%s" does not exist' % stop
 
         if start_idx >= stop_idx:
             raise CmdException, 'Incorrect patch range order'
 
         if start_idx >= stop_idx:
             raise CmdException, 'Incorrect patch range order'
@@ -353,18 +416,32 @@ def func(parser, options, args):
     else:
         sleep = 2
 
     else:
         sleep = 2
 
-    # 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:
+        # find the template file
+        if options.cover:
+            tfile_list = [options.cover]
+        else:
+            tfile_list = [os.path.join(git.get_base_dir(), 'covermail.tmpl'),
+                          os.path.join(sys.prefix,
+                                       'share/stgit/templates/covermail.tmpl')]
+
+        tmpl = None
+        for tfile in tfile_list:
+            if os.path.isfile(tfile):
+                tmpl = file(tfile).read()
+                break
+        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
         ref_id = msg_id
 
         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,
+        print 'Sending the cover message...',
         sys.stdout.flush()
 
         __send_message(smtpserver, from_addr, to_addr_list, msg, sleep,
         sys.stdout.flush()
 
         __send_message(smtpserver, from_addr, to_addr_list, msg, sleep,
@@ -376,19 +453,16 @@ def func(parser, options, args):
     if options.template:
         tfile_list = [options.template]
     else:
     if options.template:
         tfile_list = [options.template]
     else:
-        tfile_list = []
-
-    tfile_list += [os.path.join(git.base_dir, 'patchmail.tmpl'),
-                   os.path.join(sys.prefix,
-                                'share/stgit/templates/patchmail.tmpl')]
+        tfile_list = [os.path.join(git.get_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:
     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])
+        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')