#!/usr/bin/python -u
# vim: set fileencoding=utf-8 :
#
# (C) 2006,2007,2008 Guido Guenther <agx@sigxcpu.org>
#    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
#
"""run commands to build a debian package out of a git repository"""

import sys
import os, os.path
import errno
import pipes
import time
import gbp.deb_utils as du
from gbp.git_utils import (GitRepositoryError, GitRepository, build_tag)
from gbp.command_wrappers import (GitTag, Command, RunAtCommand, CommandExecFailed, 
                                  PristineTar, RemoveTree)
from gbp.config import (GbpOptionParser, GbpOptionGroup)
from gbp.errors import GbpError

# when we want to reference the index in a treeish context we call it:
index_name = "INDEX"

def git_archive_pipe(prefix, pipe, output, treeish):
    """run the git_archive pipe"""
    pipe.prepend('git archive --format=tar --prefix=%s/ %s' % (prefix, treeish), '.-')
    try:
        ret = pipe.copy('', output)
        if ret:
            print >>sys.stderr, "Error creating %s: %d" % (output, ret)
            return False
    except OSError, err:
        print >>sys.stderr, "Error creating %s: %s" % (output, err[0])
        return False
    except:
        print >>sys.stderr, "Error creating %s" % (output,)
        return False
    return True


def git_archive(cp, output_dir, treeish):
    "create an orig.tar.gz in output_dir using git_archive"
    output = os.path.join(output_dir, du.orig_file(cp))
    prefix = "%s-%s" % (cp['Source'], cp['Upstream-Version'])
    gzip_opts = "-9 -n"

    pipe = pipes.Template()
    pipe.append('gzip -c %s' % gzip_opts,  '--')
    return git_archive_pipe(prefix, pipe, output, treeish)


def dump_tree(export_dir, treeish):
    "dump a tree to output_dir"
    output_dir = os.path.dirname(export_dir)
    prefix = os.path.basename(export_dir)
    pipe = pipes.Template()
    pipe.append('tar -C %s -xf -' % output_dir,  '-.')
    return git_archive_pipe(prefix, pipe, '', treeish)


def move_old_export(target):
    """move a build tree away if it exists"""
    try:
        os.mkdir(target)
    except OSError, (e, msg):
        if e == errno.EEXIST:
            os.rename(target, "%s.obsolete.%s" % (target, time.time()))


def prepare_output_dir(dir):
    output_dir = dir
    if not dir:
        output_dir = '..'
    output_dir = os.path.abspath(output_dir)

    try:
        os.mkdir(output_dir)
    except OSError, (e, msg):
        if e != errno.EEXIST:
            raise GbpError, "Cannot create output dir %s" % output_dir
    return output_dir

def pristine_tar_build_orig(repo, cp, output_dir, options):
    """
    build orig using pristine-tar
    @return: True: orig.tar.gz build, False: noop
    """
    if options.pristine_tar:
        pt = PristineTar()
        if not repo.has_branch(pt.branch):
            print >>sys.stderr, 'Pristine-tar branch "%s" not found' % pt.branch
        pt.checkout(os.path.join(output_dir, du.orig_file(cp)))
        return True
    else:
        return False

def git_archive_build_orig(repo, cp, output_dir, options):
    """build orig using git-archive"""
    # --upstream-branch was given on the command line, so use this:
    if options.upstream_branch != GbpOptionParser.defaults['upstream-branch']:
        upstream_tree = options.upstream_branch
    else:
        upstream_tree = build_tag(options.upstream_tag, cp['Upstream-Version'])
        # fall back to the upstream-branch tip if the tag doesn't exist
        if not repo.has_treeish(upstream_tree):
            upstream_tree = GbpOptionParser.defaults['upstream-branch']
    print "%s does not exist, creating from '%s'" % (du.orig_file(cp), upstream_tree)
    if not repo.has_treeish(upstream_tree):
        raise GbpError # git-ls-tree printed an error message already
    if not git_archive(cp, output_dir, upstream_tree):
        raise GbpError, "Cannot create upstream tarball at '%s'" % output_dir

def main(argv):
    changelog = 'debian/changelog'
    default_tree = 'HEAD'
    retval = 0
    prefix = "git-"

    args = [ arg for arg in argv[1:] if arg.find('--%s' % prefix) == 0 ]
    dpkg_args = [ arg for arg in argv[1:] if arg.find('--%s' % prefix) == -1 ]

    for arg in [ "--help", "-h", "--version" ]:
        if arg in dpkg_args:
            args.append(arg)

    parser = GbpOptionParser(command=os.path.basename(argv[0]), prefix=prefix)
    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.tar.gz options", "options related to .orig.tar.gz creation")
    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_config_file_option(option_name = "ignore-new", dest="ignore_new",
                      help="build with uncommited changes in the source tree", action="store_true")
    parser.add_option("--git-verbose", action="store_true", dest="verbose", default=False,
                      help="verbose command execution")
    tag_group.add_option("--git-tag", action="store_true", dest="tag", default=False,
                      help="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 post-tag hooks")
    tag_group.add_config_file_option(option_name="sign-tags", dest="sign_tags",
                      help="sign git tags, default is '%(sign-tags)s'", action="store_true")
    tag_group.add_config_file_option(option_name="keyid", dest="keyid",
                      help="GPG keyid to sign tags with, default is '%(keyid)s'")
    tag_group.add_config_file_option(option_name="debian-tag", dest="debian_tag",
                      help="format string for debian tags, default is '%(debian-tag)s'")
    tag_group.add_config_file_option(option_name="upstream-tag", dest="upstream_tag",
                      help="format string for upstream tags, default is '%(upstream-tag)s'")
    orig_group.add_config_file_option(option_name="pristine-tar", dest="pristine_tar",
                      help="use pristine-tar to create .orig.tar.gz, default is '%(pristine-tar)s'",
                      action="store_true")
    orig_group.add_config_file_option(option_name="no-create-orig", dest="no_create_orig",
                      help="don't create orig.tar.gz", action="store_true")
    orig_group.add_config_file_option(option_name="tarball-dir", dest="tarball_dir",
                      help="location to look for external tarballs")
    branch_group.add_config_file_option(option_name="upstream-branch", dest="upstream_branch",
                      help="upstream branch, default is '%(upstream-branch)s'")
    branch_group.add_config_file_option(option_name="debian-branch", dest='debian_branch',
                      help="branch the debian patch is being developed on, default is '%(debian-branch)s'")
    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 build the package, default is '%(cleaner)s'")
    cmd_group.add_config_file_option(option_name="posttag", dest="posttag",
                      help="hook to execute after a successfull tag operation, default is '%(posttag)s'")
    export_group.add_config_file_option(option_name="export-dir", dest="export_dir",
                      help="before building export source into EXPORT_DIR, default is '%(export-dir)s'")
    export_group.add_option("--git-export", dest="treeish", default=default_tree,
                      help="export treeish object TREEISH, default is '%s'" % default_tree)
    export_group.add_option("--git-dont-purge", action="store_false", dest="purge", default=True,
                      help="retain exported build directory after build")
    (options, args) = parser.parse_args(args)

    if options.verbose:
        Command.verbose = True

    try:
        repo = GitRepository(os.path.curdir)
    except GitRepositoryError:
        print >>sys.stderr, "%s is not a git repository" % (os.path.abspath('.'))
        return 1
    else:
        repo_dir = os.path.abspath(os.path.curdir)

    try:
        if not options.ignore_new:
            Command(options.cleaner, shell=True)()
            (ret, out) = repo.is_clean()
            if not ret:
                print >>sys.stderr, "You have uncommitted changes in your source tree:"
                print >>sys.stderr, out
                raise GbpError, "Use --git-ignore-new to ignore."

            branch = repo.get_branch()
            if branch != options.debian_branch:
                print >>sys.stderr, "You are not on branch '%s' but on '%s'" % (options.debian_branch, branch)
                raise GbpError, "Use --git-ignore-new to ignore or --git-debian-branch to set the branch name."

        try:
            cp = du.parse_changelog(changelog)
        except du.NoChangelogError:
            raise GbpError, "'%s' does not exist, not a debian package" % changelog
        except du.ParseChangeLogError, err:
            raise GbpError, "Error parsing Changelog: %s" % err

        if not options.tag_only:

            output_dir = prepare_output_dir(options.export_dir)
            if options.tarball_dir:
                tarball_dir = options.tarball_dir
            else:
                tarball_dir = output_dir

            # Export to another build dir if requested:
            if options.export_dir:
                # write a tree of the index if necessary:
                if options.treeish == index_name:
                    tree = repo.write_tree()
                else:
                    tree = options.treeish
                if not repo.has_treeish(tree):
                    raise GbpError # git-ls-tree printed an error message already
                tmp_dir = os.path.join(output_dir, "%s-tmp" % cp['Source'])
                print "Exporting '%s' to '%s'" % (options.treeish, tmp_dir)
                dump_tree(tmp_dir, tree)
                cp = du.parse_changelog(os.path.join(tmp_dir, 'debian', 'changelog'))
                if du.is_native(cp):
                    version = cp['Debian-Version']
                else:
                    version = cp['Upstream-Version']
                export_dir = os.path.join(output_dir, "%s-%s" % (cp['Source'], version))
                print "Moving '%s' to '%s'" % (tmp_dir, export_dir)
                move_old_export(export_dir)
                os.rename(tmp_dir, export_dir)

            # Get/build the orig.tar.gz if necessary:
            if not du.is_native(cp):
                # look in tarball_dir first, if found force a symlink to it
                if options.tarball_dir:
                    print "Looking for orig tarball '%s' at '%s'" % (du.orig_file(cp), tarball_dir)
                    if not du.symlink_orig(cp, tarball_dir, output_dir, force=True):
                        print "Orig tarball '%s' not found at '%s'" % (du.orig_file(cp), tarball_dir)
                    else:
                        print "Orig tarball '%s' found at '%s'" % (du.orig_file(cp), tarball_dir)
                # build an orig unless the user forbidds it
                if not options.no_create_orig and not du.has_orig(cp, output_dir):
                    if not pristine_tar_build_orig(repo, cp, output_dir, options):
                        git_archive_build_orig(repo, cp, output_dir, options)

            if options.export_dir:
                build_dir = export_dir
            else:
                build_dir = repo_dir

            # Finally build the package:
            RunAtCommand(options.builder, dpkg_args, shell=True)(dir=build_dir)

        if options.tag or options.tag_only:
            try:
                version = cp['Version']
            except KeyError:
                raise GbpError, "Can't parse version from changelog"
            else:
                print "Tagging %s" % version
                GitTag(options.sign_tags, options.keyid)(build_tag(options.debian_tag, version),
                       msg="Debian release %s" % version)
                if(options.posttag):
                    Command(options.posttag, shell=True)()

    except CommandExecFailed:
        retval = 1
    except GbpError, err:
        if len(err.__str__()):
            print >>sys.stderr, err
        retval = 1

    if not options.tag_only:
        if options.export_dir and options.purge and not retval:
            RemoveTree(export_dir)()

    return retval

if __name__ == '__main__':
    sys.exit(main(sys.argv))

# vim:et:ts=4:sw=4:et:sts=4:ai:set list listchars=tab\:»·,trail\:·:
