[git-buildpackage] [PATCH 11/11] Introduce gbp-buildpackage-rpm tool

Guido Günther agx at sigxcpu.org
Thu Apr 2 09:21:23 CEST 2015


Hi Markus,
On Thu, Jan 12, 2012 at 03:39:12PM +0200, Markus Lehtonen wrote:
> Initial version of gbp-buildpackage-rpm - tool for building RPM packages
> from git. Bigger differences to the (Debian) gbp-buildpackage are that
> buildpackage-rpm always does the build under a separate build directory
> (../rpmbuild by default) and that packaging files of a non-native
> package may be maintained in an orphan branch, without any sourcecode.
> Also, whether the package is native or not is determined by the
> existence of an upstream branch or from an explicit config setting.
> 
> Signed-off-by: Markus Lehtonen <markus.lehtonen at linux.intel.com>
> Signed-off-by: Ed Bartosh <eduard.bartosh at intel.com>
> Signed-off-by: Zhang Qiang <qiang.z.zhang at intel.com>
> ---
>  debian/git-buildpackage-rpm.install          |   1 +
>  gbp/config.py                                |  21 +-
>  gbp/scripts/buildpackage_rpm.py              | 580 +++++++++++++++++++++++++
>  gbp/scripts/pq_rpm.py                        |   2 +-
>  tests/component/rpm/__init__.py              |  21 +-
>  tests/component/rpm/data                     |   2 +-
>  tests/component/rpm/test_buildpackage_rpm.py | 611 +++++++++++++++++++++++++++
>  7 files changed, 1228 insertions(+), 10 deletions(-)
>  create mode 100755 gbp/scripts/buildpackage_rpm.py
>  create mode 100644 tests/component/rpm/test_buildpackage_rpm.py
> 
> diff --git a/debian/git-buildpackage-rpm.install b/debian/git-buildpackage-rpm.install
> index 2568b4c..5178cb3 100644
> --- a/debian/git-buildpackage-rpm.install
> +++ b/debian/git-buildpackage-rpm.install
> @@ -1,3 +1,4 @@
>  usr/lib/python2.?/dist-packages/gbp/rpm/
>  usr/lib/python2.7/dist-packages/gbp/scripts/import_srpm.py
>  usr/lib/python2.7/dist-packages/gbp/scripts/pq_rpm.py
> +usr/lib/python2.7/dist-packages/gbp/scripts/buildpackage_rpm.py
> diff --git a/gbp/config.py b/gbp/config.py
> index 9f8e7fc..5760824 100644
> --- a/gbp/config.py
> +++ b/gbp/config.py
> @@ -101,6 +101,7 @@ class GbpOptionParser(OptionParser):
>                   'sign-tags'       : 'False',
>                   'force-create'    : 'False',
>                   'no-create-orig'  : 'False',
> +                 'cleaner'         : '/bin/true',
>                   'keyid'           : '',
>                   'posttag'         : '',
>                   'postbuild'       : '',
> @@ -561,7 +562,6 @@ class GbpOptionParserDebian(GbpOptionParser):
>      defaults = dict(GbpOptionParser.defaults)
>      defaults.update( {
>                         'builder'            : 'debuild -i -I',
> -                       'cleaner'            : '/bin/true',
>                       } )
>  
>  
> @@ -576,7 +576,14 @@ class GbpOptionParserRpm(GbpOptionParser):
>              'packaging-branch'          : 'master',
>              'packaging-dir'             : '',
>              'packaging-tag'             : 'packaging/%(version)s',
> +            'packaging-tag-msg'         : '%(pkg)s %(vendor)s release '\
> +                                          '%(version)s',
>              'spec-file'                 : '',
> +            'export-dir'                : '../rpmbuild',
> +            'native'                    : 'auto',
> +            'builder'                   : 'rpmbuild',
> +            'export-specdir'            : 'SPECS',
> +            'export-sourcedir'          : 'SOURCES',
>                      })
>  
>      help = dict(GbpOptionParser.help)
> @@ -596,9 +603,21 @@ class GbpOptionParserRpm(GbpOptionParser):
>              'packaging-tag':
>                  "Format string for packaging tags, RPM counterpart of the "
>                  "'debian-tag' option, default is '%(packaging-tag)s'",
> +            'packaging-tag-msg':
> +                  ("Format string for packaging tag messages, "
> +                   "default is '%(packaging-tag-msg)s'"),
>              'spec-file':
>                  "Spec file to use, causes the packaging-dir option to be "
>                  "ignored, default is '%(spec-file)s'",
> +            'native':
> +                "Treat this package as native, default is '%(native)s'",
> +            'export-specdir':
> +                "Subdir (under EXPORT_DIR) where package spec file is "
> +                "exported default is '%(export-specdir)s'",
> +            'export-sourcedir':
> +                "Subdir (under EXPORT_DIR) where packaging sources (other than "
> +                "the spec file) are exported, default is "
> +                "'%(export-sourcedir)s'",
>                   })
>  
>  # vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·:
> diff --git a/gbp/scripts/buildpackage_rpm.py b/gbp/scripts/buildpackage_rpm.py
> new file mode 100755
> index 0000000..4c0c996
> --- /dev/null
> +++ b/gbp/scripts/buildpackage_rpm.py
> @@ -0,0 +1,580 @@
> +# vim: set fileencoding=utf-8 :
> +#
> +# (C) 2006-2011 Guido Guenther <agx at sigxcpu.org>
> +# (C) 2012-2015 Intel Corporation <markus.lehtonen at linux.intel.com>
> +#    This program is free software; you can redistribute it and/or modify
> +#    it under the terms of the GNU General Public License as published by
> +#    the Free Software Foundation; either version 2 of the License, or
> +#    (at your option) any later version.
> +#
> +#    This program is distributed in the hope that it will be useful,
> +#    but WITHOUT ANY WARRANTY; without even the implied warranty of
> +#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +#    GNU General Public License for more details.
> +#
> +#    You should have received a copy of the GNU General Public License
> +#    along with this program; if not, write to the Free Software
> +#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
> +#
> +"""Build an RPM package out of a Git repository"""
> +
> +import ConfigParser
> +import os
> +import shutil
> +import sys
> +
> +import gbp.log
> +import gbp.notifications
> +import gbp.rpm as rpm
> +from gbp.command_wrappers import Command, RunAtCommand, CommandExecFailed
> +from gbp.config import GbpOptionParserRpm, GbpOptionGroup
> +from gbp.errors import GbpError
> +from gbp.format import format_str
> +from gbp.pkg import compressor_opts
> +from gbp.rpm.git import GitRepositoryError, RpmGitRepository
> +from gbp.rpm.policy import RpmPkgPolicy
> +from gbp.tmpfile import init_tmpdir, del_tmpdir, tempfile
> +from gbp.scripts.common.buildpackage import (index_name, wc_name,
> +                                             git_archive_submodules,
> +                                             git_archive_single, dump_tree,
> +                                             write_wc, drop_index)
> +from gbp.scripts.pq_rpm import parse_spec
> +
> +
> +def makedir(path):
> +    """Create directory"""
> +    try:
> +        if not os.path.exists(path):
> +            os.makedirs(path)
> +    except OSError as err:
> +        raise GbpError("Cannot create dir %s: %s" % (path, err))
> +    return path
> +
> +
> +def git_archive(repo, spec, output_dir, treeish, comp_level,
> +                with_submodules):
> +    "Create a compressed orig tarball in output_dir using git_archive"
> +    comp_opts = ''
> +    if spec.orig_src['compression']:
> +        comp_opts = compressor_opts[spec.orig_src['compression']][0]
> +
> +    output = os.path.join(output_dir, spec.orig_src['filename'])
> +
> +    try:
> +        if repo.has_submodules(treeish) and with_submodules:
> +            repo.update_submodules()
> +            git_archive_submodules(repo, treeish, output,
> +                                   spec.orig_src['prefix'],
> +                                   spec.orig_src['compression'],
> +                                   comp_level, comp_opts,
> +                                   spec.orig_src['archive_fmt'])
> +
> +        else:
> +            git_archive_single(treeish, output, spec.orig_src['prefix'],
> +                               spec.orig_src['compression'], comp_level,
> +                               comp_opts, spec.orig_src['archive_fmt'])
> +    except (GitRepositoryError, CommandExecFailed):
> +        gbp.log.err("Error generating submodules' archives")
> +        return False
> +    return True
> +
> +
> +def prepare_upstream_tarball(repo, spec, options, output_dir):
> +    """Make sure we have an upstream tarball"""
> +    # look in tarball_dir first, if found force a symlink to it
> +    orig_file = spec.orig_src['filename']
> +    if options.tarball_dir:
> +        gbp.log.debug("Looking for orig tarball '%s' at '%s'" %
> +                      (orig_file, options.tarball_dir))
> +        if not RpmPkgPolicy.symlink_orig(orig_file, options.tarball_dir,
> +                                         output_dir, force=True):
> +            gbp.log.info("Orig tarball '%s' not found at '%s'" %
> +                         (orig_file, options.tarball_dir))
> +        else:
> +            gbp.log.info("Orig tarball '%s' found at '%s'" %
> +                         (orig_file, options.tarball_dir))
> +
> +    # build an orig unless the user forbids it, always build (and overwrite
> +    # pre-existing) if user forces it
> +    if options.force_create or (not options.no_create_orig and not
> +                                RpmPkgPolicy.has_orig(orig_file, output_dir)):
> +        if not pristine_tar_build_orig(repo, orig_file, output_dir, options):
> +            upstream_tree = git_archive_build_orig(repo, spec, output_dir,
> +                                                   options)
> +            if options.pristine_tar_commit:
> +                if repo.pristine_tar.has_commit(orig_file):
> +                    gbp.log.debug("%s already on pristine tar branch" %
> +                                  orig_file)
> +                else:
> +                    archive = os.path.join(output_dir, orig_file)
> +                    gbp.log.debug("Adding %s to pristine-tar branch" %
> +                                  archive)
> +                    repo.pristine_tar.commit(archive, upstream_tree)
> +
> +
> +def pristine_tar_build_orig(repo, orig_file, output_dir, options):
> +    """Build orig using pristine-tar"""
> +    if options.pristine_tar:
> +        if not repo.has_branch(repo.pristine_tar_branch):
> +            gbp.log.warn('Pristine-tar branch "%s" not found' %
> +                         repo.pristine_tar.branch)
> +        try:
> +            repo.pristine_tar.checkout(os.path.join(output_dir, orig_file))
> +            return True
> +        except CommandExecFailed:
> +            if options.pristine_tar_commit:
> +                gbp.log.debug("pristine-tar checkout failed, "
> +                              "will commit tarball due to "
> +                              "'--pristine-tar-commit'")
> +            elif not options.force_create:
> +                raise
> +    return False
> +
> +def get_upstream_tree(repo, spec, options):
> +    """Determine the upstream tree from the given options"""
> +    if options.upstream_tree.upper() == 'TAG':
> +        tag_str_fields = {'upstreamversion': spec.upstreamversion,
> +                          'version': spec.upstreamversion}
> +        upstream_tree = repo.version_to_tag(options.upstream_tag,
> +                                            tag_str_fields)
> +    elif options.upstream_tree.upper() == 'BRANCH':
> +        if not repo.has_branch(options.upstream_branch):
> +            raise GbpError("%s is not a valid branch" % options.upstream_branch)
> +        upstream_tree = options.upstream_branch
> +    else:
> +        upstream_tree = get_tree(repo, options.upstream_tree)
> +    if not repo.has_treeish(upstream_tree):
> +        raise GbpError('Invalid upstream treeish %s' % upstream_tree)
> +    return upstream_tree
> +
> +
> +def get_tree(repo, tree_name):
> +    """
> +    Get/create a tree-ish to be used for exporting and diffing. Accepts
> +    special keywords for git index and working copies.
> +    """
> +    try:
> +        if tree_name == index_name:
> +            # Write a tree of the index
> +            tree = repo.write_tree()
> +        elif tree_name == wc_name:
> +            # Write a tree of the working copy
> +            tree = write_wc(repo)
> +        else:
> +            tree = tree_name
> +    except GitRepositoryError as err:
> +        raise GbpError(err)
> +    if not repo.has_treeish(tree):
> +        raise GbpError('Invalid treeish object %s' % tree)
> +
> +    return tree
> +
> +
> +def get_current_branch(repo):
> +    """Get the currently checked-out branch"""
> +    try:
> +        branch = repo.get_branch()
> +    except GitRepositoryError:
> +        branch = None
> +    return branch
> +
> +
> +def git_archive_build_orig(repo, spec, output_dir, options):
> +    """
> +    Build orig tarball using git-archive
> +
> +    @param repo: our git repository
> +    @type repo: L{RpmGitRepository}
> +    @param spec: spec file of the package
> +    @type spec: L{SpecFile}
> +    @param output_dir: where to put the tarball
> +    @type output_dir: C{Str}
> +    @param options: the parsed options
> +    @type options: C{dict} of options
> +    @return: the tree we built the tarball from
> +    @rtype: C{str}
> +    """
> +    upstream_tree = get_upstream_tree(repo, spec, options)
> +    gbp.log.info("%s does not exist, creating from '%s'" % \
> +                 (spec.orig_src['filename'], upstream_tree))
> +    if spec.orig_src['compression']:
> +        gbp.log.debug("Building upstream source archive with compression "\
> +                      "'%s -%s'" % (spec.orig_src['compression'],
> +                                    options.comp_level))
> +    if not git_archive(repo, spec, output_dir, upstream_tree,
> +                       options.comp_level, options.with_submodules):
> +        raise GbpError("Cannot create upstream tarball at '%s'" % \
> +                        output_dir)
> +    return upstream_tree
> +
> +
> +def is_native(repo, options):
> +    """Determine whether a package is native or non-native"""
> +    if options.native.is_auto():
> +        if repo.has_branch(options.upstream_branch):
> +            return False
> +        # Check remotes, too
> +        for remote_branch in repo.get_remote_branches():
> +            remote, branch = remote_branch.split('/', 1)
> +            if branch == options.upstream_branch:
> +                gbp.log.debug("Found upstream branch '%s' from remote '%s'" %
> +                               (remote, branch))
> +                return False
> +        return True
> +
> +    return options.native.is_on()
> +
> +
> +def setup_builder(options, builder_args):
> +    """Setup args and options for builder script"""
> +    if options.builder == 'rpmbuild':
> +        if len(builder_args) == 0:
> +            builder_args.append('-ba')
> +        builder_args.extend([
> +            '--define "_topdir %s"' % os.path.abspath(options.export_dir),
> +            '--define "_specdir %%_topdir/%s"' % options.export_specdir,
> +            '--define "_sourcedir %%_topdir/%s"' % options.export_sourcedir])
> +
> +
> +def create_packaging_tag(repo, commit, spec, options):
> +    """Create a packaging/release Git tag"""
> +    version_dict = dict(spec.version,
> +                        version=rpm.compose_version_str(spec.version))
> +
> +    # Compose tag name and message
> +    tag_name_fields = dict(version_dict, vendor=options.vendor.lower())
> +    tag_name = repo.version_to_tag(options.packaging_tag, tag_name_fields)
> +
> +    tag_msg = format_str(options.packaging_tag_msg,
> +                         dict(version_dict, pkg=spec.name,
> +                              vendor=options.vendor))
> +
> +    # (Re-)create Git tag
> +    if options.retag and repo.has_tag(tag_name):
> +        repo.delete_tag(tag_name)
> +    repo.create_tag(name=tag_name, msg=tag_msg, sign=options.sign_tags,
> +                    keyid=options.keyid, commit=commit)
> +    return tag_name
> +
> +
> +def disable_hooks(options):
> +    """Disable all hooks (except for builder)"""
> +    for hook in ['cleaner', 'postexport', 'prebuild', 'postbuild', 'posttag']:
> +        if getattr(options, hook):
> +            gbp.log.info("Disabling '%s' hook" % hook)
> +            setattr(options, hook, '')
> +
> +
> +def build_parser(name, prefix=None):
> +    """Construct config/option parser"""
> +    try:
> +        parser = GbpOptionParserRpm(command=os.path.basename(name),
> +                                    prefix=prefix)
> +    except ConfigParser.ParsingError as err:
> +        gbp.log.err(err)
> +        return None
> +
> +    tag_group = GbpOptionGroup(parser, "tag options",
> +                    "options related to git tag creation")
> +    branch_group = GbpOptionGroup(parser, "branch options",
> +                    "branch layout options")
> +    cmd_group = GbpOptionGroup(parser, "external command options",
> +                    "how and when to invoke external commands and hooks")
> +    orig_group = GbpOptionGroup(parser, "orig tarball options",
> +                    "options related to the creation of the orig tarball")
> +    export_group = GbpOptionGroup(parser, "export build-tree options",
> +                    "alternative build tree related options")
> +    parser.add_option_group(tag_group)
> +    parser.add_option_group(orig_group)
> +    parser.add_option_group(branch_group)
> +    parser.add_option_group(cmd_group)
> +    parser.add_option_group(export_group)
> +
> +    parser.add_boolean_config_file_option(option_name="ignore-new",
> +                    dest="ignore_new")
> +    parser.add_option("--git-verbose", action="store_true", dest="verbose",
> +                    default=False, help="verbose command execution")
> +    parser.add_config_file_option(option_name="tmp-dir", dest="tmp_dir")
> +    parser.add_config_file_option(option_name="color", dest="color",
> +                    type='tristate')
> +    parser.add_config_file_option(option_name="color-scheme",
> +                    dest="color_scheme")
> +    parser.add_config_file_option(option_name="notify", dest="notify",
> +                    type='tristate')
> +    parser.add_config_file_option(option_name="vendor", action="store",
> +                    dest="vendor")
> +    parser.add_config_file_option(option_name="native", dest="native",
> +                    type='tristate')
> +    tag_group.add_option("--git-tag", action="store_true", dest="tag",
> +                    default=False,
> +                    help="create a tag after a successful build")
> +    tag_group.add_option("--git-tag-only", action="store_true", dest="tag_only",
> +                    default=False,
> +                    help="don't build, only tag and run the posttag hook")
> +    tag_group.add_option("--git-retag", action="store_true", dest="retag",
> +                    default=False, help="don't fail if the tag already exists")
> +    tag_group.add_boolean_config_file_option(option_name="sign-tags",
> +                    dest="sign_tags")
> +    tag_group.add_config_file_option(option_name="keyid", dest="keyid")
> +    tag_group.add_config_file_option(option_name="packaging-tag",
> +                    dest="packaging_tag")
> +    tag_group.add_config_file_option(option_name="packaging-tag-msg",
> +                    dest="packaging_tag_msg")
> +    tag_group.add_config_file_option(option_name="upstream-tag",
> +                    dest="upstream_tag")
> +    orig_group.add_config_file_option(option_name="upstream-tree",
> +                    dest="upstream_tree")
> +    orig_group.add_boolean_config_file_option(option_name="pristine-tar",
> +                    dest="pristine_tar")
> +    orig_group.add_boolean_config_file_option(option_name="pristine-tar-commit",
> +                    dest="pristine_tar_commit")
> +    orig_group.add_config_file_option(option_name="force-create",
> +                    dest="force_create", action="store_true",
> +                    help="force creation of upstream source tarball")
> +    orig_group.add_config_file_option(option_name="no-create-orig",
> +                    dest="no_create_orig", action="store_true",
> +                    help="don't create upstream source tarball")
> +    orig_group.add_config_file_option(option_name="tarball-dir",
> +                    dest="tarball_dir", type="path",
> +                    help="location to look for external tarballs")
> +    orig_group.add_config_file_option(option_name="compression-level",
> +                    dest="comp_level",
> +                    help="Compression level, default is "
> +                         "'%(compression-level)s'")
> +    branch_group.add_config_file_option(option_name="upstream-branch",
> +                    dest="upstream_branch")
> +    branch_group.add_config_file_option(option_name="packaging-branch",
> +                    dest="packaging_branch")
> +    branch_group.add_boolean_config_file_option(option_name = "ignore-branch",
> +                    dest="ignore_branch")
> +    branch_group.add_boolean_config_file_option(option_name = "submodules",
> +                    dest="with_submodules")
> +    cmd_group.add_config_file_option(option_name="builder", dest="builder",
> +                    help="command to build the package, default is "
> +                         "'%(builder)s'")
> +    cmd_group.add_config_file_option(option_name="cleaner", dest="cleaner",
> +                    help="command to clean the working copy, default is "
> +                         "'%(cleaner)s'")
> +    cmd_group.add_config_file_option(option_name="prebuild", dest="prebuild",
> +                    help="command to run before a build, default is "
> +                         "'%(prebuild)s'")
> +    cmd_group.add_config_file_option(option_name="postexport",
> +                    dest="postexport",
> +                    help="command to run after exporting the source tree, "
> +                         "default is '%(postexport)s'")
> +    cmd_group.add_config_file_option(option_name="postbuild", dest="postbuild",
> +                    help="hook run after a successful build, default is "
> +                         "'%(postbuild)s'")
> +    cmd_group.add_config_file_option(option_name="posttag", dest="posttag",
> +                    help="hook run after a successful tag operation, default "
> +                         "is '%(posttag)s'")
> +    cmd_group.add_boolean_config_file_option(option_name="hooks", dest="hooks")
> +    export_group.add_config_file_option(option_name="export-dir",
> +                    dest="export_dir", type="path",
> +                    help="Build topdir, also export the sources under "
> +                         "EXPORT_DIR, default is '%(export-dir)s'")
> +    export_group.add_config_file_option(option_name="export-specdir",
> +                    dest="export_specdir", type="path")
> +    export_group.add_config_file_option(option_name="export-sourcedir",
> +                    dest="export_sourcedir", type="path")
> +    export_group.add_config_file_option("export", dest="export",
> +                    metavar="TREEISH",
> +                    help="export treeish object TREEISH, default is "
> +                         "'%(export)s'")
> +    export_group.add_config_file_option(option_name="packaging-dir",
> +                    dest="packaging_dir")
> +    export_group.add_config_file_option(option_name="spec-file",
> +                    dest="spec_file")
> +    return parser
> +
> +
> +def parse_args(argv, prefix):
> +    """Parse config and command line arguments"""
> +    args = [arg for arg in argv[1:] if arg.find('--%s' % prefix) == 0]
> +    builder_args = [arg for arg in argv[1:] if arg.find('--%s' % prefix) == -1]
> +
> +    # We handle these although they don't have a --git- prefix
> +    for arg in [ "--help", "-h", "--version" ]:
> +        if arg in builder_args:
> +            args.append(arg)
> +
> +    parser = build_parser(argv[0], prefix=prefix)
> +    if not parser:
> +        return None, None, None
> +    options, args = parser.parse_args(args)
> +
> +    gbp.log.setup(options.color, options.verbose, options.color_scheme)
> +    if not options.hooks:
> +        disable_hooks(options)
> +    if options.retag:
> +        if not options.tag and not options.tag_only:
> +            gbp.log.err("'--%sretag' needs either '--%stag' or '--%stag-only'" %
> +                        (prefix, prefix, prefix))
> +            return None, None, None
> +
> +    return options, args, builder_args
> +
> +
> +def main(argv):
> +    """Entry point for gbp-buildpackage-rpm"""
> +    retval = 0
> +    prefix = "git-"
> +    spec = None
> +
> +    options, gbp_args, builder_args = parse_args(argv, prefix)
> +
> +    if not options:
> +        return 1
> +
> +    try:
> +        repo = RpmGitRepository(os.path.curdir)
> +    except GitRepositoryError:
> +        gbp.log.err("%s is not a git repository" % (os.path.abspath('.')))
> +        return 1
> +
> +    branch = get_current_branch(repo)
> +
> +    try:
> +        init_tmpdir(options.tmp_dir, prefix='buildpackage-rpm_')
> +
> +        tree = get_tree(repo, options.export)
> +        spec = parse_spec(options, repo, treeish=tree)
> +
> +        Command(options.cleaner, shell=True)()
> +        if not options.ignore_new:
> +            ret, out = repo.is_clean()
> +            if not ret:
> +                gbp.log.err("You have uncommitted changes in your source tree:")
> +                gbp.log.err(out)
> +                raise GbpError("Use --git-ignore-new to ignore.")
> +
> +        if not options.ignore_new and not options.ignore_branch:
> +            if branch != options.packaging_branch:
> +                gbp.log.err("You are not on branch '%s' but on '%s'" %
> +                            (options.packaging_branch, branch))
> +                raise GbpError("Use --git-ignore-branch to ignore or "
> +                               "--git-packaging-branch to set the branch name.")
> +
> +        # Dump from git to a temporary directory:
> +        packaging_tree = '%s:%s' % (tree, options.packaging_dir)
> +        dump_dir = tempfile.mkdtemp(prefix='packaging_')
> +        gbp.log.debug("Dumping packaging files to '%s'" % dump_dir)
> +        if not dump_tree(repo, dump_dir, packaging_tree, False, False):
> +            raise GbpError
> +        # Re-parse spec from dump dir to get version etc.
> +        spec = rpm.SpecFile(os.path.join(dump_dir, spec.specfile))
> +
> +        if not options.tag_only:
> +            # Setup builder opts
> +            setup_builder(options, builder_args)
> +
> +            # Prepare final export dirs
> +            export_dir = makedir(options.export_dir)
> +            source_dir = makedir(os.path.join(export_dir,
> +                                 options.export_sourcedir))
> +            spec_dir = makedir(os.path.join(export_dir, options.export_specdir))
> +
> +            # Move packaging files to final export dir
> +            gbp.log.debug("Exporting packaging files from '%s' to '%s'" %
> +                          (dump_dir, export_dir))
> +            for fname in os.listdir(dump_dir):
> +                src = os.path.join(dump_dir, fname)
> +                if fname == spec.specfile:
> +                    dst = os.path.join(spec_dir, fname)
> +                else:
> +                    dst = os.path.join(source_dir, fname)
> +                try:
> +                    shutil.copy2(src, dst)
> +                except IOError as err:
> +                    raise GbpError("Error exporting packaging files: %s" % err)
> +            spec.specdir = os.path.abspath(spec_dir)
> +
> +            # Get/build the orig tarball
> +            if is_native(repo, options):
> +                if spec.orig_src and not options.no_create_orig:
> +                    # Just build source archive from the exported tree
> +                    gbp.log.info("Creating (native) source archive %s from '%s'"
> +                                 % (spec.orig_src['filename'], tree))
> +                    if spec.orig_src['compression']:
> +                        gbp.log.debug("Building source archive with "
> +                                      "compression '%s -%s'" %
> +                                      (spec.orig_src['compression'],
> +                                       options.comp_level))
> +                    if not git_archive(repo, spec, source_dir, tree,
> +                                       options.comp_level,
> +                                       options.with_submodules):
> +                        raise GbpError("Cannot create source tarball at '%s'" %
> +                                        source_dir)
> +            # Non-native packages: create orig tarball from upstream
> +            elif spec.orig_src:
> +                prepare_upstream_tarball(repo, spec, options, source_dir)
> +
> +            # Run postexport hook
> +            if options.postexport:
> +                RunAtCommand(options.postexport, shell=True,
> +                             extra_env={'GBP_GIT_DIR': repo.git_dir,
> +                                        'GBP_TMP_DIR': export_dir}
> +                             )(dir=export_dir)
> +            # Do actual build
> +            if not options.tag_only:
> +                if options.prebuild:
> +                    RunAtCommand(options.prebuild, shell=True,
> +                                 extra_env={'GBP_GIT_DIR': repo.git_dir,
> +                                            'GBP_BUILD_DIR': export_dir}
> +                                 )(dir=export_dir)
> +
> +                # Finally build the package:
> +                if options.builder.startswith("rpmbuild"):
> +                    builder_args.append(os.path.join(spec.specdir,
> +                                        spec.specfile))
> +                else:
> +                    builder_args.append(spec.specfile)
> +                RunAtCommand(options.builder, builder_args, shell=True,
> +                             extra_env={'GBP_BUILD_DIR': export_dir}
> +                             )(dir=export_dir)
> +                if options.postbuild:
> +                    changes = os.path.abspath("%s/%s.changes" % (source_dir,
> +                                                                 spec.name))
> +                    gbp.log.debug("Looking for changes file %s" % changes)
> +                    Command(options.postbuild, shell=True,
> +                            extra_env={'GBP_CHANGES_FILE': changes,
> +                                       'GBP_BUILD_DIR': export_dir})()
> +
> +        # Tag (note: tags the exported version)
> +        if options.tag or options.tag_only:
> +            gbp.log.info("Tagging %s" % rpm.compose_version_str(spec.version))
> +            tag = create_packaging_tag(repo, tree, spec, options)
> +            if options.posttag:
> +                sha = repo.rev_parse("%s^{}" % tag)
> +                Command(options.posttag, shell=True,
> +                        extra_env={'GBP_TAG': tag,
> +                                   'GBP_BRANCH': branch,
> +                                   'GBP_SHA1': sha})()
> +
> +    except CommandExecFailed:
> +        retval = 1
> +    except GitRepositoryError as err:
> +        gbp.log.err("Git command failed: %s" % err)
> +        retval = 1
> +    except GbpError, err:
> +        if len(err.__str__()):
> +            gbp.log.err(err)
> +        retval = 1
> +    finally:
> +        drop_index()
> +        del_tmpdir()
> +
> +    if not options.tag_only:
> +        if spec and options.notify:
> +            summary = "Gbp-rpm %s" % ["failed", "successful"][not retval]
> +            message = ("Build of %s %s %s" % (spec.name,
> +                            rpm.compose_version_str(spec.version),
> +                            ["failed", "succeeded"][not retval]))
> +            if not gbp.notifications.notify(summary, message, options.notify):
> +                gbp.log.err("Failed to send notification")
> +                retval = 1
> +
> +    return retval
> +
> +if __name__ == '__main__':
> +    sys.exit(main(sys.argv))
> diff --git a/gbp/scripts/pq_rpm.py b/gbp/scripts/pq_rpm.py
> index c4f9d4d..cbbcf23 100755
> --- a/gbp/scripts/pq_rpm.py
> +++ b/gbp/scripts/pq_rpm.py
> @@ -159,7 +159,6 @@ def parse_spec(options, repo, treeish=None):
>      """
>      try:
>          if options.spec_file:
> -            options.packaging_dir = os.path.dirname(options.spec_file)
>              if not treeish:
>                  spec = SpecFile(options.spec_file)
>              else:
> @@ -175,6 +174,7 @@ def parse_spec(options, repo, treeish=None):
>          raise GbpError("Can't parse spec: %s" % err)
>      relpath = spec.specpath if treeish else os.path.relpath(spec.specpath,
>                                                              repo.path)
> +    options.packaging_dir = os.path.dirname(relpath)
>      gbp.log.debug("Using '%s' from '%s'" % (relpath, treeish or 'working copy'))
>      return spec
>  
> diff --git a/tests/component/rpm/__init__.py b/tests/component/rpm/__init__.py
> index 7353c27..3983025 100644
> --- a/tests/component/rpm/__init__.py
> +++ b/tests/component/rpm/__init__.py
> @@ -46,10 +46,9 @@ class RepoManifest(object):
>      def projects_iter(self):
>          """Return an iterator over projects"""
>          for prj_e in self._doc.getElementsByTagName('project'):
> -            branches = {}
> +            branches = []
>              for br_e in prj_e.getElementsByTagName('branch'):
> -                rev = br_e.getAttribute('revision')
> -                branches[br_e.getAttribute('name')] = rev
> +                branches.append(dict(br_e.attributes.items()))
>              yield prj_e.getAttribute('name'), branches
>  
>  
> @@ -67,7 +66,7 @@ class RpmRepoTestBase(ComponentTestBase):
>      """Baseclass for tests run in a Git repository with packaging data"""
>  
>      @classmethod
> -    def setup_class(cls):
> +    def setup_class(cls, mangle_branch_names=True):
>          """Initializations only made once per test run"""
>          super(RpmRepoTestBase, cls).setup_class()
>          cls.manifest = RepoManifest(os.path.join(RPM_TEST_DATA_DIR,
> @@ -93,9 +92,17 @@ class RpmRepoTestBase(ComponentTestBase):
>              # Fetch all remote refs of the orig repo, too
>              repo.fetch('origin', tags=True,
>                         refspec='refs/remotes/*:refs/upstream/*')
> -            for branch, rev in brs.iteritems():
> -                repo.create_branch(branch, rev)
> -            repo.force_head('master', hard=True)
> +            master_branch = 'master'
> +            for branch in brs:
> +                if mangle_branch_names:
> +                    branch_name = branch['name']
> +                else:
> +                    branch_name = branch['orig_name']
> +                    if branch['name'] == 'master':
> +                        master_branch = branch_name
> +                repo.create_branch(branch_name, branch['revision'])
> +            repo.set_branch(master_branch)
> +            repo.force_head('HEAD', hard=True)
>              cls.orig_repos[prj] = repo
>  
>      @classmethod
> diff --git a/tests/component/rpm/data b/tests/component/rpm/data
> index bae44dd..ff090c1 160000
> --- a/tests/component/rpm/data
> +++ b/tests/component/rpm/data
> @@ -1 +1 @@
> -Subproject commit bae44ddc98ae0ed15ae078cb7c2fc597dee48da5
> +Subproject commit ff090c1cf946e3df67795ce3c1437ec6246dbf37
> diff --git a/tests/component/rpm/test_buildpackage_rpm.py b/tests/component/rpm/test_buildpackage_rpm.py
> new file mode 100644
> index 0000000..92ed115
> --- /dev/null
> +++ b/tests/component/rpm/test_buildpackage_rpm.py
> @@ -0,0 +1,611 @@
> +# vim: set fileencoding=utf-8 :
> +#
> +# (C) 2013-2015 Intel Corporation <markus.lehtonen at linux.intel.com>
> +#    This program is free software; you can redistribute it and/or modify
> +#    it under the terms of the GNU General Public License as published by
> +#    the Free Software Foundation; either version 2 of the License, or
> +#    (at your option) any later version.
> +#
> +#    This program is distributed in the hope that it will be useful,
> +#    but WITHOUT ANY WARRANTY; without even the implied warranty of
> +#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +#    GNU General Public License for more details.
> +#
> +#    You should have received a copy of the GNU General Public License
> +#    along with this program; if not, write to the Free Software
> +#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
> +"""Unit tests for the gbp-buildpackage-rpm tool"""
> +
> +import glob
> +import mock
> +import os
> +import re
> +import shutil
> +import stat
> +import subprocess
> +
> +from nose.tools import assert_raises, eq_, ok_ # pylint: disable=E0611
> +
> +from gbp.git import GitRepository
> +from gbp.scripts.buildpackage_rpm import main as gbp_rpm
> +from tests.component.rpm import RpmRepoTestBase, RPM_TEST_DATA_DIR
> +from tests.testutils import ls_tar, ls_zip
> +
> +# Disable "Method could be a function warning"
> +#   pylint: disable=R0201
> +# Disable "Too many public methods"
> +#   pylint: disable=R0904
> +
> +
> +DATA_DIR = os.path.join(RPM_TEST_DATA_DIR, 'rpm')
> +ORIG_DATA_DIR = os.path.join(RPM_TEST_DATA_DIR, 'orig')
> +
> +MOCK_NOTIFICATIONS = []
> +
> +
> +def mock_gbp(args):
> +    """Wrapper for gbp-buildpackage-rpm"""
> +    return gbp_rpm(['arg0', '--git-notify=off'] + args +
> +                   ['-ba', '--clean', '--target=noarch', '--nodeps'])
> +
> +def mock_notify(summary, message, notify_opt):
> +    """Mock notification system"""
> +    # Auto will succeed
> +    if notify_opt.is_auto():
> +        MOCK_NOTIFICATIONS.append((summary, message))
> +        return True
> +    # Otherwise fail
> +    return False
> +
> +
> +class TestGbpRpm(RpmRepoTestBase):
> +    """Basic tests for git-rpm-ch"""
> +
> +    @staticmethod
> +    def ls_rpm(rpm):
> +        """List the contents of an rpm package"""
> +        args = ['rpm', '-q', '--qf',
> +                '[%{FILEDIGESTS %{FILEMODES} %{FILENAMES}\n]', '-p']
> +        popen = subprocess.Popen(args + [rpm], stdout=subprocess.PIPE,
> +                                 stderr=subprocess.PIPE)
> +        stdout, stderr = popen.communicate()
> +        if popen.returncode:
> +            raise Exception("Failed to get file metadata for %s: %s" %
> +                            (rpm, stderr))
> +        return sorted([(nam, mod, dig) for dig, mod, nam in
> +                        [lin.split(None, 2) for lin in stdout.splitlines()]])
> +
> +    @staticmethod
> +    def check_rpms(directory):
> +        """Check build results"""
> +        # Only check files, at least for now
> +        files = glob.glob(directory + '/*rpm')
> +        assert files, "No rpms (%s)found in %s" % (files, directory)
> +        for path in files:
> +            ref_file = os.path.join(DATA_DIR, os.path.basename(path))
> +            eq_(TestGbpRpm.ls_rpm(path), TestGbpRpm.ls_rpm(ref_file))
> +
> +    @staticmethod
> +    def check_and_rm_file(filepath, content):
> +        """Check file content and remove it"""
> +        with open(filepath) as fobj:
> +            eq_(fobj.read(), content)
> +        os.unlink(filepath)
> +
> +    @classmethod
> +    def setup_class(cls, **kwargs):
> +        """Setup unit tests"""
> +        # Don't mangle branch names so that we're able to build the packages
> +        super(TestGbpRpm, cls).setup_class(mangle_branch_names=False, **kwargs)
> +
> +    def test_invalid_args(self):
> +        """Check graceful exit when called with invalid args"""
> +        GitRepository.create('.')
> +        with assert_raises(SystemExit):
> +            mock_gbp(['--git-invalid-arg'])
> +
> +    def test_outside_repo(self):
> +        """Run outside a git repository"""
> +        eq_(mock_gbp([]), 1)
> +        self._check_log(0, 'gbp:error: %s is not a git repository' %
> +                            os.path.abspath('.'))
> +
> +    def test_invalid_config_file(self):
> +        """Test invalid config file"""
> +        # Create and commit dummy invalid config file
> +        repo = GitRepository.create('.')
> +        with open('.gbp.conf', 'w') as conffd:
> +            conffd.write('foobar\n')
> +        repo.add_files('.gbp.conf')
> +        repo.commit_all('Add conf')
> +        eq_(mock_gbp([]), 1)
> +        self._check_log(0, 'gbp:error: File contains no section headers.')
> +
> +    def test_native_build(self):
> +        """Basic test of native pkg"""
> +        self.init_test_repo('gbp-test-native')
> +        eq_(mock_gbp([]), 0)
> +        self.check_rpms('../rpmbuild/RPMS/*')
> +        shutil.rmtree('../rpmbuild')
> +
> +        eq_(mock_gbp(['--git-native=off']), 1)
> +        self._check_log(0, 'gbp:error: Invalid upstream treeish upstream/')
> +
> +    def test_native_build2(self):
> +        """Basic test of another native pkg"""
> +        self.init_test_repo('gbp-test-native2')
> +        eq_(mock_gbp([]), 0)
> +        self.check_rpms('../rpmbuild/RPMS/*')
> +
> +    def test_non_native_build(self):
> +        """Basic test of non-native pkg"""
> +        repo = self.init_test_repo('gbp-test')
> +        eq_(mock_gbp([]), 0)
> +        self.check_rpms('../rpmbuild/RPMS/*')
> +
> +        # Test nativity guessing from remote branches by creating a dummy
> +        # remote branch
> +        repo.update_ref('refs/remotes/fooremote/foobranch',
> +                        'srcdata/gbp-test/upstream')
> +        eq_(mock_gbp(['--git-upstream-branch=foobranch']), 0)
> +
> +    def test_option_native(self):
> +        """Test the --git-native option"""
> +        self.init_test_repo('gbp-test2')
> +        eq_(mock_gbp([]), 0)
> +        self.check_rpms('../rpmbuild/RPMS/*')
> +        shutil.rmtree('../rpmbuild')
> +
> +        # Building this pkg should succeed, but no patches generated,
> +        # only one "manually maintained" patch
> +        eq_(mock_gbp(['--git-native=on']), 0)
> +        self.check_rpms('../rpmbuild/RPMS/*')
> +        eq_(len(glob.glob('../rpmbuild/SOURCES/*patch')), 1)
> +
> +    def test_options_ignore(self):
> +        """Test the --git-ignore-[new|untracked] options"""
> +        self.init_test_repo('gbp-test-native')
> +
> +        # Create an untracked file
> +        with open('untracked-file', 'w') as fobj:
> +            fobj.write('this file is not tracked\n')
> +
> +        # Modify tracked file
> +        with open('README', 'a') as fobj:
> +            fobj.write('new stuff\n')
> +
> +        eq_(mock_gbp([]), 1)
> +        eq_(mock_gbp(['--git-ignore-new']), 0)
> +
> +    @mock.patch('gbp.notifications.notify', mock_notify)
> +    def test_option_notify(self):
> +        """Test the --git-notify option"""
> +        self.init_test_repo('gbp-test-native')
> +
> +        eq_(mock_gbp(['--git-notify=auto']), 0)
> +        summary, message = MOCK_NOTIFICATIONS.pop()
> +        ok_(re.match(r'Gbp-rpm successful', summary), summary)
> +        ok_(re.match(r'Build of \S+ \S+ succeeded', message), message)
> +
> +        # Mock-notification will fail with "on" setting
> +        eq_(mock_gbp(['--git-notify=on']), 1)
> +        self._check_log(-1, "gbp:error: Failed to send notification")
> +
> +        # No notification when "off"
> +        eq_(mock_gbp(['--git-notify=off']), 0)
> +        eq_(len(MOCK_NOTIFICATIONS), 0)
> +
> +    def test_option_tmp_dir(self):
> +        """Test the --git-tmp-dir option"""
> +        self.init_test_repo('gbp-test-native')
> +
> +        eq_(mock_gbp(['--git-tmp-dir=../gbptmp', '--git-builder=true']), 0)
> +        ok_(os.path.isdir('../gbptmp'))
> +
> +        # Check tmpdir access/creation error
> +        os.chmod('../gbptmp', 0)
> +        try:
> +            eq_(mock_gbp(['--git-tmp-dir=../gbptmp/foo', '--git-builder=true']), 1)
> +        finally:
> +            os.chmod('../gbptmp', stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC)
> +
> +    def test_tagging(self):
> +        """Test tagging options"""
> +        repo = self.init_test_repo('gbp-test-native')
> +
> +        # Build and tag
> +        eq_(mock_gbp(['--git-tag', '--git-packaging-tag=rel-tag']), 0)
> +        self.check_rpms('../rpmbuild/RPMS/*')
> +        ok_(repo.has_tag('rel-tag'))
> +        sha = repo.rev_parse('HEAD')
> +        eq_(sha, repo.rev_parse('rel-tag^0'))
> +        self.check_rpms('../rpmbuild/RPMS/*')
> +
> +        # Should fail if the tag already exists
> +        eq_(mock_gbp(['--git-tag', '--git-packaging-tag=rel-tag']), 1)
> +
> +        # Re-tag
> +        eq_(mock_gbp(['--git-retag', '--git-packaging-tag=rel-tag']), 1)
> +        self._check_log(-1, "gbp:error: '--git-retag' needs either '--git-tag'")
> +
> +        eq_(mock_gbp(['--git-tag', '--git-packaging-tag=rel-tag',
> +                     '--git-retag', '--git-export=HEAD^']), 0)
> +        ok_(repo.has_tag('rel-tag'))
> +        sha2 = repo.rev_parse('HEAD^')
> +        ok_(sha2 != sha)
> +        eq_(sha2, repo.rev_parse('rel-tag^0'))
> +
> +        # Tag-only
> +        shutil.rmtree('../rpmbuild')
> +        eq_(mock_gbp(['--git-tag-only', '--git-packaging-tag=rel-tag2']), 0)
> +        ok_(not os.path.exists('../rpmbuild'))
> +        ok_(repo.has_tag('rel-tag2'))
> +
> +        # Valid tag format string keys
> +        tag_keys = ['upstreamversion', 'release', 'version', 'vendor']
> +        # Should fail if the fag format has invalid keys (foo here)
> +        tag_fmt = '_'.join(['%(' + key + ')s' for key in tag_keys + ['foo']])
> +        eq_(mock_gbp(['--git-tag', '--git-packaging-tag=%(foo)s']), 1)
> +        # Remove 'foo' and should succeed
> +        tag_fmt = '_'.join(['%(' + key + ')s' for key in tag_keys])
> +        eq_(mock_gbp(['--git-tag-only', '--git-packaging-tag=%s' % tag_fmt]), 0)
> +
> +    def test_option_upstream_tree(self):
> +        """Test the --git-upstream-tree option"""
> +        repo = self.init_test_repo('gbp-test')
> +
> +        # Dummy update to upstream branch
> +        pkg_branch = repo.get_branch()
> +        upstr_branch = 'srcdata/gbp-test/upstream'
> +        orig_files = ['gbp-test/' + path for \
> +                path in self.ls_tree(repo, upstr_branch)] + ['gbp-test']
> +        repo.set_branch(upstr_branch)
> +        with open('new-file', 'w') as fobj:
> +            fobj.write('New file\n')
> +        with open('new-file2', 'w') as fobj:
> +            fobj.write('New file 2\n')
> +        repo.add_files(['new-file', 'new-file2'])
> +        repo.commit_files('new-file', 'New content')
> +        repo.commit_files('new-file2', 'New content 2')
> +        repo.set_branch(pkg_branch)
> +
> +        # TAG (default) does not contain the new files
> +        eq_(mock_gbp([]), 0)
> +        tar_files = ls_tar('../rpmbuild/SOURCES/gbp-test-1.1.tar.bz2')
> +        self.check_files(orig_files, tar_files)
> +        shutil.rmtree('../rpmbuild')
> +
> +        # Branch contains them both
> +        eq_(mock_gbp(['--git-upstream-tree=BRANCH']), 0)
> +        tar_files = ls_tar('../rpmbuild/SOURCES/gbp-test-1.1.tar.bz2')
> +        self.check_files(orig_files +
> +                         ['gbp-test/new-file', 'gbp-test/new-file2'], tar_files)
> +        shutil.rmtree('../rpmbuild')
> +
> +        # The first "extra-commit" in upstream contains only one new file
> +        eq_(mock_gbp(['--git-upstream-tree=%s^' % upstr_branch]), 0)
> +        tar_files = ls_tar('../rpmbuild/SOURCES/gbp-test-1.1.tar.bz2')
> +        self.check_files(orig_files + ['gbp-test/new-file'], tar_files)
> +        shutil.rmtree('../rpmbuild')
> +
> +        # Test invalid upstream treeish
> +        eq_(mock_gbp(['--git-upstream-tree=TAG',
> +                      '--git-upstream-tag=invalid-tag']), 1)
> +        self._check_log(-1, ".*Invalid upstream treeish invalid-tag")
> +        eq_(mock_gbp(['--git-upstream-tree=BRANCH', '--git-native=no',
> +                      '--git-upstream-branch=invalid-branch']), 1)
> +        self._check_log(-1, ".*invalid-branch is not a valid branch")
> +        eq_(mock_gbp(['--git-upstream-tree=invalid-tree']), 1)
> +        self._check_log(-1, ".*Invalid treeish object")
> +
> +    def test_pristine_tar(self):
> +        """Test pristine-tar"""
> +        repo = self.init_test_repo('gbp-test')
> +
> +        # Pristine-tar checkout fails when no pristine-tar branch
> +        eq_(mock_gbp(['--git-pristine-tar',
> +                      '--git-export=srcdata/gbp-test/release/1.1-2']), 1)
> +        self._check_log(-1, ".*Pristine-tar couldn't checkout")
> +
> +        # Create pristine-tar branch and try again
> +        repo.create_branch('pristine-tar', 'srcdata/gbp-test/pristine_tar')
> +        eq_(mock_gbp(['--git-pristine-tar',
> +                      '--git-export=srcdata/gbp-test/release/1.1-2']), 0)
> +        self.check_rpms('../rpmbuild/RPMS/*')
> +
> +    def test_pristine_tar_commit(self):
> +        """Test committing upstream tarball to pristine-tar"""
> +        repo = self.init_test_repo('gbp-test')
> +
> +        eq_(repo.has_branch('pristine-tar'), False)
> +        eq_(mock_gbp(['--git-pristine-tar-commit',
> +                      '--git-export=srcdata/gbp-test/release/1.0-1']), 0)
> +        eq_(len(repo.get_commits(until='pristine-tar')), 1)
> +        shutil.rmtree('../rpmbuild')
> +
> +        # Using --git-pristine-tar and --git-pristine-tar-commit should be ok
> +        eq_(mock_gbp(['--git-pristine-tar', '--git-pristine-tar-commit']), 0)
> +        eq_(len(repo.get_commits(until='pristine-tar')), 2)
> +        shutil.rmtree('../rpmbuild')
> +
> +        # Second time no pristine-tar should not be commited
> +        eq_(mock_gbp(['--git-pristine-tar-commit']), 0)
> +        eq_(len(repo.get_commits(until='pristine-tar')), 2)
> +
> +    def test_tarball_dir(self):
> +        """Test a separate tarball cache"""
> +        self.init_test_repo('gbp-test')
> +
> +        # Create and populate tarball cache
> +        tarball_dir = '../tarballs'
> +        os.mkdir(tarball_dir)
> +        shutil.copy2(os.path.join(ORIG_DATA_DIR, 'gbp-test-1.0.tar.bz2'),
> +                     tarball_dir)
> +
> +        # Test build when tarball is found from cache
> +        eq_(mock_gbp(['--git-export=srcdata/gbp-test/release/1.0-1',
> +                      '--git-tarball-dir=%s' % tarball_dir]), 0)
> +        ok_(os.path.islink(os.path.join('..', 'rpmbuild', 'SOURCES',
> +                                        'gbp-test-1.0.tar.bz2')))
> +
> +        # Test build when tarball is not found from cache
> +        eq_(mock_gbp(['--git-export=srcdata/gbp-test/release/1.1-2',
> +                      '--git-tarball-dir=%s' % tarball_dir]), 0)
> +        ok_(os.path.isfile(os.path.join('..', 'rpmbuild', 'SOURCES',
> +                                        'gbp-test-1.1.tar.bz2')))
> +
> +    def test_packaging_branch_options(self):
> +        """Test the --packaging-branch and --ignore-branch cmdline options"""
> +        repo = self.init_test_repo('gbp-test-native')
> +
> +        eq_(mock_gbp(['--git-packaging-branch=foo']), 1)
> +        self._check_log(-2, "gbp:error: You are not on branch 'foo'")
> +
> +        eq_(mock_gbp(['--git-packaging-branch=foo', '--git-ignore-branch']), 0)
> +
> +        # Test building when not on any branch
> +        repo.set_branch(repo.rev_parse('HEAD'))
> +        eq_(mock_gbp(['--git-builder=true']), 1)
> +        eq_(mock_gbp(['--git-ignore-branch', '--git-builder=true']), 0)
> +
> +    def test_option_submodules(self):
> +        """Test the --git-submodules option"""
> +        repo = self.init_test_repo('gbp-test')
> +
> +        # Create submodule to upstream branch
> +        sub_repo = self.orig_repos['gbp-test-native']
> +        pkg_branch = repo.get_branch()
> +        upstr_branch = 'srcdata/gbp-test/upstream'
> +        repo.set_branch(upstr_branch)
> +        repo.add_submodule(sub_repo.path)
> +        repo.commit_all('Add submodule')
> +        repo.set_branch(pkg_branch)
> +
> +        sub_files = self.ls_tree(sub_repo, 'HEAD')
> +        upstr_files = ['gbp-test/' + path for
> +                            path in self.ls_tree(repo, upstr_branch)]
> +
> +        # Test the "no" option
> +        eq_(mock_gbp(['--git-no-submodules', '--git-upstream-tree=%s' %
> +                      upstr_branch, '--git-ignore-new']), 0)
> +        tar_files = ls_tar('../rpmbuild/SOURCES/gbp-test-1.1.tar.bz2', False)
> +        self.check_files(upstr_files, tar_files)
> +        shutil.rmtree('../rpmbuild')
> +
> +        # Test the "yes" option
> +        eq_(mock_gbp(['--git-submodules', '--git-upstream-tree=%s' %
> +                      upstr_branch, '--git-ignore-new']), 0)
> +        tar_files = ls_tar('../rpmbuild/SOURCES/gbp-test-1.1.tar.bz2', False)
> +        ref_files = upstr_files + ['gbp-test/gbp-test-native.repo/' + path for
> +                                        path in sub_files]
> +        self.check_files(ref_files, tar_files)
> +        shutil.rmtree('../rpmbuild')
> +
> +        # Test submodule failure
> +        shutil.rmtree('gbp-test-native.repo')
> +        repo.create('gbp-test-native.repo')
> +        eq_(mock_gbp(['--git-submodules', '--git-upstream-tree=%s' %
> +                      upstr_branch, '--git-ignore-new']), 1)
> +
> +    def test_option_submodules_native(self):
> +        """Test the --git-submodules option for native packages"""
> +        repo = self.init_test_repo('gbp-test-native')
> +
> +        # Create submodule
> +        sub_repo = self.orig_repos['gbp-test-native2']
> +        repo.add_submodule(sub_repo.path)
> +        repo.commit_all('Add submodule')
> +
> +        sub_files = self.ls_tree(sub_repo, 'HEAD')
> +        master_files = ['gbp-test-native-1.0/' + path for
> +                            path in self.ls_tree(repo, 'HEAD')]
> +
> +        # Test
> +        eq_(mock_gbp(['--git-submodules']), 0)
> +        zip_files = ls_zip('../rpmbuild/SOURCES/gbp-test-native-1.0.zip', False)
> +        ref_files = master_files + \
> +                    ['gbp-test-native-1.0/gbp-test-native2.repo/' + path for
> +                                        path in sub_files]
> +        self.check_files(ref_files, zip_files)
> +
> +        # Test submodule failure
> +        shutil.rmtree('gbp-test-native2.repo')
> +        repo.create('gbp-test-native2.repo')
> +        eq_(mock_gbp(['--git-submodules', '--git-ignore-new']), 1)
> +
> +    def test_option_builder(self):
> +        """Test --git-builder option and it's args"""
> +        self.init_test_repo('gbp-test-native')
> +        base_args = ['arg0', '--git-notify=off']
> +
> +        # Try rpmbuild with default args
> +        eq_(gbp_rpm(base_args + ['--git-builder=rpmbuild', '--nodeps']), 0)
> +
> +        # Build without builder args
> +        builder_script = 'echo -n $* > builder_args.txt'
> +        eq_(gbp_rpm(base_args + ['--git-builder=%s' % builder_script]), 0)
> +        with open('../rpmbuild/builder_args.txt') as fobj:
> +            args = fobj.read()
> +        eq_(args, 'gbp-test-native.spec')
> +
> +        # Build with builder args
> +        eq_(gbp_rpm(base_args + ['--git-builder=%s' % builder_script,
> +                                 '--arg1', '--arg2']), 0)
> +        with open('../rpmbuild/builder_args.txt') as fobj:
> +            args = fobj.read()
> +        eq_(args, '--arg1 --arg2 gbp-test-native.spec')
> +
> +    def test_option_cleaner(self):
> +        """Test --git-cleaner option"""
> +        self.init_test_repo('gbp-test-native')
> +
> +        # Make repo dirty
> +        with open('untracked-file', 'w') as fobj:
> +            fobj.write('this file is not tracked\n')
> +
> +        # Build on dirty repo should fail
> +        eq_(mock_gbp([]), 1)
> +
> +        # Build should succeed with cleaner
> +        eq_(mock_gbp(['--git-cleaner=rm untracked-file']), 0)
> +
> +    def test_hook_options(self):
> +        """Test different hook options"""
> +        self.init_test_repo('gbp-test-native')
> +
> +        cleaner = 'echo -n cleaner >> ../hooks'
> +        postexport = 'echo -n postexport >> $GBP_TMP_DIR/../hooks'
> +        prebuild = 'echo -n prebuild >> $GBP_BUILD_DIR/../hooks'
> +        postbuild = 'echo -n postbuild >> $GBP_BUILD_DIR/../hooks'
> +        posttag = 'echo -n posttag >> ../hooks'
> +        args = ['--git-cleaner=%s' % cleaner,
> +                '--git-postexport=%s' % postexport,
> +                '--git-prebuild=%s' % prebuild,
> +                '--git-postbuild=%s' % postbuild,
> +                '--git-posttag=%s' % posttag]
> +
> +        # Only cleaner and posttag is run when tagging
> +        eq_(mock_gbp(args + ['--git-tag-only', '--git-packaging-tag=tag1']), 0)
> +        self.check_and_rm_file('../hooks', 'cleanerposttag')
> +
> +        # Export and build scripts are run when not tagging
> +        eq_(mock_gbp(args), 0)
> +        self.check_and_rm_file('../hooks', 'cleanerpostexportprebuildpostbuild')
> +        shutil.rmtree('../rpmbuild')
> +
> +        # All hooks are run when building
> +        eq_(mock_gbp(args + ['--git-tag', '--git-packaging-tag=tag2']), 0)
> +        self.check_and_rm_file('../hooks',
> +                               'cleanerpostexportprebuildpostbuildposttag')
> +        shutil.rmtree('../rpmbuild')
> +
> +        # Run with hooks disabled
> +        eq_(mock_gbp(args + ['--git-no-hooks']), 0)
> +        ok_(not os.path.exists('../hooks'))
> +
> +    def test_builddir_options(self):
> +        """Test the options related to different build directories"""
> +        self.init_test_repo('gbp-test-native')
> +
> +        eq_(mock_gbp(['--git-export-dir=../foo',
> +                      '--git-export-sourcedir=source',
> +                      '--git-export-specdir=spec']), 0)
> +
> +        # Check all directories
> +        eq_(set(os.listdir('../foo')),
> +            set(['BUILD', 'BUILDROOT', 'RPMS', 'source', 'spec', 'SRPMS']))
> +
> +    def test_export_failure(self):
> +        """Test export dir permission problems"""
> +        self.init_test_repo('gbp-test-native')
> +        s_rwx = stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC
> +
> +        # Pre-create all files
> +        eq_(mock_gbp(['--git-builder=true']), 0)
> +
> +        # Error in exporting packaging files
> +        os.chmod('../rpmbuild/SOURCES', 0)
> +        try:
> +            eq_(mock_gbp(['--git-builder=true']), 1)
> +        finally:
> +            os.chmod('../rpmbuild/SOURCES', s_rwx)
> +        self._check_log(-1, ".*Error exporting packaging files")
> +
> +        # Error in creating archive
> +        os.chmod('../rpmbuild/SOURCES/gbp-test-native-1.0.zip', 0)
> +        try:
> +            eq_(mock_gbp(['--git-builder=true']), 1)
> +        finally:
> +            os.chmod('../rpmbuild/SOURCES/gbp-test-native-1.0.zip', s_rwx)
> +        self._check_log(-1, ".*Error creating ../rpmbuild/SOURCES/.*.zip")
> +
> +    def test_option_export(self):
> +        """Test the --git-export-option"""
> +        repo = self.init_test_repo('gbp-test')
> +
> +        # Test exporting of some other commit than HEAD
> +        eq_(mock_gbp(['--git-export=srcdata/gbp-test/release/1.0-1']), 0)
> +        eq_(os.listdir('../rpmbuild/RPMS/noarch'),
> +                       ['gbp-test-1.0-1.noarch.rpm'])
> +        self.check_rpms('../rpmbuild/RPMS/*')
> +
> +        # Modify one tracked file, create one untracked and one ignored file
> +        with open('foo.txt', 'a') as fobj:
> +            fobj.write('staged')
> +            fobj.flush()
> +            repo.add_files('foo.txt')
> +            fobj.write('unstaged')
> +        with open('untracked', 'w') as fobj:
> +            fobj.write('untracked')
> +        with open('ignored.tmp', 'w') as fobj:
> +            fobj.write('ignored')
> +
> +        base_args = ['--git-ignore-new', '--git-builder=true']
> +        # Test exporting of git index
> +        foo_txt_index = repo.show('HEAD:foo.txt') + 'staged'
> +        eq_(mock_gbp(base_args + ['--git-export=INDEX']), 0)
> +        self.check_and_rm_file('../rpmbuild/SOURCES/foo.txt', foo_txt_index)
> +        ok_(not os.path.exists('../rpmbuild/SOURCES/untracked'))
> +        ok_(not os.path.exists('../rpmbuild/SOURCES/ignored.tmp'))
> +        shutil.rmtree('../rpmbuild')
> +
> +        # Test exporting of working copy (include all files)
> +        eq_(mock_gbp(base_args + ['--git-export=WC']), 0)
> +        foo_txt_wc = repo.show('HEAD:foo.txt') + 'staged' + 'unstaged'
> +        self.check_and_rm_file('../rpmbuild/SOURCES/foo.txt', foo_txt_wc)
> +        self.check_and_rm_file('../rpmbuild/SOURCES/untracked', 'untracked')
> +        self.check_and_rm_file('../rpmbuild/SOURCES/ignored.tmp', 'ignored')
> +        shutil.rmtree('../rpmbuild')
> +
> +        # Test exporting an invalid treeish
> +        eq_(mock_gbp(base_args + ['--git-export=invalid-treeish']), 1)
> +        self._check_log(-1, "gbp:error: Invalid treeish object invalid-treeish")
> +
> +    def test_option_spec_file(self):
> +        """Test the --git-spec-file cmdline option"""
> +        repo = self.init_test_repo('gbp-test2')
> +
> +        eq_(mock_gbp(['--git-spec-file=foo.spec']), 1)
> +        self._check_log(-1, "gbp:error: Can't parse spec: Git error")
> +
> +        eq_(mock_gbp(['--git-spec-file=']), 1)
> +        self._check_log(-1, "gbp:error: Can't parse spec: Multiple spec files")
> +
> +        eq_(mock_gbp(['--git-spec-file=packaging/gbp-test2.spec']), 0)
> +
> +        # No spec file found error
> +        repo.set_branch('srcdata/gbp-test2/upstream')
> +        eq_(mock_gbp([]), 1)
> +        self._check_log(-1, ".*Can't parse spec: No spec file found")
> +
> +    def test_option_packaging_dir(self):
> +        """Test the --git-packaging-dir cmdline option"""
> +        self.init_test_repo('gbp-test-native')
> +
> +        eq_(mock_gbp(['--git-packaging-dir=foo']), 1)
> +        self._check_log(-1, "gbp:error: Can't parse spec: No spec file found")
> +
> +        # Packaging dir should be taken from spec file if it is defined
> +        eq_(mock_gbp(['--git-packaging-dir=foo',
> +                      '--git-spec-file=packaging/gbp-test-native.spec']), 0)
> +
> -- 
> 2.1.4

This looks good from a code point but we could really use some docs how
to use this tool - like a workflow (import-rpm, pq_rpm,
buildpackage-rpm). That would make it simpler to evaluate the usefulness
of the new options. Do you have something online for this already?

Thanks a lot for all of this! I will stop with this series here until we
have the basic support merged. O.k.?

Cheers,
 -- Guido


More information about the git-buildpackage mailing list