提交 129ee05e 编写于 作者: J Joerg Jaspert

Merge remote-tracking branch 'ansgar/pu/multiarchive-1' into merge

* ansgar/pu/multiarchive-1:
  dak/init_dirs.py: only create directories for active keyrings
  dak/init_dirs.py: do not use Dir::Pool
  change documentation style
  Python modules should not be executable
  daklib/archive.py, daklib/checks.py: implement transition blocks
  daklib/archive.py: use method to decide which policy queue to use
  daklib/archive.py, daklib/checks.py: implement upload blocks
  daklib/dbconn.py: use apt_pkg.TagSection instead of implementing our own parser
  daklib/archive.py: check for source when copying binaries
Signed-off-by: NJoerg Jaspert <joerg@debian.org>
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
...@@ -131,16 +131,9 @@ def create_directories(): ...@@ -131,16 +131,9 @@ def create_directories():
process_keyring(Cnf['Dinstall::SigningPubKeyring'], secret=True) process_keyring(Cnf['Dinstall::SigningPubKeyring'], secret=True)
# Process public keyrings # Process public keyrings
for keyring in session.query(Keyring).all(): for keyring in session.query(Keyring).filter_by(active=True):
process_keyring(keyring.keyring_name) process_keyring(keyring.keyring_name)
# Process pool directories
for component in session.query(Component):
directory = os.path.join( Cnf['Dir::Pool'], component.component_name )
do_dir(directory, '%s pool' % component.component_name)
# Process dists directories # Process dists directories
# TODO: Store location of each suite in database # TODO: Store location of each suite in database
for suite in session.query(Suite): for suite in session.query(Suite):
......
此差异已折叠。
...@@ -20,14 +20,14 @@ ...@@ -20,14 +20,14 @@
"""module provided pre-acceptance tests """module provided pre-acceptance tests
Please read the documentation for the `Check` class for the interface. Please read the documentation for the L{Check} class for the interface.
""" """
from daklib.config import Config from daklib.config import Config
from .dbconn import * from daklib.dbconn import *
import daklib.dbconn as dbconn import daklib.dbconn as dbconn
from .regexes import * from daklib.regexes import *
from .textutils import fix_maintainer, ParseMaintError from daklib.textutils import fix_maintainer, ParseMaintError
import daklib.lintian as lintian import daklib.lintian as lintian
import daklib.utils as utils import daklib.utils as utils
...@@ -48,37 +48,37 @@ class Reject(Exception): ...@@ -48,37 +48,37 @@ class Reject(Exception):
class Check(object): class Check(object):
"""base class for checks """base class for checks
checks are called by daklib.archive.ArchiveUpload. Failing tests should checks are called by L{daklib.archive.ArchiveUpload}. Failing tests should
raise a `daklib.checks.Reject` exception including a human-readable raise a L{daklib.checks.Reject} exception including a human-readable
description why the upload should be rejected. description why the upload should be rejected.
""" """
def check(self, upload): def check(self, upload):
"""do checks """do checks
Args: @type upload: L{daklib.archive.ArchiveUpload}
upload (daklib.archive.ArchiveUpload): upload to check @param upload: upload to check
Raises: @raise daklib.checks.Reject: upload should be rejected
daklib.checks.Reject
""" """
raise NotImplemented raise NotImplemented
def per_suite_check(self, upload, suite): def per_suite_check(self, upload, suite):
"""do per-suite checks """do per-suite checks
Args: @type upload: L{daklib.archive.ArchiveUpload}
upload (daklib.archive.ArchiveUpload): upload to check @param upload: upload to check
suite (daklib.dbconn.Suite): suite to check
Raises: @type suite: L{daklib.dbconn.Suite}
daklib.checks.Reject @param suite: suite to check
@raise daklib.checks.Reject: upload should be rejected
""" """
raise NotImplemented raise NotImplemented
@property @property
def forcable(self): def forcable(self):
"""allow to force ignore failing test """allow to force ignore failing test
True if it is acceptable to force ignoring a failing test, C{True} if it is acceptable to force ignoring a failing test,
False otherwise C{False} otherwise
""" """
return False return False
...@@ -438,6 +438,91 @@ class ACLCheck(Check): ...@@ -438,6 +438,91 @@ class ACLCheck(Check):
return True return True
class UploadBlockCheck(Check):
"""check for upload blocks"""
def check(self, upload):
session = upload.session
control = upload.changes.changes
source = re_field_source.match(control['Source']).group('package')
version = control['Version']
blocks = session.query(UploadBlock).filter_by(source=source) \
.filter((UploadBlock.version == version) | (UploadBlock.version == None))
for block in blocks:
if block.fingerprint == upload.fingerprint:
raise Reject('Manual upload block in place for package {0} and fingerprint {1}:\n{2}'.format(source, upload.fingerprint.fingerprint, block.reason))
if block.uid == upload.fingerprint.uid:
raise Reject('Manual upload block in place for package {0} and uid {1}:\n{2}'.format(source, block.uid.uid, block.reason))
return True
class TransitionCheck(Check):
"""check for a transition"""
def check(self, upload):
if 'source' not in upload.changes.architectures:
return True
transitions = self.get_transitions()
if transitions is None:
return True
source = re_field_source.match(control['Source']).group('package')
for trans in transitions:
t = transitions[trans]
source = t["source"]
expected = t["new"]
# Will be None if nothing is in testing.
current = get_source_in_suite(source, "testing", session)
if current is not None:
compare = apt_pkg.version_compare(current.version, expected)
if current is None or compare < 0:
# This is still valid, the current version in testing is older than
# the new version we wait for, or there is none in testing yet
# Check if the source we look at is affected by this.
if source in t['packages']:
# The source is affected, lets reject it.
rejectmsg = "{0}: part of the {1} transition.\n\n".format(source, trans)
if current is not None:
currentlymsg = "at version {0}".format(current.version)
else:
currentlymsg = "not present in testing"
rejectmsg += "Transition description: {0}\n\n".format(t["reason"])
rejectmsg += "\n".join(textwrap.wrap("""Your package
is part of a testing transition designed to get {0} migrated (it is
currently {1}, we need version {2}). This transition is managed by the
Release Team, and {3} is the Release-Team member responsible for it.
Please mail debian-release@lists.debian.org or contact {3} directly if you
need further assistance. You might want to upload to experimental until this
transition is done.""".format(source, currentlymsg, expected,t["rm"])))
raise Reject(rejectmsg)
return True
def get_transitions(self):
cnf = Config()
path = cnf.get('Dinstall::ReleaseTransitions', '')
if path == '' or not os.path.exists(path):
return None
contents = file(path, 'r').read()
try:
transitions = yaml.load(contents)
return transitions
except yaml.YAMLError as msg:
utils.warn('Not checking transitions, the transitions file is broken: {0}'.format(msg))
return None
class NoSourceOnlyCheck(Check): class NoSourceOnlyCheck(Check):
"""Check for source-only upload """Check for source-only upload
......
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
...@@ -42,6 +42,8 @@ import traceback ...@@ -42,6 +42,8 @@ import traceback
import commands import commands
import signal import signal
from daklib.gpg import SignedFile
try: try:
# python >= 2.6 # python >= 2.6
import json import json
...@@ -2408,60 +2410,6 @@ __all__.append('SrcContents') ...@@ -2408,60 +2410,6 @@ __all__.append('SrcContents')
################################################################################ ################################################################################
from debian.debfile import Deb822
# Temporary Deb822 subclass to fix bugs with : handling; see #597249
class Dak822(Deb822):
def _internal_parser(self, sequence, fields=None):
# The key is non-whitespace, non-colon characters before any colon.
key_part = r"^(?P<key>[^: \t\n\r\f\v]+)\s*:\s*"
single = re.compile(key_part + r"(?P<data>\S.*?)\s*$")
multi = re.compile(key_part + r"$")
multidata = re.compile(r"^\s(?P<data>.+?)\s*$")
wanted_field = lambda f: fields is None or f in fields
if isinstance(sequence, basestring):
sequence = sequence.splitlines()
curkey = None
content = ""
for line in self.gpg_stripped_paragraph(sequence):
m = single.match(line)
if m:
if curkey:
self[curkey] = content
if not wanted_field(m.group('key')):
curkey = None
continue
curkey = m.group('key')
content = m.group('data')
continue
m = multi.match(line)
if m:
if curkey:
self[curkey] = content
if not wanted_field(m.group('key')):
curkey = None
continue
curkey = m.group('key')
content = ""
continue
m = multidata.match(line)
if m:
content += '\n' + line # XXX not m.group('data')?
continue
if curkey:
self[curkey] = content
class DBSource(ORMObject): class DBSource(ORMObject):
def __init__(self, source = None, version = None, maintainer = None, \ def __init__(self, source = None, version = None, maintainer = None, \
changedby = None, poolfile = None, install_date = None, fingerprint = None): changedby = None, poolfile = None, install_date = None, fingerprint = None):
...@@ -2494,7 +2442,9 @@ class DBSource(ORMObject): ...@@ -2494,7 +2442,9 @@ class DBSource(ORMObject):
@return: fields is the dsc information in a dictionary form @return: fields is the dsc information in a dictionary form
''' '''
fullpath = self.poolfile.fullpath fullpath = self.poolfile.fullpath
fields = Dak822(open(self.poolfile.fullpath, 'r')) contents = open(fullpath, 'r').read()
signed_file = SignedFile(contents, keyrings=[], require_signature=False)
fields = apt_pkg.TagSection(signed_file.contents)
return fields return fields
metadata = association_proxy('key', 'value') metadata = association_proxy('key', 'value')
......
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
...@@ -123,16 +123,22 @@ class FilesystemTransaction(object): ...@@ -123,16 +123,22 @@ class FilesystemTransaction(object):
self.actions = [] self.actions = []
def copy(self, source, destination, link=True, symlink=False, mode=None): def copy(self, source, destination, link=True, symlink=False, mode=None):
"""copy `source` to `destination` """copy C{source} to C{destination}
Args: @type source: str
source (str): source file @param source: source file
destination (str): destination file
Kwargs: @type destination: str
link (bool): Try hardlinking, falling back to copying. @param destination: destination file
symlink (bool): Create a symlink instead
mode (int): Permissions to change `destination` to. @type link: bool
@param link: try hardlinking, falling back to copying
@type symlink: bool
@param symlink: create a symlink instead of copying
@type mode: int
@param mode: permissions to change C{destination} to
""" """
if isinstance(mode, str) or isinstance(mode, unicode): if isinstance(mode, str) or isinstance(mode, unicode):
mode = int(mode, 8) mode = int(mode, 8)
...@@ -140,37 +146,38 @@ class FilesystemTransaction(object): ...@@ -140,37 +146,38 @@ class FilesystemTransaction(object):
self.actions.append(_FilesystemCopyAction(source, destination, link=link, symlink=symlink, mode=mode)) self.actions.append(_FilesystemCopyAction(source, destination, link=link, symlink=symlink, mode=mode))
def move(self, source, destination, mode=None): def move(self, source, destination, mode=None):
"""move `source` to `destination` """move C{source} to C{destination}
@type source: str
@param source: source file
Args: @type destination: str
source (str): source file @param destination: destination file
destination (str): destination file
Kwargs: @type mode: int
mode (int): Permissions to change `destination` to. @param mode: permissions to change C{destination} to
""" """
self.copy(source, destination, link=True, mode=mode) self.copy(source, destination, link=True, mode=mode)
self.unlink(source) self.unlink(source)
def unlink(self, path): def unlink(self, path):
"""unlink `path` """unlink C{path}
Args: @type path: str
path (str): file to unlink @param path: file to unlink
""" """
self.actions.append(_FilesystemUnlinkAction(path)) self.actions.append(_FilesystemUnlinkAction(path))
def create(self, path, mode=None): def create(self, path, mode=None):
"""create `filename` and return file handle """create C{filename} and return file handle
Args: @type filename: str
filename (str): file to create @param filename: file to create
Kwargs: @type mode: int
mode (int): Permissions for the new file @param mode: permissions for the new file
Returns: @return: file handle of the new file
file handle of the new file
""" """
if isinstance(mode, str) or isinstance(mode, unicode): if isinstance(mode, str) or isinstance(mode, unicode):
mode = int(mode, 8) mode = int(mode, 8)
......
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
...@@ -29,31 +29,36 @@ import tempfile ...@@ -29,31 +29,36 @@ import tempfile
class UploadCopy(object): class UploadCopy(object):
"""export a policy queue upload """export a policy queue upload
This class can be used in a with-statements: This class can be used in a with-statement::
with UploadCopy(...) as copy: with UploadCopy(...) as copy:
... ...
Doing so will provide a temporary copy of the upload in the directory Doing so will provide a temporary copy of the upload in the directory
given by the `directory` attribute. The copy will be removed on leaving given by the C{directory} attribute. The copy will be removed on leaving
the with-block. the with-block.
Args:
upload (daklib.dbconn.PolicyQueueUpload)
""" """
def __init__(self, upload): def __init__(self, upload):
"""initializer
@type upload: L{daklib.dbconn.PolicyQueueUpload}
@param upload: upload to handle
"""
self.directory = None self.directory = None
self.upload = upload self.upload = upload
def export(self, directory, mode=None, symlink=True): def export(self, directory, mode=None, symlink=True):
"""export a copy of the upload """export a copy of the upload
Args: @type directory: str
directory (str) @param directory: directory to export to
Kwargs: @type mode: int
mode (int): permissions to use for the copied files @param mode: permissions to use for the copied files
symlink (bool): use symlinks instead of copying the files (default: True)
@type symlink: bool
@param symlink: use symlinks instead of copying the files
""" """
with FilesystemTransaction() as fs: with FilesystemTransaction() as fs:
source = self.upload.source source = self.upload.source
...@@ -103,9 +108,10 @@ class PolicyQueueUploadHandler(object): ...@@ -103,9 +108,10 @@ class PolicyQueueUploadHandler(object):
def __init__(self, upload, session): def __init__(self, upload, session):
"""initializer """initializer
Args: @type upload: L{daklib.dbconn.PolicyQueueUpload}
upload (daklib.dbconn.PolicyQueueUpload): upload to process @param upload: upload to process
session: database session
@param session: database session
""" """
self.upload = upload self.upload = upload
self.session = session self.session = session
...@@ -169,8 +175,8 @@ class PolicyQueueUploadHandler(object): ...@@ -169,8 +175,8 @@ class PolicyQueueUploadHandler(object):
def reject(self, reason): def reject(self, reason):
"""mark upload as rejected """mark upload as rejected
Args: @type reason: str
reason (str): reason for the rejection @param reason: reason for the rejection
""" """
fn1 = 'REJECT.{0}'.format(self._changes_prefix) fn1 = 'REJECT.{0}'.format(self._changes_prefix)
assert re_file_safe.match(fn1) assert re_file_safe.match(fn1)
...@@ -190,8 +196,8 @@ class PolicyQueueUploadHandler(object): ...@@ -190,8 +196,8 @@ class PolicyQueueUploadHandler(object):
def get_action(self): def get_action(self):
"""get current action """get current action
Returns: @rtype: str
string giving the current action, one of 'ACCEPT', 'ACCEPTED', 'REJECT' @return: string giving the current action, one of 'ACCEPT', 'ACCEPTED', 'REJECT'
""" """
changes_prefix = self._changes_prefix changes_prefix = self._changes_prefix
...@@ -206,18 +212,19 @@ class PolicyQueueUploadHandler(object): ...@@ -206,18 +212,19 @@ class PolicyQueueUploadHandler(object):
def missing_overrides(self, hints=None): def missing_overrides(self, hints=None):
"""get missing override entries for the upload """get missing override entries for the upload
Kwargs: @type hints: list of dict
hints (list of dict): suggested hints for new overrides in the same @param hints: suggested hints for new overrides in the same format as
format as the return value the return value
Returns: @return: list of dicts with the following keys:
list of dicts with the following keys:
package: package name - package: package name
priority: default priority (from upload) - priority: default priority (from upload)
section: default section (from upload) - section: default section (from upload)
component: default component (from upload) - component: default component (from upload)
type: type of required override ('dsc', 'deb' or 'udeb') - type: type of required override ('dsc', 'deb' or 'udeb')
All values are strings.
All values are strings.
""" """
# TODO: use Package-List field # TODO: use Package-List field
missing = [] missing = []
......
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
文件模式从 100755 更改为 100644
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
"""module to handle uploads not yet installed to the archive """module to handle uploads not yet installed to the archive
This module provides classes to handle uploads not yet installed to the This module provides classes to handle uploads not yet installed to the
archive. Central is the `Changes` class which represents a changes file. archive. Central is the L{Changes} class which represents a changes file.
It provides methods to access the included binary and source packages. It provides methods to access the included binary and source packages.
""" """
...@@ -25,8 +25,9 @@ import apt_inst ...@@ -25,8 +25,9 @@ import apt_inst
import apt_pkg import apt_pkg
import os import os
import re import re
from .gpg import SignedFile
from .regexes import * from daklib.gpg import SignedFile
from daklib.regexes import *
class InvalidChangesException(Exception): class InvalidChangesException(Exception):
pass pass
...@@ -54,35 +55,52 @@ class InvalidFilenameException(Exception): ...@@ -54,35 +55,52 @@ class InvalidFilenameException(Exception):
class HashedFile(object): class HashedFile(object):
"""file with checksums """file with checksums
Attributes:
filename (str): name of the file
size (long): size in bytes
md5sum (str): MD5 hash in hexdigits
sha1sum (str): SHA1 hash in hexdigits
sha256sum (str): SHA256 hash in hexdigits
section (str): section or None
priority (str): priority or None
""" """
def __init__(self, filename, size, md5sum, sha1sum, sha256sum, section=None, priority=None): def __init__(self, filename, size, md5sum, sha1sum, sha256sum, section=None, priority=None):
self.filename = filename self.filename = filename
"""name of the file
@type: str
"""
self.size = size self.size = size
"""size in bytes
@type: long
"""
self.md5sum = md5sum self.md5sum = md5sum
"""MD5 hash in hexdigits
@type: str
"""
self.sha1sum = sha1sum self.sha1sum = sha1sum
"""SHA1 hash in hexdigits
@type: str
"""
self.sha256sum = sha256sum self.sha256sum = sha256sum
"""SHA256 hash in hexdigits
@type: str
"""
self.section = section self.section = section
"""section or C{None}
@type: str or C{None}
"""
self.priority = priority self.priority = priority
"""priority or C{None}
@type: str of C{None}
"""
def check(self, directory): def check(self, directory):
"""Validate hashes """Validate hashes
Check if size and hashes match the expected value. Check if size and hashes match the expected value.
Args: @type directory: str
directory (str): directory the file is located in @param directory: directory the file is located in
Raises: @raise InvalidHashException: hash mismatch
InvalidHashException: hash mismatch
""" """
path = os.path.join(directory, self.filename) path = os.path.join(directory, self.filename)
fh = open(path, 'r') fh = open(path, 'r')
...@@ -108,15 +126,17 @@ class HashedFile(object): ...@@ -108,15 +126,17 @@ class HashedFile(object):
def parse_file_list(control, has_priority_and_section): def parse_file_list(control, has_priority_and_section):
"""Parse Files and Checksums-* fields """Parse Files and Checksums-* fields
Args: @type control: dict-like
control (dict-like): control file to take fields from @param control: control file to take fields from
has_priority_and_section (bool): Files include section and priority (as in .changes)
@type has_priority_and_section: bool
@param has_priority_and_section: Files field include section and priority
(as in .changes)
Raises: @raise InvalidChangesException: missing fields or other grave errors
InvalidChangesException: missing fields or other grave errors
Returns: @rtype: dict
dictonary mapping filenames to `daklib.upload.HashedFile` objects @return: dict mapping filenames to L{daklib.upload.HashedFile} objects
""" """
entries = {} entries = {}
...@@ -172,32 +192,28 @@ def parse_file_list(control, has_priority_and_section): ...@@ -172,32 +192,28 @@ def parse_file_list(control, has_priority_and_section):
class Changes(object): class Changes(object):
"""Representation of a .changes file """Representation of a .changes file
Attributes:
architectures (list of str): list of architectures included in the upload
binaries (list of daklib.upload.Binary): included binary packages
binary_names (list of str): names of included binary packages
byhand_files (list of daklib.upload.HashedFile): included byhand files
bytes (int): total size of files included in this upload in bytes
changes (dict-like): dict to access fields of the .changes file
closed_bugs (list of str): list of bugs closed by this upload
directory (str): directory the .changes is located in
distributions (list of str): list of target distributions for the upload
filename (str): name of the .changes file
files (dict): dict mapping filenames to daklib.upload.HashedFile objects
path (str): path to the .changes files
primary_fingerprint (str): fingerprint of the PGP key used for the signature
source (daklib.upload.Source or None): included source
valid_signature (bool): True if the changes has a valid signature
""" """
def __init__(self, directory, filename, keyrings, require_signature=True): def __init__(self, directory, filename, keyrings, require_signature=True):
if not re_file_safe.match(filename): if not re_file_safe.match(filename):
raise InvalidChangesException('{0}: unsafe filename'.format(filename)) raise InvalidChangesException('{0}: unsafe filename'.format(filename))
self.directory = directory self.directory = directory
"""directory the .changes is located in
@type: str
"""
self.filename = filename self.filename = filename
"""name of the .changes file
@type: str
"""
data = open(self.path).read() data = open(self.path).read()
self._signed_file = SignedFile(data, keyrings, require_signature) self._signed_file = SignedFile(data, keyrings, require_signature)
self.changes = apt_pkg.TagSection(self._signed_file.contents) self.changes = apt_pkg.TagSection(self._signed_file.contents)
"""dict to access fields of the .changes file
@type: dict-like
"""
self._binaries = None self._binaries = None
self._source = None self._source = None
self._files = None self._files = None
...@@ -206,26 +222,44 @@ class Changes(object): ...@@ -206,26 +222,44 @@ class Changes(object):
@property @property
def path(self): def path(self):
"""path to the .changes file
@type: str
"""
return os.path.join(self.directory, self.filename) return os.path.join(self.directory, self.filename)
@property @property
def primary_fingerprint(self): def primary_fingerprint(self):
"""fingerprint of the key used for signing the .changes file
@type: str
"""
return self._signed_file.primary_fingerprint return self._signed_file.primary_fingerprint
@property @property
def valid_signature(self): def valid_signature(self):
"""C{True} if the .changes has a valid signature
@type: bool
"""
return self._signed_file.valid return self._signed_file.valid
@property @property
def architectures(self): def architectures(self):
"""list of architectures included in the upload
@type: list of str
"""
return self.changes['Architecture'].split() return self.changes['Architecture'].split()
@property @property
def distributions(self): def distributions(self):
"""list of target distributions for the upload
@type: list of str
"""
return self.changes['Distribution'].split() return self.changes['Distribution'].split()
@property @property
def source(self): def source(self):
"""included source or C{None}
@type: L{daklib.upload.Source} or C{None}
"""
if self._source is None: if self._source is None:
source_files = [] source_files = []
for f in self.files.itervalues(): for f in self.files.itervalues():
...@@ -237,6 +271,9 @@ class Changes(object): ...@@ -237,6 +271,9 @@ class Changes(object):
@property @property
def binaries(self): def binaries(self):
"""included binary packages
@type: list of L{daklib.upload.Binary}
"""
if self._binaries is None: if self._binaries is None:
binaries = [] binaries = []
for f in self.files.itervalues(): for f in self.files.itervalues():
...@@ -247,6 +284,9 @@ class Changes(object): ...@@ -247,6 +284,9 @@ class Changes(object):
@property @property
def byhand_files(self): def byhand_files(self):
"""included byhand files
@type: list of L{daklib.upload.HashedFile}
"""
byhand = [] byhand = []
for f in self.files.itervalues(): for f in self.files.itervalues():
...@@ -260,35 +300,47 @@ class Changes(object): ...@@ -260,35 +300,47 @@ class Changes(object):
@property @property
def binary_names(self): def binary_names(self):
"""names of included binary packages
@type: list of str
"""
return self.changes['Binary'].split() return self.changes['Binary'].split()
@property @property
def closed_bugs(self): def closed_bugs(self):
"""bugs closed by this upload
@type: list of str
"""
return self.changes.get('Closes', '').split() return self.changes.get('Closes', '').split()
@property @property
def files(self): def files(self):
"""dict mapping filenames to L{daklib.upload.HashedFile} objects
@type: dict
"""
if self._files is None: if self._files is None:
self._files = parse_file_list(self.changes, True) self._files = parse_file_list(self.changes, True)
return self._files return self._files
@property @property
def bytes(self): def bytes(self):
"""total size of files included in this upload in bytes
@type: number
"""
count = 0 count = 0
for f in self.files.itervalues(): for f in self.files.itervalues():
count += f.size count += f.size
return count return count
def __cmp__(self, other): def __cmp__(self, other):
"""Compare two changes packages """compare two changes files
We sort by source name and version first. If these are identical, We sort by source name and version first. If these are identical,
we sort changes that include source before those without source (so we sort changes that include source before those without source (so
that sourceful uploads get processed first), and finally fall back that sourceful uploads get processed first), and finally fall back
to the filename (this should really never happen). to the filename (this should really never happen).
Returns: @rtype: number
-1 if self < other, 0 if self == other, 1 if self > other @return: n where n < 0 if self < other, n = 0 if self == other, n > 0 if self > other
""" """
ret = cmp(self.changes.get('Source'), other.changes.get('Source')) ret = cmp(self.changes.get('Source'), other.changes.get('Source'))
...@@ -313,25 +365,25 @@ class Changes(object): ...@@ -313,25 +365,25 @@ class Changes(object):
class Binary(object): class Binary(object):
"""Representation of a binary package """Representation of a binary package
Attributes:
component (str): component name
control (dict-like): dict to access fields in DEBIAN/control
hashed_file (HashedFile): HashedFile object for the .deb
""" """
def __init__(self, directory, hashed_file): def __init__(self, directory, hashed_file):
self.hashed_file = hashed_file self.hashed_file = hashed_file
"""file object for the .deb
@type: HashedFile
"""
path = os.path.join(directory, hashed_file.filename) path = os.path.join(directory, hashed_file.filename)
data = apt_inst.DebFile(path).control.extractdata("control") data = apt_inst.DebFile(path).control.extractdata("control")
self.control = apt_pkg.TagSection(data) self.control = apt_pkg.TagSection(data)
"""dict to access fields in DEBIAN/control
@type: dict-like
"""
@property @property
def source(self): def source(self):
"""Get source package name and version """get tuple with source package name and version
@type: tuple of str
Returns:
tuple containing source package name and version
""" """
source = self.control.get("Source", None) source = self.control.get("Source", None)
if source is None: if source is None:
...@@ -346,10 +398,8 @@ class Binary(object): ...@@ -346,10 +398,8 @@ class Binary(object):
@property @property
def type(self): def type(self):
"""Get package type """package type ('deb' or 'udeb')
@type: str
Returns:
String with the package type ('deb' or 'udeb')
""" """
match = re_file_binary.match(self.hashed_file.filename) match = re_file_binary.match(self.hashed_file.filename)
if not match: if not match:
...@@ -358,6 +408,9 @@ class Binary(object): ...@@ -358,6 +408,9 @@ class Binary(object):
@property @property
def component(self): def component(self):
"""component name
@type: str
"""
fields = self.control['Section'].split('/') fields = self.control['Section'].split('/')
if len(fields) > 1: if len(fields) > 1:
return fields[0] return fields[0]
...@@ -365,18 +418,13 @@ class Binary(object): ...@@ -365,18 +418,13 @@ class Binary(object):
class Source(object): class Source(object):
"""Representation of a source package """Representation of a source package
Attributes:
component (str): guessed component name. Might be wrong!
dsc (dict-like): dict to access fields in the .dsc file
hashed_files (list of daklib.upload.HashedFile): list of source files (including .dsc)
files (dict): dictonary mapping filenames to HashedFile objects for
additional source files (not including .dsc)
primary_fingerprint (str): fingerprint of the PGP key used for the signature
valid_signature (bool): True if the dsc has a valid signature
""" """
def __init__(self, directory, hashed_files, keyrings, require_signature=True): def __init__(self, directory, hashed_files, keyrings, require_signature=True):
self.hashed_files = hashed_files self.hashed_files = hashed_files
"""list of source files (including the .dsc itself)
@type: list of L{HashedFile}
"""
self._dsc_file = None self._dsc_file = None
for f in hashed_files: for f in hashed_files:
if re_file_dsc.match(f.filename): if re_file_dsc.match(f.filename):
...@@ -388,24 +436,46 @@ class Source(object): ...@@ -388,24 +436,46 @@ class Source(object):
data = open(dsc_file_path, 'r').read() data = open(dsc_file_path, 'r').read()
self._signed_file = SignedFile(data, keyrings, require_signature) self._signed_file = SignedFile(data, keyrings, require_signature)
self.dsc = apt_pkg.TagSection(self._signed_file.contents) self.dsc = apt_pkg.TagSection(self._signed_file.contents)
"""dict to access fields in the .dsc file
@type: dict-like
"""
self._files = None self._files = None
@property @property
def files(self): def files(self):
"""dict mapping filenames to L{HashedFile} objects for additional source files
This list does not include the .dsc itself.
@type: dict
"""
if self._files is None: if self._files is None:
self._files = parse_file_list(self.dsc, False) self._files = parse_file_list(self.dsc, False)
return self._files return self._files
@property @property
def primary_fingerprint(self): def primary_fingerprint(self):
"""fingerprint of the key used to sign the .dsc
@type: str
"""
return self._signed_file.primary_fingerprint return self._signed_file.primary_fingerprint
@property @property
def valid_signature(self): def valid_signature(self):
"""C{True} if the .dsc has a valid signature
@type: bool
"""
return self._signed_file.valid return self._signed_file.valid
@property @property
def component(self): def component(self):
"""guessed component name
Might be wrong. Don't rely on this.
@type: str
"""
if 'Section' not in self.dsc: if 'Section' not in self.dsc:
return 'main' return 'main'
fields = self.dsc['Section'].split('/') fields = self.dsc['Section'].split('/')
......
...@@ -1550,7 +1550,6 @@ def get_packages_from_ftp(root, suite, component, architecture): ...@@ -1550,7 +1550,6 @@ def get_packages_from_ftp(root, suite, component, architecture):
@rtype: TagFile @rtype: TagFile
@return: apt_pkg class containing package data @return: apt_pkg class containing package data
""" """
filename = "%s/dists/%s/%s/binary-%s/Packages.gz" % (root, suite, component, architecture) filename = "%s/dists/%s/%s/binary-%s/Packages.gz" % (root, suite, component, architecture)
(fd, temp_file) = temp_filename() (fd, temp_file) = temp_filename()
...@@ -1576,15 +1575,20 @@ def deb_extract_control(fh): ...@@ -1576,15 +1575,20 @@ def deb_extract_control(fh):
################################################################################ ################################################################################
def mail_addresses_for_upload(maintainer, changed_by, fingerprint): def mail_addresses_for_upload(maintainer, changed_by, fingerprint):
"""Mail addresses to contact for an upload """mail addresses to contact for an upload
@type maintainer: str
@param maintainer: Maintainer field of the .changes file
Args: @type changed_by: str
maintainer (str): Maintainer field of the changes file @param changed_by: Changed-By field of the .changes file
changed_by (str): Changed-By field of the changes file
fingerprint (str): Fingerprint of the PGP key used to sign the upload
Returns: @type fingerprint: str
List of RFC 2047-encoded mail addresses to contact regarding this upload @param fingerprint: fingerprint of the key used to sign the upload
@rtype: list of str
@return: list of RFC 2047-encoded mail addresses to contact regarding
this upload
""" """
addresses = [maintainer] addresses = [maintainer]
if changed_by != maintainer: if changed_by != maintainer:
...@@ -1600,14 +1604,16 @@ def mail_addresses_for_upload(maintainer, changed_by, fingerprint): ...@@ -1600,14 +1604,16 @@ def mail_addresses_for_upload(maintainer, changed_by, fingerprint):
################################################################################ ################################################################################
def call_editor(text="", suffix=".txt"): def call_editor(text="", suffix=".txt"):
"""Run editor and return the result as a string """run editor and return the result as a string
@type text: str
@param text: initial text
Kwargs: @type suffix: str
text (str): initial text @param suffix: extension for temporary file
suffix (str): extension for temporary file
Returns: @rtype: str
string with the edited text @return: string with the edited text
""" """
editor = os.environ.get('VISUAL', os.environ.get('EDITOR', 'vi')) editor = os.environ.get('VISUAL', os.environ.get('EDITOR', 'vi'))
tmp = tempfile.NamedTemporaryFile(suffix=suffix, delete=False) tmp = tempfile.NamedTemporaryFile(suffix=suffix, delete=False)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册