# 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
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:
- import cPickle as pickle
-except ImportError:
- import pickle
-
-try:
- import cStringIO as StringIO
-except ImportError:
- import StringIO
-
if not hasattr(__builtins__, "set"):
from sets import Set as set
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
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
from portage.output import bold, create_color_func, darkgreen, \
green, nocolor, red, turquoise, yellow
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=""
max_desc_len = 100
allowed_filename_chars="a-zA-Z0-9._-+:"
-allowed_filename_chars_set = {}
-map(allowed_filename_chars_set.setdefault, map(chr, range(ord('a'), ord('z')+1)))
-map(allowed_filename_chars_set.setdefault, map(chr, range(ord('A'), ord('Z')+1)))
-map(allowed_filename_chars_set.setdefault, map(chr, range(ord('0'), ord('9')+1)))
-map(allowed_filename_chars_set.setdefault, map(chr, map(ord, [".", "-", "_", "+", ":"])))
+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()
nocolor()
def warn(txt):
- print "repoman: " + txt
+ print("repoman: " + txt)
def err(txt):
warn(txt)
'fix' : 'Fix simple QA issues (stray digests, missing digests)',
'full' : 'Scan directory tree and print all issues (not a summary)',
'help' : 'Show this screen',
- 'last' : 'Remember report from last run',
- 'lfull' : 'Remember report from last run (full listing)',
'manifest' : 'Generate a Manifest (fetches files if necessary)',
'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]")
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-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('--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"))
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]))
break
if not opts.mode:
- opts.mode = 'full' #default to full
+ opts.mode = 'full'
if opts.mode == 'ci':
opts.mode = 'commit' # backwards compat shortcut
"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",
"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",
"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)",
"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",
"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",
"ebuild.badheader":"This ebuild has a malformed header",
"metadata.missing":"Missing metadata.xml files",
"metadata.bad":"Bad metadata.xml files",
+ "metadata.warning":"Warnings in metadata.xml files",
"virtual.versioned":"PROVIDE contains virtuals with versions",
"virtual.exists":"PROVIDE contains existing package names",
"virtual.unavailable":"PROVIDE contains a virtual which contains no profile default",
- "usage.obsolete":"The ebuild makes use of an obsolete construct"
+ "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 = 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",
"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",
"SRC_URI.mirror",
"file.size",
"inherit.autotools",
"java.eclassesnotused",
-"metadata.missing",
-"metadata.bad",
+"metadata.warning",
"virtual.versioned",
"virtual.exists",
"virtual.unavailable",
"usage.obsolete",
-"LIVEVCS.stable"
+"upstream.workaround",
+"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
"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",
"sys-devel/bison",
"sys-devel/dev86",
"sys-devel/flex",
- "sys-devel/libtool",
"sys-devel/m4",
"sys-devel/pmake",
"x11-misc/bdftopcf",
# file.executable
no_exec = frozenset(["Manifest","ChangeLog","metadata.xml"])
-def last(full=False):
- """Print the results of the last repoman run
- Args:
- full - Print the complete results, if false, print a summary
- Returns:
- Doesn't return (invokes sys.exit()
- """
- #Retrieve and unpickle stats and fails from saved files
- savedf=open(os.path.join(portage.const.CACHE_PATH, 'repo.stats'),'r')
- stats = pickle.load(savedf)
- savedf.close()
- savedf=open(os.path.join(portage.const.CACHE_PATH, 'repo.fails'),'r')
- fails = pickle.load(savedf)
- savedf.close()
-
- #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=0
-
- dofull = options.mode not in ("full", "lfull")
-
- for x in qacats:
- if not stats[x]:
- continue
- dowarn = 1
- if x not in qawarnings:
- dofail = 1
-
- print
- print green("RepoMan remembers...")
- print
- style_file = ConsoleStyleFile(sys.stdout)
- 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)
- print
- if dofull:
- print bold("Note: type \"repoman lfull\" for a complete listing of repomans last run.")
- print
- if dowarn and not dofail:
- print green("RepoMan sez:"),"\"You only gave me a partial QA payment last time?\n I took it, but I wasn't happy.\""
- elif not dofail:
- print green("RepoMan sez:"),"\"If everyone were like you, I'd be out of business!\""
- print
- sys.exit(0)
-
options, arguments = ParseArgs(sys.argv, qahelp)
-if options.mode in ('last', 'lfull'):
- last('lfull' in options.mode)
+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):]
vcs = None
if os.path.isdir("CVS"):
vcs = "cvs"
if os.path.isdir(".svn"):
vcs = "svn"
+elif os.path.isdir(os.path.join(portdir_overlay, ".git")):
+ vcs = "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 != "git":
+ vcs_global_opts = "-q"
+ else:
+ vcs_global_opts = ""
+vcs_global_opts = vcs_global_opts.split()
if vcs == "cvs" and \
"commit" == options.mode 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
logging.info("Not in a version controlled repository; enabling pretend mode.")
options.pretend = True
-try:
- portdir, portdir_overlay, mydir = utilities.FindPortdir(repoman_settings)
-except ValueError:
- sys.exit(1)
+# 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)
+repoman_settings.backup_changes('PORTDIR_OVERLAY')
-os.environ["PORTDIR"] = portdir
-if portdir_overlay != portdir:
- os.environ["PORTDIR_OVERLAY"] = portdir_overlay
-else:
- os.environ["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 = "' + os.environ['PORTDIR'] + '"')
-logging.info('PORTDIR_OVERLAY = "' + os.environ['PORTDIR_OVERLAY']+'"')
+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
-# Now that PORTDIR_OVERLAY is properly overridden, create the portdb.
-repoman_settings = portage.config(local_config=False,
- config_incrementals=portage.const.INCREMENTALS)
-trees = portage.create_trees()
-trees["/"]["porttree"].settings = repoman_settings
-portdb = trees["/"]["porttree"].dbapi
portdb.mysettings = repoman_settings
-setconfig = load_default_config(repoman_settings, trees["/"])
-root_config = RootConfig(repoman_settings, trees["/"], setconfig)
+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"])
-# dep_zapdeps looks at the vardbapi, but it shouldn't for repoman.
-del trees["/"]["vartree"]
-
-myreporoot = os.path.basename(portdir_overlay)
-myreporoot += mydir[len(portdir_overlay):]
reposplit = myreporoot.split(os.path.sep)
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)
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.")
-# 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])
+
+ global_pmasklines.append(portage.util.grabfile_package(
+ os.path.join(path, 'profiles', 'package.mask'), recursive=1))
+
+ 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()
-# retrieve a list of current licenses in portage
-liclist = set(portage.listdir(os.path.join(portdir, "licenses")))
if not liclist:
logging.fatal("Couldn't find licenses?")
sys.exit(1)
-if portdir_overlay != portdir:
- liclist.update(portage.listdir(os.path.join(portdir_overlay, "licenses")))
-# retrieve list of offical keywords
-kwlist = set(portage.grabfile(os.path.join(portdir, "profiles", "arch.list")))
if not kwlist:
logging.fatal("Couldn't read KEYWORDS from arch.list")
sys.exit(1)
-if portdir_overlay != portdir:
- kwlist.update(portage.grabfile(
- os.path.join(portdir_overlay, "profiles", "arch.list")))
+if not uselist:
+ logging.fatal("Couldn't find use.desc?")
+ sys.exit(1)
scanlist=[]
if repolevel==2:
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
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
+repo_subdir_len = len(repo_subdir)
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, 'rb')):
- 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]]]
+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)
- 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)
+ 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={}
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. :-/
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
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()
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":
+ strip_levels = repolevel - 1
+
+ mychanged = os.popen("git diff-index --name-only --diff-filter=M HEAD").readlines()
+ if strip_levels:
+ mychanged = [elem[repo_subdir_len:] for elem in mychanged \
+ if elem[:repo_subdir_len] == repo_subdir]
+ mychanged = ["./" + elem[:-1] for elem in mychanged]
+
+ mynew = os.popen("git diff-index --name-only --diff-filter=A HEAD").readlines()
+ if strip_levels:
+ mynew = [elem[repo_subdir_len:] for elem in mynew \
+ if elem[:repo_subdir_len] == repo_subdir]
+ mynew = ["./" + elem[:-1] for elem in mynew]
if vcs:
new_ebuilds.update(x for x in mynew if x.endswith(".ebuild"))
modified_changelogs.update(x for x in chain(mychanged, mynew) \
if os.path.basename(x) == "ChangeLog")
-have_masked = False
+have_pmasked = False
+have_dev_keywords = False
dofail = 0
arch_caches={}
arch_xmatch_caches = {}
(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.
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)
if options.mode == "manifest" 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
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 \
+ '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)
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"):
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))
# 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(portage.pkgcmp)
- for i in xrange(len(ebuildlist)):
+ ebuildlist.sort(key=cmp_sort_key(portage.pkgcmp))
+ for i in range(len(ebuildlist)):
ebuildlist[i] = pkgsplits[ebuildlist[i]]
del pkgsplits
continue
for y in checkdirlist:
- for c in y.strip(os.path.sep):
- if c not in allowed_filename_chars_set:
- stats["file.name"] += 1
- fails["file.name"].append("%s/%s: char '%s'" % (checkdir, y, c))
- break
+ 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 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")
s = s[s.rfind("\n") + 1:]
fails["file.UTF8"].append("%s/%s: line %i, just after: '%s'" % (checkdir, y, line, s))
- if vcs and check_ebuild_notadded:
+ if vcs == "git" and check_ebuild_notadded:
+ myf = os.popen("git ls-files --others %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") and check_ebuild_notadded:
try:
if vcs == "cvs":
myf=open(checkdir+"/CVS/Entries","r")
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]))
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"])
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"):
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
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)
- for c in os.path.basename(y.rstrip(os.path.sep)):
- if c not in allowed_filename_chars_set:
- stats["file.name"] += 1
- fails["file.name"].append("%s/files/%s: char '%s'" % (checkdir, y, c))
- break
+ 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 = 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
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")
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))
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")
del metadata_bad
+ muselist = frozenset(muselist)
- changelog_path = "ChangeLog"
- if repolevel < 3:
- changelog_path = os.path.join(pkgdir, changelog_path)
- if repolevel < 2:
- changelog_path = os.path.join(catdir, changelog_path)
- changelog_path = os.path.join(".", changelog_path)
+ 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")
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 and check_ebuild_notadded and y not in eadded:
+ if vcs in ("cvs", "svn") 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]:
stats["ebuild.invalidname"]=stats["ebuild.invalidname"]+1
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.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.
"%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
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")
previous_keywords = slot_keywords.get(myaux["SLOT"])
if previous_keywords is None:
slot_keywords[myaux["SLOT"]] = set()
- else:
+ elif not live_ebuild:
dropped_keywords = previous_keywords.difference(ebuild_archs)
if dropped_keywords:
stats["KEYWORDS.dropped"] += 1
Ebuilds that inherit a "Live" eclass (darcs,subversion,git,cvs,etc..) should
not be allowed to be marked stable
"""
- if live_eclasses.intersection(pkg.inherited):
+ if live_ebuild:
bad_stable_keywords = []
for keyword in keywords:
if not keyword.startswith("~") and \
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()]]
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:
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)
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):
" 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
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:
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))
# Syntax Checks
relative_path = os.path.join(x, y + ".ebuild")
full_path = os.path.join(repodir, relative_path)
- f = open(full_path, 'rb')
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()
- del f
+ # All ebuilds should have utf_8 encoding.
+ 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
+ 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
for prof in profiles[arch]:
- if prof[1] not in ("stable", "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)
+ 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()
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()
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 \
portdb.xmatch("list-visible", x)
if ismasked:
- have_masked = True
+ 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
else:
suffix=""
matchmode = "minimum-visible"
-
- if prof[1] == "dev":
+
+ 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]]:
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.
stats["ebuild.allmasked"]+=1
fails["ebuild.allmasked"].append(x)
+ # check if there are unused local USE-descriptions in metadata.xml
+ 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)
-#Pickle and save results for instant reuse in last and lfull
-if os.access(portage.const.CACHE_PATH, os.W_OK):
- for myobj, fname in (stats, "repo.stats"), (fails, "repo.fails"):
- fpath = os.path.join(portage.const.CACHE_PATH, fname)
- savef = open(fpath, 'w')
- pickle.dump(myobj, savef)
- savef.close()
- portage.apply_secpass_permissions(fpath, gid=portage.portage_gid,
- mode=0664)
-
-# TODO(antarus) This function and last () look familiar ;)
-
#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 not in ("full", "lfull")
+dofull = options.mode != 'full'
for x in qacats:
if not stats[x]:
# 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 = StringIO.StringIO()
+qa_output = StringIO()
style_file = ConsoleStyleFile(sys.stdout)
if options.mode == 'commit' and \
(not commitmessage or not commitmessage.strip()):
mygroups[xs[0]]+=[seperator.join(xs[1:])]
return mygroups
-if have_masked and not (options.without_mask or options.ignore_masked):
- print bold("Note: use --without-mask to check " + \
- "KEYWORDS on dependencies of masked packages")
+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.")
+ 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.")
-
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, e:
+ except SystemExit as e:
raise # TODO propogate 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()
+
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."
+ print("!!! files dir is not added! Please correct this.")
sys.exit(-1)
elif xs[-1]=="Manifest":
# It's a manifest... auto add
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)+")")
retval=0
else:
if vcs == "cvs":
retval=os.system("cvs add "+" ".join(myautoadd))
if vcs == "svn":
retval=os.system("svn add "+" ".join(myautoadd))
+ elif vcs == "git":
+ retval=os.system("git add "+" ".join(myautoadd))
if retval:
- print "!!! Exiting on vcs (shell) error code:",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.")
+ 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":
expansion = set("./" + prop.split(" - ")[0] \
for prop in props if " - " in prop)
+ elif vcs == "git":
+ strip_levels = repolevel - 1
+
+ mychanged = os.popen("git diff-index --name-only --diff-filter=M HEAD").readlines()
+ if strip_levels:
+ mychanged = [elem[repo_subdir_len:] for elem in mychanged \
+ if elem[:repo_subdir_len] == repo_subdir]
+ mychanged = ["./" + elem[:-1] for elem in mychanged]
+
+ mynew = os.popen("git diff-index --name-only --diff-filter=A HEAD").readlines()
+ if strip_levels:
+ mynew = [elem[repo_subdir_len:] for elem in mynew \
+ if elem[:repo_subdir_len] == repo_subdir]
+ mynew = ["./" + elem[:-1] for elem in mynew]
+
+ myremoved = os.popen("git diff-index --name-only --diff-filter=D HEAD").readlines()
+ if strip_levels:
+ myremoved = [elem[repo_subdir_len:] for elem in myremoved \
+ if elem[:repo_subdir_len] == repo_subdir]
+ myremoved = ["./" + elem[:-1] for elem in myremoved]
+
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.
mymanifests = set()
- changed_set = set()
- new_set = set()
- for f in mychanged:
- if "Manifest" == os.path.basename(f):
- mymanifests.add(f)
- else:
- changed_set.add(f)
- for f in mynew:
+ myupdates = set()
+ for f in mychanged + mynew:
if "Manifest" == os.path.basename(f):
mymanifests.add(f)
else:
- new_set.add(f)
- mychanged = list(changed_set)
- mynew = list(new_set)
+ myupdates.add(f)
+ if vcs == 'git':
+ myupdates.difference_update(myremoved)
+ myupdates = list(myupdates)
mymanifests = list(mymanifests)
- del changed_set, new_set
- myupdates = mychanged + mynew
myheaders = []
mydirty = []
headerstring = "'\$(Header|Id)"
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 "*",green(str(len(myupdates))),"files being committed...",green(str(len(myheaders))),"have headers that will change."
- print "*","Files with headers will cause the manifests to be made and recommited."
- logging.info("myupdates:", str(myupdates))
- logging.info("myheaders:", str(myheaders))
+ 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()
+ 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.")
+ logging.info("myupdates: %s", myupdates)
+ logging.info("myheaders: %s", myheaders)
commitmessage = options.commitmsg
if options.commitmsgfile:
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:
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)
sys.stderr.write("Failed to insert portage version in message!\n")
sys.stderr.flush()
portage_version = "Unknown"
- unameout = platform.system() + " " + platform.release() + " "
+ unameout = platform.system() + " "
if platform.system() in ["Darwin", "SunOS"]:
unameout += platform.processor()
else:
commitmessage += ", RepoMan options: --force"
commitmessage += ")"
- if myupdates or myremoved:
+ if vcs != 'git' and (myupdates or myremoved):
myfiles = myupdates + myremoved
if not myheaders and "sign" not in repoman_settings.features:
myfiles += mymanifests
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,
+ # 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)
- retval = None
- if options.pretend:
- if vcs == "cvs":
- print "(cvs -q commit -F %s %s)" % \
- (commitmessagefile, " ".join(myfiles))
- if vcs == "svn":
- print "(svn commit -F %s %s)" % \
- (commitmessagefile, " ".join(myfiles))
- else:
- if vcs == "cvs":
- retval = spawn(["cvs", "-q", "commit",
- "-F", commitmessagefile] + myfiles,
- env=os.environ)
- if vcs == "svn":
- retval = spawn(["svn", "commit",
- "-F", commitmessagefile] + myfiles,
- env=os.environ)
try:
- os.unlink(commitmessagefile)
- except OSError:
- pass
- if retval:
- print "!!! Exiting on cvs (shell) error code:",retval
- sys.exit(retval)
+ 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):
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:
write_atomic(x, "".join(mylines))
manifest_commit_required = True
- if myupdates or myremoved or mynew:
- myfiles=myupdates+myremoved+mynew
+ if vcs != 'git' 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]
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:
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.
if repolevel < 3 and "sign" in repoman_settings.features:
- if options.pretend:
- if vcs == "cvs":
- print "(cvs -q commit -F commitmessagefile)"
- if vcs == "svn":
- print "(svn -q commit -F commitmessagefile)"
- else:
- fd, commitmessagefile = tempfile.mkstemp(".repoman.msg")
- mymsg = os.fdopen(fd, "w")
- mymsg.write(commitmessage)
- mymsg.write("\n (Unsigned Manifest commit)")
- mymsg.close()
- if vcs == "cvs":
- retval=os.system("cvs -q commit -F "+commitmessagefile)
- if vcs == "svn":
- retval=os.system("svn -q commit -F "+commitmessagefile)
+
+ fd, commitmessagefile = tempfile.mkstemp(".repoman.msg")
+ mymsg = os.fdopen(fd, "w")
+ mymsg.write(commitmessage)
+ mymsg.write("\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
- if retval:
- print "!!! Exiting on cvs (shell) error code:",retval
- sys.exit(retval)
manifest_commit_required = False
signed = False
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("/")
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
- if manifest_commit_required or signed:
+ 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:
- if vcs == "cvs":
- print "(cvs -q commit -F commitmessagefile)"
- if vcs == "svn":
- print "(svn -q commit -F commitmessagefile)"
+ print("(%s)" % (" ".join(update_index_cmd),))
else:
- fd, commitmessagefile = tempfile.mkstemp(".repoman.msg")
- mymsg = os.fdopen(fd, "w")
- mymsg.write(commitmessage)
- if signed:
- mymsg.write("\n (Signed Manifest commit)")
+ 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 == 'git' or manifest_commit_required or signed:
+
+ myfiles = mymanifests[:]
+ if vcs == 'git':
+ myfiles += myupdates
+ myfiles += myremoved
+ myfiles.sort()
+
+ fd, commitmessagefile = tempfile.mkstemp(".repoman.msg")
+ mymsg = os.fdopen(fd, "w")
+ mymsg.write(commitmessage)
+ if signed:
+ mymsg.write("\n (Signed Manifest commit)")
+ else:
+ mymsg.write("\n (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)
+ commit_cmd.extend(["-F", commitmessagefile])
+ commit_cmd.extend(f.lstrip("./") for f in myfiles)
+
+ try:
+ if options.pretend:
+ print("(%s)" % (" ".join(commit_cmd),))
else:
- mymsg.write("\n (Unsigned Manifest commit)")
- mymsg.close()
- if vcs == "cvs":
- retval=os.system("cvs -q commit -F "+commitmessagefile)
- if vcs == "svn":
- retval=os.system("svn -q commit -F "+commitmessagefile)
+ 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
- if retval:
- print "!!! Exiting on cvs (shell) error code:",retval
- sys.exit(retval)
- 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)