drop IMAGE -- people have had years to catch up (trunk r14694)
[portage.git] / bin / repoman
index f3c93addfc25ebcde39551fd9b0926e76fd07c74..9b1fd461e5be0627ea77dd117172c1f9ac475ea1 100755 (executable)
@@ -7,13 +7,17 @@
 # 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 codecs
-import commands
+try:
+       from subprocess import getstatusoutput as subprocess_getstatusoutput
+except ImportError:
+       from commands import getstatusoutput as subprocess_getstatusoutput
 import errno
 import formatter
 import logging
 import optparse
-import os
 import re
 import signal
 import stat
@@ -22,14 +26,10 @@ import tempfile
 import time
 import platform
 
-from itertools import chain, izip
+from io import StringIO
+from itertools import chain
 from stat import S_ISDIR, ST_CTIME
 
-try:
-       from cStringIO import StringIO
-except ImportError:
-       from StringIO import StringIO
-
 if not hasattr(__builtins__, "set"):
        from sets import Set as set
 
@@ -40,6 +40,9 @@ except ImportError:
        sys.path.insert(0, osp.join(osp.dirname(osp.dirname(osp.realpath(__file__))), "pym"))
        import portage
 portage._disable_legacy_globals()
+from portage import os
+from portage import _encodings
+from portage import _unicode_encode
 
 try:
        from repoman.checks import run_checks
@@ -50,8 +53,9 @@ except ImportError:
        from repoman.checks import run_checks
        from repoman import utilities
 
-from _emerge import Package, RootConfig
-from portage.sets import load_default_config
+from _emerge.Package import Package
+from _emerge.RootConfig import RootConfig
+from portage._sets import load_default_config
 
 import portage.checksum
 import portage.const
@@ -68,6 +72,9 @@ from portage.output import bold, create_color_func, darkgreen, \
 from portage.output import ConsoleStyleFile, StyleWriter
 from portage.util import cmp_sort_key, writemsg_level
 
+if sys.hexversion >= 0x3000000:
+       basestring = str
+
 util.initialize_logger()
 
 # 14 is the length of DESCRIPTION=""
@@ -77,7 +84,7 @@ disallowed_filename_chars_re = re.compile(r'[^a-zA-Z0-9._\-+:]')
 bad = create_color_func("BAD")
 
 # A sane umask is needed for files that portage creates.
-os.umask(022)
+os.umask(0o22)
 repoman_settings = portage.config(local_config=False,
        config_incrementals=portage.const.INCREMENTALS)
 repoman_settings.lock()
@@ -87,7 +94,7 @@ if repoman_settings.get("NOCOLOR", "").lower() in ("yes", "true") or \
        nocolor()
 
 def warn(txt):
-       print "repoman: " + txt
+       print("repoman: " + txt)
 
 def err(txt):
        warn(txt)
@@ -146,7 +153,7 @@ def ParseArgs(args, qahelp):
                'scan' : 'Scan directory tree for QA issues' 
        }
 
-       mode_keys = modes.keys()
+       mode_keys = list(modes)
        mode_keys.sort()
 
        parser = RepomanOptionParser(formatter=RepomanHelpFormatter(), usage="%prog [options] [mode]")
@@ -191,7 +198,7 @@ def ParseArgs(args, qahelp):
        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=modes.keys(), 
+       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"))
@@ -201,7 +208,7 @@ def ParseArgs(args, qahelp):
 
        parser.on_tail("\n " + green("QA keyword".ljust(20) + " Description\n"))
 
-       sorted_qa = qahelp.keys()
+       sorted_qa = list(qahelp)
        sorted_qa.sort()
        for k in sorted_qa:
                parser.on_tail(" %s %s\n" % (k.ljust(20), qahelp[k]))
@@ -254,7 +261,8 @@ qahelp={
        "changelog.notadded":"ChangeLogs that exist but have not been added to cvs",
        "filedir.missing":"Package lacks a files directory",
        "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 20k",
+       "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",
@@ -266,6 +274,7 @@ qahelp={
        "LICENSE.missing":"Ebuilds that have a missing or 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.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.missing":"Ebuilds that have a missing or empty SLOT variable",
@@ -282,9 +291,13 @@ qahelp={
        "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)",
@@ -297,14 +310,17 @@ qahelp={
        "variable.invalidchar":"A variable contains an invalid character that is not part of the ASCII character set",
        "variable.readonly":"Assigning a readonly variable",
        "LIVEVCS.stable":"This ebuild is a live checkout from a VCS but has stable keywords.",
-       "IUSE.invalid":"This ebuild has a variable in IUSE that is not in the use.desc or use.local.desc file",
+       "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.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",
        "RDEPEND.suspect":"RDEPEND contains a package that usually only belongs in DEPEND.",
        "RESTRICT.invalid":"This ebuild contains invalid RESTRICT values.",
-       "digestentry.unused":"Some files listed in the Manifest aren't referenced in SRC_URI",
+       "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",
@@ -320,13 +336,14 @@ qahelp={
        "upstream.workaround":"The ebuild works around an upstream bug, an upstream bug should be filed and tracked in bugs.gentoo.org"
 }
 
-qacats = qahelp.keys()
+qacats = list(qahelp)
 qacats.sort()
 
 qawarnings = set((
 "changelog.missing",
 "changelog.notadded",
-"digestentry.unused",
+"digest.assumed",
+"digest.unused",
 "ebuild.notadded",
 "ebuild.nostable",
 "ebuild.allmasked",
@@ -335,11 +352,13 @@ qawarnings = set((
 "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",
 "KEYWORDS.dropped",
 "KEYWORDS.stupid",
 "KEYWORDS.missing",
 "IUSE.undefined",
+"PDEPEND.suspect",
 "RDEPEND.implicit",
 "RDEPEND.suspect",
 "RESTRICT.invalid",
@@ -356,14 +375,14 @@ qawarnings = set((
 "virtual.unavailable",
 "usage.obsolete",
 "upstream.workaround",
-"LIVEVCS.stable"
+"LIVEVCS.stable",
+"LIVEVCS.unmasked",
 ))
 
 non_ascii_re = re.compile(r'[^\x00-\x7f]')
 
 missingvars=["KEYWORDS","LICENSE","DESCRIPTION","HOMEPAGE","SLOT"]
 allvars = set(x for x in portage.auxdbkeys if not x.startswith("UNUSED_"))
-allvars.discard("CDEPEND")
 allvars.update(Package.metadata_keys)
 allvars = sorted(allvars)
 commitmessage=None
@@ -395,10 +414,10 @@ suspect_rdepend = frozenset([
        "dev-lang/swig",
        "dev-lang/yasm",
        "dev-perl/extutils-pkgconfig",
-       "dev-python/setuptools",
        "dev-util/byacc",
        "dev-util/cmake",
        "dev-util/ftjam",
+       "dev-util/gperf",
        "dev-util/gtk-doc",
        "dev-util/gtk-doc-am",
        "dev-util/intltool",
@@ -415,7 +434,6 @@ suspect_rdepend = frozenset([
        "sys-devel/bison",
        "sys-devel/dev86",
        "sys-devel/flex",
-       "sys-devel/libtool",
        "sys-devel/m4",
        "sys-devel/pmake",
        "x11-misc/bdftopcf",
@@ -428,7 +446,7 @@ no_exec = frozenset(["Manifest","ChangeLog","metadata.xml"])
 options, arguments = ParseArgs(sys.argv, qahelp)
 
 if options.version:
-       print "Portage", portage.VERSION
+       print("Portage", portage.VERSION)
        sys.exit(0)
 
 # Set this to False when an extraordinary issue (generally
@@ -474,7 +492,7 @@ if vcs == "cvs" and \
                prefix = bad(" * ")
                from textwrap import wrap
                for line in wrap(msg, 70):
-                       print prefix + line
+                       print(prefix + line)
                sys.exit(1)
        del repo_lines
 
@@ -511,6 +529,14 @@ logging.info('Setting paths:')
 logging.info('PORTDIR = "' + portdir + '"')
 logging.info('PORTDIR_OVERLAY = "%s"' % env['PORTDIR_OVERLAY'])
 
+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.mysettings = repoman_settings
 root_config = RootConfig(repoman_settings, trees[root], None)
 # We really only need to cache the metadata that's necessary for visibility
@@ -525,10 +551,10 @@ repolevel = len(reposplit)
 # 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 recommited for a package."
-       print red("***")
+       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 recommited for a package.")
+       print(red("***"))
        err("Unable to identify level we're commiting from for %s" % '/'.join(reposplit))
 
 startdir = normalize_path(mydir)
@@ -540,32 +566,126 @@ 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.")
 
-# setup a uselist from portage
-uselist=[]
-try:
-       uselist=portage.grabfile(portdir+"/profiles/use.desc")
-       for l in range(0,len(uselist)):
-               uselist[l]=uselist[l].split()[0]
-       for var in repoman_settings["USE_EXPAND"].split():
-               vardescs = portage.grabfile(portdir+"/profiles/desc/"+var.lower()+".desc")
-               for l in range(0, len(vardescs)):
-                       uselist.append(var.lower() + "_" + vardescs[l].split()[0])
-except (IOError, OSError, ParseError), e:
-       logging.exception("Couldn't read USE flags from use.desc")
-       sys.exit(1)
+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
+               self.sub_path = normalize_path(sub_path.lstrip(os.sep))
+               self.tree_path = tree_path
+               self.abs_path = os.path.join(tree_path, 'profiles', self.sub_path)
+
+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])
 
-# get lists of valid keywords and licenses
-kwlist = set(portage.grabfile(os.path.join(portdir, "profiles", "arch.list")))
-liclist = set(portage.listdir(os.path.join(portdir, "licenses")))
+       global_pmasklines.append(portage.util.grabfile_package(
+               os.path.join(path, 'profiles', 'package.mask'), recursive=1))
 
-if portdir_overlay != portdir:
-       for porttree in portdb.porttrees[1:]:
-               try:
-                       liclist.update(os.listdir(os.path.join(porttree, "licenses")))
-               except OSError:
-                       pass
-               kwlist.update(portage.grabfile(os.path.join(porttree,
-                       "profiles", "arch.list")))
+       desc_path = os.path.join(path, 'profiles', 'profiles.desc')
+       try:
+               desc_file = codecs.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], portdir)
+                       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
+                       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(portage.dep_getkey(x), []).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)
+
+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?")
@@ -575,6 +695,10 @@ 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
@@ -609,47 +733,6 @@ scanlist.sort()
 
 logging.debug("Found the following packages to scan:\n%s" % '\n'.join(scanlist))
 
-profiles={}
-valid_profile_types = frozenset(["dev", "exp", "stable"])
-descfile=portdir+"/profiles/profiles.desc"
-if os.path.exists(descfile):
-       for i, x in enumerate(open(descfile)):
-               if x[0]=="#":
-                       continue
-               arch=x.split()
-               if len(arch) == 0:
-                       continue
-               if len(arch)!=3:
-                       err("wrong format: \"" + bad(x.strip()) + "\" in " + \
-                               descfile + " line %d" % (i+1, ))
-               elif arch[0] not in kwlist:
-                       err("invalid arch: \"" + bad(arch[0]) + "\" in " + \
-                               descfile + " line %d" % (i+1, ))
-               elif arch[2] not in valid_profile_types:
-                       err("invalid profile type: \"" + bad(arch[2]) + "\" in " + \
-                               descfile + " line %d" % (i+1, ))
-               if not os.path.isdir(portdir+"/profiles/"+arch[1]):
-                       print "Invalid "+arch[2]+" profile ("+arch[1]+") for arch "+arch[0]
-                       continue
-               if arch[0] in profiles:
-                       profiles[arch[0]]+= [[arch[1], arch[2]]]
-               else:
-                       profiles[arch[0]] = [[arch[1], arch[2]]]
-
-       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
-else:
-       print red("profiles.desc does not exist: "+descfile)
-       print red("You need to do \"cvs update\" in profiles dir.")
-       print
-       sys.exit(1)
-
 def dev_keywords(profiles):
        """
        Create a set of KEYWORDS values that exist in 'dev'
@@ -658,12 +741,12 @@ def dev_keywords(profiles):
        want to add the --include-dev option.
        """
        type_arch_map = {}
-       for arch, arch_profiles in profiles.iteritems():
-               for profile_path, profile_type in arch_profiles:
-                       arch_set = type_arch_map.get(profile_type)
+       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[profile_type] = arch_set
+                               type_arch_map[prof.status] = arch_set
                        arch_set.add(arch)
 
        dev_keywords = type_arch_map.get('dev', set())
@@ -687,9 +770,9 @@ metadata_dtd = os.path.join(repoman_settings["DISTDIR"], 'metadata.dtd')
 if options.mode == "manifest":
        pass
 elif not find_binary('xmllint'):
-       print red("!!! xmllint not found. Can't check metadata.xml.\n")
+       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"
+               print(red("!!!")+" sorry, xmllint is needed.  failing\n")
                sys.exit(1)
 else:
        #hardcoded paths/urls suck. :-/
@@ -705,35 +788,35 @@ else:
                else:
                        must_fetch=0
 
-       except (OSError,IOError), e:
+       except (OSError,IOError) as e:
                if e.errno != 2:
-                       print red("!!!")+" caught exception '%s' for %s/metadata.dtd, bailing" % (str(e), portage.CACHE_PATH)
+                       print(red("!!!")+" caught exception '%s' for %s/metadata.dtd, bailing" % (str(e), portage.CACHE_PATH))
                        sys.exit(1)
 
        if must_fetch:
-               print 
-               print green("***")+" the local copy of metadata.dtd needs to be refetched, doing that now"
-               print
+               print() 
+               print(green("***")+" the local copy of metadata.dtd needs to be refetched, doing that now")
+               print()
                val = 0
                try:
                        try:
                                os.unlink(metadata_dtd)
-                       except OSError, e:
+                       except OSError as e:
                                if e.errno != errno.ENOENT:
                                        raise
                                del e
                        val=portage.fetch(['http://www.gentoo.org/dtd/metadata.dtd'],repoman_settings,fetchonly=0, \
                                try_mirrors=0)
 
-               except SystemExit, e:
+               except SystemExit as e:
                        raise  # Need to propogate this
-               except Exception,e:
-                       print
-                       print red("!!!")+" attempting to fetch 'http://www.gentoo.org/dtd/metadata.dtd', caught"
-                       print red("!!!")+" exception '%s' though." % str(e)
+               except Exception as e:
+                       print()
+                       print(red("!!!")+" attempting to fetch 'http://www.gentoo.org/dtd/metadata.dtd', caught")
+                       print(red("!!!")+" exception '%s' though." % str(e))
                        val=0
                if not val:
-                       print red("!!!")+" fetching new metadata.dtd failed, aborting"
+                       print(red("!!!")+" fetching new metadata.dtd failed, aborting")
                        sys.exit(1)
        #this can be problematic if xmllint changes their output
        xmllint_capable=True
@@ -744,9 +827,9 @@ if options.mode == 'commit' and vcs:
 if options.mode == "manifest":
        pass
 elif options.pretend:
-       print green("\nRepoMan does a once-over of the neighborhood...")
+       print(green("\nRepoMan does a once-over of the neighborhood..."))
 else:
-       print green("\nRepoMan scours the neighborhood...")
+       print(green("\nRepoMan scours the neighborhood..."))
 
 new_ebuilds = set()
 modified_changelogs = set()
@@ -795,7 +878,7 @@ 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 = portage.flatten(repoman_settings.thirdpartymirrors().values())
+thirdpartymirrors = portage.flatten(list(repoman_settings.thirdpartymirrors().values()))
 
 for x in scanlist:
        #ebuilds and digests added to cvs respectively.
@@ -823,7 +906,7 @@ for x in scanlist:
                                        fetchlist_dict=fetchlist_dict)
                                mf.create(requiredDistfiles=None,
                                        assumeDistHashesAlways=True)
-                               for distfiles in fetchlist_dict.itervalues():
+                               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)
@@ -835,7 +918,7 @@ for x in scanlist:
 
                repoman_settings["O"] = checkdir
                if not portage.digestgen([], repoman_settings, myportdb=portdb):
-                       print "Unable to generate manifest."
+                       print("Unable to generate manifest.")
                        dofail = 1
                if options.mode == "manifest":
                        if not dofail and options.force and auto_assumed and \
@@ -865,7 +948,7 @@ for x in scanlist:
        pkgs = {}
        for y in checkdirlist:
                if y in no_exec and \
-                       stat.S_IMODE(os.stat(os.path.join(checkdir, y)).st_mode) & 0111:
+                       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"):
@@ -873,7 +956,7 @@ for x in scanlist:
                        ebuildlist.append(pf)
                        cpv = "%s/%s" % (catdir, pf)
                        try:
-                               myaux = dict(izip(allvars, portdb.aux_get(cpv, allvars)))
+                               myaux = dict(zip(allvars, portdb.aux_get(cpv, allvars)))
                        except KeyError:
                                stats["ebuild.syntax"] += 1
                                fails["ebuild.syntax"].append(os.path.join(x, y))
@@ -891,12 +974,12 @@ for x in scanlist:
 
        # Sort ebuilds in ascending order for the KEYWORDS.dropped check.
        pkgsplits = {}
-       for i in xrange(len(ebuildlist)):
+       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 xrange(len(ebuildlist)):
+       for i in range(len(ebuildlist)):
                ebuildlist[i] = pkgsplits[ebuildlist[i]]
        del pkgsplits
 
@@ -922,9 +1005,11 @@ for x in scanlist:
                        continue
                try:
                        line = 1
-                       for l in codecs.open(checkdir+"/"+y, "r", "utf8"):
+                       for l in codecs.open(_unicode_encode(os.path.join(checkdir, y),
+                               encoding=_encodings['fs'], errors='strict'),
+                               mode='r', encoding=_encodings['repo.content']):
                                line +=1
-               except UnicodeDecodeError, ue:
+               except UnicodeDecodeError as ue:
                        stats["file.UTF8"] += 1
                        s = ue.object[:ue.start]
                        l2 = s.count("\n")
@@ -996,7 +1081,7 @@ for x in scanlist:
        for mykey in fetchlist_dict:
                try:
                        myfiles_all.extend(fetchlist_dict[mykey])
-               except portage.exception.InvalidDependString, e:
+               except portage.exception.InvalidDependString as e:
                        src_uri_error = True
                        try:
                                portdb.aux_get(mykey, ["SRC_URI"])
@@ -1017,8 +1102,12 @@ for x in scanlist:
                myfiles_all = set(myfiles_all)
                for entry in mydigests:
                        if entry not in myfiles_all:
-                               stats["digestentry.unused"] += 1
-                               fails["digestentry.unused"].append(checkdir+"::"+entry)
+                               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"):
@@ -1032,7 +1121,7 @@ for x in scanlist:
                        full_path = os.path.join(repodir, relative_path)
                        try:
                                mystat = os.stat(full_path)
-                       except OSError, oe:
+                       except OSError as oe:
                                if oe.errno == 2:
                                        # don't worry about it.  it likely was removed via fix above.
                                        continue
@@ -1046,10 +1135,14 @@ for x in scanlist:
                                        if z == "CVS" or z == ".svn":
                                                continue
                                        filesdirlist.append(y+"/"+z)
-                       # current policy is no files over 20k, this is the check.
+                       # 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) + "K) "+x+"/files/"+y)
+                               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)))
@@ -1059,7 +1152,7 @@ for x in scanlist:
                                        (checkdir, y, m.group(0)))
 
                        if desktop_file_validate and desktop_pattern.match(y):
-                               status, cmd_output = commands.getstatusoutput(
+                               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
@@ -1076,8 +1169,11 @@ for x in scanlist:
                                                        relative_path + ': %s' % error_match.group(1))
 
        del mydigests
-
-       if "ChangeLog" not in checkdirlist:
+       # 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.
+       if vcs not in ( "git", ) and "ChangeLog" not in checkdirlist:
                stats["changelog.missing"]+=1
                fails["changelog.missing"].append(x+"/ChangeLog")
        
@@ -1096,7 +1192,7 @@ for x in scanlist:
                        f = open(os.path.join(checkdir, "metadata.xml"))
                        utilities.parse_metadata_use(f, muselist)
                        f.close()
-               except (EnvironmentError, ParseError), e:
+               except (EnvironmentError, ParseError) as e:
                        metadata_bad = True
                        stats["metadata.bad"] += 1
                        fails["metadata.bad"].append("%s/metadata.xml: %s" % (x, e))
@@ -1106,13 +1202,13 @@ for x in scanlist:
                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 = commands.getstatusoutput(
+                       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:"
+                               print(red("!!!") + " metadata.xml is invalid:")
                                for z in out.splitlines():
-                                       print red("!!! ")+z
+                                       print(red("!!! ")+z)
                                stats["metadata.bad"]+=1
                                fails["metadata.bad"].append(x+"/metadata.xml")
 
@@ -1139,7 +1235,7 @@ for x in scanlist:
                        stats['changelog.ebuildadded'] += 1
                        fails['changelog.ebuildadded'].append(relative_path)
 
-               if stat.S_IMODE(os.stat(full_path).st_mode) & 0111:
+               if stat.S_IMODE(os.stat(full_path).st_mode) & 0o111:
                        stats["file.executable"] += 1
                        fails["file.executable"].append(x+"/"+y+".ebuild")
                if vcs in ("cvs", "svn") and check_ebuild_notadded and y not in eadded:
@@ -1152,18 +1248,26 @@ for x in scanlist:
                        fails["ebuild.invalidname"].append(x+"/"+y+".ebuild")
                        continue
                elif myesplit[0]!=pkgdir:
-                       print pkgdir,myesplit[0]
+                       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:
+                       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.iteritems():
+               for k, v in myaux.items():
                        if not isinstance(v, basestring):
                                continue
                        m = non_ascii_re.search(v)
@@ -1191,20 +1295,9 @@ for x in scanlist:
                                        "%s: '%s' found in thirdpartymirrors" % \
                                        (relative_path, mirror))
 
-               # Test for negative logic and bad words in the RESTRICT var.
-               #for x in myaux[allvars.index("RESTRICT")].split():
-               #       if x.startswith("no"):
-               #               print "Bad RESTRICT value: %s" % x
-               try:
-                       myaux["PROVIDE"] = portage.dep.use_reduce(
-                               portage.dep.paren_reduce(myaux["PROVIDE"]), matchall=1)
-               except portage.exception.InvalidDependString, e:
-                       stats["PROVIDE.syntax"] = stats["PROVIDE.syntax"] + 1
-                       fails["PROVIDE.syntax"].append(mykey+".ebuild PROVIDE: "+str(e))
-                       del e
-                       continue
-               myaux["PROVIDE"] = " ".join(portage.flatten(myaux["PROVIDE"]))
-               for myprovide in myaux["PROVIDE"].split():
+               # The Package class automatically evaluates USE conditionals.
+               for myprovide in portage.flatten(portage.dep.use_reduce(
+                       portage.dep.paren_reduce(pkg.metadata['PROVIDE']), matchall=1)):
                        prov_cp = portage.dep_getkey(myprovide)
                        if prov_cp != myprovide:
                                stats["virtual.versioned"]+=1
@@ -1293,6 +1386,10 @@ for x in scanlist:
                                                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()]]
@@ -1337,14 +1434,14 @@ for x in scanlist:
                        except ValueError:
                                badsyntax.append("parenthesis mismatch")
                                mydeplist = []
-                       except portage.exception.InvalidDependString, e:
+                       except portage.exception.InvalidDependString as e:
                                badsyntax.append(str(e))
                                del e
                                mydeplist = []
 
                        try:
                                portage.dep.use_reduce(mydeplist, matchall=1)
-                       except portage.exception.InvalidDependString, e:
+                       except portage.exception.InvalidDependString as e:
                                badsyntax.append(str(e))
 
                        for token in operator_tokens:
@@ -1362,7 +1459,12 @@ for x in scanlist:
                        if mytype in ("DEPEND", "RDEPEND", "PDEPEND"):
                                for token in mydepstr.split():
                                        if token in operator_tokens or \
-                                               token.endswith("?"):
+                                               token[-1:] == "?":
+                                               if token == "test?" and \
+                                                       mytype in ("RDEPEND", "PDEPEND"):
+                                                       stats[mytype + '.suspect'] += 1
+                                                       fails[mytype + '.suspect'].append(relative_path + \
+                                                               ": 'test?' USE conditional in %s" % mytype)
                                                continue
                                        try:
                                                atom = portage.dep.Atom(token)
@@ -1377,11 +1479,11 @@ for x in scanlist:
                                                        portage.dep_getkey(atom) == "virtual/jdk":
                                                        stats['java.eclassesnotused'] += 1
                                                        fails['java.eclassesnotused'].append(relative_path)
-                                               elif mytype == "RDEPEND":
+                                               elif mytype in ("PDEPEND", "RDEPEND"):
                                                        if not is_blocker and \
                                                                portage.dep_getkey(atom) in suspect_rdepend:
-                                                               stats['RDEPEND.suspect'] += 1
-                                                               fails['RDEPEND.suspect'].append(
+                                                               stats[mytype + '.suspect'] += 1
+                                                               fails[mytype + '.suspect'].append(
                                                                        relative_path + ": '%s'" % atom)
                                                if eapi == "0":
                                                        if portage.dep.dep_getslot(atom):
@@ -1404,14 +1506,22 @@ for x in scanlist:
                                                                " not supported with EAPI='%s':" + \
                                                                " '%s'") % (mytype, eapi, 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(filter(lambda x:x=="LICENSE", type_list))
-               badprovsyntax = len(filter(lambda x:x=="PROVIDE", type_list))
+               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
@@ -1481,7 +1591,7 @@ for x in scanlist:
                try:
                        myrestrict = portage.dep.use_reduce(
                                portage.dep.paren_reduce(myaux["RESTRICT"]), matchall=1)
-               except portage.exception.InvalidDependString, e:
+               except portage.exception.InvalidDependString as e:
                        stats["RESTRICT.syntax"] = stats["RESTRICT.syntax"] + 1
                        fails["RESTRICT.syntax"].append(
                                "%s: RESTRICT: %s" % (relative_path, e))
@@ -1498,7 +1608,9 @@ for x in scanlist:
                full_path = os.path.join(repodir, relative_path)
                try:
                        # All ebuilds should have utf_8 encoding.
-                       f = codecs.open(full_path, mode='r', encoding='utf_8')
+                       f = codecs.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
@@ -1524,23 +1636,20 @@ for x in scanlist:
                                
                        for prof in profiles[arch]:
 
-                               if prof[1] not in ("stable", "dev") or \
-                                       prof[1] == "dev" and not options.include_dev:
+                               if prof.status not in ("stable", "dev") or \
+                                       prof.status == "dev" and not options.include_dev:
                                        continue
 
-                               profdir = portdir+"/profiles/"+prof[0]
-       
-                               if prof[0] in arch_caches:
-                                       dep_settings = arch_caches[prof[0]]
-                               else:
+                               dep_settings = arch_caches.get(prof.sub_path)
+                               if dep_settings is None:
                                        dep_settings = portage.config(
-                                               config_profile_path=profdir,
+                                               config_profile_path=prof.abs_path,
                                                config_incrementals=portage.const.INCREMENTALS,
                                                local_config=False,
                                                env=env)
                                        if options.without_mask:
                                                dep_settings.pmaskdict.clear()
-                                       arch_caches[prof[0]] = dep_settings
+                                       arch_caches[prof.sub_path] = dep_settings
                                        while True:
                                                try:
                                                        # Protect ACCEPT_KEYWORDS from config.regenerate()
@@ -1549,7 +1658,7 @@ for x in scanlist:
                                                except ValueError:
                                                        break
 
-                               xmatch_cache_key = (prof[0], tuple(groups))
+                               xmatch_cache_key = (prof.sub_path, tuple(groups))
                                xcache = arch_xmatch_caches.get(xmatch_cache_key)
                                if xcache is None:
                                        portdb.melt()
@@ -1571,7 +1680,8 @@ for x in scanlist:
                                        prov_cp = portage.dep_getkey(myprovide)
                                        if prov_cp not in dep_settings.getvirtuals():
                                                stats["virtual.unavailable"]+=1
-                                               fails["virtual.unavailable"].append(x+"/"+y+".ebuild: "+keyword+"("+prof[0]+") "+prov_cp)
+                                               fails["virtual.unavailable"].append("%s: %s(%s) %s" % \
+                                                       (relative_path, keyword, prof.sub_path, prov_cp))
 
                                if not baddepsyntax:
                                        ismasked = os.path.join(catdir, y) not in \
@@ -1593,7 +1703,7 @@ for x in scanlist:
                                                have_dev_keywords = \
                                                        bool(dev_keywords.intersection(keywords))
 
-                                       if prof[1] == "dev":
+                                       if prof.status == "dev":
                                                suffix=suffix+"indev"
 
                                        for mytype,mypos in [["DEPEND",len(missingvars)],["RDEPEND",len(missingvars)+1],["PDEPEND",len(missingvars)+2]]:
@@ -1602,33 +1712,28 @@ for x in scanlist:
                                                myvalue = myaux[mytype]
                                                if not myvalue:
                                                        continue
-                                               try:
-                                                       mydep = portage.dep_check(myvalue, portdb,
-                                                               dep_settings, use="all", mode=matchmode,
-                                                               trees=trees)
-                                               except KeyError, e:
-                                                       stats[mykey]=stats[mykey]+1
-                                                       fails[mykey].append(x+"/"+y+".ebuild: "+keyword+"("+prof[0]+") "+repr(e[0]))
-                                                       continue
-       
-                                               if mydep[0]==1:
-                                                       if mydep[1]!=[]:
+
+                                               success, atoms = portage.dep_check(myvalue, portdb,
+                                                       dep_settings, use="all", mode=matchmode,
+                                                       trees=trees)
+
+                                               if success:
+                                                       if atoms:
                                                                #we have some unsolvable deps
                                                                #remove ! deps, which always show up as unsatisfiable
-                                                               d=0
-                                                               while d<len(mydep[1]):
-                                                                       if mydep[1][d][0]=="!":
-                                                                               del mydep[1][d]
-                                                                       else:
-                                                                               d += 1
+                                                               atoms = [str(atom) for atom in atoms if not atom.blocker]
                                                                #if we emptied out our list, continue:
-                                                               if not mydep[1]:
+                                                               if not atoms:
                                                                        continue
                                                                stats[mykey]=stats[mykey]+1
-                                                               fails[mykey].append(x+"/"+y+".ebuild: "+keyword+"("+prof[0]+") "+repr(mydep[1]))
+                                                               fails[mykey].append("%s: %s(%s) %s" % \
+                                                                       (relative_path, keyword,
+                                                                       prof.sub_path, repr(atoms)))
                                                else:
                                                        stats[mykey]=stats[mykey]+1
-                                                       fails[mykey].append(x+"/"+y+".ebuild: "+keyword+"("+prof[0]+") "+repr(mydep[1]))
+                                                       fails[mykey].append("%s: %s(%s) %s" % \
+                                                               (relative_path, keyword,
+                                                               prof.sub_path, repr(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.
@@ -1713,48 +1818,48 @@ if have_dev_keywords and not options.include_dev:
        suggest_include_dev = True
 
 if suggest_ignore_masked or suggest_include_dev:
-       print
+       print()
        if suggest_ignore_masked:
-               print bold("Note: use --without-mask to check " + \
-                       "KEYWORDS on dependencies of masked packages")
+               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
+               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.")
+               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.\""
+               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!\""
+               print(green("RepoMan sez:"),"\"If everyone were like you, I'd be out of business!\"")
        elif dofail:
-               print turquoise("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"
+               print(turquoise("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:") + \
+               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"
+                       "              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 turquoise("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"
+                       print(bad("The --force option has been disabled due to extraordinary issues."))
+               print(turquoise("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"
+               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, e:
+               except SystemExit as e:
                        raise  # TODO propogate this
                except:
                        err("Error retrieving CVS tree; exiting.")
@@ -1762,7 +1867,7 @@ else:
                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, e:
+               except SystemExit as e:
                        raise  # TODO propogate this
                except:
                        err("Error retrieving SVN info; exiting.")
@@ -1777,7 +1882,7 @@ else:
                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."
+                               print("!!! files dir is not added! Please correct this.")
                                sys.exit(-1)
                        elif xs[-1]=="Manifest":
                                # It's a manifest... auto add
@@ -1785,14 +1890,14 @@ else:
                                del myunadded[x]
 
        if myautoadd:
-               print ">>> Auto-Adding missing Manifest(s)..."
+               print(">>> Auto-Adding missing Manifest(s)...")
                if options.pretend:
                        if vcs == "cvs":
-                               print "(cvs add "+" ".join(myautoadd)+")"
+                               print("(cvs add "+" ".join(myautoadd)+")")
                        if vcs == "svn":
-                               print "(svn add "+" ".join(myautoadd)+")"
+                               print("(svn add "+" ".join(myautoadd)+")")
                        elif vcs == "git":
-                               print "(git add "+" ".join(myautoadd)+")"
+                               print("(git add "+" ".join(myautoadd)+")")
                        retval=0
                else:
                        if vcs == "cvs":
@@ -1807,12 +1912,12 @@ else:
                        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.")
+               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
+                       print("   ",x)
+               print()
+               print()
                sys.exit(1)
 
        if vcs == "cvs":
@@ -1867,11 +1972,11 @@ else:
 
        if vcs:
                if not (mychanged or mynew or myremoved):
-                       print green("RepoMan sez:"), "\"Doing nothing is not always good for QA.\""
-                       print
-                       print "(Didn't find any changed files...)"
-                       print
-                       sys.exit(0)
+                       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.
@@ -1902,20 +2007,20 @@ else:
                        if myfile not in expansion:
                                continue
 
-               myout = commands.getstatusoutput("egrep -q "+headerstring+" "+myfile)
+               myout = subprocess_getstatusoutput("egrep -q "+headerstring+" "+myfile)
                if myout[0] == 0:
                        myheaders.append(myfile)
 
-       print "* %s files being committed..." % green(str(len(myupdates))),
+       print("* %s files being committed..." % green(str(len(myupdates))), end=' ')
        if vcs == 'git':
                # With git, 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
+               print()
        else:
-               print "%s have headers that will change." % green(str(len(myheaders)))
-               print "* Files with headers will cause the " + \
-                       "manifests to be made and recommited."
+               print("%s have headers that will change." % green(str(len(myheaders))))
+               print("* Files with headers will cause the " + \
+                       "manifests to be made and recommited.")
        logging.info("myupdates: %s", myupdates)
        logging.info("myheaders: %s", myheaders)
 
@@ -1926,7 +2031,7 @@ else:
                        commitmessage = f.read()
                        f.close()
                        del f
-               except (IOError, OSError), e:
+               except (IOError, OSError) as e:
                        if e.errno == errno.ENOENT:
                                portage.writemsg("!!! File Not Found: --commitmsgfile='%s'\n" % options.commitmsgfile)
                        else:
@@ -1944,7 +2049,7 @@ else:
                except KeyboardInterrupt:
                        exithandler()
                if not commitmessage or not commitmessage.strip():
-                       print "* no commit message?  aborting commit."
+                       print("* no commit message?  aborting commit.")
                        sys.exit(1)
        commitmessage = commitmessage.rstrip()
        portage_version = getattr(portage, "VERSION", None)
@@ -1972,12 +2077,12 @@ else:
                mymsg.write(commitmessage)
                mymsg.close()
 
-               print
-               print green("Using commit message:")
-               print green("------------------------------------------------------------------------------")
-               print commitmessage
-               print green("------------------------------------------------------------------------------")
-               print
+               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,
@@ -1993,7 +2098,7 @@ else:
 
                try:
                        if options.pretend:
-                               print "(%s)" % (" ".join(commit_cmd),)
+                               print("(%s)" % (" ".join(commit_cmd),))
                        else:
                                retval = spawn(commit_cmd, env=os.environ)
                                if retval != os.EX_OK:
@@ -2030,7 +2135,7 @@ else:
                if "PORTAGE_GPG_DIR" in repoman_settings:
                        gpgcmd += " --homedir "+repoman_settings["PORTAGE_GPG_DIR"]
                if options.pretend:
-                       print "("+gpgcmd+" "+filename+")"
+                       print("("+gpgcmd+" "+filename+")")
                else:
                        rValue = os.system(gpgcmd+" "+filename)
                        if rValue == os.EX_OK:
@@ -2082,7 +2187,7 @@ else:
                                portage.digestgen([], repoman_settings, manifestonly=1,
                                        myportdb=portdb)
                elif repolevel==1: # repo-cvsroot
-                       print green("RepoMan sez:"), "\"You're rather crazy... doing the entire repository.\"\n"
+                       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:
@@ -2098,7 +2203,7 @@ else:
                                portage.digestgen([], repoman_settings, manifestonly=1,
                                        myportdb=portdb)
                else:
-                       print red("I'm confused... I don't know where I am!")
+                       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.
@@ -2119,7 +2224,7 @@ else:
 
                        try:
                                if options.pretend:
-                                       print "(%s)" % (" ".join(commit_cmd),)
+                                       print("(%s)" % (" ".join(commit_cmd),))
                                else:
                                        retval = spawn(commit_cmd, env=os.environ)
                                        if retval:
@@ -2158,7 +2263,7 @@ else:
                                                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"
+                               print(green("RepoMan sez:"), "\"You're rather crazy... doing the entire repository.\"\n")
                                mydone=[]
                                for x in myfiles:
                                        xs=x.split("/")
@@ -2173,7 +2278,7 @@ else:
                                        if not os.path.isdir(repoman_settings["O"]):
                                                continue
                                        gpgsign(os.path.join(repoman_settings["O"], "Manifest"))
-               except portage.exception.PortageException, e:
+               except portage.exception.PortageException as e:
                        portage.writemsg("!!! %s\n" % str(e))
                        portage.writemsg("!!! Disabled FEATURES='sign'\n")
                        signed = False
@@ -2190,7 +2295,7 @@ else:
                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),)
+                       print("(%s)" % (" ".join(update_index_cmd),))
                else:
                        retval = spawn(update_index_cmd, env=os.environ)
                        if retval != os.EX_OK:
@@ -2230,7 +2335,7 @@ else:
 
                try:
                        if options.pretend:
-                               print "(%s)" % (" ".join(commit_cmd),)
+                               print("(%s)" % (" ".join(commit_cmd),))
                        else:
                                retval = spawn(commit_cmd, env=os.environ)
                                if retval != os.EX_OK:
@@ -2244,11 +2349,11 @@ else:
                        except OSError:
                                pass
 
-       print
+       print()
        if vcs:
-               print "Commit complete."
+               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"
+               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)