import os
import copy
import shutil
import glob
import re
import logging

import django.db
import django.contrib.auth.models

import debian.debian_support

import mini_buildd.config
import mini_buildd.misc
import mini_buildd.dist
import mini_buildd.files
import mini_buildd.gnupg
import mini_buildd.reprepro

import mini_buildd.models.base
import mini_buildd.models.distribution

LOG = logging.getLogger(__name__)


class EmailAddress(mini_buildd.models.base.Model):
    address = django.db.models.EmailField(primary_key=True, max_length=255)
    name = django.db.models.CharField(blank=True, max_length=255)

    class Meta(mini_buildd.models.base.Model.Meta):
        verbose_name_plural = "Email addresses"

    def __str__(self):
        return f"{self.name} <{self.address}>"


class Repository(mini_buildd.models.base.StatusModel):
    identity = django.db.models.CharField(primary_key=True, max_length=50, default="test",
                                          help_text="""\
The id of the reprepro repository, placed in
'repositories/<ID>'. It can also be used in 'version enforcement
string' (true for the default layout) -- in this context, it
plays the same role as the well-known 'bpo' version string from
Debian backports.
""")

    layout = django.db.models.ForeignKey(mini_buildd.models.distribution.Layout,
                                         on_delete=django.db.models.CASCADE)
    distributions = django.db.models.ManyToManyField(mini_buildd.models.distribution.Distribution)

    allow_unauthenticated_uploads = django.db.models.BooleanField(default=False,
                                                                  help_text="Allow unauthenticated user uploads.")

    extra_uploader_keyrings = django.db.models.TextField(blank=True,
                                                         help_text="""\
Extra keyrings, line by line, to be allowed as uploaders (in addition to configured django users).

Example:
---
# Allow Debian maintainers (must install the 'debian-keyring' package)"
/usr/share/keyrings/debian-keyring.gpg"
# Allow from some local keyring file"
/etc/my-schlingels.gpg"
---
""")

    notify = django.db.models.ManyToManyField(EmailAddress,
                                              blank=True,
                                              help_text="Addresses that get all notification emails unconditionally.")
    notify_changed_by = django.db.models.BooleanField(default=False,
                                                      help_text="Notify the address in the 'Changed-By' field of the uploaded changes file.")
    notify_maintainer = django.db.models.BooleanField(default=False,
                                                      help_text="Notify the address in the 'Maintainer' field of the uploaded changes file.")

    reprepro_morguedir = django.db.models.BooleanField(default=False,
                                                       help_text="Move files deleted from repo pool to 'morguedir' (see reprepro).")

    external_home_url = django.db.models.URLField(blank=True)

    LETHAL_DEPENDENCIES = False

    class Meta(mini_buildd.models.base.StatusModel.Meta):
        verbose_name_plural = "Repositories"

    class Admin(mini_buildd.models.base.StatusModel.Admin):
        readonly_fields = []
        filter_horizontal = ("distributions", "notify",)

        def get_readonly_fields(self, _request, obj=None):
            """Forbid change identity on existing repository."""
            fields = copy.copy(self.readonly_fields)
            if obj:
                fields.append("identity")
            return fields

    def __str__(self):
        return f"{self.identity}: {' '.join([d.base_source.codename + ('' if d.mbd_is_active() else '*') for d in self.mbd_sorted_distributions()])}"

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.mbd_path = mini_buildd.config.ROUTES["repositories"].path.join(self.identity)
        self.mbd_reprepro = mini_buildd.reprepro.Reprepro(basedir=self.mbd_path)

    def clean(self, *args, **kwargs):
        super().clean(*args, **kwargs)
        self.mbd_validate_regex(r"^[a-z0-9]+$", self.identity, "identity")

    def mbd_get_meta_distributions(self, distribution, suite_option):
        try:
            result = []
            for p in self.layout.mbd_get_extra_option("Meta-Distributions", "").split():
                meta, d = p.split("=")
                dist = f"{d.split('-')[0]}-{self.identity}-{d.split('-')[1]}"
                if dist == self.mbd_get_distribution_string(distribution, suite_option):
                    result.append(meta)
            return result
        except BaseException as e:
            raise mini_buildd.HTTPBadRequest(f"Please fix syntax error in extra option 'Meta-Distributions' in layout '{self.layout}'") from e

    def mbd_get_distribution_string(self, distribution, suite, rollback=None):
        dist_string = f"{distribution.base_source.codename}-{self.identity}-{suite.suite.name}"

        if rollback is not None:
            if rollback not in list(range(suite.rollback)):
                raise mini_buildd.HTTPBadRequest(f"{dist_string}: Rollback number out of range: {rollback} ({list(range(suite.rollback))})")
            dist_string += f"-rollback{rollback}"

        return dist_string

    def mbd_get_apt_pin(self, distribution, suite):
        return f"release n={self.mbd_get_distribution_string(distribution, suite)}, o={mini_buildd.get_daemon().model.mbd_get_archive_origin()}"

    def mbd_get_apt_preferences(self, distribution, suite, prio=1):
        return f"Package: *\nPin: {self.mbd_get_apt_pin(distribution, suite)}\nPin-Priority: {prio}\n"

    def mbd_distribution_strings(self, **suiteoption_filter):
        """Return a list with all full distributions strings, optionally matching a suite options filter (unstable, experimental,...)."""
        result = []
        for d in self.mbd_sorted_distributions():
            result += [self.mbd_get_distribution_string(d, s) for s in self.layout.suiteoption_set.filter(**suiteoption_filter)]
        return result

    @classmethod
    def mbd_get_apt_keys(cls, distribution):
        result = mini_buildd.get_daemon().gnupg.pub_key
        for e in distribution.extra_sources.all():
            for k in e.source.apt_keys.all():
                result += k.key
        return result

    def mbd_get_internal_suite_dependencies(self, suite_option):
        result = []

        # Add ourselves
        result.append(suite_option)

        if suite_option.experimental:
            # Add all non-experimental suites
            for s in self.layout.suiteoption_set.all().filter(experimental=False):
                result.append(s)
        else:
            # Add all suites that we migrate to
            s = suite_option.migrates_to
            while s:
                result.append(s)
                s = s.migrates_to

        return result

    def mbd_get_distribution_strings(self):
        """List of all distribution strings (except rollbacks) in this repo."""
        return [self.mbd_get_distribution_string(d, s) for d in self.distributions.all() for s in self.layout.suiteoption_set.all()]

    @classmethod
    def __mbd_subst_placeholders(cls, value, repository, distribution):
        return mini_buildd.misc.subst_placeholders(
            value,
            {"IDENTITY": repository.identity,
             "CODEVERSION": distribution.base_source.codeversion})

    def mbd_get_mandatory_version_regex(self, distribution, suite_option):
        return self.__mbd_subst_placeholders(
            self.layout.experimental_mandatory_version_regex if suite_option.experimental else self.layout.mandatory_version_regex,
            self, distribution)

    def mbd_get_default_version(self, distribution, suite_option):
        return self.__mbd_subst_placeholders(
            self.layout.experimental_default_version if suite_option.experimental else self.layout.default_version,
            self, distribution)

    def mbd_get_apt_line(self, distribution, suite_option, rollback=None, snapshot=None):
        dist_str = self.mbd_get_distribution_string(distribution, suite_option, rollback=rollback)
        if snapshot:
            snapshots = self.mbd_reprepro.get_snapshots(dist_str)
            if snapshot not in snapshots:
                raise mini_buildd.HTTPBadRequest(f"No such snapshot for {dist_str}: '{snapshot}' (available: {','.join(snapshots)})")
            dist_str += os.path.join("/", "snapshots", snapshot)

        return mini_buildd.files.AptLine(mini_buildd.config.URIS["repositories"]["static"].url_join(),
                                         self.identity,
                                         dist_str,
                                         distribution.components.all(),
                                         comment=f"{mini_buildd.get_daemon().model.mbd_get_archive_origin()} '{dist_str}': {self.mbd_get_apt_pin(distribution, suite_option)}")

    def mbd_get_apt_build_sources_list(self, distribution, suite_option):
        sources_list = mini_buildd.files.SourcesList()

        sources_list.append(distribution.base_source.mbd_get_apt_line(limit_components=distribution.components.all()))
        for e in distribution.extra_sources.all():
            sources_list.append(e.source.mbd_get_apt_line(limit_components=distribution.components.all()))
        for s in self.mbd_get_internal_suite_dependencies(suite_option):
            sources_list.append(self.mbd_get_apt_line(distribution, s))

        return sources_list

    def mbd_get_apt_build_preferences(self, distribution, suite_option, internal_apt_priority_override=None):
        result = ""

        # Get preferences for all extra (prioritized) sources
        for e in distribution.extra_sources.all():
            result += e.mbd_get_apt_preferences() + "\n"

        # Get preferences for all internal sources
        internal_prio = internal_apt_priority_override if internal_apt_priority_override else distribution.mbd_get_extra_option("Internal-APT-Priority", 1)
        for s in self.mbd_get_internal_suite_dependencies(suite_option):
            result += self.mbd_get_apt_preferences(distribution, s, prio=internal_prio) + "\n"

        return result

    def _mbd_reprepro_conf_options(self):
        return mini_buildd.files.File("options",
                                      snippet=(f"gnupghome {mini_buildd.config.ROUTES['home'].path.join('.gnupg')}\n"
                                               f"{'morguedir +b/morguedir' if self.reprepro_morguedir else ''}\n"))

    def _mbd_reprepro_conf_distributions(self):
        dist_template = ("Codename: {distribution}\n"
                         "Suite: {distribution}\n"
                         "Label: {distribution}\n"
                         "AlsoAcceptFor: {meta_distributions}\n"
                         "Origin: {origin}\n"
                         "Components: {components}\n"
                         "UDebComponents: {components}\n"
                         "Architectures: source {architectures}\n"
                         "Description: {desc}\n"
                         "SignWith: default\n"
                         "NotAutomatic: {na}\n"
                         "ButAutomaticUpgrades: {bau}\n"
                         "DebIndices: Packages Release . .gz .xz\n"
                         "DscIndices: Sources Release . .gz .xz\n"
                         "Contents: .gz\n\n")

        result = ""
        for d in self.distributions.all():
            for s in self.layout.suiteoption_set.all():
                result += dist_template.format(
                    distribution=self.mbd_get_distribution_string(d, s),
                    meta_distributions=" ".join(self.mbd_get_meta_distributions(d, s)),
                    origin=mini_buildd.get_daemon().model.mbd_get_archive_origin(),
                    components=" ".join(d.mbd_get_components()),
                    architectures=" ".join([x.name for x in d.architectures.all()]),
                    desc=self.mbd_get_distribution_string(d, s),
                    na="yes" if s.not_automatic else "no",
                    bau="yes" if s.but_automatic_upgrades else "no")

                for r in range(s.rollback):
                    result += dist_template.format(
                        distribution=self.mbd_get_distribution_string(d, s, r),
                        meta_distributions="",
                        origin=mini_buildd.get_daemon().model.mbd_get_archive_origin(),
                        components=" ".join(d.mbd_get_components()),
                        architectures=" ".join([x.name for x in d.architectures.all()]),
                        desc=self.mbd_get_distribution_string(d, s, r),
                        na="yes",
                        bau="no")

        return mini_buildd.files.File("distributions", snippet=result)

    def mbd_reprepro_update_config(self):
        if mini_buildd.files.Dir(os.path.join(self.mbd_path, "conf")).add(self._mbd_reprepro_conf_options()) \
                                                                     .add(self._mbd_reprepro_conf_distributions()).update():
            self.mbd_reprepro.reindex(self.mbd_get_distribution_strings())

    def mbd_find_dsc_path(self, distribution, source, version, components=None):
        """Get DSC pool path of an installed source (``<repo>/pool/...``)."""
        subdir = source[:4] if source.startswith("lib") else source[0]

        for component in [c.name for c in sorted(distribution.components.all())] if components is None else components:
            dsc = f"{self.identity}/pool/{component}/{subdir}/{source}/{mini_buildd.files.DebianName(source, version).dsc()}"
            route = mini_buildd.config.ROUTES["repositories"]
            if os.path.exists(route.path.join(dsc)):
                LOG.debug(f"Found DSC in pool: {dsc}")
                return component, dsc

        # Not found in pool
        return None, None

    def _mbd_package_shift_rollbacks(self, distribution, suite_option, package_name):
        reprepro_output = ""
        for r in range(suite_option.rollback - 1, -1, -1):
            src = self.mbd_get_distribution_string(distribution, suite_option, None if r == 0 else r - 1)
            dst = self.mbd_get_distribution_string(distribution, suite_option, r)
            LOG.info(f"Rollback: Moving {package_name}: {src} to {dst}")
            try:
                reprepro_output += self.mbd_reprepro.migrate(package_name, src, dst)
            except BaseException as e:
                mini_buildd.log_exception(LOG, "Rollback failed (ignoring)", e)
        return reprepro_output

    def _mbd_package_migrate(self, package, distribution, suite, rollback=None, version=None, log_event=True):
        reprepro_output = ""

        src_dist = self.mbd_get_distribution_string(distribution, suite)
        dst_dist = None
        pkg_show = list(self.mbd_reprepro.ils(package))
        src_pkg = None

        if rollback is not None:
            dst_dist = src_dist
            LOG.info(f"Rollback restore of '{package}' from rollback {rollback} to '{dst_dist}'")
            if self.mbd_reprepro.find_in(pkg_show, distribution=dst_dist):
                raise mini_buildd.HTTPBadRequest(f"Package '{package}' exists in '{dst_dist}': Remove first to restore rollback")

            rob_dist = self.mbd_get_distribution_string(distribution, suite, rollback=rollback)
            src_pkg = self.mbd_reprepro.find_in(pkg_show, distribution=rob_dist, version=version)
            if src_pkg is None:
                raise mini_buildd.HTTPBadRequest(f"Package '{package} ({'*' if version is None else version})' has no such version in rollback '{rollback}'")

            # Actually migrate package in reprepro
            reprepro_output += self.mbd_reprepro.migrate(package, rob_dist, dst_dist, version)
        else:
            # Get src and dst dist strings, and check we are configured to migrate
            if not suite.migrates_to:
                raise mini_buildd.HTTPBadRequest(f"You can't migrate from '{src_dist}'")
            dst_dist = self.mbd_get_distribution_string(distribution, suite.migrates_to)

            # Check if package is in src_dst
            src_pkg = self.mbd_reprepro.find_in(pkg_show, distribution=src_dist, version=version)
            if src_pkg is None:
                raise mini_buildd.HTTPBadRequest(f"Package '{package} ({'*' if version is None else version})' not in '{src_dist}'")

            # Check that version is not already migrated
            dst_pkg = self.mbd_reprepro.find_in(pkg_show, distribution=dst_dist)
            if dst_pkg is not None and src_pkg["version"] == dst_pkg["version"]:
                raise mini_buildd.HTTPBadRequest(f"Version '{src_pkg['version']}' already migrated to '{dst_dist}'")

            # Shift rollbacks in the destination distributions
            if dst_pkg is not None:
                reprepro_output += self._mbd_package_shift_rollbacks(distribution, suite.migrates_to, package)

            # Actually migrate package in reprepro
            reprepro_output += self.mbd_reprepro.migrate(package, src_dist, dst_dist, version)

        if log_event:
            mini_buildd.get_daemon().events.log(mini_buildd.events.Type.MIGRATED, mini_buildd.changes.Changes({**src_pkg, **{"distribution": dst_dist}}, create_events=True))

        return reprepro_output

    def mbd_package_migrate(self, package, distribution, suite, full=False, rollback=None, version=None):
        reprepro_output = ""
        if full:
            while suite.migrates_to is not None:
                reprepro_output += self._mbd_package_migrate(package, distribution, suite, rollback=rollback, version=version)
                suite = suite.migrates_to
        else:
            reprepro_output = self._mbd_package_migrate(package, distribution, suite, rollback=rollback, version=version)

        return reprepro_output

    def mbd_package_remove(self, package, distribution, suite, rollback=None, version=None, without_rollback=False):
        reprepro_output = ""

        dist_str = self.mbd_get_distribution_string(distribution, suite, rollback)
        src_pkg = self.mbd_reprepro.find_in(self.mbd_reprepro.ils(package), distribution=dist_str, version=version)
        if not src_pkg:
            raise mini_buildd.HTTPBadRequest(f"Package '{package}' not in '{dist_str}'")

        if rollback is None:
            if not without_rollback:
                reprepro_output += self._mbd_package_shift_rollbacks(distribution, suite, package)
            # Remove package
            reprepro_output += self.mbd_reprepro.remove(package, dist_str, version)
        else:
            # Rollback removal
            reprepro_output += self.mbd_reprepro.remove(package, dist_str, version)

            # Fix up empty rollback dist
            for r in range(rollback, suite.rollback - 1):
                src = self.mbd_get_distribution_string(distribution, suite, r + 1)
                dst = self.mbd_get_distribution_string(distribution, suite, r)
                try:
                    reprepro_output += self.mbd_reprepro.migrate(package, src, dst)
                    reprepro_output += self.mbd_reprepro.remove(package, src)
                except BaseException as e:
                    mini_buildd.log_exception(LOG, f"Rollback: Moving '{package}' from '{src}' to '{dst}' FAILED (ignoring)", e, logging.WARN)

        # Notify
        mini_buildd.get_daemon().events.log(mini_buildd.events.Type.REMOVED, mini_buildd.changes.Changes(src_pkg, create_events=True))

        return reprepro_output

    def mbd_package_precheck(self, distribution, suite_option, package, version):
        # 1st, check that the given version matches the distribution's version restrictions
        mandatory_regex = self.mbd_get_mandatory_version_regex(distribution, suite_option)
        if not re.compile(mandatory_regex).search(version):
            raise mini_buildd.HTTPBadRequest(f"Version restrictions failed for suite '{suite_option.suite.name}': '{mandatory_regex}' not in '{version}'")

        pkg_show = list(self.mbd_reprepro.ils(package))
        dist_str = self.mbd_get_distribution_string(distribution, suite_option)

        # 2nd: Check whether the very same version is already in any distribution
        pkg_version_in_repo = self.mbd_reprepro.find_in(pkg_show, version=version)
        if pkg_version_in_repo:
            raise mini_buildd.HTTPBadRequest(f"Package '{package}' with same version '{version}' already installed in '{pkg_version_in_repo['distribution']}'")

        # 3rd: Check that distribution's current version is smaller than the to-be installed version
        pkg_in_dist = self.mbd_reprepro.find_in(pkg_show, distribution=dist_str)
        if pkg_in_dist and debian.debian_support.Version(version) < debian.debian_support.Version(pkg_in_dist["version"]):
            raise mini_buildd.HTTPBadRequest(f"Package '{package}' has greater version '{pkg_in_dist['version']}' installed in '{dist_str}'")

    def _mbd_buildresult_install(self, buildresult, dist_str):
        # Don't try install if skipped
        if buildresult.cget("Sbuild-Status") == "skipped":
            LOG.info(f"Skipped: {buildresult}")
        else:
            with mini_buildd.misc.TemporaryDirectory(prefix="buildresult-untar-") as tmpdir:
                buildresult.untar(tmpdir)
                self.mbd_reprepro.install(" ".join(glob.glob(os.path.join(tmpdir, "*.changes"))), dist_str)
                LOG.info(f"Installed: {buildresult}")

    def mbd_package_install(self, distribution, suite_option, changes, buildresults):
        """Install a dict arch:buildresult of successful build results."""
        # Get the full distribution str
        dist_str = self.mbd_get_distribution_string(distribution, suite_option)

        # Check that all mandatory archs are present
        missing_mandatory_archs = [arch for arch in distribution.mbd_get_mandatory_architectures() if arch not in buildresults]
        if missing_mandatory_archs:
            raise mini_buildd.HTTPBadRequest(f"{len(missing_mandatory_archs)} mandatory architecture(s) missing: {' '.join(missing_mandatory_archs)}")

        # Get the (source) package name
        package = changes["Source"]
        LOG.debug(f"Package install: Package={package}")

        # Shift current package up in the rollback distributions (unless this is the initial install)
        is_installed = self.mbd_reprepro.find_in(self.mbd_reprepro.ils(package), distribution=dist_str)
        if is_installed:
            self._mbd_package_shift_rollbacks(distribution, suite_option, package)

        # First, install the dsc
        self.mbd_reprepro.install_dsc(changes.dsc_file_path(), dist_str)

        # Second, install all build results; if any buildresult fails to install, rollback changes to repository
        try:
            for buildresult in list(buildresults.values()):
                self._mbd_buildresult_install(buildresult, dist_str)
        except Exception as e:
            self.mbd_reprepro.remove(package, dist_str)
            if is_installed:
                self._mbd_package_migrate(package, distribution, suite_option, rollback=0, log_event=False)
            mini_buildd.log_exception(LOG, "Binary install failed", e)
            raise mini_buildd.HTTPInternal(f"Binary install failed for {package}: {mini_buildd.e2http(e)}")

    def mbd_sorted_distributions(self):
        return sorted(self.distributions.all(), reverse=True)

    def mbd_icodenames(self):
        for d in self.mbd_sorted_distributions():
            yield d.base_source.codename

    def mbd_prepare(self):
        """Idempotent repository preparation. This may be used as-is as mbd_sync."""
        # Architecture sanity checks
        for d in self.mbd_sorted_distributions():
            if not d.architectureoption_set.all().filter(optional=False):
                raise mini_buildd.HTTPBadRequest(f"{d}: There must be at least one mandatory architecture!")
            if len(d.architectureoption_set.all().filter(optional=False, build_architecture_all=True)) != 1:
                raise mini_buildd.HTTPBadRequest(f"{d}: There must be exactly one one arch-all architecture!")

        # Check that the codenames of the distribution are unique
        codenames = []
        for d in self.distributions.all():
            if d.base_source.codename in codenames:
                raise mini_buildd.HTTPBadRequest(f"Multiple distribution codename in: {d}")
            codenames.append(d.base_source.codename)

            # Check for mandatory component "main"
            if not d.components.all().filter(name="main"):
                raise mini_buildd.HTTPBadRequest(f"Mandatory component 'main' missing in: {d}")

        self.mbd_reprepro_update_config()

    def mbd_sync(self):
        self.mbd_prepare()

    def mbd_remove(self):
        if os.path.exists(self.mbd_path):
            shutil.rmtree(self.mbd_path)

    def mbd_check(self):
        self.mbd_reprepro_update_config()
        self.mbd_reprepro.check()

        # Check for ambiguity with other repos in meta distribution maps
        get_meta_distribution_map()

    def mbd_get_dependencies(self):
        result = []
        for d in self.distributions.all():
            result.append(d.base_source)
            result += [e.source for e in d.extra_sources.all()]
        return result

    def mbd_parse_dist(self, dist, check_uploadable=False):
        distribution = self.distributions.all().filter(base_source__codename__exact=dist.codename).first()
        if distribution is None:
            raise mini_buildd.HTTPBadRequest(f"No distribution for codename '{dist.codename}' in repository '{self.identity}'")

        suite = self.layout.suiteoption_set.filter(suite__name=dist.suite).first()
        if suite is None:
            raise mini_buildd.HTTPBadRequest(f"No distribution for suite '{dist.suite}' in repository '{self.identity}'")

        if check_uploadable:
            if dist.rollback_no is not None or not suite.uploadable:
                raise mini_buildd.HTTPBadRequest(f"Distribution '{dist.get()}' not uploadable")

            if not distribution.mbd_is_active():
                raise mini_buildd.HTTPUnavailable(f"Distribution '{dist.get()}' not active")

            if not self.mbd_is_active():
                raise mini_buildd.HTTPUnavailable(f"Repository '{self.identity}' not active")

        return distribution, suite


def get_meta_distribution_map():
    """Get a dict of the meta distributions: meta -> actual."""
    result = {}
    for r in Repository.objects.all():
        for d in r.distributions.all():
            for s in r.layout.suiteoption_set.all():
                for m in r.mbd_get_meta_distributions(d, s):
                    distribution = r.mbd_get_distribution_string(d, s)
                    if m in result:
                        raise mini_buildd.HTTPInternal(f"Ambiguous Meta-Distributions ({m}={distribution} or {result[m]}). "
                                                       f"Please check Repositories and Layouts (see Layouts/Meta-Distributions in Administrators Manual).")
                    result[m] = distribution

    LOG.debug(f"Got meta distribution map: {result}")
    return result


def map_distribution(dist_str):
    """Map incoming distribution to internal."""
    return get_meta_distribution_map().get(dist_str, dist_str)


def get(identity):
    """Get repository object with user error handling."""
    repository = Repository.objects.filter(pk=identity).first()
    if repository is None:
        raise mini_buildd.HTTPBadRequest(f"No such repository: {identity}")
    return repository


def parse_dist(dist, check_uploadable=False):
    repository = get(dist.repository)
    return (repository,) + repository.mbd_parse_dist(dist, check_uploadable=check_uploadable)


def parse_diststr(diststr, check_uploadable=False):
    dist = mini_buildd.dist.Dist(diststr)
    return (dist,) + parse_dist(dist, check_uploadable=check_uploadable)
