chiark / gitweb /
Merge branch 'proposed'
authorCatalin Marinas <catalin.marinas@gmail.com>
Sat, 8 Nov 2008 22:00:42 +0000 (22:00 +0000)
committerCatalin Marinas <catalin.marinas@gmail.com>
Sat, 8 Nov 2008 22:00:42 +0000 (22:00 +0000)
stgit/argparse.py
stgit/commands/common.py
stgit/commands/edit.py
stgit/commands/imprt.py
stgit/commands/mail.py
stgit/commands/new.py
stgit/commands/pick.py
stgit/utils.py
t/t1900-mail.sh
t/t3400-pick.sh [new file with mode: 0755]

index 0adb8ffba566e358c6cb948520ba80d88f33c857..0a5f66a7f95fdd6ebb76dcb236bd4d733e4890ce 100644 (file)
@@ -220,9 +220,6 @@ def _person_opts(person, short):
 def author_options():
     return _person_opts('author', 'auth')
 
-def author_committer_options():
-    return _person_opts('author', 'auth') + _person_opts('committer', 'comm')
-
 class CompgenBase(object):
     def actions(self, var): return set()
     def words(self, var): return set()
index 15fdde2517a7248a2cb3bc86647b34ef74c5e53d..10d08171112e77909d17a8fc9f9718c52af56fba 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 4904f6807fcaed2aa4cdd1a8f3fac75fb2453e02..ed785aa12ad026d035301b21e54379ebab584935 100644 (file)
@@ -63,7 +63,7 @@ options = [
         short = 'Invoke interactive editor'),
     ] + (argparse.sign_options() +
          argparse.message_options(save_template = True) +
-         argparse.author_committer_options() + argparse.diff_opts_option())
+         argparse.author_options() + argparse.diff_opts_option())
 
 directory = common.DirectoryHasRepositoryLib()
 
@@ -88,7 +88,7 @@ def func(parser, options, args):
 
     cd, failed_diff = edit.auto_edit_patch(
         stack.repository, cd, msg = options.message, contains_diff = True,
-        author = options.author, committer = options.committer,
+        author = options.author, committer = lambda p: p,
         sign_str = options.sign_str)
 
     if options.save_template:
index 3eb29ba32dc5af41634d79a5a80894caf97a56cf..97a6a09d78d55bad5a22867602efc00055427c4a 100644 (file)
@@ -77,10 +77,6 @@ options = [
         short = 'Use AUTHEMAIL as the author e-mail'),
     opt('--authdate',
         short = 'Use AUTHDATE as the author date'),
-    opt('--commname',
-        short = 'Use COMMNAME as the committer name'),
-    opt('--commemail',
-        short = 'Use COMMEMAIL as the committer e-mail'),
     ] + argparse.sign_options()
 
 directory = DirectoryHasRepository(log = True)
@@ -130,8 +126,6 @@ def __create_patch(filename, message, author_name, author_email,
     if not message:
         can_edit = False
 
-    committer_name = committer_email = None
-
     if options.author:
         options.authname, options.authemail = name_email(options.author)
 
@@ -142,17 +136,11 @@ def __create_patch(filename, message, author_name, author_email,
         author_email = options.authemail
     if options.authdate:
         author_date = options.authdate
-    if options.commname:
-        committer_name = options.commname
-    if options.commemail:
-        committer_email = options.commemail
 
     crt_series.new_patch(patch, message = message, can_edit = False,
                          author_name = author_name,
                          author_email = author_email,
-                         author_date = author_date,
-                         committer_name = committer_name,
-                         committer_email = committer_email)
+                         author_date = author_date)
 
     if not diff:
         out.warn('No diff found, creating empty patch')
index d666515378e27a337de01148d360b15f60b75d78..72d0133cdadaa339a4ce63d1eb9b1a1fd12da941 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
index 067882aa694ba8f9d0c1b826091232e91925c894..151cfe98ed5a7f559a746298f5a926649cfc0011 100644 (file)
@@ -40,7 +40,7 @@ the patch, unless the '--message' flag already specified one. The
 editor."""
 
 args = []
-options = (argparse.author_committer_options()
+options = (argparse.author_options()
            + argparse.message_options(save_template = True)
            + argparse.sign_options())
 
@@ -72,9 +72,8 @@ def func(parser, options, args):
     if options.message != None:
         cd = cd.set_message(options.message)
 
-    # Modify author and committer data.
-    cd = (cd.set_author(options.author(cd.author))
-            .set_committer(options.committer(cd.committer)))
+    # Modify author data.
+    cd = cd.set_author(options.author(cd.author))
 
     # Add Signed-off-by: or similar.
     if options.sign_str != None:
index 760918b8eff90c0942aa553833588610498c0103..ee08c01c5219114647217ac73df315199f9c3b36 100644 (file)
@@ -64,6 +64,8 @@ def __pick_commit(commit_id, patchname, options):
 
     if options.name:
         patchname = options.name
+    if patchname:
+        patchname = find_patch_name(patchname, crt_series.patch_exists)
 
     if options.parent:
         parent = git_id(crt_series, options.parent)
@@ -177,11 +179,16 @@ def func(parser, options, args):
         if options.parent:
             raise CmdException, '--parent can only be specified with one patch'
 
-    if (options.fold or options.update) and not crt_series.get_current():
+    if options.update and not crt_series.get_current():
         raise CmdException, 'No patches applied'
 
     if commit_id:
-        __pick_commit(commit_id, None, options)
+        # Try to guess a patch name if the argument was <branch>:<patch>
+        try:
+            patchname = args[0].split(':')[1]
+        except IndexError:
+            patchname = None
+        __pick_commit(commit_id, patchname, options)
     else:
         if options.unapplied:
             patches.reverse()
index 751cb4a1aea84c421dce8cffa857f65704cbeae5..81035a59b97189fdddc0d817c6ffdac694f1da10 100644 (file)
@@ -200,6 +200,15 @@ def edit_string(s, filename):
     os.remove(filename)
     return s
 
+def find_patch_name(patchname, unacceptable):
+    """Find a patch name which is acceptable."""
+    if unacceptable(patchname):
+        suffix = 0
+        while unacceptable('%s-%d' % (patchname, suffix)):
+            suffix += 1
+        patchname = '%s-%d' % (patchname, suffix)
+    return patchname
+
 def patch_name_from_msg(msg):
     """Return a string to be used as a patch name. This is generated
     from the top line of the string passed as argument."""
@@ -220,12 +229,7 @@ def make_patch_name(msg, unacceptable, default_name = 'patch'):
     patchname = patch_name_from_msg(msg)
     if not patchname:
         patchname = default_name
-    if unacceptable(patchname):
-        suffix = 0
-        while unacceptable('%s-%d' % (patchname, suffix)):
-            suffix += 1
-        patchname = '%s-%d' % (patchname, suffix)
-    return patchname
+    return find_patch_name(patchname, unacceptable)
 
 # any and all functions are builtin in Python 2.5 and higher, but not
 # in 2.4.
index cfdc6f36e770fb0e82cde3c9f1c85033f61700ee..cea6769236939bf060db865144415bb625b8d38a 100755 (executable)
@@ -47,4 +47,37 @@ test_expect_success \
     [ "$t1" = "$t2" ]
     '
 
+test_expect_success \
+    'Check the To:, Cc: and Bcc: headers' \
+    '
+    stg mail --to=a@a --cc="b@b, c@c" --bcc=d@d $(stg top) -m \
+        -t ../../templates/patchmail.tmpl > mbox &&
+    test "$(cat mbox | grep -e "^To:")" = "To: a@a" &&
+    test "$(cat mbox | grep -e "^Cc:")" = "Cc: b@b, c@c" &&
+    test "$(cat mbox | grep -e "^Bcc:")" = "Bcc: d@d"
+    '
+
+test_expect_success \
+    'Check the --auto option' \
+    '
+    stg edit --sign &&
+    stg mail --to=a@a --cc="b@b, c@c" --bcc=d@d --auto $(stg top) -m \
+        -t ../../templates/patchmail.tmpl > mbox &&
+    test "$(cat mbox | grep -e "^To:")" = "To: a@a" &&
+    test "$(cat mbox | grep -e "^Cc:")" = \
+        "Cc: C O Mitter <committer@example.com>, b@b, c@c" &&
+    test "$(cat mbox | grep -e "^Bcc:")" = "Bcc: d@d"
+    '
+
+test_expect_success \
+    'Check the e-mail address duplicates' \
+    '
+    stg mail --to="a@a, b b <b@b>" --cc="b@b, c@c" \
+        --bcc="c@c, d@d, committer@example.com" --auto $(stg top) -m \
+        -t ../../templates/patchmail.tmpl > mbox &&
+    test "$(cat mbox | grep -e "^To:")" = "To: b b <b@b>, a@a" &&
+    test "$(cat mbox | grep -e "^Cc:")" = "Cc: c@c" &&
+    test "$(cat mbox | grep -e "^Bcc:")" = "Bcc: committer@example.com, d@d"
+    '
+
 test_done
diff --git a/t/t3400-pick.sh b/t/t3400-pick.sh
new file mode 100755 (executable)
index 0000000..3bd5c4f
--- /dev/null
@@ -0,0 +1,59 @@
+#!/bin/sh
+test_description='Test the pick command'
+
+. ./test-lib.sh
+
+test_expect_success \
+       'Initialize the StGIT repository' \
+       '
+       stg init &&
+       stg new A -m "a" && echo A > a && git add a && stg refresh &&
+       stg new B -m "b" && echo B > b && git add b && stg refresh &&
+       stg branch --clone foo &&
+       stg new C -m "c" && echo C > c && git add c && stg refresh &&
+       stg new D-foo -m "d" && echo D > d && git add d && stg refresh &&
+       stg branch master
+       '
+
+test_expect_success \
+       'Pick remote patch' \
+       '
+       stg pick foo:C &&
+       test "$(echo $(stg series --applied --noprefix))" = "A B C"
+       '
+
+test_expect_success \
+       'Pick --unapplied remote patch' \
+       '
+       stg pick --unapplied --ref-branch foo --name D D-foo &&
+       test "$(echo $(stg series --applied --noprefix))" = "A B C" &&
+       test "$(echo $(stg series --unapplied --noprefix))" = "D"
+       '
+
+test_expect_success \
+       'Pick local unapplied patch' \
+       '
+       stg pick D &&
+       test "$(echo $(stg series --applied --noprefix))" = "A B C D-0" &&
+       test "$(echo $(stg series --unapplied --noprefix))" = "D"
+       '
+
+test_expect_success \
+       'Pick --fold --reverse local patch' \
+       '
+       stg pick --fold --reverse D &&
+       stg refresh && stg clean &&
+       test "$(echo $(stg series --applied --noprefix))" = "A B C" &&
+       test "$(echo $(stg series --unapplied --noprefix))" = "D"
+       '
+
+test_expect_success \
+       'Pick --fold without applied patches' \
+       '
+       stg pop --all &&
+       stg pick --fold D &&
+       test "$(echo $(stg series --unapplied --noprefix))" = "A B C D" &&
+       test "$(echo $(stg status))" = "A d"
+       '
+
+test_done