Remove e-mail address duplicates
authorCatalin Marinas <catalin.marinas@gmail.com>
Sat, 8 Nov 2008 21:18:08 +0000 (21:18 +0000)
committerCatalin Marinas <catalin.marinas@gmail.com>
Sat, 8 Nov 2008 21:18:08 +0000 (21:18 +0000)
This is a fix for bug #10902. The 'mail' command now filters the
duplicate e-mail addresses before generating the message to be sent.
This was a problem especially with the --auto option.

Signed-off-by: Catalin Marinas <catalin.marinas@gmail.com>
stgit/commands/common.py
stgit/commands/mail.py

index 15fdde2..10d0817 100644 (file)
@@ -18,7 +18,7 @@ along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 """
 
-import sys, os, os.path, re
+import sys, os, os.path, re, email.Utils
 from stgit.exception import *
 from stgit.utils import *
 from stgit.out import *
@@ -233,8 +233,8 @@ def parse_patches(patch_args, patch_list, boundary = 0, ordered = False):
     return patches
 
 def name_email(address):
-    p = parse_name_email(address)
-    if p:
+    p = email.Utils.parseaddr(address)
+    if p[1]:
         return p
     else:
         raise CmdException('Incorrect "name <email>"/"email (name)" string: %s'
@@ -247,25 +247,19 @@ def name_email_date(address):
     else:
         raise CmdException('Incorrect "name <email> date" string: %s' % address)
 
-def address_or_alias(addr_str):
-    """Return the address if it contains an e-mail address or look up
+def address_or_alias(addr_pair):
+    """Return a name-email tuple the e-mail address is valid or look up
     the aliases in the config files.
     """
-    def __address_or_alias(addr):
-        if not addr:
-            return None
-        if addr.find('@') >= 0:
-            # it's an e-mail address
-            return addr
-        alias = config.get('mail.alias.'+addr)
-        if alias:
-            # it's an alias
-            return alias
-        raise CmdException, 'unknown e-mail alias: %s' % addr
-
-    addr_list = [__address_or_alias(addr.strip())
-                 for addr in addr_str.split(',')]
-    return ', '.join([addr for addr in addr_list if addr])
+    addr = addr_pair[1]
+    if '@' in addr:
+        # it's an e-mail address
+        return addr_pair
+    alias = config.get('mail.alias.' + addr)
+    if alias:
+        # it's an alias
+        return name_email(alias)
+    raise CmdException, 'unknown e-mail alias: %s' % addr
 
 def prepare_rebase(crt_series):
     # pop all patches
index d666515..72d0133 100644 (file)
@@ -155,19 +155,19 @@ def __get_sender():
             sender = str(git.user())
         except git.GitException:
             sender = str(git.author())
-
     if not sender:
         raise CmdException, 'unknown sender details'
+    sender = email.Utils.parseaddr(sender)
+
+    return email.Utils.formataddr(address_or_alias(sender))
 
-    return address_or_alias(sender)
+def __addr_list(msg, header):
+    return [addr for name, addr in
+            email.Utils.getaddresses(msg.get_all(header, []))]
 
 def __parse_addresses(msg):
     """Return a two elements tuple: (from, [to])
     """
-    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'
@@ -177,7 +177,7 @@ def __parse_addresses(msg):
     if len(to_addr_list) == 0:
         raise CmdException, 'No "To/Cc/Bcc" addresses'
 
-    return (from_addr_list[0], to_addr_list)
+    return (from_addr_list[0], set(to_addr_list))
 
 def __send_message_sendmail(sendmail, msg):
     """Send the message using the sendmail command.
@@ -231,18 +231,24 @@ 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] = address_or_alias(', '.join([crt_addr, addr]))
-            else:
-                msg[header] = address_or_alias(addr)
+    def __addr_pairs(msg, header, extra):
+        pairs = email.Utils.getaddresses(msg.get_all(header, []) + extra)
+        # remove pairs without an address and resolve the aliases
+        return [address_or_alias(p) for p in pairs if p[1]]
+
+    def __update_header(header, addr = '', ignore = ()):
+        addr_pairs = __addr_pairs(msg, header, [addr])
+        del msg[header]
+        # remove the duplicates and filter the addresses
+        addr_dict = dict((addr, email.Utils.formataddr((name, addr)))
+                         for name, addr in addr_pairs if addr not in ignore)
+        if addr_dict:
+            msg[header] = ', '.join(addr_dict.itervalues())
+        return set(addr_dict.iterkeys())
 
     to_addr = ''
     cc_addr = ''
+    extra_cc_addr = ''
     bcc_addr = ''
 
     autobcc = config.get('stgit.autobcc') or ''
@@ -250,18 +256,27 @@ def __build_address_headers(msg, options, extra_cc = []):
     if options.to:
         to_addr = ', '.join(options.to)
     if options.cc:
-        cc_addr = ', '.join(options.cc + extra_cc)
-        cc_addr = ', '.join(options.cc + extra_cc)
-    elif extra_cc:
-        cc_addr = ', '.join(extra_cc)
+        cc_addr = ', '.join(options.cc)
+    if extra_cc:
+        extra_cc_addr = ', '.join(extra_cc)
     if options.bcc:
         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)
+    # if an address is on a header, ignore it from the rest
+    to_set = __update_header('To', to_addr)
+    cc_set = __update_header('Cc', cc_addr, to_set)
+    bcc_set = __update_header('Bcc', bcc_addr, to_set.union(cc_set))
+
+    # --auto generated addresses, don't include the sender
+    from_set = __update_header('From')
+    __update_header('Cc', extra_cc_addr, to_set.union(bcc_set).union(from_set))
+
+    # update other address headers
+    __update_header('Reply-To')
+    __update_header('Mail-Reply-To')
+    __update_header('Mail-Followup-To')
 
 def __get_signers_list(msg):
     """Return the address list generated from signed-off-by and