diff options
Diffstat (limited to 'portage_with_autodep/bin/repoman')
-rwxr-xr-x | portage_with_autodep/bin/repoman | 2672 |
1 files changed, 2672 insertions, 0 deletions
diff --git a/portage_with_autodep/bin/repoman b/portage_with_autodep/bin/repoman new file mode 100755 index 0000000..f1fbc24 --- /dev/null +++ b/portage_with_autodep/bin/repoman @@ -0,0 +1,2672 @@ +#!/usr/bin/python -O +# Copyright 1999-2011 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 + +# Next to do: dep syntax checking in mask files +# Then, check to make sure deps are satisfiable (to avoid "can't find match for" problems) +# that last one is tricky because multiple profiles need to be checked. + +from __future__ import print_function + +import calendar +import copy +import errno +import formatter +import io +import logging +import optparse +import re +import signal +import stat +import sys +import tempfile +import time +import platform + +try: + from urllib.request import urlopen as urllib_request_urlopen +except ImportError: + from urllib import urlopen as urllib_request_urlopen + +from itertools import chain +from stat import S_ISDIR + +try: + import portage +except ImportError: + from os import path as osp + sys.path.insert(0, osp.join(osp.dirname(osp.dirname(osp.realpath(__file__))), "pym")) + import portage +portage._disable_legacy_globals() +portage.dep._internal_warnings = True + +try: + import xml.etree.ElementTree + from xml.parsers.expat import ExpatError +except ImportError: + msg = ["Please enable python's \"xml\" USE flag in order to use repoman."] + from portage.output import EOutput + out = EOutput() + for line in msg: + out.eerror(line) + sys.exit(1) + +from portage import os +from portage import subprocess_getstatusoutput +from portage import _encodings +from portage import _unicode_encode +from repoman.checks import run_checks +from repoman import utilities +from repoman.herdbase import make_herd_base +from _emerge.Package import Package +from _emerge.RootConfig import RootConfig +from _emerge.userquery import userquery +import portage.checksum +import portage.const +from portage import cvstree, normalize_path +from portage import util +from portage.exception import (FileNotFound, MissingParameter, + ParseError, PermissionDenied) +from portage.manifest import Manifest +from portage.process import find_binary, spawn +from portage.output import bold, create_color_func, \ + green, nocolor, red +from portage.output import ConsoleStyleFile, StyleWriter +from portage.util import cmp_sort_key, writemsg_level +from portage.package.ebuild.digestgen import digestgen +from portage.eapi import eapi_has_slot_deps, \ + eapi_has_use_deps, eapi_has_strong_blocks, eapi_has_iuse_defaults, \ + eapi_has_required_use, eapi_has_use_dep_defaults + +if sys.hexversion >= 0x3000000: + basestring = str + +util.initialize_logger() + +# 14 is the length of DESCRIPTION="" +max_desc_len = 100 +allowed_filename_chars="a-zA-Z0-9._-+:" +disallowed_filename_chars_re = re.compile(r'[^a-zA-Z0-9._\-+:]') +pv_toolong_re = re.compile(r'[0-9]{19,}') +bad = create_color_func("BAD") + +# A sane umask is needed for files that portage creates. +os.umask(0o22) +# Repoman sets it's own ACCEPT_KEYWORDS and we don't want it to +# behave incrementally. +repoman_incrementals = tuple(x for x in \ + portage.const.INCREMENTALS if x != 'ACCEPT_KEYWORDS') +repoman_settings = portage.config(local_config=False) +repoman_settings.lock() + +if repoman_settings.get("NOCOLOR", "").lower() in ("yes", "true") or \ + repoman_settings.get('TERM') == 'dumb' or \ + not sys.stdout.isatty(): + nocolor() + +def warn(txt): + print("repoman: " + txt) + +def err(txt): + warn(txt) + sys.exit(1) + +def exithandler(signum=None, frame=None): + logging.fatal("Interrupted; exiting...") + if signum is None: + sys.exit(1) + else: + sys.exit(128 + signum) + +signal.signal(signal.SIGINT,exithandler) + +class RepomanHelpFormatter(optparse.IndentedHelpFormatter): + """Repoman needs it's own HelpFormatter for now, because the default ones + murder the help text.""" + + def __init__(self, indent_increment=1, max_help_position=24, width=150, short_first=1): + optparse.HelpFormatter.__init__(self, indent_increment, max_help_position, width, short_first) + + def format_description(self, description): + return description + +class RepomanOptionParser(optparse.OptionParser): + """Add the on_tail function, ruby has it, optionParser should too + """ + + def __init__(self, *args, **kwargs): + optparse.OptionParser.__init__(self, *args, **kwargs) + self.tail = "" + + def on_tail(self, description): + self.tail += description + + def format_help(self, formatter=None): + result = optparse.OptionParser.format_help(self, formatter) + result += self.tail + return result + + +def ParseArgs(argv, qahelp): + """This function uses a customized optionParser to parse command line arguments for repoman + Args: + argv - a sequence of command line arguments + qahelp - a dict of qa warning to help message + Returns: + (opts, args), just like a call to parser.parse_args() + """ + + if argv and sys.hexversion < 0x3000000 and not isinstance(argv[0], unicode): + argv = [portage._unicode_decode(x) for x in argv] + + modes = { + 'commit' : 'Run a scan then commit changes', + 'ci' : 'Run a scan then commit changes', + 'fix' : 'Fix simple QA issues (stray digests, missing digests)', + 'full' : 'Scan directory tree and print all issues (not a summary)', + 'help' : 'Show this screen', + 'manifest' : 'Generate a Manifest (fetches files if necessary)', + 'manifest-check' : 'Check Manifests for missing or incorrect digests', + 'scan' : 'Scan directory tree for QA issues' + } + + mode_keys = list(modes) + mode_keys.sort() + + parser = RepomanOptionParser(formatter=RepomanHelpFormatter(), usage="%prog [options] [mode]") + parser.description = green(" ".join((os.path.basename(argv[0]), "1.2"))) + parser.description += "\nCopyright 1999-2007 Gentoo Foundation" + parser.description += "\nDistributed under the terms of the GNU General Public License v2" + parser.description += "\nmodes: " + " | ".join(map(green,mode_keys)) + + parser.add_option('-a', '--ask', dest='ask', action='store_true', default=False, + help='Request a confirmation before commiting') + + parser.add_option('-m', '--commitmsg', dest='commitmsg', + help='specify a commit message on the command line') + + parser.add_option('-M', '--commitmsgfile', dest='commitmsgfile', + help='specify a path to a file that contains a commit message') + + parser.add_option('-p', '--pretend', dest='pretend', default=False, + action='store_true', help='don\'t commit or fix anything; just show what would be done') + + parser.add_option('-q', '--quiet', dest="quiet", action="count", default=0, + help='do not print unnecessary messages') + + parser.add_option('-f', '--force', dest='force', default=False, action='store_true', + help='Commit with QA violations') + + parser.add_option('--vcs', dest='vcs', + help='Force using specific VCS instead of autodetection') + + parser.add_option('-v', '--verbose', dest="verbosity", action='count', + help='be very verbose in output', default=0) + + parser.add_option('-V', '--version', dest='version', action='store_true', + help='show version info') + + parser.add_option('-x', '--xmlparse', dest='xml_parse', action='store_true', + default=False, help='forces the metadata.xml parse check to be carried out') + + parser.add_option('-i', '--ignore-arches', dest='ignore_arches', action='store_true', + default=False, help='ignore arch-specific failures (where arch != host)') + + parser.add_option('-I', '--ignore-masked', dest='ignore_masked', action='store_true', + default=False, help='ignore masked packages (not allowed with commit mode)') + + parser.add_option('-d', '--include-dev', dest='include_dev', action='store_true', + default=False, help='include dev profiles in dependency checks') + + parser.add_option('--unmatched-removal', dest='unmatched_removal', action='store_true', + default=False, help='enable strict checking of package.mask and package.unmask files for unmatched removal atoms') + + parser.add_option('--without-mask', dest='without_mask', action='store_true', + default=False, help='behave as if no package.mask entries exist (not allowed with commit mode)') + + parser.add_option('--mode', type='choice', dest='mode', choices=list(modes), + help='specify which mode repoman will run in (default=full)') + + parser.on_tail("\n " + green("Modes".ljust(20) + " Description\n")) + + for k in mode_keys: + parser.on_tail(" %s %s\n" % (k.ljust(20), modes[k])) + + parser.on_tail("\n " + green("QA keyword".ljust(20) + " Description\n")) + + sorted_qa = list(qahelp) + sorted_qa.sort() + for k in sorted_qa: + parser.on_tail(" %s %s\n" % (k.ljust(20), qahelp[k])) + + opts, args = parser.parse_args(argv[1:]) + + if opts.mode == 'help': + parser.print_help(short=False) + + for arg in args: + if arg in modes: + if not opts.mode: + opts.mode = arg + break + else: + parser.error("invalid mode: %s" % arg) + + if not opts.mode: + opts.mode = 'full' + + if opts.mode == 'ci': + opts.mode = 'commit' # backwards compat shortcut + + if opts.mode == 'commit' and not (opts.force or opts.pretend): + if opts.ignore_masked: + parser.error('Commit mode and --ignore-masked are not compatible') + if opts.without_mask: + parser.error('Commit mode and --without-mask are not compatible') + + # Use the verbosity and quiet options to fiddle with the loglevel appropriately + for val in range(opts.verbosity): + logger = logging.getLogger() + logger.setLevel(logger.getEffectiveLevel() - 10) + + for val in range(opts.quiet): + logger = logging.getLogger() + logger.setLevel(logger.getEffectiveLevel() + 10) + + return (opts, args) + +qahelp={ + "CVS/Entries.IO_error":"Attempting to commit, and an IO error was encountered access the Entries file", + "desktop.invalid":"desktop-file-validate reports errors in a *.desktop file", + "ebuild.invalidname":"Ebuild files with a non-parseable or syntactically incorrect name (or using 2.1 versioning extensions)", + "ebuild.namenomatch":"Ebuild files that do not have the same name as their parent directory", + "changelog.ebuildadded":"An ebuild was added but the ChangeLog was not modified", + "changelog.missing":"Missing ChangeLog files", + "ebuild.notadded":"Ebuilds that exist but have not been added to cvs", + "ebuild.patches":"PATCHES variable should be a bash array to ensure white space safety", + "changelog.notadded":"ChangeLogs that exist but have not been added to cvs", + "dependency.unknown" : "Ebuild has a dependency that refers to an unknown package (which may be provided by an overlay)", + "file.executable":"Ebuilds, digests, metadata.xml, Manifest, and ChangeLog do note need the executable bit", + "file.size":"Files in the files directory must be under 20 KiB", + "file.size.fatal":"Files in the files directory must be under 60 KiB", + "file.name":"File/dir name must be composed of only the following chars: %s " % allowed_filename_chars, + "file.UTF8":"File is not UTF8 compliant", + "inherit.autotools":"Ebuild inherits autotools but does not call eautomake, eautoconf or eautoreconf", + "inherit.deprecated":"Ebuild inherits a deprecated eclass", + "java.eclassesnotused":"With virtual/jdk in DEPEND you must inherit a java eclass", + "wxwidgets.eclassnotused":"Ebuild DEPENDs on x11-libs/wxGTK without inheriting wxwidgets.eclass", + "KEYWORDS.dropped":"Ebuilds that appear to have dropped KEYWORDS for some arch", + "KEYWORDS.missing":"Ebuilds that have a missing or empty KEYWORDS variable", + "KEYWORDS.stable":"Ebuilds that have been added directly with stable KEYWORDS", + "KEYWORDS.stupid":"Ebuilds that use KEYWORDS=-* instead of package.mask", + "LICENSE.missing":"Ebuilds that have a missing or empty LICENSE variable", + "LICENSE.virtual":"Virtuals that have a non-empty LICENSE variable", + "DESCRIPTION.missing":"Ebuilds that have a missing or empty DESCRIPTION variable", + "DESCRIPTION.toolong":"DESCRIPTION is over %d characters" % max_desc_len, + "EAPI.definition":"EAPI is defined after an inherit call (must be defined before)", + "EAPI.deprecated":"Ebuilds that use features that are deprecated in the current EAPI", + "EAPI.incompatible":"Ebuilds that use features that are only available with a different EAPI", + "EAPI.unsupported":"Ebuilds that have an unsupported EAPI version (you must upgrade portage)", + "SLOT.invalid":"Ebuilds that have a missing or invalid SLOT variable value", + "HOMEPAGE.missing":"Ebuilds that have a missing or empty HOMEPAGE variable", + "HOMEPAGE.virtual":"Virtuals that have a non-empty HOMEPAGE variable", + "DEPEND.bad":"User-visible ebuilds with bad DEPEND settings (matched against *visible* ebuilds)", + "RDEPEND.bad":"User-visible ebuilds with bad RDEPEND settings (matched against *visible* ebuilds)", + "PDEPEND.bad":"User-visible ebuilds with bad PDEPEND settings (matched against *visible* ebuilds)", + "DEPEND.badmasked":"Masked ebuilds with bad DEPEND settings (matched against *all* ebuilds)", + "RDEPEND.badmasked":"Masked ebuilds with RDEPEND settings (matched against *all* ebuilds)", + "PDEPEND.badmasked":"Masked ebuilds with PDEPEND settings (matched against *all* ebuilds)", + "DEPEND.badindev":"User-visible ebuilds with bad DEPEND settings (matched against *visible* ebuilds) in developing arch", + "RDEPEND.badindev":"User-visible ebuilds with bad RDEPEND settings (matched against *visible* ebuilds) in developing arch", + "PDEPEND.badindev":"User-visible ebuilds with bad PDEPEND settings (matched against *visible* ebuilds) in developing arch", + "DEPEND.badmaskedindev":"Masked ebuilds with bad DEPEND settings (matched against *all* ebuilds) in developing arch", + "RDEPEND.badmaskedindev":"Masked ebuilds with RDEPEND settings (matched against *all* ebuilds) in developing arch", + "PDEPEND.badmaskedindev":"Masked ebuilds with PDEPEND settings (matched against *all* ebuilds) in developing arch", + "PDEPEND.suspect":"PDEPEND contains a package that usually only belongs in DEPEND.", + "DEPEND.syntax":"Syntax error in DEPEND (usually an extra/missing space/parenthesis)", + "RDEPEND.syntax":"Syntax error in RDEPEND (usually an extra/missing space/parenthesis)", + "PDEPEND.syntax":"Syntax error in PDEPEND (usually an extra/missing space/parenthesis)", + "DEPEND.badtilde":"DEPEND uses the ~ dep operator with a non-zero revision part, which is useless (the revision is ignored)", + "RDEPEND.badtilde":"RDEPEND uses the ~ dep operator with a non-zero revision part, which is useless (the revision is ignored)", + "PDEPEND.badtilde":"PDEPEND uses the ~ dep operator with a non-zero revision part, which is useless (the revision is ignored)", + "LICENSE.syntax":"Syntax error in LICENSE (usually an extra/missing space/parenthesis)", + "PROVIDE.syntax":"Syntax error in PROVIDE (usually an extra/missing space/parenthesis)", + "PROPERTIES.syntax":"Syntax error in PROPERTIES (usually an extra/missing space/parenthesis)", + "RESTRICT.syntax":"Syntax error in RESTRICT (usually an extra/missing space/parenthesis)", + "REQUIRED_USE.syntax":"Syntax error in REQUIRED_USE (usually an extra/missing space/parenthesis)", + "SRC_URI.syntax":"Syntax error in SRC_URI (usually an extra/missing space/parenthesis)", + "SRC_URI.mirror":"A uri listed in profiles/thirdpartymirrors is found in SRC_URI", + "ebuild.syntax":"Error generating cache entry for ebuild; typically caused by ebuild syntax error or digest verification failure", + "ebuild.output":"A simple sourcing of the ebuild produces output; this breaks ebuild policy.", + "ebuild.nesteddie":"Placing 'die' inside ( ) prints an error, but doesn't stop the ebuild.", + "variable.invalidchar":"A variable contains an invalid character that is not part of the ASCII character set", + "variable.readonly":"Assigning a readonly variable", + "variable.usedwithhelpers":"Ebuild uses D, ROOT, ED, EROOT or EPREFIX with helpers", + "LIVEVCS.stable":"This ebuild is a live checkout from a VCS but has stable keywords.", + "LIVEVCS.unmasked":"This ebuild is a live checkout from a VCS but has keywords and is not masked in the global package.mask.", + "IUSE.invalid":"This ebuild has a variable in IUSE that is not in the use.desc or its metadata.xml file", + "IUSE.missing":"This ebuild has a USE conditional which references a flag that is not listed in IUSE", + "IUSE.undefined":"This ebuild does not define IUSE (style guideline says to define IUSE even when empty)", + "LICENSE.invalid":"This ebuild is listing a license that doesnt exist in portages license/ dir.", + "KEYWORDS.invalid":"This ebuild contains KEYWORDS that are not listed in profiles/arch.list or for which no valid profile was found", + "RDEPEND.implicit":"RDEPEND is unset in the ebuild which triggers implicit RDEPEND=$DEPEND assignment (prior to EAPI 4)", + "RDEPEND.suspect":"RDEPEND contains a package that usually only belongs in DEPEND.", + "RESTRICT.invalid":"This ebuild contains invalid RESTRICT values.", + "digest.assumed":"Existing digest must be assumed correct (Package level only)", + "digest.missing":"Some files listed in SRC_URI aren't referenced in the Manifest", + "digest.unused":"Some files listed in the Manifest aren't referenced in SRC_URI", + "ebuild.nostable":"There are no ebuilds that are marked as stable for your ARCH", + "ebuild.allmasked":"All ebuilds are masked for this package (Package level only)", + "ebuild.majorsyn":"This ebuild has a major syntax error that may cause the ebuild to fail partially or fully", + "ebuild.minorsyn":"This ebuild has a minor syntax error that contravenes gentoo coding style", + "ebuild.badheader":"This ebuild has a malformed header", + "eprefixify.defined":"The ebuild uses eprefixify, but does not inherit the prefix eclass", + "manifest.bad":"Manifest has missing or incorrect digests", + "metadata.missing":"Missing metadata.xml files", + "metadata.bad":"Bad metadata.xml files", + "metadata.warning":"Warnings in metadata.xml files", + "portage.internal":"The ebuild uses an internal Portage function", + "virtual.oldstyle":"The ebuild PROVIDEs an old-style virtual (see GLEP 37)", + "usage.obsolete":"The ebuild makes use of an obsolete construct", + "upstream.workaround":"The ebuild works around an upstream bug, an upstream bug should be filed and tracked in bugs.gentoo.org" +} + +qacats = list(qahelp) +qacats.sort() + +qawarnings = set(( +"changelog.missing", +"changelog.notadded", +"dependency.unknown", +"digest.assumed", +"digest.unused", +"ebuild.notadded", +"ebuild.nostable", +"ebuild.allmasked", +"ebuild.nesteddie", +"desktop.invalid", +"DEPEND.badmasked","RDEPEND.badmasked","PDEPEND.badmasked", +"DEPEND.badindev","RDEPEND.badindev","PDEPEND.badindev", +"DEPEND.badmaskedindev","RDEPEND.badmaskedindev","PDEPEND.badmaskedindev", +"DEPEND.badtilde", "RDEPEND.badtilde", "PDEPEND.badtilde", +"DESCRIPTION.toolong", +"EAPI.deprecated", +"HOMEPAGE.virtual", +"LICENSE.virtual", +"KEYWORDS.dropped", +"KEYWORDS.stupid", +"KEYWORDS.missing", +"IUSE.undefined", +"PDEPEND.suspect", +"RDEPEND.implicit", +"RDEPEND.suspect", +"RESTRICT.invalid", +"SRC_URI.mirror", +"ebuild.minorsyn", +"ebuild.badheader", +"ebuild.patches", +"file.size", +"inherit.autotools", +"inherit.deprecated", +"java.eclassesnotused", +"wxwidgets.eclassnotused", +"metadata.warning", +"portage.internal", +"usage.obsolete", +"upstream.workaround", +"virtual.oldstyle", +"LIVEVCS.stable", +"LIVEVCS.unmasked", +)) + +non_ascii_re = re.compile(r'[^\x00-\x7f]') + +missingvars = ["KEYWORDS", "LICENSE", "DESCRIPTION", "HOMEPAGE"] +allvars = set(x for x in portage.auxdbkeys if not x.startswith("UNUSED_")) +allvars.update(Package.metadata_keys) +allvars = sorted(allvars) +commitmessage=None +for x in missingvars: + x += ".missing" + if x not in qacats: + logging.warn('* missingvars values need to be added to qahelp ("%s")' % x) + qacats.append(x) + qawarnings.add(x) + +valid_restrict = frozenset(["binchecks", "bindist", + "fetch", "installsources", "mirror", + "primaryuri", "strip", "test", "userpriv"]) + +live_eclasses = frozenset([ + "bzr", + "cvs", + "darcs", + "git", + "git-2", + "mercurial", + "subversion", + "tla", +]) + +suspect_rdepend = frozenset([ + "app-arch/cabextract", + "app-arch/rpm2targz", + "app-doc/doxygen", + "dev-lang/nasm", + "dev-lang/swig", + "dev-lang/yasm", + "dev-perl/extutils-pkgconfig", + "dev-util/byacc", + "dev-util/cmake", + "dev-util/ftjam", + "dev-util/gperf", + "dev-util/gtk-doc", + "dev-util/gtk-doc-am", + "dev-util/intltool", + "dev-util/jam", + "dev-util/pkgconfig", + "dev-util/scons", + "dev-util/unifdef", + "dev-util/yacc", + "media-gfx/ebdftopcf", + "sys-apps/help2man", + "sys-devel/autoconf", + "sys-devel/automake", + "sys-devel/bin86", + "sys-devel/bison", + "sys-devel/dev86", + "sys-devel/flex", + "sys-devel/m4", + "sys-devel/pmake", + "virtual/linux-sources", + "x11-misc/bdftopcf", + "x11-misc/imake", +]) + +metadata_dtd_uri = 'http://www.gentoo.org/dtd/metadata.dtd' +# force refetch if the local copy creation time is older than this +metadata_dtd_ctime_interval = 60 * 60 * 24 * 7 # 7 days + +# file.executable +no_exec = frozenset(["Manifest","ChangeLog","metadata.xml"]) + +options, arguments = ParseArgs(sys.argv, qahelp) + +if options.version: + print("Portage", portage.VERSION) + sys.exit(0) + +# Set this to False when an extraordinary issue (generally +# something other than a QA issue) makes it impossible to +# commit (like if Manifest generation fails). +can_force = True + +portdir, portdir_overlay, mydir = utilities.FindPortdir(repoman_settings) +if portdir is None: + sys.exit(1) + +myreporoot = os.path.basename(portdir_overlay) +myreporoot += mydir[len(portdir_overlay):] + +if options.vcs: + if options.vcs in ('cvs', 'svn', 'git', 'bzr', 'hg'): + vcs = options.vcs + else: + vcs = None +else: + vcses = utilities.FindVCS() + if len(vcses) > 1: + print(red('*** Ambiguous workdir -- more than one VCS found at the same depth: %s.' % ', '.join(vcses))) + print(red('*** Please either clean up your workdir or specify --vcs option.')) + sys.exit(1) + elif vcses: + vcs = vcses[0] + else: + vcs = None + +# Note: We don't use ChangeLogs in distributed SCMs. +# It will be generated on server side from scm log, +# before package moves to the rsync server. +# This is needed because we try to avoid merge collisions. +check_changelog = vcs in ('cvs', 'svn') + +# Disable copyright/mtime check if vcs does not preserve mtime (bug #324075). +vcs_preserves_mtime = vcs not in ('git',) + +vcs_local_opts = repoman_settings.get("REPOMAN_VCS_LOCAL_OPTS", "").split() +vcs_global_opts = repoman_settings.get("REPOMAN_VCS_GLOBAL_OPTS") +if vcs_global_opts is None: + if vcs in ('cvs', 'svn'): + vcs_global_opts = "-q" + else: + vcs_global_opts = "" +vcs_global_opts = vcs_global_opts.split() + +if vcs == "cvs" and \ + "commit" == options.mode and \ + "RMD160" not in portage.checksum.hashorigin_map: + from portage.util import grablines + repo_lines = grablines("./CVS/Repository") + if repo_lines and \ + "gentoo-x86" == repo_lines[0].strip().split(os.path.sep)[0]: + msg = "Please install " \ + "pycrypto or enable python's ssl USE flag in order " \ + "to enable RMD160 hash support. See bug #198398 for " \ + "more information." + prefix = bad(" * ") + from textwrap import wrap + for line in wrap(msg, 70): + print(prefix + line) + sys.exit(1) + del repo_lines + +if options.mode == 'commit' and not options.pretend and not vcs: + logging.info("Not in a version controlled repository; enabling pretend mode.") + options.pretend = True + +# Ensure that PORTDIR_OVERLAY contains the repository corresponding to $PWD. +repoman_settings = portage.config(local_config=False) +repoman_settings['PORTDIR_OVERLAY'] = "%s %s" % \ + (repoman_settings.get('PORTDIR_OVERLAY', ''), portdir_overlay) +# We have to call the config constructor again so +# that config.repositories is initialized correctly. +repoman_settings = portage.config(local_config=False, env=dict(os.environ, + PORTDIR_OVERLAY=repoman_settings['PORTDIR_OVERLAY'])) + +root = '/' +trees = { + root : {'porttree' : portage.portagetree(root, settings=repoman_settings)} +} +portdb = trees[root]['porttree'].dbapi + +# Constrain dependency resolution to the master(s) +# that are specified in layout.conf. +portdir_overlay = os.path.realpath(portdir_overlay) +repo_info = portdb._repo_info[portdir_overlay] +portdb.porttrees = list(repo_info.eclass_db.porttrees) +portdir = portdb.porttrees[0] + +# Generate an appropriate PORTDIR_OVERLAY value for passing into the +# profile-specific config constructor calls. +env = os.environ.copy() +env['PORTDIR'] = portdir +env['PORTDIR_OVERLAY'] = ' '.join(portdb.porttrees[1:]) + +logging.info('Setting paths:') +logging.info('PORTDIR = "' + portdir + '"') +logging.info('PORTDIR_OVERLAY = "%s"' % env['PORTDIR_OVERLAY']) + +# It's confusing if these warnings are displayed without the user +# being told which profile they come from, so disable them. +env['FEATURES'] = env.get('FEATURES', '') + ' -unknown-features-warn' + +categories = [] +for path in set([portdir, portdir_overlay]): + categories.extend(portage.util.grabfile( + os.path.join(path, 'profiles', 'categories'))) +repoman_settings.categories = tuple(sorted( + portage.util.stack_lists([categories], incremental=1))) +del categories + +portdb.settings = repoman_settings +root_config = RootConfig(repoman_settings, trees[root], None) +# We really only need to cache the metadata that's necessary for visibility +# filtering. Anything else can be discarded to reduce memory consumption. +portdb._aux_cache_keys.clear() +portdb._aux_cache_keys.update(["EAPI", "KEYWORDS", "SLOT"]) + +reposplit = myreporoot.split(os.path.sep) +repolevel = len(reposplit) + +# check if it's in $PORTDIR/$CATEGORY/$PN , otherwise bail if commiting. +# Reason for this is if they're trying to commit in just $FILESDIR/*, the Manifest needs updating. +# this check ensures that repoman knows where it is, and the manifest recommit is at least possible. +if options.mode == 'commit' and repolevel not in [1,2,3]: + print(red("***")+" Commit attempts *must* be from within a vcs co, category, or package directory.") + print(red("***")+" Attempting to commit from a packages files directory will be blocked for instance.") + print(red("***")+" This is intended behaviour, to ensure the manifest is recommitted for a package.") + print(red("***")) + err("Unable to identify level we're commiting from for %s" % '/'.join(reposplit)) + +startdir = normalize_path(mydir) +repodir = startdir +for x in range(0, repolevel - 1): + repodir = os.path.dirname(repodir) +repodir = os.path.realpath(repodir) + +def caterror(mycat): + err(mycat+" is not an official category. Skipping QA checks in this directory.\nPlease ensure that you add "+catdir+" to "+repodir+"/profiles/categories\nif it is a new category.") + +class ProfileDesc(object): + __slots__ = ('abs_path', 'arch', 'status', 'sub_path', 'tree_path',) + def __init__(self, arch, status, sub_path, tree_path): + self.arch = arch + self.status = status + if sub_path: + sub_path = normalize_path(sub_path.lstrip(os.sep)) + self.sub_path = sub_path + self.tree_path = tree_path + if tree_path: + self.abs_path = os.path.join(tree_path, 'profiles', self.sub_path) + else: + self.abs_path = tree_path + + def __str__(self): + if self.sub_path: + return self.sub_path + return 'empty profile' + +profile_list = [] +valid_profile_types = frozenset(['dev', 'exp', 'stable']) + +# get lists of valid keywords, licenses, and use +kwlist = set() +liclist = set() +uselist = set() +global_pmasklines = [] + +for path in portdb.porttrees: + try: + liclist.update(os.listdir(os.path.join(path, "licenses"))) + except OSError: + pass + kwlist.update(portage.grabfile(os.path.join(path, + "profiles", "arch.list"))) + + use_desc = portage.grabfile(os.path.join(path, 'profiles', 'use.desc')) + for x in use_desc: + x = x.split() + if x: + uselist.add(x[0]) + + expand_desc_dir = os.path.join(path, 'profiles', 'desc') + try: + expand_list = os.listdir(expand_desc_dir) + except OSError: + pass + else: + for fn in expand_list: + if not fn[-5:] == '.desc': + continue + use_prefix = fn[:-5].lower() + '_' + for x in portage.grabfile(os.path.join(expand_desc_dir, fn)): + x = x.split() + if x: + uselist.add(use_prefix + x[0]) + + global_pmasklines.append(portage.util.grabfile_package( + os.path.join(path, 'profiles', 'package.mask'), recursive=1, verify_eapi=True)) + + desc_path = os.path.join(path, 'profiles', 'profiles.desc') + try: + desc_file = io.open(_unicode_encode(desc_path, + encoding=_encodings['fs'], errors='strict'), + mode='r', encoding=_encodings['repo.content'], errors='replace') + except EnvironmentError: + pass + else: + for i, x in enumerate(desc_file): + if x[0] == "#": + continue + arch = x.split() + if len(arch) == 0: + continue + if len(arch) != 3: + err("wrong format: \"" + bad(x.strip()) + "\" in " + \ + desc_path + " line %d" % (i+1, )) + elif arch[0] not in kwlist: + err("invalid arch: \"" + bad(arch[0]) + "\" in " + \ + desc_path + " line %d" % (i+1, )) + elif arch[2] not in valid_profile_types: + err("invalid profile type: \"" + bad(arch[2]) + "\" in " + \ + desc_path + " line %d" % (i+1, )) + profile_desc = ProfileDesc(arch[0], arch[2], arch[1], path) + if not os.path.isdir(profile_desc.abs_path): + logging.error( + "Invalid %s profile (%s) for arch %s in %s line %d", + arch[2], arch[1], arch[0], desc_path, i+1) + continue + if os.path.exists( + os.path.join(profile_desc.abs_path, 'deprecated')): + continue + profile_list.append(profile_desc) + desc_file.close() + +repoman_settings['PORTAGE_ARCHLIST'] = ' '.join(sorted(kwlist)) +repoman_settings.backup_changes('PORTAGE_ARCHLIST') + +global_pmasklines = portage.util.stack_lists(global_pmasklines, incremental=1) +global_pmaskdict = {} +for x in global_pmasklines: + global_pmaskdict.setdefault(x.cp, []).append(x) +del global_pmasklines + +def has_global_mask(pkg): + mask_atoms = global_pmaskdict.get(pkg.cp) + if mask_atoms: + pkg_list = [pkg] + for x in mask_atoms: + if portage.dep.match_from_list(x, pkg_list): + return x + return None + +# Ensure that profile sub_path attributes are unique. Process in reverse order +# so that profiles with duplicate sub_path from overlays will override +# profiles with the same sub_path from parent repos. +profiles = {} +profile_list.reverse() +profile_sub_paths = set() +for prof in profile_list: + if prof.sub_path in profile_sub_paths: + continue + profile_sub_paths.add(prof.sub_path) + profiles.setdefault(prof.arch, []).append(prof) + +# Use an empty profile for checking dependencies of +# packages that have empty KEYWORDS. +prof = ProfileDesc('**', 'stable', '', '') +profiles.setdefault(prof.arch, []).append(prof) + +for x in repoman_settings.archlist(): + if x[0] == "~": + continue + if x not in profiles: + print(red("\""+x+"\" doesn't have a valid profile listed in profiles.desc.")) + print(red("You need to either \"cvs update\" your profiles dir or follow this")) + print(red("up with the "+x+" team.")) + print() + +if not liclist: + logging.fatal("Couldn't find licenses?") + sys.exit(1) + +if not kwlist: + logging.fatal("Couldn't read KEYWORDS from arch.list") + sys.exit(1) + +if not uselist: + logging.fatal("Couldn't find use.desc?") + sys.exit(1) + +scanlist=[] +if repolevel==2: + #we are inside a category directory + catdir=reposplit[-1] + if catdir not in repoman_settings.categories: + caterror(catdir) + mydirlist=os.listdir(startdir) + for x in mydirlist: + if x == "CVS" or x.startswith("."): + continue + if os.path.isdir(startdir+"/"+x): + scanlist.append(catdir+"/"+x) + repo_subdir = catdir + os.sep +elif repolevel==1: + for x in repoman_settings.categories: + if not os.path.isdir(startdir+"/"+x): + continue + for y in os.listdir(startdir+"/"+x): + if y == "CVS" or y.startswith("."): + continue + if os.path.isdir(startdir+"/"+x+"/"+y): + scanlist.append(x+"/"+y) + repo_subdir = "" +elif repolevel==3: + catdir = reposplit[-2] + if catdir not in repoman_settings.categories: + caterror(catdir) + scanlist.append(catdir+"/"+reposplit[-1]) + repo_subdir = scanlist[-1] + os.sep +else: + msg = 'Repoman is unable to determine PORTDIR or PORTDIR_OVERLAY' + \ + ' from the current working directory' + logging.critical(msg) + sys.exit(1) + +repo_subdir_len = len(repo_subdir) +scanlist.sort() + +logging.debug("Found the following packages to scan:\n%s" % '\n'.join(scanlist)) + +def dev_keywords(profiles): + """ + Create a set of KEYWORDS values that exist in 'dev' + profiles. These are used + to trigger a message notifying the user when they might + want to add the --include-dev option. + """ + type_arch_map = {} + for arch, arch_profiles in profiles.items(): + for prof in arch_profiles: + arch_set = type_arch_map.get(prof.status) + if arch_set is None: + arch_set = set() + type_arch_map[prof.status] = arch_set + arch_set.add(arch) + + dev_keywords = type_arch_map.get('dev', set()) + dev_keywords.update(['~' + arch for arch in dev_keywords]) + return frozenset(dev_keywords) + +dev_keywords = dev_keywords(profiles) + +stats={} +fails={} + +# provided by the desktop-file-utils package +desktop_file_validate = find_binary("desktop-file-validate") +desktop_pattern = re.compile(r'.*\.desktop$') + +for x in qacats: + stats[x]=0 + fails[x]=[] + +xmllint_capable = False +metadata_dtd = os.path.join(repoman_settings["DISTDIR"], 'metadata.dtd') + +def parsedate(s): + """Parse a RFC 822 date and time string. + This is required for python3 compatibility, since the + rfc822.parsedate() function is not available.""" + + s_split = [] + for x in s.upper().split(): + for y in x.split(','): + if y: + s_split.append(y) + + if len(s_split) != 6: + return None + + # %a, %d %b %Y %H:%M:%S %Z + a, d, b, Y, H_M_S, Z = s_split + + # Convert month to integer, since strptime %w is locale-dependent. + month_map = {'JAN':1, 'FEB':2, 'MAR':3, 'APR':4, 'MAY':5, 'JUN':6, + 'JUL':7, 'AUG':8, 'SEP':9, 'OCT':10, 'NOV':11, 'DEC':12} + m = month_map.get(b) + if m is None: + return None + m = str(m).rjust(2, '0') + + return time.strptime(':'.join((Y, m, d, H_M_S)), '%Y:%m:%d:%H:%M:%S') + +def fetch_metadata_dtd(): + """ + Fetch metadata.dtd if it doesn't exist or the ctime is older than + metadata_dtd_ctime_interval. + @rtype: bool + @returns: True if successful, otherwise False + """ + + must_fetch = True + metadata_dtd_st = None + current_time = int(time.time()) + try: + metadata_dtd_st = os.stat(metadata_dtd) + except EnvironmentError as e: + if e.errno not in (errno.ENOENT, errno.ESTALE): + raise + del e + else: + # Trigger fetch if metadata.dtd mtime is old or clock is wrong. + if abs(current_time - metadata_dtd_st.st_ctime) \ + < metadata_dtd_ctime_interval: + must_fetch = False + + if must_fetch: + print() + print(green("***") + " the local copy of metadata.dtd " + \ + "needs to be refetched, doing that now") + print() + try: + url_f = urllib_request_urlopen(metadata_dtd_uri) + msg_info = url_f.info() + last_modified = msg_info.get('last-modified') + if last_modified is not None: + last_modified = parsedate(last_modified) + if last_modified is not None: + last_modified = calendar.timegm(last_modified) + + metadata_dtd_tmp = "%s.%s" % (metadata_dtd, os.getpid()) + try: + local_f = open(metadata_dtd_tmp, mode='wb') + local_f.write(url_f.read()) + local_f.close() + if last_modified is not None: + try: + os.utime(metadata_dtd_tmp, + (int(last_modified), int(last_modified))) + except OSError: + # This fails on some odd non-unix-like filesystems. + # We don't really need the mtime to be preserved + # anyway here (currently we use ctime to trigger + # fetch), so just ignore it. + pass + os.rename(metadata_dtd_tmp, metadata_dtd) + finally: + try: + os.unlink(metadata_dtd_tmp) + except OSError: + pass + + url_f.close() + + except EnvironmentError as e: + print() + print(red("!!!")+" attempting to fetch '%s', caught" % metadata_dtd_uri) + print(red("!!!")+" exception '%s' though." % (e,)) + print(red("!!!")+" fetching new metadata.dtd failed, aborting") + return False + + return True + +if options.mode == "manifest": + pass +elif not find_binary('xmllint'): + print(red("!!! xmllint not found. Can't check metadata.xml.\n")) + if options.xml_parse or repolevel==3: + print(red("!!!")+" sorry, xmllint is needed. failing\n") + sys.exit(1) +else: + if not fetch_metadata_dtd(): + sys.exit(1) + #this can be problematic if xmllint changes their output + xmllint_capable=True + +if options.mode == 'commit' and vcs: + utilities.detect_vcs_conflicts(options, vcs) + +if options.mode == "manifest": + pass +elif options.pretend: + print(green("\nRepoMan does a once-over of the neighborhood...")) +else: + print(green("\nRepoMan scours the neighborhood...")) + +new_ebuilds = set() +modified_ebuilds = set() +modified_changelogs = set() +mychanged = [] +mynew = [] +myremoved = [] + +if vcs == "cvs": + mycvstree = cvstree.getentries("./", recursive=1) + mychanged = cvstree.findchanged(mycvstree, recursive=1, basedir="./") + mynew = cvstree.findnew(mycvstree, recursive=1, basedir="./") +if vcs == "svn": + svnstatus = os.popen("svn status").readlines() + mychanged = [ "./" + elem.split()[-1:][0] for elem in svnstatus if elem and elem[:1] in "MR" ] + mynew = [ "./" + elem.split()[-1:][0] for elem in svnstatus if elem.startswith("A") ] +elif vcs == "git": + mychanged = os.popen("git diff-index --name-only --relative --diff-filter=M HEAD").readlines() + mychanged = ["./" + elem[:-1] for elem in mychanged] + + mynew = os.popen("git diff-index --name-only --relative --diff-filter=A HEAD").readlines() + mynew = ["./" + elem[:-1] for elem in mynew] +elif vcs == "bzr": + bzrstatus = os.popen("bzr status -S .").readlines() + mychanged = [ "./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem and elem[1:2] == "M" ] + mynew = [ "./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem and ( elem[1:2] == "NK" or elem[0:1] == "R" ) ] +elif vcs == "hg": + mychanged = os.popen("hg status --no-status --modified .").readlines() + mychanged = ["./" + elem.rstrip() for elem in mychanged] + mynew = os.popen("hg status --no-status --added .").readlines() + mynew = ["./" + elem.rstrip() for elem in mynew] + +if vcs: + new_ebuilds.update(x for x in mynew if x.endswith(".ebuild")) + modified_ebuilds.update(x for x in mychanged if x.endswith(".ebuild")) + modified_changelogs.update(x for x in chain(mychanged, mynew) \ + if os.path.basename(x) == "ChangeLog") + +have_pmasked = False +have_dev_keywords = False +dofail = 0 +arch_caches={} +arch_xmatch_caches = {} +shared_xmatch_caches = {"cp-list":{}} + +# Disable the "ebuild.notadded" check when not in commit mode and +# running `svn status` in every package dir will be too expensive. + +check_ebuild_notadded = not \ + (vcs == "svn" and repolevel < 3 and options.mode != "commit") + +# Build a regex from thirdpartymirrors for the SRC_URI.mirror check. +thirdpartymirrors = [] +for v in repoman_settings.thirdpartymirrors().values(): + thirdpartymirrors.extend(v) + +class _MetadataTreeBuilder(xml.etree.ElementTree.TreeBuilder): + """ + Implements doctype() as required to avoid deprecation warnings with + >=python-2.7. + """ + def doctype(self, name, pubid, system): + pass + +try: + herd_base = make_herd_base(os.path.join(repoman_settings["PORTDIR"], "metadata/herds.xml")) +except (EnvironmentError, ParseError, PermissionDenied) as e: + err(str(e)) +except FileNotFound: + # TODO: Download as we do for metadata.dtd, but add a way to + # disable for non-gentoo repoman users who may not have herds. + herd_base = None + +for x in scanlist: + #ebuilds and digests added to cvs respectively. + logging.info("checking package %s" % x) + eadded=[] + catdir,pkgdir=x.split("/") + checkdir=repodir+"/"+x + checkdir_relative = "" + if repolevel < 3: + checkdir_relative = os.path.join(pkgdir, checkdir_relative) + if repolevel < 2: + checkdir_relative = os.path.join(catdir, checkdir_relative) + checkdir_relative = os.path.join(".", checkdir_relative) + generated_manifest = False + + if options.mode == "manifest" or \ + (options.mode != 'manifest-check' and \ + 'digest' in repoman_settings.features) or \ + options.mode in ('commit', 'fix') and not options.pretend: + auto_assumed = set() + fetchlist_dict = portage.FetchlistDict(checkdir, + repoman_settings, portdb) + if options.mode == 'manifest' and options.force: + portage._doebuild_manifest_exempt_depend += 1 + try: + distdir = repoman_settings['DISTDIR'] + mf = portage.manifest.Manifest(checkdir, distdir, + fetchlist_dict=fetchlist_dict) + mf.create(requiredDistfiles=None, + assumeDistHashesAlways=True) + for distfiles in fetchlist_dict.values(): + for distfile in distfiles: + if os.path.isfile(os.path.join(distdir, distfile)): + mf.fhashdict['DIST'].pop(distfile, None) + else: + auto_assumed.add(distfile) + mf.write() + finally: + portage._doebuild_manifest_exempt_depend -= 1 + + repoman_settings["O"] = checkdir + try: + generated_manifest = digestgen( + mysettings=repoman_settings, myportdb=portdb) + except portage.exception.PermissionDenied as e: + generated_manifest = False + writemsg_level("!!! Permission denied: '%s'\n" % (e,), + level=logging.ERROR, noiselevel=-1) + + if not generated_manifest: + print("Unable to generate manifest.") + dofail = 1 + + if options.mode == "manifest": + if not dofail and options.force and auto_assumed and \ + 'assume-digests' in repoman_settings.features: + # Show which digests were assumed despite the --force option + # being given. This output will already have been shown by + # digestgen() if assume-digests is not enabled, so only show + # it here if assume-digests is enabled. + pkgs = list(fetchlist_dict) + pkgs.sort() + portage.writemsg_stdout(" digest.assumed" + \ + portage.output.colorize("WARN", + str(len(auto_assumed)).rjust(18)) + "\n") + for cpv in pkgs: + fetchmap = fetchlist_dict[cpv] + pf = portage.catsplit(cpv)[1] + for distfile in sorted(fetchmap): + if distfile in auto_assumed: + portage.writemsg_stdout( + " %s::%s\n" % (pf, distfile)) + continue + elif dofail: + sys.exit(1) + + if not generated_manifest: + repoman_settings['O'] = checkdir + repoman_settings['PORTAGE_QUIET'] = '1' + if not portage.digestcheck([], repoman_settings, strict=1): + stats["manifest.bad"] += 1 + fails["manifest.bad"].append(os.path.join(x, 'Manifest')) + repoman_settings.pop('PORTAGE_QUIET', None) + + if options.mode == 'manifest-check': + continue + + checkdirlist=os.listdir(checkdir) + ebuildlist=[] + pkgs = {} + allvalid = True + for y in checkdirlist: + if (y in no_exec or y.endswith(".ebuild")) and \ + stat.S_IMODE(os.stat(os.path.join(checkdir, y)).st_mode) & 0o111: + stats["file.executable"] += 1 + fails["file.executable"].append(os.path.join(checkdir, y)) + if y.endswith(".ebuild"): + pf = y[:-7] + ebuildlist.append(pf) + cpv = "%s/%s" % (catdir, pf) + try: + myaux = dict(zip(allvars, portdb.aux_get(cpv, allvars))) + except KeyError: + allvalid = False + stats["ebuild.syntax"] += 1 + fails["ebuild.syntax"].append(os.path.join(x, y)) + continue + except IOError: + allvalid = False + stats["ebuild.output"] += 1 + fails["ebuild.output"].append(os.path.join(x, y)) + continue + if not portage.eapi_is_supported(myaux["EAPI"]): + allvalid = False + stats["EAPI.unsupported"] += 1 + fails["EAPI.unsupported"].append(os.path.join(x, y)) + continue + pkgs[pf] = Package(cpv=cpv, metadata=myaux, + root_config=root_config, type_name="ebuild") + + # Sort ebuilds in ascending order for the KEYWORDS.dropped check. + pkgsplits = {} + for i in range(len(ebuildlist)): + ebuild_split = portage.pkgsplit(ebuildlist[i]) + pkgsplits[ebuild_split] = ebuildlist[i] + ebuildlist[i] = ebuild_split + ebuildlist.sort(key=cmp_sort_key(portage.pkgcmp)) + for i in range(len(ebuildlist)): + ebuildlist[i] = pkgsplits[ebuildlist[i]] + del pkgsplits + + slot_keywords = {} + + if len(pkgs) != len(ebuildlist): + # If we can't access all the metadata then it's totally unsafe to + # commit since there's no way to generate a correct Manifest. + # Do not try to do any more QA checks on this package since missing + # metadata leads to false positives for several checks, and false + # positives confuse users. + can_force = False + continue + + for y in checkdirlist: + m = disallowed_filename_chars_re.search(y.strip(os.sep)) + if m is not None: + stats["file.name"] += 1 + fails["file.name"].append("%s/%s: char '%s'" % \ + (checkdir, y, m.group(0))) + + if not (y in ("ChangeLog", "metadata.xml") or y.endswith(".ebuild")): + continue + try: + line = 1 + for l in io.open(_unicode_encode(os.path.join(checkdir, y), + encoding=_encodings['fs'], errors='strict'), + mode='r', encoding=_encodings['repo.content']): + line +=1 + except UnicodeDecodeError as ue: + stats["file.UTF8"] += 1 + s = ue.object[:ue.start] + l2 = s.count("\n") + line += l2 + if l2 != 0: + s = s[s.rfind("\n") + 1:] + fails["file.UTF8"].append("%s/%s: line %i, just after: '%s'" % (checkdir, y, line, s)) + + if vcs in ("git", "hg") and check_ebuild_notadded: + if vcs == "git": + myf = os.popen("git ls-files --others %s" % \ + (portage._shell_quote(checkdir_relative),)) + if vcs == "hg": + myf = os.popen("hg status --no-status --unknown %s" % \ + (portage._shell_quote(checkdir_relative),)) + for l in myf: + if l[:-1][-7:] == ".ebuild": + stats["ebuild.notadded"] += 1 + fails["ebuild.notadded"].append( + os.path.join(x, os.path.basename(l[:-1]))) + myf.close() + + if vcs in ("cvs", "svn", "bzr") and check_ebuild_notadded: + try: + if vcs == "cvs": + myf=open(checkdir+"/CVS/Entries","r") + if vcs == "svn": + myf = os.popen("svn status --depth=files --verbose " + checkdir) + if vcs == "bzr": + myf = os.popen("bzr ls -v --kind=file " + checkdir) + myl = myf.readlines() + myf.close() + for l in myl: + if vcs == "cvs": + if l[0]!="/": + continue + splitl=l[1:].split("/") + if not len(splitl): + continue + if splitl[0][-7:]==".ebuild": + eadded.append(splitl[0][:-7]) + if vcs == "svn": + if l[:1] == "?": + continue + if l[:7] == ' >': + # tree conflict, new in subversion 1.6 + continue + l = l.split()[-1] + if l[-7:] == ".ebuild": + eadded.append(os.path.basename(l[:-7])) + if vcs == "bzr": + if l[1:2] == "?": + continue + l = l.split()[-1] + if l[-7:] == ".ebuild": + eadded.append(os.path.basename(l[:-7])) + if vcs == "svn": + myf = os.popen("svn status " + checkdir) + myl=myf.readlines() + myf.close() + for l in myl: + if l[0] == "A": + l = l.rstrip().split(' ')[-1] + if l[-7:] == ".ebuild": + eadded.append(os.path.basename(l[:-7])) + except IOError: + if vcs == "cvs": + stats["CVS/Entries.IO_error"] += 1 + fails["CVS/Entries.IO_error"].append(checkdir+"/CVS/Entries") + else: + raise + continue + + mf = Manifest(checkdir, repoman_settings["DISTDIR"]) + mydigests=mf.getTypeDigests("DIST") + + fetchlist_dict = portage.FetchlistDict(checkdir, repoman_settings, portdb) + myfiles_all = [] + src_uri_error = False + for mykey in fetchlist_dict: + try: + myfiles_all.extend(fetchlist_dict[mykey]) + except portage.exception.InvalidDependString as e: + src_uri_error = True + try: + portdb.aux_get(mykey, ["SRC_URI"]) + except KeyError: + # This will be reported as an "ebuild.syntax" error. + pass + else: + stats["SRC_URI.syntax"] = stats["SRC_URI.syntax"] + 1 + fails["SRC_URI.syntax"].append( + "%s.ebuild SRC_URI: %s" % (mykey, e)) + del fetchlist_dict + if not src_uri_error: + # This test can produce false positives if SRC_URI could not + # be parsed for one or more ebuilds. There's no point in + # producing a false error here since the root cause will + # produce a valid error elsewhere, such as "SRC_URI.syntax" + # or "ebuild.sytax". + myfiles_all = set(myfiles_all) + for entry in mydigests: + if entry not in myfiles_all: + stats["digest.unused"] += 1 + fails["digest.unused"].append(checkdir+"::"+entry) + for entry in myfiles_all: + if entry not in mydigests: + stats["digest.missing"] += 1 + fails["digest.missing"].append(checkdir+"::"+entry) + del myfiles_all + + if os.path.exists(checkdir+"/files"): + filesdirlist=os.listdir(checkdir+"/files") + + # recurse through files directory + # use filesdirlist as a stack, appending directories as needed so people can't hide > 20k files in a subdirectory. + while filesdirlist: + y = filesdirlist.pop(0) + relative_path = os.path.join(x, "files", y) + full_path = os.path.join(repodir, relative_path) + try: + mystat = os.stat(full_path) + except OSError as oe: + if oe.errno == 2: + # don't worry about it. it likely was removed via fix above. + continue + else: + raise oe + if S_ISDIR(mystat.st_mode): + # !!! VCS "portability" alert! Need some function isVcsDir() or alike !!! + if y == "CVS" or y == ".svn": + continue + for z in os.listdir(checkdir+"/files/"+y): + if z == "CVS" or z == ".svn": + continue + filesdirlist.append(y+"/"+z) + # Current policy is no files over 20 KiB, these are the checks. File size between + # 20 KiB and 60 KiB causes a warning, while file size over 60 KiB causes an error. + elif mystat.st_size > 61440: + stats["file.size.fatal"] += 1 + fails["file.size.fatal"].append("("+ str(mystat.st_size//1024) + " KiB) "+x+"/files/"+y) + elif mystat.st_size > 20480: + stats["file.size"] += 1 + fails["file.size"].append("("+ str(mystat.st_size//1024) + " KiB) "+x+"/files/"+y) + + m = disallowed_filename_chars_re.search( + os.path.basename(y.rstrip(os.sep))) + if m is not None: + stats["file.name"] += 1 + fails["file.name"].append("%s/files/%s: char '%s'" % \ + (checkdir, y, m.group(0))) + + if desktop_file_validate and desktop_pattern.match(y): + status, cmd_output = subprocess_getstatusoutput( + "'%s' '%s'" % (desktop_file_validate, full_path)) + if os.WIFEXITED(status) and os.WEXITSTATUS(status) != os.EX_OK: + # Note: in the future we may want to grab the + # warnings in addition to the errors. We're + # just doing errors now since we don't want + # to generate too much noise at first. + error_re = re.compile(r'.*\s*error:\s*(.*)') + for line in cmd_output.splitlines(): + error_match = error_re.match(line) + if error_match is None: + continue + stats["desktop.invalid"] += 1 + fails["desktop.invalid"].append( + relative_path + ': %s' % error_match.group(1)) + + del mydigests + + if check_changelog and "ChangeLog" not in checkdirlist: + stats["changelog.missing"]+=1 + fails["changelog.missing"].append(x+"/ChangeLog") + + musedict = {} + #metadata.xml file check + if "metadata.xml" not in checkdirlist: + stats["metadata.missing"]+=1 + fails["metadata.missing"].append(x+"/metadata.xml") + #metadata.xml parse check + else: + metadata_bad = False + + # read metadata.xml into memory + try: + _metadata_xml = xml.etree.ElementTree.parse( + os.path.join(checkdir, "metadata.xml"), + parser=xml.etree.ElementTree.XMLParser( + target=_MetadataTreeBuilder())) + except (ExpatError, SyntaxError, EnvironmentError) as e: + metadata_bad = True + stats["metadata.bad"] += 1 + fails["metadata.bad"].append("%s/metadata.xml: %s" % (x, e)) + del e + else: + # load USE flags from metadata.xml + try: + musedict = utilities.parse_metadata_use(_metadata_xml) + except portage.exception.ParseError as e: + metadata_bad = True + stats["metadata.bad"] += 1 + fails["metadata.bad"].append("%s/metadata.xml: %s" % (x, e)) + + # Run other metadata.xml checkers + try: + utilities.check_metadata(_metadata_xml, herd_base) + except (utilities.UnknownHerdsError, ) as e: + metadata_bad = True + stats["metadata.bad"] += 1 + fails["metadata.bad"].append("%s/metadata.xml: %s" % (x, e)) + del e + + #Only carry out if in package directory or check forced + if xmllint_capable and not metadata_bad: + # xmlint can produce garbage output even on success, so only dump + # the ouput when it fails. + st, out = subprocess_getstatusoutput( + "xmllint --nonet --noout --dtdvalid '%s' '%s'" % \ + (metadata_dtd, os.path.join(checkdir, "metadata.xml"))) + if st != os.EX_OK: + print(red("!!!") + " metadata.xml is invalid:") + for z in out.splitlines(): + print(red("!!! ")+z) + stats["metadata.bad"]+=1 + fails["metadata.bad"].append(x+"/metadata.xml") + + del metadata_bad + muselist = frozenset(musedict) + + changelog_path = os.path.join(checkdir_relative, "ChangeLog") + changelog_modified = changelog_path in modified_changelogs + + allmasked = True + # detect unused local USE-descriptions + used_useflags = set() + + for y in ebuildlist: + relative_path = os.path.join(x, y + ".ebuild") + full_path = os.path.join(repodir, relative_path) + ebuild_path = y + ".ebuild" + if repolevel < 3: + ebuild_path = os.path.join(pkgdir, ebuild_path) + if repolevel < 2: + ebuild_path = os.path.join(catdir, ebuild_path) + ebuild_path = os.path.join(".", ebuild_path) + if check_changelog and not changelog_modified \ + and ebuild_path in new_ebuilds: + stats['changelog.ebuildadded'] += 1 + fails['changelog.ebuildadded'].append(relative_path) + + if vcs in ("cvs", "svn", "bzr") and check_ebuild_notadded and y not in eadded: + #ebuild not added to vcs + stats["ebuild.notadded"]=stats["ebuild.notadded"]+1 + fails["ebuild.notadded"].append(x+"/"+y+".ebuild") + myesplit=portage.pkgsplit(y) + if myesplit is None or myesplit[0] != x.split("/")[-1] \ + or pv_toolong_re.search(myesplit[1]) \ + or pv_toolong_re.search(myesplit[2]): + stats["ebuild.invalidname"]=stats["ebuild.invalidname"]+1 + fails["ebuild.invalidname"].append(x+"/"+y+".ebuild") + continue + elif myesplit[0]!=pkgdir: + print(pkgdir,myesplit[0]) + stats["ebuild.namenomatch"]=stats["ebuild.namenomatch"]+1 + fails["ebuild.namenomatch"].append(x+"/"+y+".ebuild") + continue + + pkg = pkgs[y] + + if pkg.invalid: + allvalid = False + for k, msgs in pkg.invalid.items(): + for msg in msgs: + stats[k] = stats[k] + 1 + fails[k].append("%s %s" % (relative_path, msg)) + continue + + myaux = pkg.metadata + eapi = myaux["EAPI"] + inherited = pkg.inherited + live_ebuild = live_eclasses.intersection(inherited) + + for k, v in myaux.items(): + if not isinstance(v, basestring): + continue + m = non_ascii_re.search(v) + if m is not None: + stats["variable.invalidchar"] += 1 + fails["variable.invalidchar"].append( + ("%s: %s variable contains non-ASCII " + \ + "character at position %s") % \ + (relative_path, k, m.start() + 1)) + + if not src_uri_error: + # Check that URIs don't reference a server from thirdpartymirrors. + for uri in portage.dep.use_reduce( \ + myaux["SRC_URI"], matchall=True, is_src_uri=True, eapi=eapi, flat=True): + contains_mirror = False + for mirror in thirdpartymirrors: + if uri.startswith(mirror): + contains_mirror = True + break + if not contains_mirror: + continue + + stats["SRC_URI.mirror"] += 1 + fails["SRC_URI.mirror"].append( + "%s: '%s' found in thirdpartymirrors" % \ + (relative_path, mirror)) + + if myaux.get("PROVIDE"): + stats["virtual.oldstyle"]+=1 + fails["virtual.oldstyle"].append(relative_path) + + for pos, missing_var in enumerate(missingvars): + if not myaux.get(missing_var): + if catdir == "virtual" and \ + missing_var in ("HOMEPAGE", "LICENSE"): + continue + if live_ebuild and missing_var == "KEYWORDS": + continue + myqakey=missingvars[pos]+".missing" + stats[myqakey]=stats[myqakey]+1 + fails[myqakey].append(x+"/"+y+".ebuild") + + if catdir == "virtual": + for var in ("HOMEPAGE", "LICENSE"): + if myaux.get(var): + myqakey = var + ".virtual" + stats[myqakey] = stats[myqakey] + 1 + fails[myqakey].append(relative_path) + + # 14 is the length of DESCRIPTION="" + if len(myaux['DESCRIPTION']) > max_desc_len: + stats['DESCRIPTION.toolong'] += 1 + fails['DESCRIPTION.toolong'].append( + "%s: DESCRIPTION is %d characters (max %d)" % \ + (relative_path, len(myaux['DESCRIPTION']), max_desc_len)) + + keywords = myaux["KEYWORDS"].split() + stable_keywords = [] + for keyword in keywords: + if not keyword.startswith("~") and \ + not keyword.startswith("-"): + stable_keywords.append(keyword) + if stable_keywords: + if ebuild_path in new_ebuilds: + stable_keywords.sort() + stats["KEYWORDS.stable"] += 1 + fails["KEYWORDS.stable"].append( + x + "/" + y + ".ebuild added with stable keywords: %s" % \ + " ".join(stable_keywords)) + + ebuild_archs = set(kw.lstrip("~") for kw in keywords \ + if not kw.startswith("-")) + + previous_keywords = slot_keywords.get(myaux["SLOT"]) + if previous_keywords is None: + slot_keywords[myaux["SLOT"]] = set() + elif ebuild_archs and not live_ebuild: + dropped_keywords = previous_keywords.difference(ebuild_archs) + if dropped_keywords: + stats["KEYWORDS.dropped"] += 1 + fails["KEYWORDS.dropped"].append( + relative_path + ": %s" % \ + " ".join(sorted(dropped_keywords))) + + slot_keywords[myaux["SLOT"]].update(ebuild_archs) + + # KEYWORDS="-*" is a stupid replacement for package.mask and screws general KEYWORDS semantics + if "-*" in keywords: + haskeyword = False + for kw in keywords: + if kw[0] == "~": + kw = kw[1:] + if kw in kwlist: + haskeyword = True + if not haskeyword: + stats["KEYWORDS.stupid"] += 1 + fails["KEYWORDS.stupid"].append(x+"/"+y+".ebuild") + + """ + Ebuilds that inherit a "Live" eclass (darcs,subversion,git,cvs,etc..) should + not be allowed to be marked stable + """ + if live_ebuild: + bad_stable_keywords = [] + for keyword in keywords: + if not keyword.startswith("~") and \ + not keyword.startswith("-"): + bad_stable_keywords.append(keyword) + del keyword + if bad_stable_keywords: + stats["LIVEVCS.stable"] += 1 + fails["LIVEVCS.stable"].append( + x + "/" + y + ".ebuild with stable keywords:%s " % \ + bad_stable_keywords) + del bad_stable_keywords + + if keywords and not has_global_mask(pkg): + stats["LIVEVCS.unmasked"] += 1 + fails["LIVEVCS.unmasked"].append(relative_path) + + if options.ignore_arches: + arches = [[repoman_settings["ARCH"], repoman_settings["ARCH"], + repoman_settings["ACCEPT_KEYWORDS"].split()]] + else: + arches=[] + for keyword in myaux["KEYWORDS"].split(): + if (keyword[0]=="-"): + continue + elif (keyword[0]=="~"): + arches.append([keyword, keyword[1:], [keyword[1:], keyword]]) + else: + arches.append([keyword, keyword, [keyword]]) + allmasked = False + if not arches: + # Use an empty profile for checking dependencies of + # packages that have empty KEYWORDS. + arches.append(['**', '**', ['**']]) + + unknown_pkgs = {} + baddepsyntax = False + badlicsyntax = False + badprovsyntax = False + catpkg = catdir+"/"+y + + inherited_java_eclass = "java-pkg-2" in inherited or \ + "java-pkg-opt-2" in inherited + inherited_wxwidgets_eclass = "wxwidgets" in inherited + operator_tokens = set(["||", "(", ")"]) + type_list, badsyntax = [], [] + for mytype in ("DEPEND", "RDEPEND", "PDEPEND", + "LICENSE", "PROPERTIES", "PROVIDE"): + mydepstr = myaux[mytype] + + token_class = None + if mytype in ("DEPEND", "RDEPEND", "PDEPEND"): + token_class=portage.dep.Atom + + try: + atoms = portage.dep.use_reduce(mydepstr, matchall=1, flat=True, \ + is_valid_flag=pkg.iuse.is_valid_flag, token_class=token_class) + except portage.exception.InvalidDependString as e: + atoms = None + badsyntax.append(str(e)) + + if atoms and mytype in ("DEPEND", "RDEPEND", "PDEPEND"): + if mytype in ("RDEPEND", "PDEPEND") and \ + "test?" in mydepstr.split(): + stats[mytype + '.suspect'] += 1 + fails[mytype + '.suspect'].append(relative_path + \ + ": 'test?' USE conditional in %s" % mytype) + + for atom in atoms: + if atom == "||": + continue + + if not atom.blocker and \ + not portdb.cp_list(atom.cp) and \ + not atom.cp.startswith("virtual/"): + unknown_pkgs.setdefault(atom.cp, set()).add( + (mytype, atom.unevaluated_atom)) + + is_blocker = atom.blocker + + if mytype == "DEPEND" and \ + not is_blocker and \ + not inherited_java_eclass and \ + atom.cp == "virtual/jdk": + stats['java.eclassesnotused'] += 1 + fails['java.eclassesnotused'].append(relative_path) + elif mytype == "DEPEND" and \ + not is_blocker and \ + not inherited_wxwidgets_eclass and \ + atom.cp == "x11-libs/wxGTK": + stats['wxwidgets.eclassnotused'] += 1 + fails['wxwidgets.eclassnotused'].append( + relative_path + ": DEPENDs on x11-libs/wxGTK" + " without inheriting wxwidgets.eclass") + elif mytype in ("PDEPEND", "RDEPEND"): + if not is_blocker and \ + atom.cp in suspect_rdepend: + stats[mytype + '.suspect'] += 1 + fails[mytype + '.suspect'].append( + relative_path + ": '%s'" % atom) + + if atom.operator == "~" and \ + portage.versions.catpkgsplit(atom.cpv)[3] != "r0": + stats[mytype + '.badtilde'] += 1 + fails[mytype + '.badtilde'].append( + (relative_path + ": %s uses the ~ operator" + " with a non-zero revision:" + \ + " '%s'") % (mytype, atom)) + + type_list.extend([mytype] * (len(badsyntax) - len(type_list))) + + for m,b in zip(type_list, badsyntax): + stats[m+".syntax"] += 1 + fails[m+".syntax"].append(catpkg+".ebuild "+m+": "+b) + + badlicsyntax = len([z for z in type_list if z == "LICENSE"]) + badprovsyntax = len([z for z in type_list if z == "PROVIDE"]) + baddepsyntax = len(type_list) != badlicsyntax + badprovsyntax + badlicsyntax = badlicsyntax > 0 + badprovsyntax = badprovsyntax > 0 + + # uselist checks - global + myuse = [] + default_use = [] + for myflag in myaux["IUSE"].split(): + flag_name = myflag.lstrip("+-") + used_useflags.add(flag_name) + if myflag != flag_name: + default_use.append(myflag) + if flag_name not in uselist: + myuse.append(flag_name) + + # uselist checks - metadata + for mypos in range(len(myuse)-1,-1,-1): + if myuse[mypos] and (myuse[mypos] in muselist): + del myuse[mypos] + + if default_use and not eapi_has_iuse_defaults(eapi): + for myflag in default_use: + stats['EAPI.incompatible'] += 1 + fails['EAPI.incompatible'].append( + (relative_path + ": IUSE defaults" + \ + " not supported with EAPI='%s':" + \ + " '%s'") % (eapi, myflag)) + + for mypos in range(len(myuse)): + stats["IUSE.invalid"]=stats["IUSE.invalid"]+1 + fails["IUSE.invalid"].append(x+"/"+y+".ebuild: %s" % myuse[mypos]) + + # license checks + if not badlicsyntax: + # Parse the LICENSE variable, remove USE conditions and + # flatten it. + licenses = portage.dep.use_reduce(myaux["LICENSE"], matchall=1, flat=True) + # Check each entry to ensure that it exists in PORTDIR's + # license directory. + for lic in licenses: + # Need to check for "||" manually as no portage + # function will remove it without removing values. + if lic not in liclist and lic != "||": + stats["LICENSE.invalid"]=stats["LICENSE.invalid"]+1 + fails["LICENSE.invalid"].append(x+"/"+y+".ebuild: %s" % lic) + + #keyword checks + myuse = myaux["KEYWORDS"].split() + for mykey in myuse: + myskey=mykey[:] + if myskey[0]=="-": + myskey=myskey[1:] + if myskey[0]=="~": + myskey=myskey[1:] + if mykey!="-*": + if myskey not in kwlist: + stats["KEYWORDS.invalid"] += 1 + fails["KEYWORDS.invalid"].append(x+"/"+y+".ebuild: %s" % mykey) + elif myskey not in profiles: + stats["KEYWORDS.invalid"] += 1 + fails["KEYWORDS.invalid"].append(x+"/"+y+".ebuild: %s (profile invalid)" % mykey) + + #restrict checks + myrestrict = None + try: + myrestrict = portage.dep.use_reduce(myaux["RESTRICT"], matchall=1, flat=True) + except portage.exception.InvalidDependString as e: + stats["RESTRICT.syntax"] = stats["RESTRICT.syntax"] + 1 + fails["RESTRICT.syntax"].append( + "%s: RESTRICT: %s" % (relative_path, e)) + del e + if myrestrict: + myrestrict = set(myrestrict) + mybadrestrict = myrestrict.difference(valid_restrict) + if mybadrestrict: + stats["RESTRICT.invalid"] += len(mybadrestrict) + for mybad in mybadrestrict: + fails["RESTRICT.invalid"].append(x+"/"+y+".ebuild: %s" % mybad) + #REQUIRED_USE check + required_use = myaux["REQUIRED_USE"] + if required_use: + if not eapi_has_required_use(eapi): + stats['EAPI.incompatible'] += 1 + fails['EAPI.incompatible'].append( + relative_path + ": REQUIRED_USE" + \ + " not supported with EAPI='%s'" % (eapi,)) + try: + portage.dep.check_required_use(required_use, (), + pkg.iuse.is_valid_flag) + except portage.exception.InvalidDependString as e: + stats["REQUIRED_USE.syntax"] = stats["REQUIRED_USE.syntax"] + 1 + fails["REQUIRED_USE.syntax"].append( + "%s: REQUIRED_USE: %s" % (relative_path, e)) + del e + + # Syntax Checks + relative_path = os.path.join(x, y + ".ebuild") + full_path = os.path.join(repodir, relative_path) + if not vcs_preserves_mtime: + if ebuild_path not in new_ebuilds and \ + ebuild_path not in modified_ebuilds: + pkg.mtime = None + try: + # All ebuilds should have utf_8 encoding. + f = io.open(_unicode_encode(full_path, + encoding=_encodings['fs'], errors='strict'), + mode='r', encoding=_encodings['repo.content']) + try: + for check_name, e in run_checks(f, pkg): + stats[check_name] += 1 + fails[check_name].append(relative_path + ': %s' % e) + finally: + f.close() + except UnicodeDecodeError: + # A file.UTF8 failure will have already been recorded above. + pass + + if options.force: + # The dep_check() calls are the most expensive QA test. If --force + # is enabled, there's no point in wasting time on these since the + # user is intent on forcing the commit anyway. + continue + + for keyword,arch,groups in arches: + + if arch not in profiles: + # A missing profile will create an error further down + # during the KEYWORDS verification. + continue + + for prof in profiles[arch]: + + if prof.status not in ("stable", "dev") or \ + prof.status == "dev" and not options.include_dev: + continue + + dep_settings = arch_caches.get(prof.sub_path) + if dep_settings is None: + dep_settings = portage.config( + config_profile_path=prof.abs_path, + config_incrementals=repoman_incrementals, + local_config=False, + _unmatched_removal=options.unmatched_removal, + env=env) + if options.without_mask: + dep_settings._mask_manager = \ + copy.deepcopy(dep_settings._mask_manager) + dep_settings._mask_manager._pmaskdict.clear() + arch_caches[prof.sub_path] = dep_settings + + xmatch_cache_key = (prof.sub_path, tuple(groups)) + xcache = arch_xmatch_caches.get(xmatch_cache_key) + if xcache is None: + portdb.melt() + portdb.freeze() + xcache = portdb.xcache + xcache.update(shared_xmatch_caches) + arch_xmatch_caches[xmatch_cache_key] = xcache + + trees["/"]["porttree"].settings = dep_settings + portdb.settings = dep_settings + portdb.xcache = xcache + # for package.use.mask support inside dep_check + dep_settings.setcpv(pkg) + dep_settings["ACCEPT_KEYWORDS"] = " ".join(groups) + # just in case, prevent config.reset() from nuking these. + dep_settings.backup_changes("ACCEPT_KEYWORDS") + + if not baddepsyntax: + ismasked = not ebuild_archs or \ + pkg.cpv not in portdb.xmatch("list-visible", pkg.cp) + if ismasked: + if not have_pmasked: + have_pmasked = bool(dep_settings._getMaskAtom( + pkg.cpv, pkg.metadata)) + if options.ignore_masked: + continue + #we are testing deps for a masked package; give it some lee-way + suffix="masked" + matchmode = "minimum-all" + else: + suffix="" + matchmode = "minimum-visible" + + if not have_dev_keywords: + have_dev_keywords = \ + bool(dev_keywords.intersection(keywords)) + + if prof.status == "dev": + suffix=suffix+"indev" + + for mytype,mypos in [["DEPEND",len(missingvars)],["RDEPEND",len(missingvars)+1],["PDEPEND",len(missingvars)+2]]: + + mykey=mytype+".bad"+suffix + myvalue = myaux[mytype] + if not myvalue: + continue + + success, atoms = portage.dep_check(myvalue, portdb, + dep_settings, use="all", mode=matchmode, + trees=trees) + + if success: + if atoms: + for atom in atoms: + if not atom.blocker: + # Don't bother with dependency.unknown + # for cases in which *DEPEND.bad is + # triggered. + unknown_pkgs.pop(atom.cp, None) + + if not prof.sub_path: + # old-style virtuals currently aren't + # resolvable with empty profile, since + # 'virtuals' mappings are unavailable + # (it would be expensive to search + # for PROVIDE in all ebuilds) + atoms = [atom for atom in atoms if not \ + (atom.cp.startswith('virtual/') and \ + not portdb.cp_list(atom.cp))] + + #we have some unsolvable deps + #remove ! deps, which always show up as unsatisfiable + atoms = [str(atom.unevaluated_atom) \ + for atom in atoms if not atom.blocker] + + #if we emptied out our list, continue: + if not atoms: + continue + stats[mykey]=stats[mykey]+1 + fails[mykey].append("%s: %s(%s) %s" % \ + (relative_path, keyword, + prof, repr(atoms))) + else: + stats[mykey]=stats[mykey]+1 + fails[mykey].append("%s: %s(%s) %s" % \ + (relative_path, keyword, + prof, repr(atoms))) + + if not baddepsyntax and unknown_pkgs: + all_unknown = set() + all_unknown.update(*unknown_pkgs.values()) + type_map = {} + for mytype, atom in all_unknown: + type_map.setdefault(mytype, set()).add(atom) + for mytype, atoms in type_map.items(): + stats["dependency.unknown"] += 1 + fails["dependency.unknown"].append("%s: %s: %s" % + (relative_path, mytype, ", ".join(sorted(atoms)))) + + # Check for 'all unstable' or 'all masked' -- ACCEPT_KEYWORDS is stripped + # XXX -- Needs to be implemented in dep code. Can't determine ~arch nicely. + #if not portage.portdb.xmatch("bestmatch-visible",x): + # stats["ebuild.nostable"]+=1 + # fails["ebuild.nostable"].append(x) + if ebuildlist and allmasked and repolevel == 3: + stats["ebuild.allmasked"]+=1 + fails["ebuild.allmasked"].append(x) + + # check if there are unused local USE-descriptions in metadata.xml + # (unless there are any invalids, to avoid noise) + if allvalid: + for myflag in muselist.difference(used_useflags): + stats["metadata.warning"] += 1 + fails["metadata.warning"].append( + "%s/metadata.xml: unused local USE-description: '%s'" % \ + (x, myflag)) + +if options.mode == "manifest": + sys.exit(dofail) + +#dofail will be set to 1 if we have failed in at least one non-warning category +dofail=0 +#dowarn will be set to 1 if we tripped any warnings +dowarn=0 +#dofull will be set if we should print a "repoman full" informational message +dofull = options.mode != 'full' + +for x in qacats: + if not stats[x]: + continue + dowarn = 1 + if x not in qawarnings: + dofail = 1 + +if dofail or \ + (dowarn and not (options.quiet or options.mode == "scan")): + dofull = 0 + +# Save QA output so that it can be conveniently displayed +# in $EDITOR while the user creates a commit message. +# Otherwise, the user would not be able to see this output +# once the editor has taken over the screen. +qa_output = io.StringIO() +style_file = ConsoleStyleFile(sys.stdout) +if options.mode == 'commit' and \ + (not commitmessage or not commitmessage.strip()): + style_file.write_listener = qa_output +console_writer = StyleWriter(file=style_file, maxcol=9999) +console_writer.style_listener = style_file.new_styles + +f = formatter.AbstractFormatter(console_writer) + +utilities.format_qa_output(f, stats, fails, dofull, dofail, options, qawarnings) + +style_file.flush() +del console_writer, f, style_file +qa_output = qa_output.getvalue() +qa_output = qa_output.splitlines(True) + +def grouplist(mylist,seperator="/"): + """(list,seperator="/") -- Takes a list of elements; groups them into + same initial element categories. Returns a dict of {base:[sublist]} + From: ["blah/foo","spork/spatula","blah/weee/splat"] + To: {"blah":["foo","weee/splat"], "spork":["spatula"]}""" + mygroups={} + for x in mylist: + xs=x.split(seperator) + if xs[0]==".": + xs=xs[1:] + if xs[0] not in mygroups: + mygroups[xs[0]]=[seperator.join(xs[1:])] + else: + mygroups[xs[0]]+=[seperator.join(xs[1:])] + return mygroups + +suggest_ignore_masked = False +suggest_include_dev = False + +if have_pmasked and not (options.without_mask or options.ignore_masked): + suggest_ignore_masked = True +if have_dev_keywords and not options.include_dev: + suggest_include_dev = True + +if suggest_ignore_masked or suggest_include_dev: + print() + if suggest_ignore_masked: + print(bold("Note: use --without-mask to check " + \ + "KEYWORDS on dependencies of masked packages")) + + if suggest_include_dev: + print(bold("Note: use --include-dev (-d) to check " + \ + "dependencies for 'dev' profiles")) + print() + +if options.mode != 'commit': + if dofull: + print(bold("Note: type \"repoman full\" for a complete listing.")) + if dowarn and not dofail: + print(green("RepoMan sez:"),"\"You're only giving me a partial QA payment?\n I'll take it this time, but I'm not happy.\"") + elif not dofail: + print(green("RepoMan sez:"),"\"If everyone were like you, I'd be out of business!\"") + elif dofail: + print(bad("Please fix these important QA issues first.")) + print(green("RepoMan sez:"),"\"Make your QA payment on time and you'll never see the likes of me.\"\n") + sys.exit(1) +else: + if dofail and can_force and options.force and not options.pretend: + print(green("RepoMan sez:") + \ + " \"You want to commit even with these QA issues?\n" + \ + " I'll take it this time, but I'm not happy.\"\n") + elif dofail: + if options.force and not can_force: + print(bad("The --force option has been disabled due to extraordinary issues.")) + print(bad("Please fix these important QA issues first.")) + print(green("RepoMan sez:"),"\"Make your QA payment on time and you'll never see the likes of me.\"\n") + sys.exit(1) + + if options.pretend: + print(green("RepoMan sez:"), "\"So, you want to play it safe. Good call.\"\n") + + myunadded = [] + if vcs == "cvs": + try: + myvcstree=portage.cvstree.getentries("./",recursive=1) + myunadded=portage.cvstree.findunadded(myvcstree,recursive=1,basedir="./") + except SystemExit as e: + raise # TODO propagate this + except: + err("Error retrieving CVS tree; exiting.") + if vcs == "svn": + try: + svnstatus=os.popen("svn status --no-ignore").readlines() + myunadded = [ "./"+elem.rstrip().split()[1] for elem in svnstatus if elem.startswith("?") or elem.startswith("I") ] + except SystemExit as e: + raise # TODO propagate this + except: + err("Error retrieving SVN info; exiting.") + if vcs == "git": + # get list of files not under version control or missing + myf = os.popen("git ls-files --others") + myunadded = [ "./" + elem[:-1] for elem in myf ] + myf.close() + if vcs == "bzr": + try: + bzrstatus=os.popen("bzr status -S .").readlines() + myunadded = [ "./"+elem.rstrip().split()[1].split('/')[-1:][0] for elem in bzrstatus if elem.startswith("?") or elem[0:2] == " D" ] + except SystemExit as e: + raise # TODO propagate this + except: + err("Error retrieving bzr info; exiting.") + if vcs == "hg": + myunadded = os.popen("hg status --no-status --unknown .").readlines() + myunadded = ["./" + elem.rstrip() for elem in myunadded] + + # Mercurial doesn't handle manually deleted files as removed from + # the repository, so the user need to remove them before commit, + # using "hg remove [FILES]" + mydeleted = os.popen("hg status --no-status --deleted .").readlines() + mydeleted = ["./" + elem.rstrip() for elem in mydeleted] + + + myautoadd=[] + if myunadded: + for x in range(len(myunadded)-1,-1,-1): + xs=myunadded[x].split("/") + if xs[-1]=="files": + print("!!! files dir is not added! Please correct this.") + sys.exit(-1) + elif xs[-1]=="Manifest": + # It's a manifest... auto add + myautoadd+=[myunadded[x]] + del myunadded[x] + + if myautoadd: + print(">>> Auto-Adding missing Manifest(s)...") + if options.pretend: + if vcs == "cvs": + print("(cvs add "+" ".join(myautoadd)+")") + elif vcs == "svn": + print("(svn add "+" ".join(myautoadd)+")") + elif vcs == "git": + print("(git add "+" ".join(myautoadd)+")") + elif vcs == "bzr": + print("(bzr add "+" ".join(myautoadd)+")") + elif vcs == "hg": + print("(hg add "+" ".join(myautoadd)+")") + retval=0 + else: + if vcs == "cvs": + retval=os.system("cvs add "+" ".join(myautoadd)) + elif vcs == "svn": + retval=os.system("svn add "+" ".join(myautoadd)) + elif vcs == "git": + retval=os.system("git add "+" ".join(myautoadd)) + elif vcs == "bzr": + retval=os.system("bzr add "+" ".join(myautoadd)) + elif vcs == "hg": + retval=os.system("hg add "+" ".join(myautoadd)) + if retval: + writemsg_level("!!! Exiting on %s (shell) error code: %s\n" % \ + (vcs, retval), level=logging.ERROR, noiselevel=-1) + sys.exit(retval) + + if myunadded: + print(red("!!! The following files are in your local tree but are not added to the master")) + print(red("!!! tree. Please remove them from the local tree or add them to the master tree.")) + for x in myunadded: + print(" ",x) + print() + print() + sys.exit(1) + + if vcs == "hg" and mydeleted: + print(red("!!! The following files are removed manually from your local tree but are not")) + print(red("!!! removed from the repository. Please remove them, using \"hg remove [FILES]\".")) + for x in mydeleted: + print(" ",x) + print() + print() + sys.exit(1) + + if vcs == "cvs": + mycvstree = cvstree.getentries("./", recursive=1) + mychanged = cvstree.findchanged(mycvstree, recursive=1, basedir="./") + mynew = cvstree.findnew(mycvstree, recursive=1, basedir="./") + myremoved=portage.cvstree.findremoved(mycvstree,recursive=1,basedir="./") + bin_blob_pattern = re.compile("^-kb$") + no_expansion = set(portage.cvstree.findoption(mycvstree, bin_blob_pattern, + recursive=1, basedir="./")) + + + if vcs == "svn": + svnstatus = os.popen("svn status").readlines() + mychanged = [ "./" + elem.split()[-1:][0] for elem in svnstatus if (elem[:1] in "MR" or elem[1:2] in "M")] + mynew = [ "./" + elem.split()[-1:][0] for elem in svnstatus if elem.startswith("A")] + myremoved = [ "./" + elem.split()[-1:][0] for elem in svnstatus if elem.startswith("D")] + + # Subversion expands keywords specified in svn:keywords properties. + props = os.popen("svn propget -R svn:keywords").readlines() + expansion = dict(("./" + prop.split(" - ")[0], prop.split(" - ")[1].split()) \ + for prop in props if " - " in prop) + + elif vcs == "git": + mychanged = os.popen("git diff-index --name-only --relative --diff-filter=M HEAD").readlines() + mychanged = ["./" + elem[:-1] for elem in mychanged] + + mynew = os.popen("git diff-index --name-only --relative --diff-filter=A HEAD").readlines() + mynew = ["./" + elem[:-1] for elem in mynew] + + myremoved = os.popen("git diff-index --name-only --relative --diff-filter=D HEAD").readlines() + myremoved = ["./" + elem[:-1] for elem in myremoved] + + if vcs == "bzr": + bzrstatus = os.popen("bzr status -S .").readlines() + mychanged = [ "./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem and elem[1:2] == "M" ] + mynew = [ "./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem and ( elem[1:2] in "NK" or elem[0:1] == "R" ) ] + myremoved = [ "./" + elem.split()[-1:][0].split('/')[-1:][0] for elem in bzrstatus if elem.startswith("-") ] + myremoved = [ "./" + elem.split()[-3:-2][0].split('/')[-1:][0] for elem in bzrstatus if elem and ( elem[1:2] == "K" or elem[0:1] == "R" ) ] + # Bazaar expands nothing. + + if vcs == "hg": + mychanged = os.popen("hg status --no-status --modified .").readlines() + mychanged = ["./" + elem.rstrip() for elem in mychanged] + mynew = os.popen("hg status --no-status --added .").readlines() + mynew = ["./" + elem.rstrip() for elem in mynew] + myremoved = os.popen("hg status --no-status --removed .").readlines() + myremoved = ["./" + elem.rstrip() for elem in myremoved] + + if vcs: + if not (mychanged or mynew or myremoved or (vcs == "hg" and mydeleted)): + print(green("RepoMan sez:"), "\"Doing nothing is not always good for QA.\"") + print() + print("(Didn't find any changed files...)") + print() + sys.exit(1) + + # Manifests need to be regenerated after all other commits, so don't commit + # them now even if they have changed. + mymanifests = set() + myupdates = set() + for f in mychanged + mynew: + if "Manifest" == os.path.basename(f): + mymanifests.add(f) + else: + myupdates.add(f) + if vcs in ('git', 'hg'): + myupdates.difference_update(myremoved) + myupdates = list(myupdates) + mymanifests = list(mymanifests) + myheaders = [] + mydirty = [] + + print("* %s files being committed..." % green(str(len(myupdates))), end=' ') + if vcs not in ('cvs', 'svn'): + # With git, bzr and hg, there's never any keyword expansion, so + # there's no need to regenerate manifests and all files will be + # committed in one big commit at the end. + print() + else: + if vcs == 'cvs': + headerstring = "'\$(Header|Id).*\$'" + elif vcs == "svn": + svn_keywords = dict((k.lower(), k) for k in [ + "Rev", + "Revision", + "LastChangedRevision", + "Date", + "LastChangedDate", + "Author", + "LastChangedBy", + "URL", + "HeadURL", + "Id", + "Header", + ]) + + for myfile in myupdates: + + # for CVS, no_expansion contains files that are excluded from expansion + if vcs == "cvs": + if myfile in no_expansion: + continue + + # for SVN, expansion contains files that are included in expansion + elif vcs == "svn": + if myfile not in expansion: + continue + + # Subversion keywords are case-insensitive in svn:keywords properties, but case-sensitive in contents of files. + enabled_keywords = [] + for k in expansion[myfile]: + keyword = svn_keywords.get(k.lower()) + if keyword is not None: + enabled_keywords.append(keyword) + + headerstring = "'\$(%s).*\$'" % "|".join(enabled_keywords) + + myout = subprocess_getstatusoutput("egrep -q "+headerstring+" "+myfile) + if myout[0] == 0: + myheaders.append(myfile) + + print("%s have headers that will change." % green(str(len(myheaders)))) + print("* Files with headers will cause the manifests to be changed and committed separately.") + + logging.info("myupdates: %s", myupdates) + logging.info("myheaders: %s", myheaders) + + commitmessage = options.commitmsg + if options.commitmsgfile: + try: + f = io.open(_unicode_encode(options.commitmsgfile, + encoding=_encodings['fs'], errors='strict'), + mode='r', encoding=_encodings['content'], errors='replace') + commitmessage = f.read() + f.close() + del f + except (IOError, OSError) as e: + if e.errno == errno.ENOENT: + portage.writemsg("!!! File Not Found: --commitmsgfile='%s'\n" % options.commitmsgfile) + else: + raise + # We've read the content so the file is no longer needed. + commitmessagefile = None + if not commitmessage or not commitmessage.strip(): + try: + editor = os.environ.get("EDITOR") + if editor and utilities.editor_is_executable(editor): + commitmessage = utilities.get_commit_message_with_editor( + editor, message=qa_output) + else: + commitmessage = utilities.get_commit_message_with_stdin() + except KeyboardInterrupt: + exithandler() + if not commitmessage or not commitmessage.strip(): + print("* no commit message? aborting commit.") + sys.exit(1) + commitmessage = commitmessage.rstrip() + portage_version = getattr(portage, "VERSION", None) + if portage_version is None: + sys.stderr.write("Failed to insert portage version in message!\n") + sys.stderr.flush() + portage_version = "Unknown" + unameout = platform.system() + " " + if platform.system() in ["Darwin", "SunOS"]: + unameout += platform.processor() + else: + unameout += platform.machine() + commitmessage += "\n\n(Portage version: %s/%s/%s" % \ + (portage_version, vcs, unameout) + if options.force: + commitmessage += ", RepoMan options: --force" + commitmessage += ")" + + if options.ask and userquery('Commit changes?', True) != 'Yes': + print("* aborting commit.") + sys.exit(1) + + if vcs in ('cvs', 'svn') and (myupdates or myremoved): + myfiles = myupdates + myremoved + if not myheaders and "sign" not in repoman_settings.features: + myfiles += mymanifests + fd, commitmessagefile = tempfile.mkstemp(".repoman.msg") + mymsg = os.fdopen(fd, "wb") + mymsg.write(_unicode_encode(commitmessage)) + mymsg.close() + + print() + print(green("Using commit message:")) + print(green("------------------------------------------------------------------------------")) + print(commitmessage) + print(green("------------------------------------------------------------------------------")) + print() + + # Having a leading ./ prefix on file paths can trigger a bug in + # the cvs server when committing files to multiple directories, + # so strip the prefix. + myfiles = [f.lstrip("./") for f in myfiles] + + commit_cmd = [vcs] + commit_cmd.extend(vcs_global_opts) + commit_cmd.append("commit") + commit_cmd.extend(vcs_local_opts) + commit_cmd.extend(["-F", commitmessagefile]) + commit_cmd.extend(myfiles) + + try: + if options.pretend: + print("(%s)" % (" ".join(commit_cmd),)) + else: + retval = spawn(commit_cmd, env=os.environ) + if retval != os.EX_OK: + writemsg_level(("!!! Exiting on %s (shell) " + \ + "error code: %s\n") % (vcs, retval), + level=logging.ERROR, noiselevel=-1) + sys.exit(retval) + finally: + try: + os.unlink(commitmessagefile) + except OSError: + pass + + # Setup the GPG commands + def gpgsign(filename): + gpgcmd = repoman_settings.get("PORTAGE_GPG_SIGNING_COMMAND") + if gpgcmd is None: + raise MissingParameter("PORTAGE_GPG_SIGNING_COMMAND is unset!" + \ + " Is make.globals missing?") + if "${PORTAGE_GPG_KEY}" in gpgcmd and \ + "PORTAGE_GPG_KEY" not in repoman_settings: + raise MissingParameter("PORTAGE_GPG_KEY is unset!") + if "${PORTAGE_GPG_DIR}" in gpgcmd: + if "PORTAGE_GPG_DIR" not in repoman_settings: + repoman_settings["PORTAGE_GPG_DIR"] = \ + os.path.expanduser("~/.gnupg") + logging.info("Automatically setting PORTAGE_GPG_DIR to '%s'" \ + % repoman_settings["PORTAGE_GPG_DIR"]) + else: + repoman_settings["PORTAGE_GPG_DIR"] = \ + os.path.expanduser(repoman_settings["PORTAGE_GPG_DIR"]) + if not os.access(repoman_settings["PORTAGE_GPG_DIR"], os.X_OK): + raise portage.exception.InvalidLocation( + "Unable to access directory: PORTAGE_GPG_DIR='%s'" % \ + repoman_settings["PORTAGE_GPG_DIR"]) + gpgvars = {"FILE": filename} + for k in ("PORTAGE_GPG_DIR", "PORTAGE_GPG_KEY"): + v = repoman_settings.get(k) + if v is not None: + gpgvars[k] = v + gpgcmd = portage.util.varexpand(gpgcmd, mydict=gpgvars) + if options.pretend: + print("("+gpgcmd+")") + else: + rValue = os.system(gpgcmd) + if rValue == os.EX_OK: + os.rename(filename+".asc", filename) + else: + raise portage.exception.PortageException("!!! gpg exited with '" + str(rValue) + "' status") + + # When files are removed and re-added, the cvs server will put /Attic/ + # inside the $Header path. This code detects the problem and corrects it + # so that the Manifest will generate correctly. See bug #169500. + # Use binary mode in order to avoid potential character encoding issues. + cvs_header_re = re.compile(br'^#\s*\$Header.*\$$') + attic_str = b'/Attic/' + attic_replace = b'/' + for x in myheaders: + f = open(_unicode_encode(x, + encoding=_encodings['fs'], errors='strict'), + mode='rb') + mylines = f.readlines() + f.close() + modified = False + for i, line in enumerate(mylines): + if cvs_header_re.match(line) is not None and \ + attic_str in line: + mylines[i] = line.replace(attic_str, attic_replace) + modified = True + if modified: + portage.util.write_atomic(x, b''.join(mylines), + mode='wb') + + manifest_commit_required = True + if vcs in ('cvs', 'svn') and (myupdates or myremoved): + myfiles = myupdates + myremoved + for x in range(len(myfiles)-1, -1, -1): + if myfiles[x].count("/") < 4-repolevel: + del myfiles[x] + mydone=[] + if repolevel==3: # In a package dir + repoman_settings["O"] = startdir + digestgen(mysettings=repoman_settings, myportdb=portdb) + elif repolevel==2: # In a category dir + for x in myfiles: + xs=x.split("/") + if len(xs) < 4-repolevel: + continue + if xs[0]==".": + xs=xs[1:] + if xs[0] in mydone: + continue + mydone.append(xs[0]) + repoman_settings["O"] = os.path.join(startdir, xs[0]) + if not os.path.isdir(repoman_settings["O"]): + continue + digestgen(mysettings=repoman_settings, myportdb=portdb) + elif repolevel==1: # repo-cvsroot + print(green("RepoMan sez:"), "\"You're rather crazy... doing the entire repository.\"\n") + for x in myfiles: + xs=x.split("/") + if len(xs) < 4-repolevel: + continue + if xs[0]==".": + xs=xs[1:] + if "/".join(xs[:2]) in mydone: + continue + mydone.append("/".join(xs[:2])) + repoman_settings["O"] = os.path.join(startdir, xs[0], xs[1]) + if not os.path.isdir(repoman_settings["O"]): + continue + digestgen(mysettings=repoman_settings, myportdb=portdb) + else: + print(red("I'm confused... I don't know where I am!")) + sys.exit(1) + + # Force an unsigned commit when more than one Manifest needs to be signed. + if repolevel < 3 and "sign" in repoman_settings.features: + + fd, commitmessagefile = tempfile.mkstemp(".repoman.msg") + mymsg = os.fdopen(fd, "wb") + mymsg.write(_unicode_encode(commitmessage)) + mymsg.write(b"\n (Unsigned Manifest commit)") + mymsg.close() + + commit_cmd = [vcs] + commit_cmd.extend(vcs_global_opts) + commit_cmd.append("commit") + commit_cmd.extend(vcs_local_opts) + commit_cmd.extend(["-F", commitmessagefile]) + commit_cmd.extend(f.lstrip("./") for f in mymanifests) + + try: + if options.pretend: + print("(%s)" % (" ".join(commit_cmd),)) + else: + retval = spawn(commit_cmd, env=os.environ) + if retval: + writemsg_level(("!!! Exiting on %s (shell) " + \ + "error code: %s\n") % (vcs, retval), + level=logging.ERROR, noiselevel=-1) + sys.exit(retval) + finally: + try: + os.unlink(commitmessagefile) + except OSError: + pass + manifest_commit_required = False + + signed = False + if "sign" in repoman_settings.features: + signed = True + myfiles = myupdates + myremoved + mymanifests + try: + if repolevel==3: # In a package dir + repoman_settings["O"] = "." + gpgsign(os.path.join(repoman_settings["O"], "Manifest")) + elif repolevel==2: # In a category dir + mydone=[] + for x in myfiles: + xs=x.split("/") + if len(xs) < 4-repolevel: + continue + if xs[0]==".": + xs=xs[1:] + if xs[0] in mydone: + continue + mydone.append(xs[0]) + repoman_settings["O"] = os.path.join(".", xs[0]) + if not os.path.isdir(repoman_settings["O"]): + continue + gpgsign(os.path.join(repoman_settings["O"], "Manifest")) + elif repolevel==1: # repo-cvsroot + print(green("RepoMan sez:"), "\"You're rather crazy... doing the entire repository.\"\n") + mydone=[] + for x in myfiles: + xs=x.split("/") + if len(xs) < 4-repolevel: + continue + if xs[0]==".": + xs=xs[1:] + if "/".join(xs[:2]) in mydone: + continue + mydone.append("/".join(xs[:2])) + repoman_settings["O"] = os.path.join(".", xs[0], xs[1]) + if not os.path.isdir(repoman_settings["O"]): + continue + gpgsign(os.path.join(repoman_settings["O"], "Manifest")) + except portage.exception.PortageException as e: + portage.writemsg("!!! %s\n" % str(e)) + portage.writemsg("!!! Disabled FEATURES='sign'\n") + signed = False + + if vcs == 'git': + # It's not safe to use the git commit -a option since there might + # be some modified files elsewhere in the working tree that the + # user doesn't want to commit. Therefore, call git update-index + # in order to ensure that the index is updated with the latest + # versions of all new and modified files in the relevant portion + # of the working tree. + myfiles = mymanifests + myupdates + myfiles.sort() + update_index_cmd = ["git", "update-index"] + update_index_cmd.extend(f.lstrip("./") for f in myfiles) + if options.pretend: + print("(%s)" % (" ".join(update_index_cmd),)) + else: + retval = spawn(update_index_cmd, env=os.environ) + if retval != os.EX_OK: + writemsg_level(("!!! Exiting on %s (shell) " + \ + "error code: %s\n") % (vcs, retval), + level=logging.ERROR, noiselevel=-1) + sys.exit(retval) + + if vcs in ['git', 'bzr', 'hg'] or manifest_commit_required or signed: + + myfiles = mymanifests[:] + if vcs in ['git', 'bzr', 'hg']: + myfiles += myupdates + myfiles += myremoved + myfiles.sort() + + fd, commitmessagefile = tempfile.mkstemp(".repoman.msg") + mymsg = os.fdopen(fd, "wb") + # strip the closing parenthesis + mymsg.write(_unicode_encode(commitmessage[:-1])) + if signed: + mymsg.write(_unicode_encode( + ", signed Manifest commit with key %s)" % \ + repoman_settings["PORTAGE_GPG_KEY"])) + else: + mymsg.write(b", unsigned Manifest commit)") + mymsg.close() + + commit_cmd = [] + if options.pretend and vcs is None: + # substitute a bogus value for pretend output + commit_cmd.append("cvs") + else: + commit_cmd.append(vcs) + commit_cmd.extend(vcs_global_opts) + commit_cmd.append("commit") + commit_cmd.extend(vcs_local_opts) + if vcs == "hg": + commit_cmd.extend(["--logfile", commitmessagefile]) + commit_cmd.extend(myfiles) + else: + commit_cmd.extend(["-F", commitmessagefile]) + commit_cmd.extend(f.lstrip("./") for f in myfiles) + + try: + if options.pretend: + print("(%s)" % (" ".join(commit_cmd),)) + else: + retval = spawn(commit_cmd, env=os.environ) + if retval != os.EX_OK: + writemsg_level(("!!! Exiting on %s (shell) " + \ + "error code: %s\n") % (vcs, retval), + level=logging.ERROR, noiselevel=-1) + sys.exit(retval) + finally: + try: + os.unlink(commitmessagefile) + except OSError: + pass + + print() + if vcs: + print("Commit complete.") + else: + print("repoman was too scared by not seeing any familiar version control file that he forgot to commit anything") + print(green("RepoMan sez:"), "\"If everyone were like you, I'd be out of business!\"\n") +sys.exit(0) + |