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()
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 *
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'
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
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()
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:
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)
if not message:
can_edit = False
- committer_name = committer_email = None
-
if options.author:
options.authname, options.authemail = name_email(options.author)
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')
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'
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.
"""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 ''
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
editor."""
args = []
-options = (argparse.author_committer_options()
+options = (argparse.author_options()
+ argparse.message_options(save_template = True)
+ argparse.sign_options())
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:
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)
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()
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."""
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.
[ "$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
--- /dev/null
+#!/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