#!/usr/bin/python -u
# vim: set fileencoding=utf-8 :
#
# (C) 2006,2007 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
"""Import a Debian source package into a git repository"""

import ConfigParser
import sys
import re
import os
import tempfile
import glob
import pipes
from email.Utils import parseaddr
import gbp.command_wrappers as gbpc
from gbp.deb import (debian_version_chars, parse_changelog, unpack_orig,
                     parse_dsc, DscFile, tar_toplevel)
from gbp.git import (build_tag, create_repo, GitRepository,
                     GitRepositoryError, rfc822_date_to_git)
from gbp.config import GbpOptionParser, GbpOptionGroup
from gbp.errors import GbpError


class SkipImport(Exception):
    pass


def download_source(pkg, dirs):
    if re.match(r'[a-z]{1,5}://', pkg):
        mode='dget'
    else:
        mode='apt-get'

    dirs['download'] = os.path.abspath(tempfile.mkdtemp())
    print "Downloading '%s' using '%s'..." % (pkg, mode)
    if mode == 'apt-get':
        gbpc.RunAtCommand('apt-get',
                          ['-qq', '--download-only', 'source', pkg],
                          shell=False)(dir=dirs['download'])
    else:
        gbpc.RunAtCommand('dget',
                          ['-q', '--download-only', pkg],
                          shell=False)(dir=dirs['download'])
    dsc = glob.glob(os.path.join(dirs['download'], '*.dsc'))[0]
    return dsc


def apply_patch(diff, unpack_dir):
    "Apply patch to a source tree"
    d = os.path.abspath(unpack_dir)
    pipe = pipes.Template()
    pipe.prepend('gunzip -c %s' % diff,  '.-')
    pipe.append('patch -p1 --quiet', '-.')
    try:
        ret = pipe.copy('', '')
        if ret:
            print >>sys.stderr, "Error import %s: %d" % (diff, ret)
            return False
    except OSError, err:
        print >>sys.stderr, "Error importing %s: %s" % (diff, err[0])
        return False
    return True


def apply_deb_tgz(deb_tgz, unpack_dir):
    """Apply .debian.tar.gz (V3 source format)"""
    unpackArchive = gbpc.UnpackTarArchive(deb_tgz, ".")()
    return True


def apply_debian_patch(repo, unpack_dir, src, options, parents):
    """apply the debian patch and tag appropriately"""
    version = "%s-%s" % (src.upstream_version, src.debian_version)
    gitTag = gbpc.GitTag(options.sign_tags, options.keyid)
    try:
        os.chdir(unpack_dir)

        if src.diff and not apply_patch(src.diff, unpack_dir):
            raise GbpError

        if src.deb_tgz and not apply_deb_tgz(src.deb_tgz, unpack_dir):
            raise GbpError

        if os.path.exists('debian/rules'):
            os.chmod('debian/rules', 0755)
        os.chdir(repo.path)

        dch = parse_changelog(os.path.join(unpack_dir, 'debian/changelog'))
        date= rfc822_date_to_git(dch['Date'])
        author, email = parseaddr(dch['Maintainer'])
        if not (author and email):
            print >>sys.stderr, "Warning: failed to parse maintainer"

        commit = repo.commit_dir(unpack_dir,
                                 "Imported Debian patch %s" % version,
                                 branch = options.debian_branch,
                                 other_parents = parents,
                                 author = author,
                                 email = email,
                                 date = date)
        gitTag(build_tag(options.debian_tag, version),
               msg="Debian release %s" % version, commit=commit)
    except gbpc.CommandExecFailed:
        print >>sys.stderr, "Failed to import Debian package"
        raise GbpError
    finally:
        os.chdir(repo.path)


def print_dsc(dsc):
    if dsc.native:
        print "Debian Native Package"
        print "Version:", dsc.upstream_version
        print "Debian tarball:", dsc.tgz
    else:
        print "Upstream version:", dsc.upstream_version
        print "Debian version:", dsc.debian_version
        print "Upstream tarball:", dsc.tgz
        print "Debian diff:", dsc.diff
    if dsc.epoch:
        print "Epoch: %s" % dsc.epoch

def main(argv):
    dirs = {'top': os.path.abspath(os.curdir)}
    needs_repo = False
    ret = 0
    parents = None

    try:
        parser = GbpOptionParser(command=os.path.basename(argv[0]), prefix='',
                                 usage='%prog [options] /path/to/package.dsc')
    except ConfigParser.ParsingError, err:
        print >>sys.stderr, err
        return 1

    import_group = GbpOptionGroup(parser, "import options",
                      "pristine-tar and filtering")
    tag_group = GbpOptionGroup(parser, "tag options",
                      "options related to git tag creation")
    branch_group = GbpOptionGroup(parser, "version and branch naming options",
                      "version number and branch layout options")

    for group in [import_group, branch_group, tag_group ]:
        parser.add_option_group(group)

    parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False,
                      help="verbose command execution")
    parser.add_option("--download", action="store_true", dest="download", default=False,
                      help="download source package")
    branch_group.add_config_file_option(option_name="debian-branch",
                      dest="debian_branch")
    branch_group.add_config_file_option(option_name="upstream-branch",
                      dest="upstream_branch")

    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="debian-tag",
                      dest="debian_tag")
    tag_group.add_config_file_option(option_name="upstream-tag",
                      dest="upstream_tag")

    import_group.add_config_file_option(option_name="filter",
                      dest="filters", action="append")
    import_group.add_boolean_config_file_option(option_name="pristine-tar",
                      dest="pristine_tar")
    import_group.add_option("--ignore-same-version", action="store_true",
                      dest="ignore_same_version", default=False,
                      help="don't import already imported version")
    (options, args) = parser.parse_args(argv[1:])

    if options.verbose:
        gbpc.Command.verbose = True

    gitTag = gbpc.GitTag(options.sign_tags, options.keyid)

    try:
        if len(args) != 1:
            parser.print_help()
            raise GbpError
        else:
            pkg = args[0]
            if options.download:
                dsc = download_source(pkg, dirs=dirs)
            else:
                dsc = pkg

            src = parse_dsc(dsc)
            if src.pkgformat not in [ '1.0', '3.0' ]:
                raise GbpError, "Importing %s source format not yet supported." % src.pkgformat
            if options.verbose:
                print_dsc(src)

            try:
                repo = GitRepository('.')
                is_empty = repo.is_empty()

                (clean, out) = repo.is_clean()
                if not clean and not is_empty:
                    print >>sys.stderr, "Repository has uncommitted changes, commit these first: "
                    raise GbpError, out

            except GitRepositoryError:
                # no repo found, create one
                needs_repo = True
                is_empty = True

            if needs_repo:
                print "No git repository found, creating one."
                repo = create_repo(src.pkg)
                os.chdir(repo.path)

            dirs['tmp'] = os.path.abspath(tempfile.mkdtemp(dir='..'))
            unpack_dir = unpack_orig(src.tgz, dirs['tmp'], options.filters)
            unpack_dir = tar_toplevel(unpack_dir)

            format = [(options.upstream_tag, "Upstream"), (options.debian_tag, "Debian")][src.native]
            tag = build_tag(format[0], src.upstream_version)
            msg = "%s version %s" % (format[1], src.upstream_version)

            if options.ignore_same_version:
                if repo.find_version(options.debian_tag, src.version):
                    print "Version %s already imported." % src.version
                    raise SkipImport

            if not repo.find_version(format[0], src.upstream_version):
                print "tag %s not found, importing %s tarball" % (tag, format[1])
                if is_empty:
                    branch = None
                else:
                    branch = [options.upstream_branch,
                              options.debian_branch][src.native]
                commit = repo.commit_dir(unpack_dir,
                                         "Imported %s" % msg,
                                         branch)
                gitTag(version=tag, msg=msg, commit=commit)

                if not src.native:
                    if is_empty:
                        gbpc.GitBranch()(options.upstream_branch, commit)
                    if options.pristine_tar:
                        gbpc.PristineTar().commit(src.tgz, 'refs/heads/%s' % options.upstream_branch)
                    parents = [ options.upstream_branch ]
            if not src.native:
                if is_empty and not repo.has_branch(options.debian_branch):
                    gbpc.GitBranch()(options.debian_branch, commit)
                if src.diff or src.deb_tgz:
                    apply_debian_patch(repo, unpack_dir, src, options, parents)
                else:
                    print >>sys.stderr, "Warning: Didn't find a diff to apply."
            if repo.get_branch() == options.debian_branch:
                # Update HEAD if we modified the checkout out branch
                repo.force_head(options.debian_branch, hard=True)
    except KeyboardInterrupt:
        ret = 1
        print >>sys.stderr, "Interrupted. Aborting."
    except gbpc.CommandExecFailed:
        ret = 1
    except GitRepositoryError, msg:
        print >>sys.stderr, "Git command failed: %s" % msg
        ret = 1
    except GbpError, err:
        if len(err.__str__()):
            print >>sys.stderr, err
        ret = 1
    except SkipImport:
        pass
    finally:
        os.chdir(dirs['top'])

    for d in [ 'tmp', 'download' ]:
        if dirs.has_key(d):
            gbpc.RemoveTree(dirs[d])()

    if not ret:
        print "Everything imported under '%s'" % src.pkg
    return ret

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

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