2 # Copyright 1999-2006 Gentoo Foundation
3 # Distributed under the terms of the GNU General Public License v2
6 # Next to do: dep syntax checking in mask files
7 # Then, check to make sure deps are satisfiable (to avoid "can't find match for" problems)
8 # that last one is tricky because multiple profiles need to be checked.
25 from itertools import chain, izip
26 from stat import S_ISDIR, ST_CTIME
29 import cStringIO as StringIO
33 if not hasattr(__builtins__, "set"):
34 from sets import Set as set
39 from os import path as osp
40 sys.path.insert(0, osp.join(osp.dirname(osp.dirname(osp.realpath(__file__))), "pym"))
42 portage._disable_legacy_globals()
45 from repoman.checks import run_checks
46 from repoman import utilities
48 from os import path as osp
49 sys.path.insert(0, osp.join(osp.dirname(osp.dirname(osp.realpath(__file__))), 'pym'))
50 from repoman.checks import run_checks
51 from repoman import utilities
53 from _emerge import Package, RootConfig
54 from portage._sets import load_default_config
56 import portage.checksum
59 portage.dep._dep_check_strict = True
60 import portage.exception
61 from portage import cvstree, normalize_path
62 from portage import util
63 from portage.exception import ParseError
64 from portage.manifest import Manifest
65 from portage.process import find_binary, spawn
66 from portage.output import bold, create_color_func, darkgreen, \
67 green, nocolor, red, turquoise, yellow
68 from portage.output import ConsoleStyleFile, StyleWriter
69 from portage.util import writemsg_level
71 util.initialize_logger()
73 # 14 is the length of DESCRIPTION=""
75 allowed_filename_chars="a-zA-Z0-9._-+:"
76 allowed_filename_chars_set = {}
77 map(allowed_filename_chars_set.setdefault, map(chr, range(ord('a'), ord('z')+1)))
78 map(allowed_filename_chars_set.setdefault, map(chr, range(ord('A'), ord('Z')+1)))
79 map(allowed_filename_chars_set.setdefault, map(chr, range(ord('0'), ord('9')+1)))
80 map(allowed_filename_chars_set.setdefault, map(chr, map(ord, [".", "-", "_", "+", ":"])))
81 bad = create_color_func("BAD")
83 # A sane umask is needed for files that portage creates.
85 repoman_settings = portage.config(local_config=False,
86 config_incrementals=portage.const.INCREMENTALS)
87 repoman_settings.lock()
89 if repoman_settings.get("NOCOLOR", "").lower() in ("yes", "true") or \
90 not sys.stdout.isatty():
94 print "repoman: " + txt
100 def exithandler(signum=None, frame=None):
101 logging.fatal("Interrupted; exiting...")
103 os.kill(0, signal.SIGKILL)
105 signal.signal(signal.SIGINT,exithandler)
107 class RepomanHelpFormatter(optparse.IndentedHelpFormatter):
108 """Repoman needs it's own HelpFormatter for now, because the default ones
109 murder the help text."""
111 def __init__(self, indent_increment=1, max_help_position=24, width=150, short_first=1):
112 optparse.HelpFormatter.__init__(self, indent_increment, max_help_position, width, short_first)
114 def format_description(self, description):
117 class RepomanOptionParser(optparse.OptionParser):
118 """Add the on_tail function, ruby has it, optionParser should too
121 def __init__(self, *args, **kwargs):
122 optparse.OptionParser.__init__(self, *args, **kwargs)
125 def on_tail(self, description):
126 self.tail += description
128 def format_help(self, formatter=None):
129 result = optparse.OptionParser.format_help(self, formatter)
134 def ParseArgs(args, qahelp):
135 """This function uses a customized optionParser to parse command line arguments for repoman
137 args - a sequence of command line arguments
138 qahelp - a dict of qa warning to help message
140 (opts, args), just like a call to parser.parse_args()
144 'commit' : 'Run a scan then commit changes',
145 'ci' : 'Run a scan then commit changes',
146 'fix' : 'Fix simple QA issues (stray digests, missing digests)',
147 'full' : 'Scan directory tree and print all issues (not a summary)',
148 'help' : 'Show this screen',
149 'manifest' : 'Generate a Manifest (fetches files if necessary)',
150 'scan' : 'Scan directory tree for QA issues'
153 mode_keys = modes.keys()
156 parser = RepomanOptionParser(formatter=RepomanHelpFormatter(), usage="%prog [options] [mode]")
157 parser.description = green(" ".join((os.path.basename(args[0]), "1.2")))
158 parser.description += "\nCopyright 1999-2007 Gentoo Foundation"
159 parser.description += "\nDistributed under the terms of the GNU General Public License v2"
160 parser.description += "\nmodes: " + " | ".join(map(green,mode_keys))
162 parser.add_option('-m', '--commitmsg', dest='commitmsg',
163 help='specify a commit message on the command line')
165 parser.add_option('-M', '--commitmsgfile', dest='commitmsgfile',
166 help='specify a path to a file that contains a commit message')
168 parser.add_option('-p', '--pretend', dest='pretend', default=False,
169 action='store_true', help='don\'t commit or fix anything; just show what would be done')
171 parser.add_option('-q', '--quiet', dest="quiet", action="count", default=0,
172 help='do not print unnecessary messages')
174 parser.add_option('-f', '--force', dest='force', default=False, action='store_true',
175 help='Commit with QA violations')
177 parser.add_option('-v', '--verbose', dest="verbosity", action='count',
178 help='be very verbose in output', default=0)
180 parser.add_option('-x', '--xmlparse', dest='xml_parse', action='store_true',
181 default=False, help='forces the metadata.xml parse check to be carried out')
183 parser.add_option('-i', '--ignore-arches', dest='ignore_arches', action='store_true',
184 default=False, help='ignore arch-specific failures (where arch != host)')
186 parser.add_option('-I', '--ignore-masked', dest='ignore_masked', action='store_true',
187 default=False, help='ignore masked packages (not allowed with commit mode)')
189 parser.add_option('-d', '--include-dev', dest='include_dev', action='store_true',
190 default=False, help='include dev profiles in dependency checks')
192 parser.add_option('--without-mask', dest='without_mask', action='store_true',
193 default=False, help='behave as if no package.mask entries exist (not allowed with commit mode)')
195 parser.add_option('--mode', type='choice', dest='mode', choices=modes.keys(),
196 help='specify which mode repoman will run in (default=full)')
198 parser.on_tail("\n " + green("Modes".ljust(20) + " Description\n"))
201 parser.on_tail(" %s %s\n" % (k.ljust(20), modes[k]))
203 parser.on_tail("\n " + green("QA keyword".ljust(20) + " Description\n"))
205 sorted_qa = qahelp.keys()
208 parser.on_tail(" %s %s\n" % (k.ljust(20), qahelp[k]))
212 opts, args = parser.parse_args(args)
214 if opts.mode == 'help':
215 parser.print_help(short=False)
226 if opts.mode == 'ci':
227 opts.mode = 'commit' # backwards compat shortcut
229 if opts.mode == 'commit' and not (opts.force or opts.pretend):
230 if opts.ignore_masked:
231 parser.error('Commit mode and --ignore-masked are not compatible')
232 if opts.without_mask:
233 parser.error('Commit mode and --without-mask are not compatible')
235 # Use the verbosity and quiet options to fiddle with the loglevel appropriately
236 for val in range(opts.verbosity):
237 logger = logging.getLogger()
238 logger.setLevel(logger.getEffectiveLevel() - 10)
240 for val in range(opts.quiet):
241 logger = logging.getLogger()
242 logger.setLevel(logger.getEffectiveLevel() + 10)
247 "CVS/Entries.IO_error":"Attempting to commit, and an IO error was encountered access the Entries file",
248 "desktop.invalid":"desktop-file-validate reports errors in a *.desktop file",
249 "ebuild.invalidname":"Ebuild files with a non-parseable or syntactically incorrect name (or using 2.1 versioning extensions)",
250 "ebuild.namenomatch":"Ebuild files that do not have the same name as their parent directory",
251 "changelog.ebuildadded":"An ebuild was added but the ChangeLog was not modified",
252 "changelog.missing":"Missing ChangeLog files",
253 "ebuild.notadded":"Ebuilds that exist but have not been added to cvs",
254 "ebuild.patches":"PATCHES variable should be a bash array to ensure white space safety",
255 "changelog.notadded":"ChangeLogs that exist but have not been added to cvs",
256 "filedir.missing":"Package lacks a files directory",
257 "file.executable":"Ebuilds, digests, metadata.xml, Manifest, and ChangeLog do note need the executable bit",
258 "file.size":"Files in the files directory must be under 20k",
259 "file.name":"File/dir name must be composed of only the following chars: %s " % allowed_filename_chars,
260 "file.UTF8":"File is not UTF8 compliant",
261 "inherit.autotools":"Ebuild inherits autotools but does not call eautomake, eautoconf or eautoreconf",
262 "java.eclassesnotused":"With virtual/jdk in DEPEND you must inherit a java eclass",
263 "KEYWORDS.dropped":"Ebuilds that appear to have dropped KEYWORDS for some arch",
264 "KEYWORDS.missing":"Ebuilds that have a missing or empty KEYWORDS variable",
265 "KEYWORDS.stable":"Ebuilds that have been added directly with stable KEYWORDS",
266 "KEYWORDS.stupid":"Ebuilds that use KEYWORDS=-* instead of package.mask",
267 "LICENSE.missing":"Ebuilds that have a missing or empty LICENSE variable",
268 "DESCRIPTION.missing":"Ebuilds that have a missing or empty DESCRIPTION variable",
269 "DESCRIPTION.toolong":"DESCRIPTION is over %d characters" % max_desc_len,
270 "EAPI.incompatible":"Ebuilds that use features that are only available with a different EAPI",
271 "EAPI.unsupported":"Ebuilds that have an unsupported EAPI version (you must upgrade portage)",
272 "SLOT.missing":"Ebuilds that have a missing or empty SLOT variable",
273 "HOMEPAGE.missing":"Ebuilds that have a missing or empty HOMEPAGE variable",
274 "DEPEND.bad":"User-visible ebuilds with bad DEPEND settings (matched against *visible* ebuilds)",
275 "RDEPEND.bad":"User-visible ebuilds with bad RDEPEND settings (matched against *visible* ebuilds)",
276 "PDEPEND.bad":"User-visible ebuilds with bad PDEPEND settings (matched against *visible* ebuilds)",
277 "DEPEND.badmasked":"Masked ebuilds with bad DEPEND settings (matched against *all* ebuilds)",
278 "RDEPEND.badmasked":"Masked ebuilds with RDEPEND settings (matched against *all* ebuilds)",
279 "PDEPEND.badmasked":"Masked ebuilds with PDEPEND settings (matched against *all* ebuilds)",
280 "DEPEND.badindev":"User-visible ebuilds with bad DEPEND settings (matched against *visible* ebuilds) in developing arch",
281 "RDEPEND.badindev":"User-visible ebuilds with bad RDEPEND settings (matched against *visible* ebuilds) in developing arch",
282 "PDEPEND.badindev":"User-visible ebuilds with bad PDEPEND settings (matched against *visible* ebuilds) in developing arch",
283 "DEPEND.badmaskedindev":"Masked ebuilds with bad DEPEND settings (matched against *all* ebuilds) in developing arch",
284 "RDEPEND.badmaskedindev":"Masked ebuilds with RDEPEND settings (matched against *all* ebuilds) in developing arch",
285 "PDEPEND.badmaskedindev":"Masked ebuilds with PDEPEND settings (matched against *all* ebuilds) in developing arch",
286 "DEPEND.syntax":"Syntax error in DEPEND (usually an extra/missing space/parenthesis)",
287 "RDEPEND.syntax":"Syntax error in RDEPEND (usually an extra/missing space/parenthesis)",
288 "PDEPEND.syntax":"Syntax error in PDEPEND (usually an extra/missing space/parenthesis)",
289 "LICENSE.syntax":"Syntax error in LICENSE (usually an extra/missing space/parenthesis)",
290 "PROVIDE.syntax":"Syntax error in PROVIDE (usually an extra/missing space/parenthesis)",
291 "PROPERTIES.syntax":"Syntax error in PROPERTIES (usually an extra/missing space/parenthesis)",
292 "RESTRICT.syntax":"Syntax error in RESTRICT (usually an extra/missing space/parenthesis)",
293 "SRC_URI.syntax":"Syntax error in SRC_URI (usually an extra/missing space/parenthesis)",
294 "SRC_URI.mirror":"A uri listed in profiles/thirdpartymirrors is found in SRC_URI",
295 "ebuild.syntax":"Error generating cache entry for ebuild; typically caused by ebuild syntax error or digest verification failure",
296 "ebuild.output":"A simple sourcing of the ebuild produces output; this breaks ebuild policy.",
297 "ebuild.nesteddie":"Placing 'die' inside ( ) prints an error, but doesn't stop the ebuild.",
298 "variable.readonly":"Assigning a readonly variable",
299 "LIVEVCS.stable":"This ebuild is a live checkout from a VCS but has stable keywords.",
300 "IUSE.invalid":"This ebuild has a variable in IUSE that is not in the use.desc or use.local.desc file",
301 "IUSE.undefined":"This ebuild does not define IUSE (style guideline says to define IUSE even when empty)",
302 "LICENSE.invalid":"This ebuild is listing a license that doesnt exist in portages license/ dir.",
303 "KEYWORDS.invalid":"This ebuild contains KEYWORDS that are not listed in profiles/arch.list or for which no valid profile was found",
304 "RDEPEND.suspect":"RDEPEND contains a package that usually only belongs in DEPEND.",
305 "RESTRICT.invalid":"This ebuild contains invalid RESTRICT values.",
306 "digestentry.unused":"Some files listed in the Manifest aren't referenced in SRC_URI",
307 "ebuild.nostable":"There are no ebuilds that are marked as stable for your ARCH",
308 "ebuild.allmasked":"All ebuilds are masked for this package (Package level only)",
309 "ebuild.majorsyn":"This ebuild has a major syntax error that may cause the ebuild to fail partially or fully",
310 "ebuild.minorsyn":"This ebuild has a minor syntax error that contravenes gentoo coding style",
311 "ebuild.badheader":"This ebuild has a malformed header",
312 "metadata.missing":"Missing metadata.xml files",
313 "metadata.bad":"Bad metadata.xml files",
314 "virtual.versioned":"PROVIDE contains virtuals with versions",
315 "virtual.exists":"PROVIDE contains existing package names",
316 "virtual.unavailable":"PROVIDE contains a virtual which contains no profile default",
317 "usage.obsolete":"The ebuild makes use of an obsolete construct"
320 qacats = qahelp.keys()
325 "changelog.notadded",
326 "digestentry.unused",
332 "DEPEND.badmasked","RDEPEND.badmasked","PDEPEND.badmasked",
333 "DEPEND.badindev","RDEPEND.badindev","PDEPEND.badindev",
334 "DEPEND.badmaskedindev","RDEPEND.badmaskedindev","PDEPEND.badmaskedindev",
335 "DESCRIPTION.toolong",
348 "java.eclassesnotused",
351 "virtual.unavailable",
356 missingvars=["KEYWORDS","LICENSE","DESCRIPTION","HOMEPAGE","SLOT"]
357 allvars = set(x for x in portage.auxdbkeys if not x.startswith("UNUSED_"))
358 allvars.discard("CDEPEND")
359 allvars.update(Package.metadata_keys)
360 allvars = sorted(allvars)
362 for x in missingvars:
365 logging.warn('* missingvars values need to be added to qahelp ("%s")' % x)
369 valid_restrict = frozenset(["binchecks", "bindist",
370 "fetch", "installsources", "mirror",
371 "primaryuri", "strip", "test", "userpriv"])
373 live_eclasses = frozenset([
382 suspect_rdepend = frozenset([
383 "app-arch/cabextract",
384 "app-arch/rpm2targz",
389 "dev-perl/extutils-pkgconfig",
390 "dev-python/setuptools",
395 "dev-util/gtk-doc-am",
398 "dev-util/pkgconfig",
402 "media-gfx/ebdftopcf",
404 "sys-devel/autoconf",
405 "sys-devel/automake",
418 no_exec = frozenset(["Manifest","ChangeLog","metadata.xml"])
420 options, arguments = ParseArgs(sys.argv, qahelp)
422 # Set this to False when an extraordinary issue (generally
423 # something other than a QA issue) makes it impossible to
424 # commit (like if Manifest generation fails).
427 portdir, portdir_overlay, mydir = utilities.FindPortdir(repoman_settings)
432 if os.path.isdir("CVS"):
434 if os.path.isdir(".svn"):
436 elif os.path.isdir(os.path.join(portdir_overlay, ".git")):
439 vcs_local_opts = repoman_settings.get("REPOMAN_VCS_LOCAL_OPTS", "").split()
440 vcs_global_opts = repoman_settings.get("REPOMAN_VCS_GLOBAL_OPTS")
441 if vcs_global_opts is None:
443 vcs_global_opts = "-q"
446 vcs_global_opts = vcs_global_opts.split()
448 if vcs == "cvs" and \
449 "commit" == options.mode and \
450 "RMD160" not in portage.checksum.hashorigin_map:
451 from portage.util import grablines
452 repo_lines = grablines("./CVS/Repository")
454 "gentoo-x86" == repo_lines[0].strip().split(os.path.sep)[0]:
455 msg = "Please install " \
456 "pycrypto or enable python's ssl USE flag in order " \
457 "to enable RMD160 hash support. See bug #198398 for " \
460 from textwrap import wrap
461 for line in wrap(msg, 70):
466 if options.mode == 'commit' and not options.pretend and not vcs:
467 logging.info("Not in a version controlled repository; enabling pretend mode.")
468 options.pretend = True
470 os.environ["PORTDIR"] = portdir
471 if portdir_overlay != portdir:
472 os.environ["PORTDIR_OVERLAY"] = portdir_overlay
474 os.environ["PORTDIR_OVERLAY"] = ""
476 logging.info('Setting paths:')
477 logging.info('PORTDIR = "' + os.environ['PORTDIR'] + '"')
478 logging.info('PORTDIR_OVERLAY = "' + os.environ['PORTDIR_OVERLAY']+'"')
480 # Now that PORTDIR_OVERLAY is properly overridden, create the portdb.
481 repoman_settings = portage.config(local_config=False,
482 config_incrementals=portage.const.INCREMENTALS)
483 trees = portage.create_trees()
484 trees["/"]["porttree"].settings = repoman_settings
485 portdb = trees["/"]["porttree"].dbapi
486 portdb.mysettings = repoman_settings
487 setconfig = load_default_config(repoman_settings, trees["/"])
488 root_config = RootConfig(repoman_settings, trees["/"], setconfig)
489 # We really only need to cache the metadata that's necessary for visibility
490 # filtering. Anything else can be discarded to reduce memory consumption.
491 portdb._aux_cache_keys.clear()
492 portdb._aux_cache_keys.update(["EAPI", "KEYWORDS", "SLOT"])
493 # dep_zapdeps looks at the vardbapi, but it shouldn't for repoman.
494 del trees["/"]["vartree"]
496 myreporoot = os.path.basename(portdir_overlay)
497 myreporoot += mydir[len(portdir_overlay):]
499 reposplit = myreporoot.split(os.path.sep)
500 repolevel = len(reposplit)
502 # check if it's in $PORTDIR/$CATEGORY/$PN , otherwise bail if commiting.
503 # Reason for this is if they're trying to commit in just $FILESDIR/*, the Manifest needs updating.
504 # this check ensures that repoman knows where it is, and the manifest recommit is at least possible.
505 if options.mode == 'commit' and repolevel not in [1,2,3]:
506 print red("***")+" Commit attempts *must* be from within a vcs co, category, or package directory."
507 print red("***")+" Attempting to commit from a packages files directory will be blocked for instance."
508 print red("***")+" This is intended behaviour, to ensure the manifest is recommited for a package."
510 err("Unable to identify level we're commiting from for %s" % '/'.join(reposplit))
512 startdir = normalize_path(mydir)
514 for x in range(0, repolevel - 1):
515 repodir = os.path.dirname(repodir)
518 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.")
520 # setup a uselist from portage
523 uselist=portage.grabfile(portdir+"/profiles/use.desc")
524 for l in range(0,len(uselist)):
525 uselist[l]=uselist[l].split()[0]
526 for var in repoman_settings["USE_EXPAND"].split():
527 vardescs = portage.grabfile(portdir+"/profiles/desc/"+var.lower()+".desc")
528 for l in range(0, len(vardescs)):
529 uselist.append(var.lower() + "_" + vardescs[l].split()[0])
530 except (IOError, OSError, ParseError), e:
531 logging.exception("Couldn't read USE flags from use.desc")
534 # retrieve a list of current licenses in portage
535 liclist = set(portage.listdir(os.path.join(portdir, "licenses")))
537 logging.fatal("Couldn't find licenses?")
539 if portdir_overlay != portdir:
540 liclist.update(portage.listdir(os.path.join(portdir_overlay, "licenses")))
542 # retrieve list of offical keywords
543 kwlist = set(portage.grabfile(os.path.join(portdir, "profiles", "arch.list")))
545 logging.fatal("Couldn't read KEYWORDS from arch.list")
548 if portdir_overlay != portdir:
549 kwlist.update(portage.grabfile(
550 os.path.join(portdir_overlay, "profiles", "arch.list")))
554 #we are inside a category directory
556 if catdir not in repoman_settings.categories:
558 mydirlist=os.listdir(startdir)
560 if x == "CVS" or x.startswith("."):
562 if os.path.isdir(startdir+"/"+x):
563 scanlist.append(catdir+"/"+x)
564 repo_subdir = catdir + os.sep
566 for x in repoman_settings.categories:
567 if not os.path.isdir(startdir+"/"+x):
569 for y in os.listdir(startdir+"/"+x):
570 if y == "CVS" or y.startswith("."):
572 if os.path.isdir(startdir+"/"+x+"/"+y):
573 scanlist.append(x+"/"+y)
576 catdir = reposplit[-2]
577 if catdir not in repoman_settings.categories:
579 scanlist.append(catdir+"/"+reposplit[-1])
580 repo_subdir = scanlist[-1] + os.sep
581 repo_subdir_len = len(repo_subdir)
584 logging.debug("Found the following packages to scan:\n%s" % '\n'.join(scanlist))
587 valid_profile_types = frozenset(["dev", "exp", "stable"])
588 descfile=portdir+"/profiles/profiles.desc"
589 if os.path.exists(descfile):
590 for i, x in enumerate(open(descfile, 'rb')):
597 err("wrong format: \"" + bad(x.strip()) + "\" in " + \
598 descfile + " line %d" % (i+1, ))
599 elif arch[0] not in kwlist:
600 err("invalid arch: \"" + bad(arch[0]) + "\" in " + \
601 descfile + " line %d" % (i+1, ))
602 elif arch[2] not in valid_profile_types:
603 err("invalid profile type: \"" + bad(arch[2]) + "\" in " + \
604 descfile + " line %d" % (i+1, ))
605 if not os.path.isdir(portdir+"/profiles/"+arch[1]):
606 print "Invalid "+arch[2]+" profile ("+arch[1]+") for arch "+arch[0]
608 if arch[0] in profiles:
609 profiles[arch[0]]+= [[arch[1], arch[2]]]
611 profiles[arch[0]] = [[arch[1], arch[2]]]
613 for x in repoman_settings.archlist():
616 if x not in profiles:
617 print red("\""+x+"\" doesn't have a valid profile listed in profiles.desc.")
618 print red("You need to either \"cvs update\" your profiles dir or follow this")
619 print red("up with the "+x+" team.")
622 print red("profiles.desc does not exist: "+descfile)
623 print red("You need to do \"cvs update\" in profiles dir.")
627 def dev_keywords(profiles):
629 Create a set of KEYWORDS values that exist in 'dev'
630 profiles. These are used
631 to trigger a message notifying the user when they might
632 want to add the --include-dev option.
635 for arch, arch_profiles in profiles.iteritems():
636 for profile_path, profile_type in arch_profiles:
637 arch_set = type_arch_map.get(profile_type)
640 type_arch_map[profile_type] = arch_set
643 dev_keywords = type_arch_map.get('dev', set())
644 dev_keywords.update(['~' + arch for arch in dev_keywords])
645 return frozenset(dev_keywords)
647 dev_keywords = dev_keywords(profiles)
652 # provided by the desktop-file-utils package
653 desktop_file_validate = find_binary("desktop-file-validate")
654 desktop_pattern = re.compile(r'.*\.desktop$')
659 xmllint_capable = False
660 metadata_dtd = os.path.join(repoman_settings["DISTDIR"], 'metadata.dtd')
661 if options.mode == "manifest":
663 elif not find_binary('xmllint'):
664 print red("!!! xmllint not found. Can't check metadata.xml.\n")
665 if options.xml_parse or repolevel==3:
666 print red("!!!")+" sorry, xmllint is needed. failing\n"
669 #hardcoded paths/urls suck. :-/
673 # if it's been over a week since fetching (or the system clock is fscked), grab an updated copy of metadata.dtd
674 # clock is fscked or it's been a week. time to grab a new one.
675 ct = os.stat(metadata_dtd)[ST_CTIME]
676 if abs(time.time() - ct) > (60*60*24*7):
677 # don't trap the exception, we're watching for errno 2 (file not found), anything else is a bug.
682 except (OSError,IOError), e:
684 print red("!!!")+" caught exception '%s' for %s/metadata.dtd, bailing" % (str(e), portage.CACHE_PATH)
689 print green("***")+" the local copy of metadata.dtd needs to be refetched, doing that now"
694 os.unlink(metadata_dtd)
696 if e.errno != errno.ENOENT:
699 val=portage.fetch(['http://www.gentoo.org/dtd/metadata.dtd'],repoman_settings,fetchonly=0, \
702 except SystemExit, e:
703 raise # Need to propogate this
706 print red("!!!")+" attempting to fetch 'http://www.gentoo.org/dtd/metadata.dtd', caught"
707 print red("!!!")+" exception '%s' though." % str(e)
710 print red("!!!")+" fetching new metadata.dtd failed, aborting"
712 #this can be problematic if xmllint changes their output
715 if options.mode == 'commit' and vcs:
716 utilities.detect_vcs_conflicts(options, vcs)
718 if options.mode == "manifest":
720 elif options.pretend:
721 print green("\nRepoMan does a once-over of the neighborhood...")
723 print green("\nRepoMan scours the neighborhood...")
726 modified_changelogs = set()
732 mycvstree = cvstree.getentries("./", recursive=1)
733 mychanged = cvstree.findchanged(mycvstree, recursive=1, basedir="./")
734 mynew = cvstree.findnew(mycvstree, recursive=1, basedir="./")
736 svnstatus = os.popen("svn status").readlines()
737 mychanged = [ "./" + elem.split()[-1:][0] for elem in svnstatus if elem and elem[:1] in "MR" ]
738 mynew = [ "./" + elem.split()[-1:][0] for elem in svnstatus if elem.startswith("A") ]
740 mychanged = os.popen("git ls-files -m --with-tree=HEAD").readlines()
741 mychanged = [ "./" + elem[:-1] for elem in mychanged ]
742 mynew = os.popen("git diff --cached --name-only --diff-filter=A").readlines()
743 strip_levels = repolevel - 1
745 mynew = [elem[repo_subdir_len:] for elem in mynew \
746 if elem[:repo_subdir_len] == repo_subdir]
747 mynew = ["./" + elem[:-1] for elem in mynew]
749 new_ebuilds.update(x for x in mynew if x.endswith(".ebuild"))
750 modified_changelogs.update(x for x in chain(mychanged, mynew) \
751 if os.path.basename(x) == "ChangeLog")
754 have_dev_keywords = False
757 arch_xmatch_caches = {}
758 shared_xmatch_caches = {"cp-list":{}}
760 # Disable the "ebuild.notadded" check when not in commit mode and
761 # running `svn status` in every package dir will be too expensive.
763 check_ebuild_notadded = not \
764 (vcs == "svn" and repolevel < 3 and options.mode != "commit")
766 # Build a regex from thirdpartymirrors for the SRC_URI.mirror check.
767 thirdpartymirrors = portage.flatten(repoman_settings.thirdpartymirrors().values())
770 #ebuilds and digests added to cvs respectively.
771 logging.info("checking package %s" % x)
773 catdir,pkgdir=x.split("/")
774 checkdir=repodir+"/"+x
775 checkdir_relative = ""
777 checkdir_relative = os.path.join(pkgdir, checkdir_relative)
779 checkdir_relative = os.path.join(catdir, checkdir_relative)
780 checkdir_relative = os.path.join(".", checkdir_relative)
782 if options.mode == "manifest" or \
783 options.mode in ('commit', 'fix') and not options.pretend:
784 repoman_settings["O"] = checkdir
785 if not portage.digestgen([], repoman_settings, myportdb=portdb):
786 print "Unable to generate manifest."
788 if options.mode == "manifest":
793 checkdirlist=os.listdir(checkdir)
796 for y in checkdirlist:
797 if y in no_exec and \
798 stat.S_IMODE(os.stat(os.path.join(checkdir, y)).st_mode) & 0111:
799 stats["file.executable"] += 1
800 fails["file.executable"].append(os.path.join(checkdir, y))
801 if y.endswith(".ebuild"):
803 ebuildlist.append(pf)
804 cpv = "%s/%s" % (catdir, pf)
806 myaux = dict(izip(allvars, portdb.aux_get(cpv, allvars)))
808 stats["ebuild.syntax"] += 1
809 fails["ebuild.syntax"].append(os.path.join(x, y))
812 stats["ebuild.output"] += 1
813 fails["ebuild.output"].append(os.path.join(x, y))
815 if not portage.eapi_is_supported(myaux["EAPI"]):
816 stats["EAPI.unsupported"] += 1
817 fails["EAPI.unsupported"].append(os.path.join(x, y))
819 pkgs[pf] = Package(cpv=cpv, metadata=myaux,
820 root_config=root_config)
822 # Sort ebuilds in ascending order for the KEYWORDS.dropped check.
824 for i in xrange(len(ebuildlist)):
825 ebuild_split = portage.pkgsplit(ebuildlist[i])
826 pkgsplits[ebuild_split] = ebuildlist[i]
827 ebuildlist[i] = ebuild_split
828 ebuildlist.sort(portage.pkgcmp)
829 for i in xrange(len(ebuildlist)):
830 ebuildlist[i] = pkgsplits[ebuildlist[i]]
835 if len(pkgs) != len(ebuildlist):
836 # If we can't access all the metadata then it's totally unsafe to
837 # commit since there's no way to generate a correct Manifest.
838 # Do not try to do any more QA checks on this package since missing
839 # metadata leads to false positives for several checks, and false
840 # positives confuse users.
844 for y in checkdirlist:
845 for c in y.strip(os.path.sep):
846 if c not in allowed_filename_chars_set:
847 stats["file.name"] += 1
848 fails["file.name"].append("%s/%s: char '%s'" % (checkdir, y, c))
851 if not (y in ("ChangeLog", "metadata.xml") or y.endswith(".ebuild")):
855 for l in codecs.open(checkdir+"/"+y, "r", "utf8"):
857 except UnicodeDecodeError, ue:
858 stats["file.UTF8"] += 1
859 s = ue.object[:ue.start]
863 s = s[s.rfind("\n") + 1:]
864 fails["file.UTF8"].append("%s/%s: line %i, just after: '%s'" % (checkdir, y, line, s))
866 if vcs == "git" and check_ebuild_notadded:
867 myf = os.popen("git ls-files --others %s" % \
868 (portage._shell_quote(checkdir_relative),))
870 if l[:-1][-7:] == ".ebuild":
871 stats["ebuild.notadded"] += 1
872 fails["ebuild.notadded"].append(
873 os.path.join(x, os.path.basename(l[:-1])))
876 if vcs in ("cvs", "svn") and check_ebuild_notadded:
879 myf=open(checkdir+"/CVS/Entries","r")
881 myf = os.popen("svn status --depth=files --verbose " + checkdir)
882 myl = myf.readlines()
888 splitl=l[1:].split("/")
891 if splitl[0][-7:]==".ebuild":
892 eadded.append(splitl[0][:-7])
897 if l[-7:] == ".ebuild":
898 eadded.append(os.path.basename(l[:-7]))
900 myf = os.popen("svn status " + checkdir)
905 l = l.rstrip().split(' ')[-1]
906 if l[-7:] == ".ebuild":
907 eadded.append(os.path.basename(l[:-7]))
909 if options.mode == 'commit' and vcs == "cvs":
910 stats["CVS/Entries.IO_error"] += 1
911 fails["CVS/Entries.IO_error"].append(checkdir+"/CVS/Entries")
912 if options.mode == 'commit' and vcs == "svn":
913 stats["svn.IO_error"] += 1
914 fails["svn.IO_error"].append(checkdir+"svn info")
917 mf = Manifest(checkdir, repoman_settings["DISTDIR"])
918 mydigests=mf.getTypeDigests("DIST")
920 fetchlist_dict = portage.FetchlistDict(checkdir, repoman_settings, portdb)
922 src_uri_error = False
923 for mykey in fetchlist_dict:
925 myfiles_all.extend(fetchlist_dict[mykey])
926 except portage.exception.InvalidDependString, e:
929 portdb.aux_get(mykey, ["SRC_URI"])
931 # This will be reported as an "ebuild.syntax" error.
934 stats["SRC_URI.syntax"] = stats["SRC_URI.syntax"] + 1
935 fails["SRC_URI.syntax"].append(
936 "%s.ebuild SRC_URI: %s" % (mykey, e))
938 if not src_uri_error:
939 # This test can produce false positives if SRC_URI could not
940 # be parsed for one or more ebuilds. There's no point in
941 # producing a false error here since the root cause will
942 # produce a valid error elsewhere, such as "SRC_URI.syntax"
944 myfiles_all = set(myfiles_all)
945 for entry in mydigests:
946 if entry not in myfiles_all:
947 stats["digestentry.unused"] += 1
948 fails["digestentry.unused"].append(checkdir+"::"+entry)
951 if os.path.exists(checkdir+"/files"):
952 filesdirlist=os.listdir(checkdir+"/files")
954 # recurse through files directory
955 # use filesdirlist as a stack, appending directories as needed so people can't hide > 20k files in a subdirectory.
957 y = filesdirlist.pop(0)
958 relative_path = os.path.join(x, "files", y)
959 full_path = os.path.join(repodir, relative_path)
961 mystat = os.stat(full_path)
964 # don't worry about it. it likely was removed via fix above.
968 if S_ISDIR(mystat.st_mode):
969 # !!! VCS "portability" alert! Need some function isVcsDir() or alike !!!
970 if y == "CVS" or y == ".svn":
972 for z in os.listdir(checkdir+"/files/"+y):
973 if z == "CVS" or z == ".svn":
975 filesdirlist.append(y+"/"+z)
976 # current policy is no files over 20k, this is the check.
977 elif mystat.st_size > 20480:
978 stats["file.size"] += 1
979 fails["file.size"].append("("+ str(mystat.st_size/1024) + "K) "+x+"/files/"+y)
981 for c in os.path.basename(y.rstrip(os.path.sep)):
982 if c not in allowed_filename_chars_set:
983 stats["file.name"] += 1
984 fails["file.name"].append("%s/files/%s: char '%s'" % (checkdir, y, c))
987 if desktop_file_validate and desktop_pattern.match(y):
988 status, cmd_output = commands.getstatusoutput(
989 "'%s' '%s'" % (desktop_file_validate, full_path))
990 if os.WIFEXITED(status) and os.WEXITSTATUS(status) != os.EX_OK:
991 # Note: in the future we may want to grab the
992 # warnings in addition to the errors. We're
993 # just doing errors now since we don't want
994 # to generate too much noise at first.
995 error_re = re.compile(r'.*\s*error:\s*(.*)')
996 for line in cmd_output.splitlines():
997 error_match = error_re.match(line)
998 if error_match is None:
1000 stats["desktop.invalid"] += 1
1001 fails["desktop.invalid"].append(
1002 relative_path + ': %s' % error_match.group(1))
1006 if "ChangeLog" not in checkdirlist:
1007 stats["changelog.missing"]+=1
1008 fails["changelog.missing"].append(x+"/ChangeLog")
1010 #metadata.xml file check
1013 if "metadata.xml" not in checkdirlist:
1014 stats["metadata.missing"]+=1
1015 fails["metadata.missing"].append(x+"/metadata.xml")
1016 #metadata.xml parse check
1018 metadata_bad = False
1020 # load USE flags from metadata.xml
1022 f = open(os.path.join(checkdir, "metadata.xml"))
1023 utilities.parse_metadata_use(f, muselist)
1025 except (EnvironmentError, ParseError), e:
1027 stats["metadata.bad"] += 1
1028 fails["metadata.bad"].append("%s/metadata.xml: %s" % (x, e))
1031 #Only carry out if in package directory or check forced
1032 if xmllint_capable and not metadata_bad:
1033 # xmlint can produce garbage output even on success, so only dump
1034 # the ouput when it fails.
1035 st, out = commands.getstatusoutput(
1036 "xmllint --nonet --noout --dtdvalid '%s' '%s'" % \
1037 (metadata_dtd, os.path.join(checkdir, "metadata.xml")))
1039 print red("!!!") + " metadata.xml is invalid:"
1040 for z in out.splitlines():
1042 stats["metadata.bad"]+=1
1043 fails["metadata.bad"].append(x+"/metadata.xml")
1047 changelog_path = os.path.join(checkdir_relative, "ChangeLog")
1048 changelog_modified = changelog_path in modified_changelogs
1052 for y in ebuildlist:
1053 relative_path = os.path.join(x, y + ".ebuild")
1054 full_path = os.path.join(repodir, relative_path)
1055 ebuild_path = y + ".ebuild"
1057 ebuild_path = os.path.join(pkgdir, ebuild_path)
1059 ebuild_path = os.path.join(catdir, ebuild_path)
1060 ebuild_path = os.path.join(".", ebuild_path)
1061 if not changelog_modified and ebuild_path in new_ebuilds:
1062 stats['changelog.ebuildadded'] += 1
1063 fails['changelog.ebuildadded'].append(relative_path)
1065 if stat.S_IMODE(os.stat(full_path).st_mode) & 0111:
1066 stats["file.executable"] += 1
1067 fails["file.executable"].append(x+"/"+y+".ebuild")
1068 if vcs in ("cvs", "svn") and check_ebuild_notadded and y not in eadded:
1069 #ebuild not added to vcs
1070 stats["ebuild.notadded"]=stats["ebuild.notadded"]+1
1071 fails["ebuild.notadded"].append(x+"/"+y+".ebuild")
1072 myesplit=portage.pkgsplit(y)
1073 if myesplit is None or myesplit[0] != x.split("/")[-1]:
1074 stats["ebuild.invalidname"]=stats["ebuild.invalidname"]+1
1075 fails["ebuild.invalidname"].append(x+"/"+y+".ebuild")
1077 elif myesplit[0]!=pkgdir:
1078 print pkgdir,myesplit[0]
1079 stats["ebuild.namenomatch"]=stats["ebuild.namenomatch"]+1
1080 fails["ebuild.namenomatch"].append(x+"/"+y+".ebuild")
1084 myaux = pkg.metadata
1085 eapi = myaux["EAPI"]
1086 inherited = pkg.inherited
1087 live_ebuild = live_eclasses.intersection(inherited)
1089 if not src_uri_error:
1090 # Check that URIs don't reference a server from thirdpartymirrors.
1091 for uri in portage.flatten(portage.dep.use_reduce(
1092 portage.dep.paren_reduce(myaux["SRC_URI"]), matchall=True)):
1093 contains_mirror = False
1094 for mirror in thirdpartymirrors:
1095 if uri.startswith(mirror):
1096 contains_mirror = True
1098 if not contains_mirror:
1101 stats["SRC_URI.mirror"] += 1
1102 fails["SRC_URI.mirror"].append(
1103 "%s: '%s' found in thirdpartymirrors" % \
1104 (relative_path, mirror))
1106 # Test for negative logic and bad words in the RESTRICT var.
1107 #for x in myaux[allvars.index("RESTRICT")].split():
1108 # if x.startswith("no"):
1109 # print "Bad RESTRICT value: %s" % x
1111 myaux["PROVIDE"] = portage.dep.use_reduce(
1112 portage.dep.paren_reduce(myaux["PROVIDE"]), matchall=1)
1113 except portage.exception.InvalidDependString, e:
1114 stats["PROVIDE.syntax"] = stats["PROVIDE.syntax"] + 1
1115 fails["PROVIDE.syntax"].append(mykey+".ebuild PROVIDE: "+str(e))
1118 myaux["PROVIDE"] = " ".join(portage.flatten(myaux["PROVIDE"]))
1119 for myprovide in myaux["PROVIDE"].split():
1120 prov_cp = portage.dep_getkey(myprovide)
1121 if prov_cp != myprovide:
1122 stats["virtual.versioned"]+=1
1123 fails["virtual.versioned"].append(x+"/"+y+".ebuild: "+myprovide)
1124 prov_pkg = portage.dep_getkey(
1125 portage.best(portdb.xmatch("match-all", prov_cp)))
1126 if prov_cp == prov_pkg:
1127 stats["virtual.exists"]+=1
1128 fails["virtual.exists"].append(x+"/"+y+".ebuild: "+prov_cp)
1130 for pos, missing_var in enumerate(missingvars):
1131 if not myaux.get(missing_var):
1132 if catdir == "virtual" and \
1133 missing_var in ("HOMEPAGE", "LICENSE"):
1135 if live_ebuild and missing_var == "KEYWORDS":
1137 myqakey=missingvars[pos]+".missing"
1138 stats[myqakey]=stats[myqakey]+1
1139 fails[myqakey].append(x+"/"+y+".ebuild")
1141 # 14 is the length of DESCRIPTION=""
1142 if len(myaux['DESCRIPTION']) > max_desc_len:
1143 stats['DESCRIPTION.toolong'] += 1
1144 fails['DESCRIPTION.toolong'].append(
1145 "%s: DESCRIPTION is %d characters (max %d)" % \
1146 (relative_path, len(myaux['DESCRIPTION']), max_desc_len))
1148 keywords = myaux["KEYWORDS"].split()
1149 stable_keywords = []
1150 for keyword in keywords:
1151 if not keyword.startswith("~") and \
1152 not keyword.startswith("-"):
1153 stable_keywords.append(keyword)
1155 if ebuild_path in new_ebuilds:
1156 stable_keywords.sort()
1157 stats["KEYWORDS.stable"] += 1
1158 fails["KEYWORDS.stable"].append(
1159 x + "/" + y + ".ebuild added with stable keywords: %s" % \
1160 " ".join(stable_keywords))
1162 ebuild_archs = set(kw.lstrip("~") for kw in keywords \
1163 if not kw.startswith("-"))
1165 previous_keywords = slot_keywords.get(myaux["SLOT"])
1166 if previous_keywords is None:
1167 slot_keywords[myaux["SLOT"]] = set()
1168 elif not live_ebuild:
1169 dropped_keywords = previous_keywords.difference(ebuild_archs)
1170 if dropped_keywords:
1171 stats["KEYWORDS.dropped"] += 1
1172 fails["KEYWORDS.dropped"].append(
1173 relative_path + ": %s" % \
1174 " ".join(sorted(dropped_keywords)))
1176 slot_keywords[myaux["SLOT"]].update(ebuild_archs)
1178 # KEYWORDS="-*" is a stupid replacement for package.mask and screws general KEYWORDS semantics
1179 if "-*" in keywords:
1187 stats["KEYWORDS.stupid"] += 1
1188 fails["KEYWORDS.stupid"].append(x+"/"+y+".ebuild")
1191 Ebuilds that inherit a "Live" eclass (darcs,subversion,git,cvs,etc..) should
1192 not be allowed to be marked stable
1195 bad_stable_keywords = []
1196 for keyword in keywords:
1197 if not keyword.startswith("~") and \
1198 not keyword.startswith("-"):
1199 bad_stable_keywords.append(keyword)
1201 if bad_stable_keywords:
1202 stats["LIVEVCS.stable"] += 1
1203 fails["LIVEVCS.stable"].append(
1204 x + "/" + y + ".ebuild with stable keywords:%s " % \
1205 bad_stable_keywords)
1206 del bad_stable_keywords
1208 if options.ignore_arches:
1209 arches = [[repoman_settings["ARCH"], repoman_settings["ARCH"],
1210 repoman_settings["ACCEPT_KEYWORDS"].split()]]
1213 for keyword in myaux["KEYWORDS"].split():
1214 if (keyword[0]=="-"):
1216 elif (keyword[0]=="~"):
1217 arches.append([keyword, keyword[1:], [keyword[1:], keyword]])
1219 arches.append([keyword, keyword, [keyword]])
1222 baddepsyntax = False
1223 badlicsyntax = False
1224 badprovsyntax = False
1225 catpkg = catdir+"/"+y
1226 myiuse = set(repoman_settings.archlist())
1227 for myflag in myaux["IUSE"].split():
1228 if myflag.startswith("+"):
1232 inherited_java_eclass = "java-pkg-2" in inherited or \
1233 "java-pkg-opt-2" in inherited
1234 operator_tokens = set(["||", "(", ")"])
1235 type_list, badsyntax = [], []
1236 for mytype in ("DEPEND", "RDEPEND", "PDEPEND",
1237 "LICENSE", "PROPERTIES", "PROVIDE"):
1238 mydepstr = myaux[mytype]
1240 if mydepstr.find(" ?") != -1:
1241 badsyntax.append("'?' preceded by space")
1244 # Missing closing parenthesis will result in a ValueError
1245 mydeplist = portage.dep.paren_reduce(mydepstr)
1246 # Missing opening parenthesis will result in a final "" element
1247 if "" in mydeplist or "(" in mydeplist:
1250 badsyntax.append("parenthesis mismatch")
1252 except portage.exception.InvalidDependString, e:
1253 badsyntax.append(str(e))
1258 portage.dep.use_reduce(mydeplist, matchall=1)
1259 except portage.exception.InvalidDependString, e:
1260 badsyntax.append(str(e))
1262 for token in operator_tokens:
1263 if mydepstr.startswith(token+" "):
1264 myteststr = mydepstr[len(token):]
1266 myteststr = mydepstr
1267 if myteststr.endswith(" "+token):
1268 myteststr = myteststr[:-len(token)]
1269 while myteststr.find(" "+token+" ") != -1:
1270 myteststr = " ".join(myteststr.split(" "+token+" ", 1))
1271 if myteststr.find(token) != -1:
1272 badsyntax.append("'%s' not separated by space" % (token))
1274 if mytype in ("DEPEND", "RDEPEND", "PDEPEND"):
1275 for token in mydepstr.split():
1276 if token in operator_tokens or \
1277 token.endswith("?"):
1280 atom = portage.dep.Atom(token)
1281 except portage.exception.InvalidAtom:
1282 badsyntax.append("'%s' not a valid atom" % token)
1284 is_blocker = atom.blocker
1286 if mytype == "DEPEND" and \
1287 not is_blocker and \
1288 not inherited_java_eclass and \
1289 portage.dep_getkey(atom) == "virtual/jdk":
1290 stats['java.eclassesnotused'] += 1
1291 fails['java.eclassesnotused'].append(relative_path)
1292 elif mytype == "RDEPEND":
1293 if not is_blocker and \
1294 portage.dep_getkey(atom) in suspect_rdepend:
1295 stats['RDEPEND.suspect'] += 1
1296 fails['RDEPEND.suspect'].append(
1297 relative_path + ": '%s'" % atom)
1299 if portage.dep.dep_getslot(atom):
1300 stats['EAPI.incompatible'] += 1
1301 fails['EAPI.incompatible'].append(
1302 (relative_path + ": %s slot dependency" + \
1303 " not supported with EAPI='%s':" + \
1304 " '%s'") % (mytype, eapi, atom))
1305 if atom.use and eapi in ("0", "1"):
1306 stats['EAPI.incompatible'] += 1
1307 fails['EAPI.incompatible'].append(
1308 (relative_path + ": %s use dependency" + \
1309 " not supported with EAPI='%s':" + \
1310 " '%s'") % (mytype, eapi, atom))
1311 if atom.blocker and atom.blocker.overlap.forbid \
1312 and eapi in ("0", "1"):
1313 stats['EAPI.incompatible'] += 1
1314 fails['EAPI.incompatible'].append(
1315 (relative_path + ": %s new blocker syntax" + \
1316 " not supported with EAPI='%s':" + \
1317 " '%s'") % (mytype, eapi, atom))
1319 type_list.extend([mytype] * (len(badsyntax) - len(type_list)))
1321 for m,b in zip(type_list, badsyntax):
1322 stats[m+".syntax"] += 1
1323 fails[m+".syntax"].append(catpkg+".ebuild "+m+": "+b)
1325 badlicsyntax = len(filter(lambda x:x=="LICENSE", type_list))
1326 badprovsyntax = len(filter(lambda x:x=="PROVIDE", type_list))
1327 baddepsyntax = len(type_list) != badlicsyntax + badprovsyntax
1328 badlicsyntax = badlicsyntax > 0
1329 badprovsyntax = badprovsyntax > 0
1331 # uselist checks - global
1334 for myflag in myaux["IUSE"].split():
1335 flag_name = myflag.lstrip("+-")
1336 if myflag != flag_name:
1337 default_use.append(myflag)
1338 if flag_name not in uselist:
1339 myuse.append(flag_name)
1341 # uselist checks - metadata
1342 for mypos in range(len(myuse)-1,-1,-1):
1343 if myuse[mypos] and (myuse[mypos] in muselist):
1346 if default_use and eapi == "0":
1347 for myflag in default_use:
1348 stats['EAPI.incompatible'] += 1
1349 fails['EAPI.incompatible'].append(
1350 (relative_path + ": IUSE defaults" + \
1351 " not supported with EAPI='%s':" + \
1352 " '%s'") % (eapi, myflag))
1354 for mypos in range(len(myuse)):
1355 stats["IUSE.invalid"]=stats["IUSE.invalid"]+1
1356 fails["IUSE.invalid"].append(x+"/"+y+".ebuild: %s" % myuse[mypos])
1359 if not badlicsyntax:
1360 myuse = myaux["LICENSE"]
1361 # Parse the LICENSE variable, remove USE conditions and
1363 myuse=portage.dep.use_reduce(portage.dep.paren_reduce(myuse), matchall=1)
1364 myuse=portage.flatten(myuse)
1365 # Check each entry to ensure that it exists in PORTDIR's
1366 # license directory.
1367 for mypos in range(0,len(myuse)):
1368 # Need to check for "||" manually as no portage
1369 # function will remove it without removing values.
1370 if myuse[mypos] not in liclist and myuse[mypos] != "||":
1371 stats["LICENSE.invalid"]=stats["LICENSE.invalid"]+1
1372 fails["LICENSE.invalid"].append(x+"/"+y+".ebuild: %s" % myuse[mypos])
1375 myuse = myaux["KEYWORDS"].split()
1383 if myskey not in kwlist:
1384 stats["KEYWORDS.invalid"] += 1
1385 fails["KEYWORDS.invalid"].append(x+"/"+y+".ebuild: %s" % mykey)
1386 elif myskey not in profiles:
1387 stats["KEYWORDS.invalid"] += 1
1388 fails["KEYWORDS.invalid"].append(x+"/"+y+".ebuild: %s (profile invalid)" % mykey)
1393 myrestrict = portage.dep.use_reduce(
1394 portage.dep.paren_reduce(myaux["RESTRICT"]), matchall=1)
1395 except portage.exception.InvalidDependString, e:
1396 stats["RESTRICT.syntax"] = stats["RESTRICT.syntax"] + 1
1397 fails["RESTRICT.syntax"].append(
1398 "%s: RESTRICT: %s" % (relative_path, e))
1401 myrestrict = set(portage.flatten(myrestrict))
1402 mybadrestrict = myrestrict.difference(valid_restrict)
1404 stats["RESTRICT.invalid"] += len(mybadrestrict)
1405 for mybad in mybadrestrict:
1406 fails["RESTRICT.invalid"].append(x+"/"+y+".ebuild: %s" % mybad)
1408 relative_path = os.path.join(x, y + ".ebuild")
1409 full_path = os.path.join(repodir, relative_path)
1410 f = open(full_path, 'rb')
1412 for check_name, e in run_checks(f, pkg):
1413 stats[check_name] += 1
1414 fails[check_name].append(relative_path + ': %s' % e)
1420 # The dep_check() calls are the most expensive QA test. If --force
1421 # is enabled, there's no point in wasting time on these since the
1422 # user is intent on forcing the commit anyway.
1425 for keyword,arch,groups in arches:
1427 if arch not in profiles:
1428 # A missing profile will create an error further down
1429 # during the KEYWORDS verification.
1432 for prof in profiles[arch]:
1434 if prof[1] not in ("stable", "dev") or \
1435 prof[1] == "dev" and not options.include_dev:
1438 profdir = portdir+"/profiles/"+prof[0]
1440 if prof[0] in arch_caches:
1441 dep_settings = arch_caches[prof[0]]
1443 dep_settings = portage.config(
1444 config_profile_path=profdir,
1445 config_incrementals=portage.const.INCREMENTALS,
1447 if options.without_mask:
1448 dep_settings.pmaskdict.clear()
1449 arch_caches[prof[0]] = dep_settings
1452 # Protect ACCEPT_KEYWORDS from config.regenerate()
1454 dep_settings.incrementals.remove("ACCEPT_KEYWORDS")
1458 xmatch_cache_key = (prof[0], tuple(groups))
1459 xcache = arch_xmatch_caches.get(xmatch_cache_key)
1463 xcache = portdb.xcache
1464 xcache.update(shared_xmatch_caches)
1465 arch_xmatch_caches[xmatch_cache_key] = xcache
1467 trees["/"]["porttree"].settings = dep_settings
1468 portdb.mysettings = dep_settings
1469 portdb.xcache = xcache
1470 # for package.use.mask support inside dep_check
1471 dep_settings.setcpv(pkg)
1472 dep_settings["ACCEPT_KEYWORDS"] = " ".join(groups)
1473 # just in case, prevent config.reset() from nuking these.
1474 dep_settings.backup_changes("ACCEPT_KEYWORDS")
1476 for myprovide in myaux["PROVIDE"].split():
1477 prov_cp = portage.dep_getkey(myprovide)
1478 if prov_cp not in dep_settings.getvirtuals():
1479 stats["virtual.unavailable"]+=1
1480 fails["virtual.unavailable"].append(x+"/"+y+".ebuild: "+keyword+"("+prof[0]+") "+prov_cp)
1482 if not baddepsyntax:
1483 ismasked = os.path.join(catdir, y) not in \
1484 portdb.xmatch("list-visible", x)
1486 if not have_pmasked:
1487 have_pmasked = bool(dep_settings._getMaskAtom(
1488 pkg.cpv, pkg.metadata))
1489 if options.ignore_masked:
1491 #we are testing deps for a masked package; give it some lee-way
1493 matchmode = "minimum-all"
1496 matchmode = "minimum-visible"
1498 if not have_dev_keywords:
1499 have_dev_keywords = \
1500 bool(dev_keywords.intersection(keywords))
1502 if prof[1] == "dev":
1503 suffix=suffix+"indev"
1505 for mytype,mypos in [["DEPEND",len(missingvars)],["RDEPEND",len(missingvars)+1],["PDEPEND",len(missingvars)+2]]:
1507 mykey=mytype+".bad"+suffix
1508 myvalue = myaux[mytype]
1512 mydep = portage.dep_check(myvalue, portdb,
1513 dep_settings, use="all", mode=matchmode,
1516 stats[mykey]=stats[mykey]+1
1517 fails[mykey].append(x+"/"+y+".ebuild: "+keyword+"("+prof[0]+") "+repr(e[0]))
1522 #we have some unsolvable deps
1523 #remove ! deps, which always show up as unsatisfiable
1525 while d<len(mydep[1]):
1526 if mydep[1][d][0]=="!":
1530 #if we emptied out our list, continue:
1533 stats[mykey]=stats[mykey]+1
1534 fails[mykey].append(x+"/"+y+".ebuild: "+keyword+"("+prof[0]+") "+repr(mydep[1]))
1536 stats[mykey]=stats[mykey]+1
1537 fails[mykey].append(x+"/"+y+".ebuild: "+keyword+"("+prof[0]+") "+repr(mydep[1]))
1539 # Check for 'all unstable' or 'all masked' -- ACCEPT_KEYWORDS is stripped
1540 # XXX -- Needs to be implemented in dep code. Can't determine ~arch nicely.
1541 #if not portage.portdb.xmatch("bestmatch-visible",x):
1542 # stats["ebuild.nostable"]+=1
1543 # fails["ebuild.nostable"].append(x)
1544 if allmasked and repolevel == 3:
1545 stats["ebuild.allmasked"]+=1
1546 fails["ebuild.allmasked"].append(x)
1548 if options.mode == "manifest":
1551 #dofail will be set to 1 if we have failed in at least one non-warning category
1553 #dowarn will be set to 1 if we tripped any warnings
1555 #dofull will be set if we should print a "repoman full" informational message
1556 dofull = options.mode != 'full'
1562 if x not in qawarnings:
1566 (dowarn and not (options.quiet or options.mode == "scan")):
1569 # Save QA output so that it can be conveniently displayed
1570 # in $EDITOR while the user creates a commit message.
1571 # Otherwise, the user would not be able to see this output
1572 # once the editor has taken over the screen.
1573 qa_output = StringIO.StringIO()
1574 style_file = ConsoleStyleFile(sys.stdout)
1575 if options.mode == 'commit' and \
1576 (not commitmessage or not commitmessage.strip()):
1577 style_file.write_listener = qa_output
1578 console_writer = StyleWriter(file=style_file, maxcol=9999)
1579 console_writer.style_listener = style_file.new_styles
1581 f = formatter.AbstractFormatter(console_writer)
1583 utilities.format_qa_output(f, stats, fails, dofull, dofail, options, qawarnings)
1586 del console_writer, f, style_file
1587 qa_output = qa_output.getvalue()
1588 qa_output = qa_output.splitlines(True)
1590 def grouplist(mylist,seperator="/"):
1591 """(list,seperator="/") -- Takes a list of elements; groups them into
1592 same initial element categories. Returns a dict of {base:[sublist]}
1593 From: ["blah/foo","spork/spatula","blah/weee/splat"]
1594 To: {"blah":["foo","weee/splat"], "spork":["spatula"]}"""
1597 xs=x.split(seperator)
1600 if xs[0] not in mygroups:
1601 mygroups[xs[0]]=[seperator.join(xs[1:])]
1603 mygroups[xs[0]]+=[seperator.join(xs[1:])]
1606 if have_pmasked and not (options.without_mask or options.ignore_masked):
1607 print bold("Note: use --without-mask to check " + \
1608 "KEYWORDS on dependencies of masked packages")
1610 if have_dev_keywords and not options.include_dev:
1611 print bold("Note: use --include-dev (-d) to check " + \
1612 "dependencies for 'dev' profiles")
1614 if options.mode != 'commit':
1616 print bold("Note: type \"repoman full\" for a complete listing.")
1617 if dowarn and not dofail:
1618 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.\""
1620 print green("RepoMan sez:"),"\"If everyone were like you, I'd be out of business!\""
1622 print turquoise("Please fix these important QA issues first.")
1623 print green("RepoMan sez:"),"\"Make your QA payment on time and you'll never see the likes of me.\"\n"
1626 if dofail and can_force and options.force and not options.pretend:
1627 print green("RepoMan sez:") + \
1628 " \"You want to commit even with these QA issues?\n" + \
1629 " I'll take it this time, but I'm not happy.\"\n"
1631 if options.force and not can_force:
1632 print bad("The --force option has been disabled due to extraordinary issues.")
1633 print turquoise("Please fix these important QA issues first.")
1634 print green("RepoMan sez:"),"\"Make your QA payment on time and you'll never see the likes of me.\"\n"
1638 print green("RepoMan sez:"), "\"So, you want to play it safe. Good call.\"\n"
1643 myvcstree=portage.cvstree.getentries("./",recursive=1)
1644 myunadded=portage.cvstree.findunadded(myvcstree,recursive=1,basedir="./")
1645 except SystemExit, e:
1646 raise # TODO propogate this
1648 err("Error retrieving CVS tree; exiting.")
1651 svnstatus=os.popen("svn status --no-ignore").readlines()
1652 myunadded = [ "./"+elem.rstrip().split()[1] for elem in svnstatus if elem.startswith("?") or elem.startswith("I") ]
1653 except SystemExit, e:
1654 raise # TODO propogate this
1656 err("Error retrieving SVN info; exiting.")
1658 # get list of files not under version control or missing
1659 myf = os.popen("git ls-files --others")
1660 myunadded = [ "./" + elem[:-1] for elem in myf ]
1665 for x in range(len(myunadded)-1,-1,-1):
1666 xs=myunadded[x].split("/")
1668 print "!!! files dir is not added! Please correct this."
1670 elif xs[-1]=="Manifest":
1671 # It's a manifest... auto add
1672 myautoadd+=[myunadded[x]]
1676 print ">>> Auto-Adding missing Manifest(s)..."
1679 print "(cvs add "+" ".join(myautoadd)+")"
1681 print "(svn add "+" ".join(myautoadd)+")"
1683 print "(git add "+" ".join(myautoadd)+")"
1687 retval=os.system("cvs add "+" ".join(myautoadd))
1689 retval=os.system("svn add "+" ".join(myautoadd))
1691 retval=os.system("git add "+" ".join(myautoadd))
1693 writemsg_level("!!! Exiting on %s (shell) error code: %s\n" % \
1694 (vcs, retval), level=logging.ERROR, noiselevel=-1)
1698 print red("!!! The following files are in your local tree but are not added to the master")
1699 print red("!!! tree. Please remove them from the local tree or add them to the master tree.")
1707 mycvstree = cvstree.getentries("./", recursive=1)
1708 mychanged = cvstree.findchanged(mycvstree, recursive=1, basedir="./")
1709 mynew = cvstree.findnew(mycvstree, recursive=1, basedir="./")
1710 myremoved=portage.cvstree.findremoved(mycvstree,recursive=1,basedir="./")
1711 bin_blob_pattern = re.compile("^-kb$")
1712 no_expansion = set(portage.cvstree.findoption(mycvstree, bin_blob_pattern,
1713 recursive=1, basedir="./"))
1717 svnstatus = os.popen("svn status").readlines()
1718 mychanged = [ "./" + elem.split()[-1:][0] for elem in svnstatus if elem and elem[:1] in "MR" ]
1719 mynew = [ "./" + elem.split()[-1:][0] for elem in svnstatus if elem.startswith("A") ]
1720 myremoved = [ "./" + elem.split()[-1:][0] for elem in svnstatus if elem.startswith("D") ]
1721 # in contrast to CVS, SVN expands nothing by default.
1722 # bin_blobs historically
1723 # were just there to see what files need to be checked for
1724 # keyword expansion, which is exactly what we do here, so
1725 # slightly change the semantic meaning of "bin_blob"... In the
1726 # future we could store which keyword is expanded.
1727 props = os.popen("svn propget -R svn:keywords").readlines()
1729 # For files with multiple props set, props are delimited by newlines,
1730 # so exclude lines that don't contain " - " since each of those lines
1731 # only a contain props for a file listed on a previous line.
1732 expansion = set("./" + prop.split(" - ")[0] \
1733 for prop in props if " - " in prop)
1736 mychanged = os.popen("git ls-files -m --with-tree=HEAD").readlines()
1737 mychanged = [ "./" + elem[:-1] for elem in mychanged ]
1738 mynew = os.popen("git diff --cached --name-only --diff-filter=A").readlines()
1739 strip_levels = repolevel - 1
1741 mynew = [elem[repo_subdir_len:] for elem in mynew \
1742 if elem[:repo_subdir_len] == repo_subdir]
1743 mynew = ["./" + elem[:-1] for elem in mynew]
1744 myremoved = os.popen("git diff --cached --name-only --diff-filter=D").readlines()
1746 myremoved = [elem[repo_subdir_len:] for elem in myremoved \
1747 if elem[:repo_subdir_len] == repo_subdir]
1748 myremoved = ["./" + elem[:-1] for elem in myremoved]
1751 if not (mychanged or mynew or myremoved):
1752 print green("RepoMan sez:"), "\"Doing nothing is not always good for QA.\""
1754 print "(Didn't find any changed files...)"
1758 # Manifests need to be regenerated after all other commits, so don't commit
1759 # them now even if they have changed.
1762 for f in mychanged + mynew:
1763 if "Manifest" == os.path.basename(f):
1768 myupdates.difference_update(myremoved)
1769 myupdates = list(myupdates)
1770 mymanifests = list(mymanifests)
1773 headerstring = "'\$(Header|Id)"
1774 headerstring += ".*\$'"
1775 for myfile in myupdates:
1777 # for CVS, no_expansion contains files that are excluded from expansion
1779 if myfile in no_expansion:
1782 # for SVN, expansion contains files that are included in expansion
1784 if myfile not in expansion:
1787 myout = commands.getstatusoutput("egrep -q "+headerstring+" "+myfile)
1789 myheaders.append(myfile)
1791 print "* %s files being committed..." % green(str(len(myupdates))),
1793 # With git, there's never any keyword expansion, so there's
1794 # no need to regenerate manifests and all files will be
1795 # committed in one big commit at the end.
1798 print "%s have headers that will change." % green(str(len(myheaders)))
1799 print "* Files with headers will cause the " + \
1800 "manifests to be made and recommited."
1801 logging.info("myupdates:", str(myupdates))
1802 logging.info("myheaders:", str(myheaders))
1804 commitmessage = options.commitmsg
1805 if options.commitmsgfile:
1807 f = open(options.commitmsgfile)
1808 commitmessage = f.read()
1811 except (IOError, OSError), e:
1812 if e.errno == errno.ENOENT:
1813 portage.writemsg("!!! File Not Found: --commitmsgfile='%s'\n" % options.commitmsgfile)
1816 # We've read the content so the file is no longer needed.
1817 commitmessagefile = None
1818 if not commitmessage or not commitmessage.strip():
1820 editor = os.environ.get("EDITOR")
1821 if editor and utilities.editor_is_executable(editor):
1822 commitmessage = utilities.get_commit_message_with_editor(
1823 editor, message=qa_output)
1825 commitmessage = utilities.get_commit_message_with_stdin()
1826 except KeyboardInterrupt:
1828 if not commitmessage or not commitmessage.strip():
1829 print "* no commit message? aborting commit."
1831 commitmessage = commitmessage.rstrip()
1832 portage_version = getattr(portage, "VERSION", None)
1833 if portage_version is None:
1834 sys.stderr.write("Failed to insert portage version in message!\n")
1836 portage_version = "Unknown"
1837 unameout = platform.system() + " " + platform.release() + " "
1838 if platform.system() in ["Darwin", "SunOS"]:
1839 unameout += platform.processor()
1841 unameout += platform.machine()
1842 commitmessage += "\n(Portage version: %s/%s/%s" % \
1843 (portage_version, vcs, unameout)
1845 commitmessage += ", RepoMan options: --force"
1846 commitmessage += ")"
1848 if vcs != 'git' and (myupdates or myremoved):
1849 myfiles = myupdates + myremoved
1850 if not myheaders and "sign" not in repoman_settings.features:
1851 myfiles += mymanifests
1852 fd, commitmessagefile = tempfile.mkstemp(".repoman.msg")
1853 mymsg = os.fdopen(fd, "w")
1854 mymsg.write(commitmessage)
1858 print green("Using commit message:")
1859 print green("------------------------------------------------------------------------------")
1861 print green("------------------------------------------------------------------------------")
1864 # Having a leading ./ prefix on file paths can trigger a bug in
1865 # the cvs server when committing files to multiple directories,
1866 # so strip the prefix.
1867 myfiles = [f.lstrip("./") for f in myfiles]
1870 commit_cmd.extend(vcs_global_opts)
1871 commit_cmd.append("commit")
1872 commit_cmd.extend(vcs_local_opts)
1873 commit_cmd.extend(["-F", commitmessagefile])
1874 commit_cmd.extend(myfiles)
1878 print "(%s)" % (" ".join(commit_cmd),)
1880 retval = spawn(commit_cmd, env=os.environ)
1881 if retval != os.EX_OK:
1882 writemsg_level(("!!! Exiting on %s (shell) " + \
1883 "error code: %s\n") % (vcs, retval),
1884 level=logging.ERROR, noiselevel=-1)
1888 os.unlink(commitmessagefile)
1892 # Setup the GPG commands
1893 def gpgsign(filename):
1894 if "PORTAGE_GPG_KEY" not in repoman_settings:
1895 raise portage.exception.MissingParameter("PORTAGE_GPG_KEY is unset!")
1896 if "PORTAGE_GPG_DIR" not in repoman_settings:
1897 if "HOME" in os.environ:
1898 repoman_settings["PORTAGE_GPG_DIR"] = os.path.join(os.environ["HOME"], ".gnupg")
1899 logging.info("Automatically setting PORTAGE_GPG_DIR to %s" % repoman_settings["PORTAGE_GPG_DIR"])
1901 raise portage.exception.MissingParameter("PORTAGE_GPG_DIR is unset!")
1902 gpg_dir = repoman_settings["PORTAGE_GPG_DIR"]
1903 if gpg_dir.startswith("~") and "HOME" in os.environ:
1904 repoman_settings["PORTAGE_GPG_DIR"] = os.path.join(
1905 os.environ["HOME"], gpg_dir[1:].lstrip(os.path.sep))
1906 if not os.access(repoman_settings["PORTAGE_GPG_DIR"], os.X_OK):
1907 raise portage.exception.InvalidLocation(
1908 "Unable to access directory: PORTAGE_GPG_DIR='%s'" % \
1909 repoman_settings["PORTAGE_GPG_DIR"])
1910 gpgcmd = "gpg --sign --clearsign --yes "
1911 gpgcmd+= "--default-key "+repoman_settings["PORTAGE_GPG_KEY"]
1912 if "PORTAGE_GPG_DIR" in repoman_settings:
1913 gpgcmd += " --homedir "+repoman_settings["PORTAGE_GPG_DIR"]
1915 print "("+gpgcmd+" "+filename+")"
1917 rValue = os.system(gpgcmd+" "+filename)
1918 if rValue == os.EX_OK:
1919 os.rename(filename+".asc", filename)
1921 raise portage.exception.PortageException("!!! gpg exited with '" + str(rValue) + "' status")
1923 # When files are removed and re-added, the cvs server will put /Attic/
1924 # inside the $Header path. This code detects the problem and corrects it
1925 # so that the Manifest will generate correctly. See bug #169500.
1926 from portage.util import write_atomic
1927 cvs_header = re.compile(r'^#\s*\$Header.*\$$')
1930 mylines = f.readlines()
1933 for i, line in enumerate(mylines):
1934 if cvs_header.match(line) and "/Attic/" in line:
1935 mylines[i] = line.replace("/Attic/", "/")
1938 write_atomic(x, "".join(mylines))
1940 manifest_commit_required = True
1941 if vcs != 'git' and (myupdates or myremoved):
1942 myfiles = myupdates + myremoved
1943 for x in range(len(myfiles)-1, -1, -1):
1944 if myfiles[x].count("/") < 4-repolevel:
1947 if repolevel==3: # In a package dir
1948 repoman_settings["O"] = startdir
1949 portage.digestgen([], repoman_settings, manifestonly=1,
1951 elif repolevel==2: # In a category dir
1954 if len(xs) < 4-repolevel:
1960 mydone.append(xs[0])
1961 repoman_settings["O"] = os.path.join(startdir, xs[0])
1962 if not os.path.isdir(repoman_settings["O"]):
1964 portage.digestgen([], repoman_settings, manifestonly=1,
1966 elif repolevel==1: # repo-cvsroot
1967 print green("RepoMan sez:"), "\"You're rather crazy... doing the entire repository.\"\n"
1970 if len(xs) < 4-repolevel:
1974 if "/".join(xs[:2]) in mydone:
1976 mydone.append("/".join(xs[:2]))
1977 repoman_settings["O"] = os.path.join(startdir, xs[0], xs[1])
1978 if not os.path.isdir(repoman_settings["O"]):
1980 portage.digestgen([], repoman_settings, manifestonly=1,
1983 print red("I'm confused... I don't know where I am!")
1986 # Force an unsigned commit when more than one Manifest needs to be signed.
1987 if repolevel < 3 and "sign" in repoman_settings.features:
1989 fd, commitmessagefile = tempfile.mkstemp(".repoman.msg")
1990 mymsg = os.fdopen(fd, "w")
1991 mymsg.write(commitmessage)
1992 mymsg.write("\n (Unsigned Manifest commit)")
1996 commit_cmd.extend(vcs_global_opts)
1997 commit_cmd.append("commit")
1998 commit_cmd.extend(vcs_local_opts)
1999 commit_cmd.extend(["-F", commitmessagefile])
2000 commit_cmd.extend(f.lstrip("./") for f in mymanifests)
2004 print "(%s)" % (" ".join(commit_cmd),)
2006 retval = spawn(commit_cmd, env=os.environ)
2008 writemsg_level(("!!! Exiting on %s (shell) " + \
2009 "error code: %s\n") % (vcs, retval),
2010 level=logging.ERROR, noiselevel=-1)
2014 os.unlink(commitmessagefile)
2017 manifest_commit_required = False
2020 if "sign" in repoman_settings.features:
2022 myfiles = myupdates + myremoved + mymanifests
2024 if repolevel==3: # In a package dir
2025 repoman_settings["O"] = "."
2026 gpgsign(os.path.join(repoman_settings["O"], "Manifest"))
2027 elif repolevel==2: # In a category dir
2031 if len(xs) < 4-repolevel:
2037 mydone.append(xs[0])
2038 repoman_settings["O"] = os.path.join(".", xs[0])
2039 if not os.path.isdir(repoman_settings["O"]):
2041 gpgsign(os.path.join(repoman_settings["O"], "Manifest"))
2042 elif repolevel==1: # repo-cvsroot
2043 print green("RepoMan sez:"), "\"You're rather crazy... doing the entire repository.\"\n"
2047 if len(xs) < 4-repolevel:
2051 if "/".join(xs[:2]) in mydone:
2053 mydone.append("/".join(xs[:2]))
2054 repoman_settings["O"] = os.path.join(".", xs[0], xs[1])
2055 if not os.path.isdir(repoman_settings["O"]):
2057 gpgsign(os.path.join(repoman_settings["O"], "Manifest"))
2058 except portage.exception.PortageException, e:
2059 portage.writemsg("!!! %s\n" % str(e))
2060 portage.writemsg("!!! Disabled FEATURES='sign'\n")
2064 # It's not safe to use the git commit -a option since there might
2065 # be some modified files elsewhere in the working tree that the
2066 # user doesn't want to commit. Therefore, call git update-index
2067 # in order to ensure that the index is updated with the latest
2068 # versions of all new and modified files in the relevant portion
2069 # of the working tree.
2070 myfiles = mymanifests + myupdates
2072 update_index_cmd = ["git", "update-index"]
2073 update_index_cmd.extend(f.lstrip("./") for f in myfiles)
2075 print "(%s)" % (" ".join(update_index_cmd),)
2077 retval = spawn(update_index_cmd, env=os.environ)
2078 if retval != os.EX_OK:
2079 writemsg_level(("!!! Exiting on %s (shell) " + \
2080 "error code: %s\n") % (vcs, retval),
2081 level=logging.ERROR, noiselevel=-1)
2084 if vcs == 'git' or manifest_commit_required or signed:
2086 myfiles = mymanifests[:]
2088 myfiles += myupdates
2089 myfiles += myremoved
2092 fd, commitmessagefile = tempfile.mkstemp(".repoman.msg")
2093 mymsg = os.fdopen(fd, "w")
2094 mymsg.write(commitmessage)
2096 mymsg.write("\n (Signed Manifest commit)")
2098 mymsg.write("\n (Unsigned Manifest commit)")
2102 commit_cmd.extend(vcs_global_opts)
2103 commit_cmd.append("commit")
2104 commit_cmd.extend(vcs_local_opts)
2105 commit_cmd.extend(["-F", commitmessagefile])
2106 commit_cmd.extend(f.lstrip("./") for f in myfiles)
2110 print "(%s)" % (" ".join(commit_cmd),)
2112 retval = spawn(commit_cmd, env=os.environ)
2113 if retval != os.EX_OK:
2114 writemsg_level(("!!! Exiting on %s (shell) " + \
2115 "error code: %s\n") % (vcs, retval),
2116 level=logging.ERROR, noiselevel=-1)
2120 os.unlink(commitmessagefile)
2126 print "Commit complete."
2128 print "repoman was too scared by not seeing any familiar version control file that he forgot to commit anything"
2129 print green("RepoMan sez:"), "\"If everyone were like you, I'd be out of business!\"\n"