queue.py 28.6 KB
Newer Older
J
New.  
James Troup 已提交
1
#!/usr/bin/env python
2
# vim:set et sw=4:
J
New.  
James Troup 已提交
3

J
Joerg Jaspert 已提交
4 5 6 7 8
"""
Queue utility functions for dak

@contact: Debian FTP Master <ftpmaster@debian.org>
@copyright: 2001 - 2006 James Troup <james@nocrew.org>
J
Joerg Jaspert 已提交
9
@copyright: 2009, 2010  Joerg Jaspert <joerg@debian.org>
J
Joerg Jaspert 已提交
10 11
@license: GNU General Public License version 2 or later
"""
J
New.  
James Troup 已提交
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28

# 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

###############################################################################

J
Joerg Jaspert 已提交
29 30 31 32 33 34 35 36
import errno
import os
import stat
import sys
import time
import apt_inst
import apt_pkg
import utils
M
Mark Hymers 已提交
37 38
import commands
import shutil
39
import textwrap
40
from types import *
F
Frank Lichtenheld 已提交
41
from sqlalchemy.sql.expression import desc
42
from sqlalchemy.orm.exc import NoResultFound
43

44 45
import yaml

J
Joerg Jaspert 已提交
46
from dak_exceptions import *
47
from changes import *
48
from regexes import *
49
from config import Config
50
from holding import Holding
51
from urgencylog import UrgencyLog
52
from dbconn import *
53
from summarystats import SummaryStats
54
from utils import parse_changes, check_dsc_files, build_package_list
55
from textutils import fix_maintainer
56
from lintian import parse_lintian_output, generate_reject_messages
T
Torsten Werner 已提交
57
from contents import UnpackedSource
J
New.  
James Troup 已提交
58

59 60
################################################################################

61 62 63
def check_valid(overrides, session):
    """Check if section and priority for new overrides exist in database.

J
Joerg Jaspert 已提交
64 65
    Additionally does sanity checks:
      - debian-installer packages have to be udeb (or source)
66
      - non debian-installer packages cannot be udeb
67

68 69 70
    @type  overrides: list of dict
    @param overrides: list of overrides to check. The overrides need
                      to be given in form of a dict with the following keys:
71

72 73 74 75 76
                      - package: package name
                      - priority
                      - section
                      - component
                      - type: type of requested override ('dsc', 'deb' or 'udeb')
77

78
                      All values are strings.
79

80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
    @rtype:  bool
    @return: C{True} if all overrides are valid, C{False} if there is any
             invalid override.
    """
    all_valid = True
    for o in overrides:
        o['valid'] = True
        if session.query(Priority).filter_by(priority=o['priority']).first() is None:
            o['valid'] = False
        if session.query(Section).filter_by(section=o['section']).first() is None:
            o['valid'] = False
        if get_mapped_component(o['component'], session) is None:
            o['valid'] = False
        if o['type'] not in ('dsc', 'deb', 'udeb'):
            raise Exception('Unknown override type {0}'.format(o['type']))
        if o['type'] == 'udeb' and o['section'] != 'debian-installer':
            o['valid'] = False
        if o['section'] == 'debian-installer' and o['type'] not in ('dsc', 'udeb'):
            o['valid'] = False
        all_valid = all_valid and o['valid']
    return all_valid
101

J
New.  
James Troup 已提交
102 103
###############################################################################

104 105
def prod_maintainer(notes, upload):
    cnf = Config()
106
    changes = upload.changes
107
    whitelists = [ upload.target_suite.mail_whitelist ]
108 109 110 111

    # Here we prepare an editor and get them ready to prod...
    (fd, temp_filename) = utils.temp_filename()
    temp_file = os.fdopen(fd, 'w')
112
    temp_file.write("\n\n=====\n\n".join([note.comment for note in notes]))
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
    temp_file.close()
    editor = os.environ.get("EDITOR","vi")
    answer = 'E'
    while answer == 'E':
        os.system("%s %s" % (editor, temp_filename))
        temp_fh = utils.open_file(temp_filename)
        prod_message = "".join(temp_fh.readlines())
        temp_fh.close()
        print "Prod message:"
        print utils.prefix_multi_line_string(prod_message,"  ",include_blank_lines=1)
        prompt = "[P]rod, Edit, Abandon, Quit ?"
        answer = "XXX"
        while prompt.find(answer) == -1:
            answer = utils.our_raw_input(prompt)
            m = re_default_answer.search(prompt)
            if answer == "":
                answer = m.group(1)
            answer = answer[:1].upper()
    os.unlink(temp_filename)
    if answer == 'A':
        return
    elif answer == 'Q':
135
        return 0
136 137 138 139
    # Otherwise, do the proding...
    user_email_address = utils.whoami() + " <%s>" % (
        cnf["Dinstall::MyAdminAddress"])

140 141 142 143 144 145 146 147 148
    changed_by = changes.changedby or changes.maintainer
    maintainer = changes.maintainer
    maintainer_to = utils.mail_addresses_for_upload(maintainer, changed_by, changes.fingerprint)

    Subst = {
        '__SOURCE__': upload.changes.source,
        '__CHANGES_FILENAME__': upload.changes.changesname,
        '__MAINTAINER_TO__': ", ".join(maintainer_to),
        }
149 150 151 152 153 154 155 156 157

    Subst["__FROM_ADDRESS__"] = user_email_address
    Subst["__PROD_MESSAGE__"] = prod_message
    Subst["__CC__"] = "Cc: " + cnf["Dinstall::MyEmailAddress"]

    prod_mail_message = utils.TemplateSubst(
        Subst,cnf["Dir::Templates"]+"/process-new.prod")

    # Send the prod mail
158
    utils.send_mail(prod_mail_message, whitelists=whitelists)
159 160 161 162 163

    print "Sent prodding message"

################################################################################

164
def edit_note(note, upload, session, trainee=False):
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
    # Write the current data to a temporary file
    (fd, temp_filename) = utils.temp_filename()
    editor = os.environ.get("EDITOR","vi")
    answer = 'E'
    while answer == 'E':
        os.system("%s %s" % (editor, temp_filename))
        temp_file = utils.open_file(temp_filename)
        newnote = temp_file.read().rstrip()
        temp_file.close()
        print "New Note:"
        print utils.prefix_multi_line_string(newnote,"  ")
        prompt = "[D]one, Edit, Abandon, Quit ?"
        answer = "XXX"
        while prompt.find(answer) == -1:
            answer = utils.our_raw_input(prompt)
            m = re_default_answer.search(prompt)
            if answer == "":
                answer = m.group(1)
            answer = answer[:1].upper()
    os.unlink(temp_filename)
    if answer == 'A':
        return
    elif answer == 'Q':
188
        return 0
189 190

    comment = NewComment()
191 192
    comment.package = upload.changes.source
    comment.version = upload.changes.version
193 194
    comment.comment = newnote
    comment.author  = utils.whoami()
195
    comment.trainee = trainee
196 197 198 199 200
    session.add(comment)
    session.commit()

###############################################################################

J
Joerg Jaspert 已提交
201
# FIXME: Should move into the database
202
# suite names DMs can upload to
J
Joerg Jaspert 已提交
203
dm_suites = ['unstable', 'experimental', 'squeeze-backports']
204 205 206 207 208 209 210 211 212 213 214

def get_newest_source(source, session):
    'returns the newest DBSource object in dm_suites'
    ## the most recent version of the package uploaded to unstable or
    ## experimental includes the field "DM-Upload-Allowed: yes" in the source
    ## section of its control file
    q = session.query(DBSource).filter_by(source = source). \
        filter(DBSource.suites.any(Suite.suite_name.in_(dm_suites))). \
        order_by(desc('source.version'))
    return q.first()

215
def get_suite_version_by_source(source, session):
216 217 218 219 220
    'returns a list of tuples (suite_name, version) for source package'
    q = session.query(Suite.suite_name, DBSource.version). \
        join(Suite.sources).filter_by(source = source)
    return q.all()

221 222 223 224 225 226 227 228 229
def get_source_by_package_and_suite(package, suite_name, session):
    '''
    returns a DBSource query filtered by DBBinary.package and this package's
    suite_name
    '''
    return session.query(DBSource). \
        join(DBSource.binaries).filter_by(package = package). \
        join(DBBinary.suites).filter_by(suite_name = suite_name)

230 231 232 233 234 235 236 237 238 239
def get_suite_version_by_package(package, arch_string, session):
    '''
    returns a list of tuples (suite_name, version) for binary package and
    arch_string
    '''
    return session.query(Suite.suite_name, DBBinary.version). \
        join(Suite.binaries).filter_by(package = package). \
        join(DBBinary.architecture). \
        filter(Architecture.arch_string.in_([arch_string, 'all'])).all()

240
class Upload(object):
J
Joerg Jaspert 已提交
241 242
    """
    Everything that has to do with an upload processed.
J
New.  
James Troup 已提交
243

J
Joerg Jaspert 已提交
244
    """
245
    def __init__(self):
M
Mark Hymers 已提交
246
        self.logger = None
247 248
        self.pkg = Changes()
        self.reset()
J
New.  
James Troup 已提交
249 250 251

    ###########################################################################

252
    def update_subst(self):
J
Joerg Jaspert 已提交
253
        """ Set up the per-package template substitution mappings """
A
Ansgar Burchardt 已提交
254
        raise Exception('to be removed')
J
Joerg Jaspert 已提交
255

256 257
        cnf = Config()

258
        # If 'dak process-unchecked' crashed out in the right place, architecture may still be a string.
259
        if not self.pkg.changes.has_key("architecture") or not \
260
           isinstance(self.pkg.changes["architecture"], dict):
261 262
            self.pkg.changes["architecture"] = { "Unknown" : "" }

263
        # and maintainer2047 may not exist.
264 265
        if not self.pkg.changes.has_key("maintainer2047"):
            self.pkg.changes["maintainer2047"] = cnf["Dinstall::MyEmailAddress"]
J
New.  
James Troup 已提交
266

267 268 269
        self.Subst["__ARCHITECTURE__"] = " ".join(self.pkg.changes["architecture"].keys())
        self.Subst["__CHANGES_FILENAME__"] = os.path.basename(self.pkg.changes_file)
        self.Subst["__FILE_CONTENTS__"] = self.pkg.changes.get("filecontents", "")
J
New.  
James Troup 已提交
270 271

        # For source uploads the Changed-By field wins; otherwise Maintainer wins.
272 273 274 275 276
        if self.pkg.changes["architecture"].has_key("source") and \
           self.pkg.changes["changedby822"] != "" and \
           (self.pkg.changes["changedby822"] != self.pkg.changes["maintainer822"]):

            self.Subst["__MAINTAINER_FROM__"] = self.pkg.changes["changedby2047"]
M
Mark Hymers 已提交
277
            self.Subst["__MAINTAINER_TO__"] = "%s, %s" % (self.pkg.changes["changedby2047"], self.pkg.changes["maintainer2047"])
278
            self.Subst["__MAINTAINER__"] = self.pkg.changes.get("changed-by", "Unknown")
J
New.  
James Troup 已提交
279
        else:
280 281 282
            self.Subst["__MAINTAINER_FROM__"] = self.pkg.changes["maintainer2047"]
            self.Subst["__MAINTAINER_TO__"] = self.pkg.changes["maintainer2047"]
            self.Subst["__MAINTAINER__"] = self.pkg.changes.get("maintainer", "Unknown")
J
Joerg Jaspert 已提交
283

284 285 286 287 288 289 290 291
        # Process policy doesn't set the fingerprint field and I don't want to make it
        # do it for now as I don't want to have to deal with the case where we accepted
        # the package into PU-NEW, but the fingerprint has gone away from the keyring in
        # the meantime so the package will be remarked as rejectable.  Urgh.
        # TODO: Fix this properly
        if self.pkg.changes.has_key('fingerprint'):
            session = DBConn().session()
            fpr = get_fingerprint(self.pkg.changes['fingerprint'], session)
292
            if fpr and self.check_if_upload_is_sponsored("%s@debian.org" % fpr.uid.uid, fpr.uid.name):
293 294
                if self.pkg.changes.has_key("sponsoremail"):
                    self.Subst["__MAINTAINER_TO__"] += ", %s" % self.pkg.changes["sponsoremail"]
295
            session.close()
J
Joerg Jaspert 已提交
296

297 298
        if cnf.has_key("Dinstall::TrackingServer") and self.pkg.changes.has_key("source"):
            self.Subst["__MAINTAINER_TO__"] += "\nBcc: %s@%s" % (self.pkg.changes["source"], cnf["Dinstall::TrackingServer"])
J
New.  
James Troup 已提交
299

300
        # Apply any global override of the Maintainer field
301 302 303
        if cnf.get("Dinstall::OverrideMaintainer"):
            self.Subst["__MAINTAINER_TO__"] = cnf["Dinstall::OverrideMaintainer"]
            self.Subst["__MAINTAINER_FROM__"] = cnf["Dinstall::OverrideMaintainer"]
304

305
        self.Subst["__REJECT_MESSAGE__"] = self.package_info()
306 307
        self.Subst["__SOURCE__"] = self.pkg.changes.get("source", "Unknown")
        self.Subst["__VERSION__"] = self.pkg.changes.get("version", "Unknown")
308
        self.Subst["__SUITE__"] = ", ".join(self.pkg.changes["distribution"])
J
New.  
James Troup 已提交
309

310 311 312 313 314 315 316 317
    ###########################################################################

    def check_distributions(self):
        "Check and map the Distribution field"

        Cnf = Config()

        # Handle suite mappings
318
        for m in Cnf.value_list("SuiteMappings"):
319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334
            args = m.split()
            mtype = args[0]
            if mtype == "map" or mtype == "silent-map":
                (source, dest) = args[1:3]
                if self.pkg.changes["distribution"].has_key(source):
                    del self.pkg.changes["distribution"][source]
                    self.pkg.changes["distribution"][dest] = 1
                    if mtype != "silent-map":
                        self.notes.append("Mapping %s to %s." % (source, dest))
                if self.pkg.changes.has_key("distribution-version"):
                    if self.pkg.changes["distribution-version"].has_key(source):
                        self.pkg.changes["distribution-version"][source]=dest
            elif mtype == "map-unreleased":
                (source, dest) = args[1:3]
                if self.pkg.changes["distribution"].has_key(source):
                    for arch in self.pkg.changes["architecture"].keys():
M
Mark Hymers 已提交
335
                        if arch not in [ a.arch_string for a in get_suite_architectures(source) ]:
336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363
                            self.notes.append("Mapping %s to %s for unreleased architecture %s." % (source, dest, arch))
                            del self.pkg.changes["distribution"][source]
                            self.pkg.changes["distribution"][dest] = 1
                            break
            elif mtype == "ignore":
                suite = args[1]
                if self.pkg.changes["distribution"].has_key(suite):
                    del self.pkg.changes["distribution"][suite]
                    self.warnings.append("Ignoring %s as a target suite." % (suite))
            elif mtype == "reject":
                suite = args[1]
                if self.pkg.changes["distribution"].has_key(suite):
                    self.rejects.append("Uploads to %s are not accepted." % (suite))
            elif mtype == "propup-version":
                # give these as "uploaded-to(non-mapped) suites-to-add-when-upload-obsoletes"
                #
                # changes["distribution-version"] looks like: {'testing': 'testing-proposed-updates'}
                if self.pkg.changes["distribution"].has_key(args[1]):
                    self.pkg.changes.setdefault("distribution-version", {})
                    for suite in args[2:]:
                        self.pkg.changes["distribution-version"][suite] = suite

        # Ensure there is (still) a target distribution
        if len(self.pkg.changes["distribution"].keys()) < 1:
            self.rejects.append("No valid distribution remaining.")

        # Ensure target distributions exist
        for suite in self.pkg.changes["distribution"].keys():
364
            if not get_suite(suite.lower()):
365 366
                self.rejects.append("Unknown distribution `%s'." % (suite))

J
New.  
James Troup 已提交
367 368
    ###########################################################################

369
    def per_suite_file_checks(self, f, suite, session):
A
Ansgar Burchardt 已提交
370
        raise Exception('removed')
M
Mark Hymers 已提交
371

372
        # Handle component mappings
373
        for m in cnf.value_list("ComponentMappings"):
374 375 376 377 378 379 380 381 382 383 384
            (source, dest) = m.split()
            if entry["component"] == source:
                entry["original component"] = source
                entry["component"] = dest

    ###########################################################################

    # Sanity check the time stamps of files inside debs.
    # [Files in the near future cause ugly warnings and extreme time
    #  travel can cause errors on extraction]

M
Mark Hymers 已提交
385
    def check_if_upload_is_sponsored(self, uid_email, uid_name):
386 387 388
        for key in "maintaineremail", "changedbyemail", "maintainername", "changedbyname":
            if not self.pkg.changes.has_key(key):
                return False
389
        uid_email = '@'.join(uid_email.split('@')[:2])
M
Mark Hymers 已提交
390 391 392 393 394 395 396 397
        if uid_email in [self.pkg.changes["maintaineremail"], self.pkg.changes["changedbyemail"]]:
            sponsored = False
        elif uid_name in [self.pkg.changes["maintainername"], self.pkg.changes["changedbyname"]]:
            sponsored = False
            if uid_name == "":
                sponsored = True
        else:
            sponsored = True
398 399 400 401 402
            sponsor_addresses = utils.gpg_get_key_addresses(self.pkg.changes["fingerprint"])
            debian_emails = filter(lambda addr: addr.endswith('@debian.org'), sponsor_addresses)
            if uid_email not in debian_emails:
                if debian_emails:
                    uid_email = debian_emails[0]
M
Mark Hymers 已提交
403 404 405 406 407 408 409 410 411 412 413 414 415
            if ("source" in self.pkg.changes["architecture"] and uid_email and utils.is_email_alias(uid_email)):
                if (self.pkg.changes["maintaineremail"] not in sponsor_addresses and
                    self.pkg.changes["changedbyemail"] not in sponsor_addresses):
                        self.pkg.changes["sponsoremail"] = uid_email

        return sponsored

    def check_dm_upload(self, fpr, session):
        # Quoth the GR (http://www.debian.org/vote/2007/vote_003):
        ## none of the uploaded packages are NEW
        ## none of the packages are being taken over from other source packages
        for b in self.pkg.changes["binary"].keys():
            for suite in self.pkg.changes["distribution"].keys():
416
                for s in get_source_by_package_and_suite(b, suite, session):
M
Mark Hymers 已提交
417 418 419
                    if s.source != self.pkg.changes["source"]:
                        self.rejects.append("%s may not hijack %s from source package %s in suite %s" % (fpr.uid.uid, b, s, suite))

420
    ###########################################################################
M
Mark Hymers 已提交
421
    # End check_signed_by_key checks
422
    ###########################################################################
M
Mark Hymers 已提交
423

J
New.  
James Troup 已提交
424
    def build_summaries(self):
J
Joerg Jaspert 已提交
425
        """ Build a summary of changes the upload introduces. """
426 427

        (byhand, new, summary, override_summary) = self.pkg.file_summary()
J
New.  
James Troup 已提交
428

429
        short_summary = summary
J
New.  
James Troup 已提交
430 431

        # This is for direport's benefit...
432
        f = re_fdnic.sub("\n .\n", self.pkg.changes.get("changes", ""))
J
New.  
James Troup 已提交
433

434
        summary += "\n\nChanges:\n" + f
J
New.  
James Troup 已提交
435

436 437
        summary += "\n\nOverride entries for your package:\n" + override_summary + "\n"

438
        summary += self.announce(short_summary, 0)
J
New.  
James Troup 已提交
439

440
        return (summary, short_summary)
J
New.  
James Troup 已提交
441 442 443

    ###########################################################################

444
    def announce(self, short_summary, action):
J
Joerg Jaspert 已提交
445 446 447 448 449 450 451 452 453 454 455 456 457
        """
        Send an announce mail about a new upload.

        @type short_summary: string
        @param short_summary: Short summary text to include in the mail

        @type action: bool
        @param action: Set to false no real action will be done.

        @rtype: string
        @return: Textstring about action taken.

        """
458 459

        cnf = Config()
M
Mark Hymers 已提交
460 461 462

        # Skip all of this if not sending mail to avoid confusing people
        if cnf.has_key("Dinstall::Options::No-Mail") and cnf["Dinstall::Options::No-Mail"]:
M
Mark Hymers 已提交
463
            return ""
464 465

        # Only do announcements for source uploads with a recent dpkg-dev installed
466 467
        if float(self.pkg.changes.get("format", 0)) < 1.6 or not \
           self.pkg.changes["architecture"].has_key("source"):
468
            return ""
J
New.  
James Troup 已提交
469

M
Mark Hymers 已提交
470 471
        announcetemplate = os.path.join(cnf["Dir::Templates"], 'process-unchecked.announce')

M
Mark Hymers 已提交
472
        lists_todo = {}
473
        summary = ""
474

M
Mark Hymers 已提交
475
        # Get a unique list of target lists
476
        for dist in self.pkg.changes["distribution"].keys():
477
            suite = get_suite(dist)
478
            if suite is None: continue
M
Mark Hymers 已提交
479 480 481 482
            for tgt in suite.announce:
                lists_todo[tgt] = 1

        self.Subst["__SHORT_SUMMARY__"] = short_summary
483

M
Mark Hymers 已提交
484
        for announce_list in lists_todo.keys():
J
Joerg Jaspert 已提交
485
            summary += "Announcing to %s\n" % (announce_list)
486 487

            if action:
M
Mark Hymers 已提交
488
                self.update_subst()
489 490 491 492 493 494 495 496
                self.Subst["__ANNOUNCE_LIST_ADDRESS__"] = announce_list
                if cnf.get("Dinstall::TrackingServer") and \
                   self.pkg.changes["architecture"].has_key("source"):
                    trackingsendto = "Bcc: %s@%s" % (self.pkg.changes["source"], cnf["Dinstall::TrackingServer"])
                    self.Subst["__ANNOUNCE_LIST_ADDRESS__"] += "\n" + trackingsendto

                mail_message = utils.TemplateSubst(self.Subst, announcetemplate)
                utils.send_mail(mail_message)
497

498 499
                del self.Subst["__ANNOUNCE_LIST_ADDRESS__"]

500
        if cnf.find_b("Dinstall::CloseBugs") and cnf.has_key("Dinstall::BugServer"):
501
            summary = self.close_bugs(summary, action)
502

503 504
        del self.Subst["__SHORT_SUMMARY__"]

505
        return summary
J
New.  
James Troup 已提交
506 507 508

    ###########################################################################

509
    def check_override(self):
J
Joerg Jaspert 已提交
510 511 512 513 514 515 516 517
        """
        Checks override entries for validity. Mails "Override disparity" warnings,
        if that feature is enabled.

        Abandons the check if
          - override disparity checks are disabled
          - mail sending is disabled
        """
518 519

        cnf = Config()
J
New.  
James Troup 已提交
520

M
Mark Hymers 已提交
521
        # Abandon the check if override disparity checks have been disabled
522
        if not cnf.find_b("Dinstall::OverrideDisparityCheck"):
523
            return
J
New.  
James Troup 已提交
524

525
        summary = self.pkg.check_override()
J
New.  
James Troup 已提交
526 527

        if summary == "":
528
            return
J
New.  
James Troup 已提交
529

530 531
        overridetemplate = os.path.join(cnf["Dir::Templates"], 'process-unchecked.override-disparity')

M
Mark Hymers 已提交
532
        self.update_subst()
533 534
        self.Subst["__SUMMARY__"] = summary
        mail_message = utils.TemplateSubst(self.Subst, overridetemplate)
535
        utils.send_mail(mail_message)
536
        del self.Subst["__SUMMARY__"]
J
New.  
James Troup 已提交
537

538
    ################################################################################
539 540 541 542 543 544 545
    def get_anyversion(self, sv_list, suite):
        """
        @type sv_list: list
        @param sv_list: list of (suite, version) tuples to check

        @type suite: string
        @param suite: suite name
546

547 548
        Description: TODO
        """
M
Mark Hymers 已提交
549
        Cnf = Config()
550
        anyversion = None
551
        anysuite = [suite] + [ vc.reference.suite_name for vc in get_version_checks(suite, "Enhances") ]
552
        for (s, v) in sv_list:
553
            if s in [ x.lower() for x in anysuite ]:
554
                if not anyversion or apt_pkg.version_compare(anyversion, v) <= 0:
555 556
                    anyversion = v

557 558 559 560
        return anyversion

    ################################################################################

561
    def cross_suite_version_check(self, sv_list, filename, new_version, sourceful=False):
J
Joerg Jaspert 已提交
562
        """
563 564 565
        @type sv_list: list
        @param sv_list: list of (suite, version) tuples to check

566 567
        @type filename: string
        @param filename: XXX
568 569 570 571

        @type new_version: string
        @param new_version: XXX

J
Joerg Jaspert 已提交
572
        Ensure versions are newer than existing packages in target
573
        suites and that cross-suite version checking rules as
J
Joerg Jaspert 已提交
574 575
        set out in the conf file are satisfied.
        """
576

577 578
        cnf = Config()

579 580
        # Check versions for each target suite
        for target_suite in self.pkg.changes["distribution"].keys():
M
Mark Hymers 已提交
581 582 583 584 585 586
            # Check we can find the target suite
            ts = get_suite(target_suite)
            if ts is None:
                self.rejects.append("Cannot find target suite %s to perform version checks" % target_suite)
                continue

587 588
            must_be_newer_than = [ vc.reference.suite_name for vc in get_version_checks(target_suite, "MustBeNewerThan") ]
            must_be_older_than = [ vc.reference.suite_name for vc in get_version_checks(target_suite, "MustBeOlderThan") ]
589

590 591
            # Enforce "must be newer than target suite" even if conffile omits it
            if target_suite not in must_be_newer_than:
592
                must_be_newer_than.append(target_suite)
593 594

            for (suite, existent_version) in sv_list:
595
                vercmp = apt_pkg.version_compare(new_version, existent_version)
596 597

                if suite in must_be_newer_than and sourceful and vercmp < 1:
598
                    self.rejects.append("%s: old version (%s) in %s >= new version (%s) targeted at %s." % (filename, existent_version, suite, new_version, target_suite))
599 600

                if suite in must_be_older_than and vercmp > -1:
601
                    cansave = 0
602

603 604 605 606 607 608
                    if self.pkg.changes.get('distribution-version', {}).has_key(suite):
                        # we really use the other suite, ignoring the conflicting one ...
                        addsuite = self.pkg.changes["distribution-version"][suite]

                        add_version = self.get_anyversion(sv_list, addsuite)
                        target_version = self.get_anyversion(sv_list, target_suite)
609

610 611 612 613 614 615 616 617 618 619
                        if not add_version:
                            # not add_version can only happen if we map to a suite
                            # that doesn't enhance the suite we're propup'ing from.
                            # so "propup-ver x a b c; map a d" is a problem only if
                            # d doesn't enhance a.
                            #
                            # i think we could always propagate in this case, rather
                            # than complaining. either way, this isn't a REJECT issue
                            #
                            # And - we really should complain to the dorks who configured dak
620
                            self.warnings.append("%s is mapped to, but not enhanced by %s - adding anyways" % (suite, addsuite))
621 622
                            self.pkg.changes.setdefault("propdistribution", {})
                            self.pkg.changes["propdistribution"][addsuite] = 1
623 624 625 626 627
                            cansave = 1
                        elif not target_version:
                            # not targets_version is true when the package is NEW
                            # we could just stick with the "...old version..." REJECT
                            # for this, I think.
628
                            self.rejects.append("Won't propogate NEW packages.")
629
                        elif apt_pkg.version_compare(new_version, add_version) < 0:
630
                            # propogation would be redundant. no need to reject though.
631
                            self.warnings.append("ignoring versionconflict: %s: old version (%s) in %s <= new version (%s) targeted at %s." % (filename, existent_version, suite, new_version, target_suite))
632
                            cansave = 1
633 634
                        elif apt_pkg.version_compare(new_version, add_version) > 0 and \
                             apt_pkg.version_compare(add_version, target_version) >= 0:
635
                            # propogate!!
636
                            self.warnings.append("Propogating upload to %s" % (addsuite))
637 638
                            self.pkg.changes.setdefault("propdistribution", {})
                            self.pkg.changes["propdistribution"][addsuite] = 1
639
                            cansave = 1
640

641
                    if not cansave:
J
Joerg Jaspert 已提交
642
                        self.rejects.append("%s: old version (%s) in %s <= new version (%s) targeted at %s." % (filename, existent_version, suite, new_version, target_suite))
643 644

    ################################################################################
J
Joerg Jaspert 已提交
645

M
Mark Hymers 已提交
646
    def accepted_checks(self, overwrite_checks, session):
M
Mark Hymers 已提交
647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664
        # Recheck anything that relies on the database; since that's not
        # frozen between accept and our run time when called from p-a.

        # overwrite_checks is set to False when installing to stable/oldstable

        propogate={}
        nopropogate={}

        for checkfile in self.pkg.files.keys():
            # The .orig.tar.gz can disappear out from under us is it's a
            # duplicate of one in the archive.
            if not self.pkg.files.has_key(checkfile):
                continue

            entry = self.pkg.files[checkfile]

            # propogate in the case it is in the override tables:
            for suite in self.pkg.changes.get("propdistribution", {}).keys():
M
Mark Hymers 已提交
665
                if self.in_override_p(entry["package"], entry["component"], suite, entry.get("dbtype",""), checkfile, session):
M
Mark Hymers 已提交
666 667 668 669 670 671 672 673 674 675 676 677
                    propogate[suite] = 1
                else:
                    nopropogate[suite] = 1

        for suite in propogate.keys():
            if suite in nopropogate:
                continue
            self.pkg.changes["distribution"][suite] = 1

        for checkfile in self.pkg.files.keys():
            # Check the package is still in the override tables
            for suite in self.pkg.changes["distribution"].keys():
M
Mark Hymers 已提交
678
                if not self.in_override_p(entry["package"], entry["component"], suite, entry.get("dbtype",""), checkfile, session):
M
Mark Hymers 已提交
679
                    self.rejects.append("%s is NEW for %s." % (checkfile, suite))