queue.py 28.7 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

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

57 58
################################################################################

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

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

66 67 68
    @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:
69

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

76
                      All values are strings.
77

78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
    @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
99

J
New.  
James Troup 已提交
100 101
###############################################################################

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

    # Here we prepare an editor and get them ready to prod...
    (fd, temp_filename) = utils.temp_filename()
    temp_file = os.fdopen(fd, 'w')
110
    temp_file.write("\n\n=====\n\n".join([note.comment for note in notes]))
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
    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':
133
        return 0
134 135 136 137
    # Otherwise, do the proding...
    user_email_address = utils.whoami() + " <%s>" % (
        cnf["Dinstall::MyAdminAddress"])

138 139 140 141 142 143 144 145 146
    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),
        }
147 148 149 150 151 152 153 154 155

    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
156
    utils.send_mail(prod_mail_message, whitelists=whitelists)
157 158 159 160 161

    print "Sent prodding message"

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

162
def edit_note(note, upload, session, trainee=False):
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
    # 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':
186
        return 0
187 188

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

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

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

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()

214
def get_suite_version_by_source(source, session):
215 216 217 218 219
    '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()

220 221 222 223 224 225 226 227 228
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)

229 230 231 232 233 234 235 236 237 238
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()

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

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

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

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

255 256
        cnf = Config()

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

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

266 267 268
        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 已提交
269 270

        # For source uploads the Changed-By field wins; otherwise Maintainer wins.
271 272 273 274 275
        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 已提交
276
            self.Subst["__MAINTAINER_TO__"] = "%s, %s" % (self.pkg.changes["changedby2047"], self.pkg.changes["maintainer2047"])
277
            self.Subst["__MAINTAINER__"] = self.pkg.changes.get("changed-by", "Unknown")
J
New.  
James Troup 已提交
278
        else:
279 280 281
            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 已提交
282

283 284 285 286 287 288 289 290
        # 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)
291
            if fpr and self.check_if_upload_is_sponsored("%s@debian.org" % fpr.uid.uid, fpr.uid.name):
292 293
                if self.pkg.changes.has_key("sponsoremail"):
                    self.Subst["__MAINTAINER_TO__"] += ", %s" % self.pkg.changes["sponsoremail"]
294
            session.close()
J
Joerg Jaspert 已提交
295

296 297
        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 已提交
298

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

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

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

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

        Cnf = Config()

        # Handle suite mappings
317
        for m in Cnf.value_list("SuiteMappings"):
318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333
            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 已提交
334
                        if arch not in [ a.arch_string for a in get_suite_architectures(source) ]:
335 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
                            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():
363
            if not get_suite(suite.lower()):
364 365
                self.rejects.append("Unknown distribution `%s'." % (suite))

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

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

371
        # Handle component mappings
372
        for m in cnf.value_list("ComponentMappings"):
373 374 375 376 377 378 379 380 381 382 383
            (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 已提交
384
    def check_if_upload_is_sponsored(self, uid_email, uid_name):
385 386 387
        for key in "maintaineremail", "changedbyemail", "maintainername", "changedbyname":
            if not self.pkg.changes.has_key(key):
                return False
388
        uid_email = '@'.join(uid_email.split('@')[:2])
M
Mark Hymers 已提交
389 390 391 392 393 394 395 396
        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
397 398 399 400 401
            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 已提交
402 403 404 405 406 407 408 409 410 411 412 413 414
            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():
415
                for s in get_source_by_package_and_suite(b, suite, session):
M
Mark Hymers 已提交
416 417 418
                    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))

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

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

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

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

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

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

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

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

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

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

443
    def announce(self, short_summary, action):
J
Joerg Jaspert 已提交
444 445 446 447 448 449 450 451 452 453 454 455 456
        """
        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.

        """
457 458

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

        # 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 已提交
462
            return ""
463 464

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

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

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

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

        self.Subst["__SHORT_SUMMARY__"] = short_summary
482

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

            if action:
M
Mark Hymers 已提交
487
                self.update_subst()
488 489 490 491 492 493 494 495
                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)
496

497 498
                del self.Subst["__ANNOUNCE_LIST_ADDRESS__"]

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

502 503
        del self.Subst["__SHORT_SUMMARY__"]

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

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

508
    def check_override(self):
J
Joerg Jaspert 已提交
509 510 511 512 513 514 515 516
        """
        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
        """
517 518

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

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

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

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

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

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

537
    ################################################################################
538 539 540 541 542 543 544
    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
545

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

556 557 558 559
        return anyversion

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

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

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

        @type new_version: string
        @param new_version: XXX

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

576 577
        cnf = Config()

578 579
        # Check versions for each target suite
        for target_suite in self.pkg.changes["distribution"].keys():
M
Mark Hymers 已提交
580 581 582 583 584 585
            # 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

586 587
            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") ]
588

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

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

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

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

602 603 604 605 606 607
                    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)
608

609 610 611 612 613 614 615 616 617 618
                        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
619
                            self.warnings.append("%s is mapped to, but not enhanced by %s - adding anyways" % (suite, addsuite))
620 621
                            self.pkg.changes.setdefault("propdistribution", {})
                            self.pkg.changes["propdistribution"][addsuite] = 1
622 623 624 625 626
                            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.
627
                            self.rejects.append("Won't propogate NEW packages.")
628
                        elif apt_pkg.version_compare(new_version, add_version) < 0:
629
                            # propogation would be redundant. no need to reject though.
630
                            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))
631
                            cansave = 1
632 633
                        elif apt_pkg.version_compare(new_version, add_version) > 0 and \
                             apt_pkg.version_compare(add_version, target_version) >= 0:
634
                            # propogate!!
635
                            self.warnings.append("Propogating upload to %s" % (addsuite))
636 637
                            self.pkg.changes.setdefault("propdistribution", {})
                            self.pkg.changes["propdistribution"][addsuite] = 1
638
                            cansave = 1
639

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

    ################################################################################
J
Joerg Jaspert 已提交
644

M
Mark Hymers 已提交
645
    def accepted_checks(self, overwrite_checks, session):
M
Mark Hymers 已提交
646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663
        # 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 已提交
664
                if self.in_override_p(entry["package"], entry["component"], suite, entry.get("dbtype",""), checkfile, session):
M
Mark Hymers 已提交
665 666 667 668 669 670 671 672 673 674 675 676
                    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 已提交
677
                if not self.in_override_p(entry["package"], entry["component"], suite, entry.get("dbtype",""), checkfile, session):
M
Mark Hymers 已提交
678
                    self.rejects.append("%s is NEW for %s." % (checkfile, suite))