Beispiel #1
0
    def initialise(cls, repository, name=None, switch_to=False):
        """Initialise a Git branch to handle patch series.

        @param repository: The L{Repository} where the L{Stack} will be created
        @param name: The name of the L{Stack}
        """
        if not name:
            name = repository.current_branch_name
        # make sure that the corresponding Git branch exists
        branch = Branch(repository, name)

        dir = os.path.join(repository.directory, cls._repo_subdir, name)
        if os.path.exists(dir):
            raise StackException('%s: branch already initialized' % name)

        if switch_to:
            branch.switch_to()

        # create the stack directory and files
        utils.create_dirs(dir)
        compat_dir = os.path.join(dir, 'patches')
        utils.create_dirs(compat_dir)
        PatchOrder.create(dir)
        config.set(stackupgrade.format_version_key(name),
                   text(stackupgrade.FORMAT_VERSION))

        return repository.get_stack(name)
Beispiel #2
0
 def __decode_header(header):
     """Decode a qp-encoded e-mail header as per rfc2047"""
     try:
         decoded_words = email.header.decode_header(header)
         return text(email.header.make_header(decoded_words))
     except Exception as ex:
         raise CmdException('header decoding error: %s' % str(ex))
Beispiel #3
0
def get_commands(allow_cached=True):
    """Return list of tuples of command name, module name, command type, and
    one-line command help."""
    if allow_cached:
        try:
            from stgit.commands.cmdlist import command_list

            return command_list
        except ImportError:
            # cmdlist.py doesn't exist, so do it the expensive way.
            pass
    return sorted((
        text(getattr(mod, 'name', mod_name)),
        text(mod_name),
        _kinds[mod.kind],
        mod.help,
    ) for mod_name, mod in _find_commands())
Beispiel #4
0
 def __decode_header(header):
     """Decode a qp-encoded e-mail header as per rfc2047"""
     try:
         words_enc = decode_header(header)
         hobj = make_header(words_enc)
     except Exception as ex:
         raise CmdException('header decoding error: %s' % str(ex))
     return text(hobj)
Beispiel #5
0
 def __decode_header(header):
     """Decode a qp-encoded e-mail header as per rfc2047"""
     try:
         words_enc = decode_header(header)
         hobj = make_header(words_enc)
     except Exception as ex:
         raise CmdException('header decoding error: %s' % str(ex))
     return text(hobj)
Beispiel #6
0
 def env(self):
     env = {}
     for p, v1 in ((self.author, 'AUTHOR'), (self.committer, 'COMMITTER')):
         if p is not None:
             for attr, v2 in (('name', 'NAME'), ('email', 'EMAIL'),
                              ('date', 'DATE')):
                 if getattr(p, attr) is not None:
                     env['GIT_%s_%s' % (v1, v2)] = text(getattr(p, attr))
     return env
Beispiel #7
0
def _find_commands():
    for p in __path__:
        for fn in os.listdir(p):
            if not fn.endswith('.py'):
                continue
            mod = text(strip_suffix('.py', fn))
            m = get_command(mod)
            if not hasattr(m, 'usage'):
                continue
            yield mod, m
Beispiel #8
0
def __get_sender():
    """Return the 'authname <authemail>' string as read from the
    configuration file
    """
    sender = config.get('stgit.sender')
    if not sender:
        try:
            sender = text(git.user())
        except git.GitException:
            try:
                sender = text(git.author())
            except git.GitException:
                pass
    if not sender:
        raise CmdException('Unknown sender name and e-mail; you should for '
                           'example set git config user.name and user.email')
    sender = email.utils.parseaddr(sender)

    return email.utils.formataddr(address_or_alias(sender))
Beispiel #9
0
def _find_commands():
    for p in __path__:
        for fn in os.listdir(p):
            if not fn.endswith('.py'):
                continue
            mod = text(strip_suffix('.py', fn))
            m = get_command(mod)
            if not hasattr(m, 'usage'):
                continue
            yield mod, m
Beispiel #10
0
def get_commands(allow_cached=True):
    """Return a map from command name to a tuple of module name, command
    type, and one-line command help."""
    if allow_cached:
        try:
            from stgit.commands.cmdlist import command_list
            return command_list
        except ImportError:
            # cmdlist.py doesn't exist, so do it the expensive way.
            pass
    return dict((text(getattr(m, 'name', mod)), (mod, _kinds[m.kind], m.help))
                for mod, m in _find_commands())
Beispiel #11
0
def get_commands(allow_cached = True):
    """Return a map from command name to a tuple of module name, command
    type, and one-line command help."""
    if allow_cached:
        try:
            from stgit.commands.cmdlist import command_list
            return command_list
        except ImportError:
            # cmdlist.py doesn't exist, so do it the expensive way.
            pass
    return dict((text(getattr(m, 'name', mod)), (mod, _kinds[m.kind], m.help))
                for mod, m in _find_commands())
Beispiel #12
0
    def init(self, create_at=False, parent_remote=None, parent_branch=None):
        """Initialises the stgit series
        """
        if self.is_initialised():
            raise StackException('%s already initialized' % self.get_name())
        for d in [self._dir()]:
            if os.path.exists(d):
                raise StackException('%s already exists' % d)

        if (create_at!=False):
            git.create_branch(self.get_name(), create_at)

        os.makedirs(self.__patch_dir)

        self.set_parent(parent_remote, parent_branch)

        self.create_empty_field('applied')
        self.create_empty_field('unapplied')

        config.set(stackupgrade.format_version_key(self.get_name()),
                   text(stackupgrade.FORMAT_VERSION))
Beispiel #13
0
    def init(self, create_at=False, parent_remote=None, parent_branch=None):
        """Initialises the stgit series
        """
        if self.is_initialised():
            raise StackException('%s already initialized' % self.get_name())
        for d in [self._dir()]:
            if os.path.exists(d):
                raise StackException('%s already exists' % d)

        if create_at is not False:
            git.create_branch(self.get_name(), create_at)

        os.makedirs(self.__patch_dir)

        self.set_parent(parent_remote, parent_branch)

        self.create_empty_field('applied')
        self.create_empty_field('unapplied')

        config.set(stackupgrade.format_version_key(self.get_name()),
                   text(stackupgrade.FORMAT_VERSION))
Beispiel #14
0
    def initialise(cls, repository, name = None):
        """Initialise a Git branch to handle patch series.

        @param repository: The L{Repository} where the L{Stack} will be created
        @param name: The name of the L{Stack}
        """
        if not name:
            name = repository.current_branch_name
        # make sure that the corresponding Git branch exists
        git.Branch(repository, name)

        dir = os.path.join(repository.directory, cls.__repo_subdir, name)
        compat_dir = os.path.join(dir, 'patches')
        if os.path.exists(dir):
            raise StackException('%s: branch already initialized' % name)

        # create the stack directory and files
        utils.create_dirs(dir)
        utils.create_dirs(compat_dir)
        PatchOrder.create(dir)
        config.set(stackupgrade.format_version_key(name),
                   text(stackupgrade.FORMAT_VERSION))

        return repository.get_stack(name)
Beispiel #15
0
def __build_message(tmpl, msg_id, options, patch, patch_nr, total_nr, ref_id):
    """Build the message to be sent via SMTP
    """
    repository = directory.repository
    stack = repository.current_stack

    p = stack.patches.get(patch)

    if p.commit.data.message:
        descr = p.commit.data.message.strip()
    else:
        # provide a place holder and force the edit message option on
        descr = '<empty message>'
        options.edit_patches = True

    descr_lines = descr.split('\n')
    short_descr = descr_lines[0].strip()
    long_descr = '\n'.join(l.rstrip() for l in descr_lines[1:]).lstrip('\n')

    author = p.commit.data.author
    committer = p.commit.data.committer

    sender = __get_sender()

    if author.name_email != sender:
        fromauth = 'From: %s\n\n' % author.name_email
    else:
        fromauth = ''

    if options.version:
        version_str = '%s' % options.version
        version_space = ' '
    else:
        version_str = ''
        version_space = ''

    if options.prefix:
        prefix_str = options.prefix
    else:
        prefix_str = config.get('stgit.mail.prefix')
    if prefix_str:
        prefix_space = ' '
    else:
        prefix_str = ''
        prefix_space = ''

    total_nr_str = text(total_nr)
    patch_nr_str = text(patch_nr).zfill(len(total_nr_str))
    if not options.unrelated and total_nr > 1:
        number_str = '%s/%s' % (patch_nr_str, total_nr_str)
        number_space = ' '
    else:
        number_str = ''
        number_space = ''

    diff = repository.diff_tree(
        p.commit.data.parent.data.tree,
        p.commit.data.tree,
        diff_opts=options.diff_flags,
    )
    tmpl_dict = {
        'patch': patch,
        'sender': sender,
        'maintainer': sender,  # for backward template compatibility
        'shortdescr': short_descr,
        'longdescr': long_descr,
        'endofheaders': '',  # for backward template compatibility
        'diff': diff,
        'diffstat': diffstat(diff),
        'date': '',  # for backward template compatibility
        'version': version_str,
        'vspace': version_space,
        'prefix': prefix_str,
        'pspace': prefix_space,
        'patchnr': patch_nr_str,
        'totalnr': total_nr_str,
        'number': number_str,
        'nspace': number_space,
        'snumber': number_str.strip(),
        'fromauth': fromauth,
        'authname': author.name,
        'authemail': author.email,
        'authdate': author.date.rfc2822_format(),
        'commname': committer.name,
        'commemail': committer.email,
    }

    try:
        msg_bytes = templates.specialize_template(tmpl, tmpl_dict)
    except KeyError as err:
        raise CmdException('Unknown patch template variable: %s' % err)
    except TypeError:
        raise CmdException('Only "%(name)s" variables are '
                           'supported in the patch template')

    if options.edit_patches:
        msg_bytes = edit_bytes(msg_bytes, '.stgitmail.txt')

    # The Python email message
    try:
        msg = message_from_bytes(msg_bytes)
    except Exception as ex:
        raise CmdException('template parsing error: %s' % str(ex))

    if options.auto:
        extra_cc = __get_signers_list(descr)
    else:
        extra_cc = []

    if not options.git:
        __build_address_headers(msg, options, extra_cc)
    __build_extra_headers(msg, msg_id, ref_id)
    __encode_message(msg)

    return msg
Beispiel #16
0
def __build_cover(tmpl, msg_id, options, patches):
    """Build the cover message (series description) to be sent via SMTP
    """
    sender = __get_sender()

    if options.version:
        version_str = '%s' % options.version
        version_space = ' '
    else:
        version_str = ''
        version_space = ''

    if options.prefix:
        prefix_str = options.prefix
    else:
        prefix_str = config.get('stgit.mail.prefix')
    if prefix_str:
        prefix_space = ' '
    else:
        prefix_str = ''
        prefix_space = ''

    total_nr_str = text(len(patches))
    patch_nr_str = '0'.zfill(len(total_nr_str))
    if len(patches) > 1:
        number_str = '%s/%s' % (patch_nr_str, total_nr_str)
        number_space = ' '
    else:
        number_str = ''
        number_space = ''

    repository = directory.repository
    stack = repository.current_stack

    tmpl_dict = {
        'sender':
        sender,  # for backward template compatibility
        'maintainer':
        sender,  # for backward template compatibility
        'endofheaders':
        '',  # for backward template compatibility
        'date':
        '',
        'version':
        version_str,
        'vspace':
        version_space,
        'prefix':
        prefix_str,
        'pspace':
        prefix_space,
        'patchnr':
        patch_nr_str,
        'totalnr':
        total_nr_str,
        'number':
        number_str,
        'nspace':
        number_space,
        'snumber':
        number_str.strip(),
        'shortlog':
        __shortlog(stack, patches),
        'diffstat':
        repository.diff_tree(
            stack.base.data.tree,
            stack.top.data.tree,
            diff_opts=options.diff_flags,
            stat=True,
        ),
    }

    try:
        msg_bytes = templates.specialize_template(tmpl, tmpl_dict)
    except KeyError as err:
        raise CmdException('Unknown patch template variable: %s' % err)
    except TypeError:
        raise CmdException('Only "%(name)s" variables are '
                           'supported in the patch template')

    if options.edit_cover:
        msg_bytes = edit_bytes(msg_bytes, '.stgitmail.txt')

    # The Python email message
    try:
        msg = message_from_bytes(msg_bytes)
    except Exception as ex:
        raise CmdException('template parsing error: %s' % str(ex))

    extra_cc = []
    if options.auto:
        for pn in patches:
            p = stack.patches.get(pn)
            if p.commit.data.message:
                descr = p.commit.data.message.strip()
                extra_cc.extend(__get_signers_list(descr))
        extra_cc = list(set(extra_cc))

    if not options.git:
        __build_address_headers(msg, options, extra_cc)
    __build_extra_headers(msg, msg_id, options.in_reply_to)
    __encode_message(msg)

    return msg
Beispiel #17
0
def __build_message(tmpl, msg_id, options, patch, patch_nr, total_nr, ref_id):
    """Build the message to be sent via SMTP
    """
    p = crt_series.get_patch(patch)

    if p.get_description():
        descr = p.get_description().strip()
    else:
        # provide a place holder and force the edit message option on
        descr = '<empty message>'
        options.edit_patches = True

    descr_lines = descr.split('\n')
    short_descr = descr_lines[0].strip()
    long_descr = '\n'.join(l.rstrip() for l in descr_lines[1:]).lstrip('\n')

    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
        version_space = ' '
    else:
        version_str = ''
        version_space = ''

    if options.prefix:
        prefix_str = options.prefix
    else:
        prefix_str = config.get('stgit.mail.prefix')
    if prefix_str:
        prefix_space = ' '
    else:
        prefix_str = ''
        prefix_space = ''

    total_nr_str = text(total_nr)
    patch_nr_str = text(patch_nr).zfill(len(total_nr_str))
    if not options.unrelated and total_nr > 1:
        number_str = '%s/%s' % (patch_nr_str, total_nr_str)
        number_space = ' '
    else:
        number_str = ''
        number_space = ''

    diff = git.diff(rev1=git_id(crt_series, '%s^' % patch),
                    rev2=git_id(crt_series, '%s' % patch),
                    diff_flags=options.diff_flags)
    tmpl_dict = {
        'patch': patch,
        'sender': sender,
        # for backward template compatibility
        'maintainer': sender,
        'shortdescr': short_descr,
        'longdescr': long_descr,
        # for backward template compatibility
        'endofheaders': '',
        'diff': diff,
        'diffstat': gitlib.diffstat(diff),
        # for backward template compatibility
        'date': '',
        'version': version_str,
        'vspace': version_space,
        'prefix': prefix_str,
        'pspace': prefix_space,
        'patchnr': patch_nr_str,
        'totalnr': total_nr_str,
        'number': number_str,
        'nspace': number_space,
        'snumber': number_str.strip(),
        'fromauth': fromauth,
        'authname': authname,
        'authemail': authemail,
        'authdate': p.get_authdate(),
        'commname': commname,
        'commemail': commemail
    }
    # change None to ''
    for key in tmpl_dict:
        if not tmpl_dict[key]:
            tmpl_dict[key] = ''

    try:
        msg_string = tmpl % tmpl_dict
    except KeyError as err:
        raise CmdException('Unknown patch template variable: %s' % err)
    except TypeError:
        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)
    except Exception as ex:
        raise CmdException('template parsing error: %s' % str(ex))

    if options.auto:
        extra_cc = __get_signers_list(descr)
    else:
        extra_cc = []

    if not options.git:
        __build_address_headers(msg, options, extra_cc)
    __build_extra_headers(msg, msg_id, ref_id)
    __encode_message(msg)

    return msg
Beispiel #18
0
def __build_cover(tmpl, msg_id, options, patches):
    """Build the cover message (series description) to be sent via SMTP
    """
    sender = __get_sender()

    if options.version:
        version_str = '%s' % options.version
        version_space = ' '
    else:
        version_str = ''
        version_space = ''

    if options.prefix:
        prefix_str = options.prefix
    else:
        prefix_str = config.get('stgit.mail.prefix')
    if prefix_str:
        prefix_space = ' '
    else:
        prefix_str = ''
        prefix_space = ''

    total_nr_str = text(len(patches))
    patch_nr_str = '0'.zfill(len(total_nr_str))
    if len(patches) > 1:
        number_str = '%s/%s' % (patch_nr_str, total_nr_str)
        number_space = ' '
    else:
        number_str = ''
        number_space = ''

    tmpl_dict = {
        'sender':
        sender,  # for backward template compatibility
        'maintainer':
        sender,  # for backward template compatibility
        'endofheaders':
        '',  # for backward template compatibility
        'date':
        '',
        'version':
        version_str,
        'vspace':
        version_space,
        'prefix':
        prefix_str,
        'pspace':
        prefix_space,
        'patchnr':
        patch_nr_str,
        'totalnr':
        total_nr_str,
        'number':
        number_str,
        'nspace':
        number_space,
        'snumber':
        number_str.strip(),
        'shortlog':
        stack.shortlog(crt_series.get_patch(p) for p in reversed(patches)),
        'diffstat':
        gitlib.diffstat(
            git.diff(
                rev1=git_id(crt_series, '%s^' % patches[0]),
                rev2=git_id(crt_series, '%s' % patches[-1]),
                diff_flags=options.diff_flags,
            )),
    }

    try:
        msg_bytes = templates.specialize_template(tmpl, tmpl_dict)
    except KeyError as err:
        raise CmdException('Unknown patch template variable: %s' % err)
    except TypeError:
        raise CmdException('Only "%(name)s" variables are '
                           'supported in the patch template')

    if options.edit_cover:
        msg_bytes = edit_bytes(msg_bytes, '.stgitmail.txt')

    # The Python email message
    try:
        msg = message_from_bytes(msg_bytes)
    except Exception as ex:
        raise CmdException('template parsing error: %s' % str(ex))

    extra_cc = []
    if options.auto:
        for patch in patches:
            p = crt_series.get_patch(patch)
            if p.get_description():
                descr = p.get_description().strip()
                extra_cc.extend(__get_signers_list(descr))
        extra_cc = list(set(extra_cc))

    if not options.git:
        __build_address_headers(msg, options, extra_cc)
    __build_extra_headers(msg, msg_id, options.in_reply_to)
    __encode_message(msg)

    return msg
Beispiel #19
0
def __create_patch(filename, message, author_name, author_email, author_date,
                   diff, options):
    """Create a new patch on the stack
    """
    if options.name:
        patch = options.name
    elif filename:
        patch = os.path.basename(filename)
    else:
        patch = ''
    if options.stripname:
        patch = __strip_patch_name(patch)

    if not patch:
        if options.ignore or options.replace:

            def unacceptable_name(name):
                return False
        else:
            unacceptable_name = crt_series.patch_exists
        patch = make_patch_name(message, unacceptable_name)
    else:
        # fix possible invalid characters in the patch name
        patch = re.sub(r'[^\w.]+', '-', patch).strip('-')

    if options.ignore and patch in crt_series.get_applied():
        out.info('Ignoring already applied patch "%s"' % patch)
        return
    if options.replace and patch in crt_series.get_unapplied():
        crt_series.delete_patch(patch, keep_log=True)

    # override the automatically parsed settings
    author = options.author(Person())
    if author.name:
        author_name = author.name
    if author.email:
        author_email = author.email
    if author.date:
        author_date = text(author.date)

    sign_str = options.sign_str
    if not options.sign_str:
        sign_str = config.get('stgit.autosign')

    crt_series.new_patch(
        patch,
        message=message,
        can_edit=False,
        author_name=author_name,
        author_email=author_email,
        author_date=author_date,
        sign_str=sign_str,
    )

    if not diff:
        out.warn('No diff found, creating empty patch')
    else:
        out.start('Importing patch "%s"' % patch)
        if options.base:
            base = git_id(crt_series, options.base)
        else:
            base = None
        try:
            git.apply_patch(
                diff=diff,
                base=base,
                reject=options.reject,
                strip=options.strip,
            )
        except git.GitException:
            if not options.reject:
                crt_series.delete_patch(patch)
            raise
        crt_series.refresh_patch(
            edit=options.edit,
            show_patch=options.showdiff,
            author_date=author_date,
            backup=False,
        )
        out.done()
Beispiel #20
0
def write_string(filename, line, multiline=False, encoding='utf-8'):
    """Writes 'line' to file and truncates it
    """
    with mkdir_file(filename, 'w+', encoding) as f:
        line = text(line)
        print(line, end='' if multiline else '\n', file=f)
Beispiel #21
0
def __build_message(tmpl, msg_id, options, patch, patch_nr, total_nr, ref_id):
    """Build the message to be sent via SMTP
    """
    p = crt_series.get_patch(patch)

    if p.get_description():
        descr = p.get_description().strip()
    else:
        # provide a place holder and force the edit message option on
        descr = '<empty message>'
        options.edit_patches = True

    descr_lines = descr.split('\n')
    short_descr = descr_lines[0].strip()
    long_descr = '\n'.join(l.rstrip() for l in descr_lines[1:]).lstrip('\n')

    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
        version_space = ' '
    else:
        version_str = ''
        version_space = ''

    if options.prefix:
        prefix_str = options.prefix
    else:
        prefix_str = config.get('stgit.mail.prefix')
    if prefix_str:
        prefix_space = ' '
    else:
        prefix_str = ''
        prefix_space = ''

    total_nr_str = text(total_nr)
    patch_nr_str = text(patch_nr).zfill(len(total_nr_str))
    if not options.unrelated and total_nr > 1:
        number_str = '%s/%s' % (patch_nr_str, total_nr_str)
        number_space = ' '
    else:
        number_str = ''
        number_space = ''

    diff = git.diff(rev1 = git_id(crt_series, '%s^' % patch),
                    rev2 = git_id(crt_series, '%s' % patch),
                    diff_flags = options.diff_flags)
    tmpl_dict = {'patch':        patch,
                 'sender':       sender,
                 # for backward template compatibility
                 'maintainer':   sender,
                 'shortdescr':   short_descr,
                 'longdescr':    long_descr,
                 # for backward template compatibility
                 'endofheaders': '',
                 'diff':         diff,
                 'diffstat':     gitlib.diffstat(diff),
                 # for backward template compatibility
                 'date':         '',
                 'version':      version_str,
                 'vspace':       version_space,
                 'prefix':       prefix_str,
                 'pspace':       prefix_space,
                 'patchnr':      patch_nr_str,
                 'totalnr':      total_nr_str,
                 'number':       number_str,
                 'nspace':       number_space,
                 'snumber':      number_str.strip(),
                 'fromauth':     fromauth,
                 'authname':     authname,
                 'authemail':    authemail,
                 'authdate':     p.get_authdate(),
                 'commname':     commname,
                 'commemail':    commemail}
    # change None to ''
    for key in tmpl_dict:
        if not tmpl_dict[key]:
            tmpl_dict[key] = ''

    try:
        msg_string = tmpl % tmpl_dict
    except KeyError as err:
        raise CmdException('Unknown patch template variable: %s' % err)
    except TypeError:
        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)
    except Exception as ex:
        raise CmdException('template parsing error: %s' % str(ex))

    if options.auto:
        extra_cc = __get_signers_list(descr)
    else:
        extra_cc = []

    if not options.git:
        __build_address_headers(msg, options, extra_cc)
    __build_extra_headers(msg, msg_id, ref_id)
    __encode_message(msg)

    return msg
Beispiel #22
0
def write_string(filename, line, multiline=False, encoding='utf-8'):
    """Writes 'line' to file and truncates it
    """
    with mkdir_file(filename, 'w+', encoding) as f:
        line = text(line)
        print(line, end='' if multiline else '\n', file=f)
Beispiel #23
0
def __build_cover(tmpl, msg_id, options, patches):
    """Build the cover message (series description) to be sent via SMTP
    """
    sender = __get_sender()

    if options.version:
        version_str = '%s' % options.version
        version_space = ' '
    else:
        version_str = ''
        version_space = ''

    if options.prefix:
        prefix_str = options.prefix
    else:
        prefix_str = config.get('stgit.mail.prefix')
    if prefix_str:
        prefix_space = ' '
    else:
        prefix_str = ''
        prefix_space = ''

    total_nr_str = text(len(patches))
    patch_nr_str = '0'.zfill(len(total_nr_str))
    if len(patches) > 1:
        number_str = '%s/%s' % (patch_nr_str, total_nr_str)
        number_space = ' '
    else:
        number_str = ''
        number_space = ''

    tmpl_dict = {'sender':       sender,
                 # for backward template compatibility
                 'maintainer':   sender,
                 # for backward template compatibility
                 'endofheaders': '',
                 # for backward template compatibility
                 'date':         '',
                 'version':      version_str,
                 'vspace':       version_space,
                 'prefix':       prefix_str,
                 'pspace':       prefix_space,
                 'patchnr':      patch_nr_str,
                 'totalnr':      total_nr_str,
                 'number':       number_str,
                 'nspace':       number_space,
                 'snumber':      number_str.strip(),
                 'shortlog':     stack.shortlog(crt_series.get_patch(p)
                                                for p in reversed(patches)),
                 'diffstat':     gitlib.diffstat(git.diff(
                     rev1 = git_id(crt_series, '%s^' % patches[0]),
                     rev2 = git_id(crt_series, '%s' % patches[-1]),
                     diff_flags = options.diff_flags))}

    try:
        msg_string = tmpl % tmpl_dict
    except KeyError as err:
        raise CmdException('Unknown patch template variable: %s' % err)
    except TypeError:
        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)
    except Exception as ex:
        raise CmdException('template parsing error: %s' % str(ex))

    extra_cc = []
    if options.auto:
        for patch in patches:
            p = crt_series.get_patch(patch)
            if p.get_description():
                descr = p.get_description().strip()
                extra_cc.extend(__get_signers_list(descr))
        extra_cc = list(set(extra_cc))


    if not options.git:
        __build_address_headers(msg, options, extra_cc)
    __build_extra_headers(msg, msg_id, options.in_reply_to)
    __encode_message(msg)

    return msg