Add --binary flag to commands that generate diffs
[stgit] / stgit / commands / mail.py
index 154df9c..2fcaa5f 100644 (file)
@@ -32,10 +32,13 @@ 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.
+'~/.stgit/templates/patchmail.tmpl' 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. They can be e-mail
+addresses or aliases which are automatically expanded to the values
+stored in the [mail "alias"] section of GIT configuration files.
 
 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
@@ -52,33 +55,28 @@ 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
+  %(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',
@@ -122,41 +120,43 @@ options = [make_option('-a', '--all',
                        help = 'username for SMTP authentication'),
            make_option('-b', '--branch',
                        help = 'use BRANCH instead of the default one'),
+           make_option('--binary',
+                       help = 'output a diff even for binary files',
+                       action = 'store_true'),
            make_option('-m', '--mbox',
                        help = 'generate an mbox file instead of sending',
                        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'))
-    else:
-        return None
+    sender=config.get('stgit.sender')
+    if not sender:
+        try:
+            sender = str(git.user())
+        except git.GitException:
+            sender = str(git.author())
+
+    if not sender:
+        raise CmdException, 'unknown sender details'
 
-def __parse_addresses(addresses):
+    return address_or_alias(sender)
+
+def __parse_addresses(msg):
     """Return a two elements tuple: (from, [to])
     """
-    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 = []
-    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):
-            to_addr_list += __addr_list(line)
+    def __addr_list(msg, header):
+        return [name_addr[1] for name_addr in
+                email.Utils.getaddresses(msg.get_all(header, []))]
 
+    from_addr_list = __addr_list(msg, 'From')
     if len(from_addr_list) == 0:
         raise CmdException, 'No "From" address'
+
+    to_addr_list = __addr_list(msg, 'To') + __addr_list(msg, 'Cc') \
+                   + __addr_list(msg, 'Bcc')
     if len(to_addr_list) == 0:
         raise CmdException, 'No "To/Cc/Bcc" addresses'
 
@@ -195,18 +195,15 @@ def __build_address_headers(msg, options, extra_cc = []):
             del msg[header]
 
             if crt_addr:
-                msg[header] = ', '.join([crt_addr, addr])
+                msg[header] = address_or_alias(', '.join([crt_addr, addr]))
             else:
-                msg[header] = addr
+                msg[header] = address_or_alias(addr)
 
     to_addr = ''
     cc_addr = ''
     bcc_addr = ''
 
-    if config.has_option('stgit', 'autobcc'):
-        autobcc = config.get('stgit', 'autobcc')
-    else:
-        autobcc = ''
+    autobcc = config.get('stgit.autobcc') or ''
 
     if options.to:
         to_addr = ', '.join(options.to)
@@ -270,7 +267,7 @@ def __encode_message(msg):
     # encode the body and set the MIME and encoding headers
     msg.set_charset(charset)
 
-def edit_message(msg):
+def __edit_message(msg):
     fname = '.stgitmail.txt'
 
     # create the initial file
@@ -278,18 +275,7 @@ def edit_message(msg):
     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)
+    call_editor(fname)
 
     # read the message back
     f = file(fname)
@@ -301,9 +287,7 @@ def edit_message(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 = ''
+    sender = __get_sender()
 
     if options.version:
         version_str = ' %s' % options.version
@@ -322,7 +306,9 @@ def __build_cover(tmpl, total_nr, msg_id, options):
     else:
         number_str = ''
 
-    tmpl_dict = {'maintainer':   maintainer,
+    tmpl_dict = {'sender':       sender,
+                 # for backward template compatibility
+                 'maintainer':   sender,
                  # for backward template compatibility
                  'endofheaders': '',
                  # for backward template compatibility
@@ -342,6 +328,9 @@ def __build_cover(tmpl, total_nr, msg_id, options):
         raise CmdException, 'Only "%(name)s" variables are ' \
               'supported in the patch template'
 
+    if options.edit_cover:
+        msg_string = __edit_message(msg_string)
+
     # The Python email message
     try:
         msg = email.message_from_string(msg_string)
@@ -352,12 +341,7 @@ def __build_cover(tmpl, total_nr, msg_id, options):
     __build_extra_headers(msg, msg_id, options.refid)
     __encode_message(msg)
 
-    msg_string = msg.as_string(options.mbox)
-
-    if options.edit_cover:
-        msg_string = edit_message(msg_string)
-
-    return msg_string.strip('\n')
+    return msg
 
 def __build_message(tmpl, patch, patch_nr, total_nr, msg_id, ref_id, options):
     """Build the message to be sent via SMTP
@@ -370,9 +354,18 @@ def __build_message(tmpl, patch, patch_nr, total_nr, msg_id, ref_id, options):
     short_descr = descr_lines[0].rstrip()
     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();
+
+    sender = __get_sender()
+
+    fromauth = '%s <%s>' % (authname, authemail)
+    if fromauth != sender:
+        fromauth = 'From: %s\n\n' % fromauth
+    else:
+        fromauth = ''
 
     if options.version:
         version_str = ' %s' % options.version
@@ -392,13 +385,16 @@ def __build_message(tmpl, patch, patch_nr, total_nr, msg_id, ref_id, options):
         number_str = ''
 
     tmpl_dict = {'patch':        patch,
-                 'maintainer':   maintainer,
+                 'sender':       sender,
+                 # for backward template compatibility
+                 'maintainer':   sender,
                  'shortdescr':   short_descr,
                  'longdescr':    long_descr,
                  # for backward template compatibility
                  'endofheaders': '',
                  'diff':         git.diff(rev1 = git_id('%s//bottom' % patch),
-                                          rev2 = git_id('%s//top' % patch)),
+                                          rev2 = git_id('%s//top' % patch),
+                                          binary = options.binary),
                  'diffstat':     git.diffstat(rev1 = git_id('%s//bottom'%patch),
                                               rev2 = git_id('%s//top' % patch)),
                  # for backward template compatibility
@@ -408,11 +404,12 @@ def __build_message(tmpl, patch, patch_nr, total_nr, msg_id, ref_id, options):
                  '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]:
@@ -427,6 +424,9 @@ 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'
 
+    if options.edit_patches:
+        msg_string = __edit_message(msg_string)
+
     # The Python email message
     try:
         msg = email.message_from_string(msg_string)
@@ -442,40 +442,26 @@ def __build_message(tmpl, patch, patch_nr, total_nr, msg_id, ref_id, options):
     __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')
+    return msg
 
 def func(parser, options, args):
     """Send the patches by e-mail using the patchmail.tmpl file as
     a template
     """
-    smtpserver = config.get('stgit', 'smtpserver')
-
-    smtpuser = None
-    smtppassword = None
-    if config.has_option('stgit', 'smtpuser'):
-        smtpuser = config.get('stgit', 'smtpuser')
-    if config.has_option('stgit', 'smtppassword'):
-        smtppassword = config.get('stgit', 'smtppassword')
+    smtpserver = config.get('stgit.smtpserver')
 
     applied = crt_series.get_applied()
 
     if options.all:
         patches = applied
     elif len(args) >= 1:
-        patches = parse_patches(args, applied)
+        unapplied = crt_series.get_unapplied()
+        patches = parse_patches(args, applied + unapplied, len(applied))
     else:
         raise CmdException, 'Incorrect options. Unknown patches to send'
 
-    if options.smtp_password:
-        smtppassword = options.smtp_password
-
-    if options.smtp_user:
-        smtpuser = options.smtp_user
+    smtppassword = options.smtp_password or config.get('stgit.smtppassword')
+    smtpuser = options.smtp_user or config.get('stgit.smtpuser')
 
     if (smtppassword and not smtpuser):
         raise CmdException, 'SMTP password supplied, username needed'
@@ -491,10 +477,7 @@ def func(parser, options, args):
     else:
         ref_id = options.refid
 
-    if options.sleep != None:
-        sleep = options.sleep
-    else:
-        sleep = config.getint('stgit', 'smtpdelay')
+    sleep = options.sleep or config.getint('stgit.smtpdelay')
 
     # send the cover message (if any)
     if options.cover or options.edit_cover:
@@ -510,18 +493,19 @@ def func(parser, options, args):
         msg = __build_cover(tmpl, total_nr, msg_id, options)
         from_addr, to_addr_list = __parse_addresses(msg)
 
+        msg_string = msg.as_string(options.mbox)
+
         # subsequent e-mails are seen as replies to the first one
         if not options.noreply:
             ref_id = msg_id
 
         if options.mbox:
-            print msg
-            print
+            print msg_string
         else:
             print 'Sending the cover message...',
             sys.stdout.flush()
-            __send_message(smtpserver, from_addr, to_addr_list, msg, sleep,
-                           smtpuser, smtppassword)
+            __send_message(smtpserver, from_addr, to_addr_list, msg_string,
+                           sleep, smtpuser, smtppassword)
             print 'done'
 
     # send the patches
@@ -538,16 +522,17 @@ def func(parser, options, args):
                               options)
         from_addr, to_addr_list = __parse_addresses(msg)
 
+        msg_string = msg.as_string(options.mbox)
+
         # subsequent e-mails are seen as replies to the first one
         if not options.noreply and not ref_id:
             ref_id = msg_id
 
         if options.mbox:
-            print msg
-            print
+            print msg_string
         else:
             print 'Sending patch "%s"...' % p,
             sys.stdout.flush()
-            __send_message(smtpserver, from_addr, to_addr_list, msg, sleep,
-                           smtpuser, smtppassword)
+            __send_message(smtpserver, from_addr, to_addr_list, msg_string,
+                           sleep, smtpuser, smtppassword)
             print 'done'