da3e4fcdf5c1a02f968fff53090d20ec854c97c5
[portage.git] / bin / repoman
1 #!/usr/bin/python -O
2 # Copyright 1999-2006 Gentoo Foundation
3 # Distributed under the terms of the GNU General Public License v2
4 # $Id$
5
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.
9
10 import codecs
11 import commands
12 import errno
13 import formatter
14 import logging
15 import optparse
16 import os
17 import re
18 import signal
19 import stat
20 import sys
21 import tempfile
22 import time
23 import platform
24
25 from itertools import chain, izip
26 from stat import S_ISDIR, ST_CTIME
27
28 try:
29         import cStringIO as StringIO
30 except ImportError:
31         import StringIO
32
33 if not hasattr(__builtins__, "set"):
34         from sets import Set as set
35
36 try:
37         import portage
38 except ImportError:
39         from os import path as osp
40         sys.path.insert(0, osp.join(osp.dirname(osp.dirname(osp.realpath(__file__))), "pym"))
41         import portage
42 portage._disable_legacy_globals()
43
44 try:
45         from repoman.checks import run_checks
46         from repoman import utilities
47 except ImportError:
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
52
53 from _emerge import Package, RootConfig
54 from portage._sets import load_default_config
55
56 import portage.checksum
57 import portage.const
58 import portage.dep
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
70
71 util.initialize_logger()
72
73 # 14 is the length of DESCRIPTION=""
74 max_desc_len = 100
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")
82
83 # A sane umask is needed for files that portage creates.
84 os.umask(022)
85 repoman_settings = portage.config(local_config=False,
86         config_incrementals=portage.const.INCREMENTALS)
87 repoman_settings.lock()
88
89 if repoman_settings.get("NOCOLOR", "").lower() in ("yes", "true") or \
90         not sys.stdout.isatty():
91         nocolor()
92
93 def warn(txt):
94         print "repoman: " + txt
95
96 def err(txt):
97         warn(txt)
98         sys.exit(1)
99
100 def exithandler(signum=None, frame=None):
101         logging.fatal("Interrupted; exiting...")
102         sys.exit(1)
103         os.kill(0, signal.SIGKILL)
104
105 signal.signal(signal.SIGINT,exithandler)
106
107 class RepomanHelpFormatter(optparse.IndentedHelpFormatter):
108         """Repoman needs it's own HelpFormatter for now, because the default ones
109         murder the help text."""
110         
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)
113
114         def format_description(self, description):
115                 return description
116
117 class RepomanOptionParser(optparse.OptionParser):
118         """Add the on_tail function, ruby has it, optionParser should too
119         """
120         
121         def __init__(self, *args, **kwargs):
122                 optparse.OptionParser.__init__(self, *args, **kwargs)
123                 self.tail = ""
124
125         def on_tail(self, description):
126                 self.tail += description
127
128         def format_help(self, formatter=None):
129                 result = optparse.OptionParser.format_help(self, formatter)
130                 result += self.tail
131                 return result
132
133
134 def ParseArgs(args, qahelp):
135         """This function uses a customized optionParser to parse command line arguments for repoman
136         Args:
137           args - a sequence of command line arguments
138                 qahelp - a dict of qa warning to help message
139         Returns:
140           (opts, args), just like a call to parser.parse_args()
141         """
142
143         modes = {
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' 
151         }
152
153         mode_keys = modes.keys()
154         mode_keys.sort()
155
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))
161
162         parser.add_option('-m', '--commitmsg', dest='commitmsg',
163                 help='specify a commit message on the command line')
164
165         parser.add_option('-M', '--commitmsgfile', dest='commitmsgfile',
166                 help='specify a path to a file that contains a commit message')
167
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')
170         
171         parser.add_option('-q', '--quiet', dest="quiet", action="count", default=0,
172                 help='do not print unnecessary messages')
173
174         parser.add_option('-f', '--force', dest='force', default=False, action='store_true',
175                 help='Commit with QA violations')
176
177         parser.add_option('-v', '--verbose', dest="verbosity", action='count',
178                 help='be very verbose in output', default=0)
179
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')
182
183         parser.add_option('-i', '--ignore-arches', dest='ignore_arches', action='store_true',
184                 default=False, help='ignore arch-specific failures (where arch != host)')
185
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)')
188
189         parser.add_option('-d', '--include-dev', dest='include_dev', action='store_true',
190                 default=False, help='include dev profiles in dependency checks')
191
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)')
194
195         parser.add_option('--mode', type='choice', dest='mode', choices=modes.keys(), 
196                 help='specify which mode repoman will run in (default=full)')
197
198         parser.on_tail("\n " + green("Modes".ljust(20) + " Description\n"))
199
200         for k in mode_keys:
201                 parser.on_tail(" %s %s\n" % (k.ljust(20), modes[k]))
202
203         parser.on_tail("\n " + green("QA keyword".ljust(20) + " Description\n"))
204
205         sorted_qa = qahelp.keys()
206         sorted_qa.sort()
207         for k in sorted_qa:
208                 parser.on_tail(" %s %s\n" % (k.ljust(20), qahelp[k]))
209
210         if not args:
211                 args = sys.argv
212         opts, args = parser.parse_args(args)
213
214         if opts.mode == 'help':
215                 parser.print_help(short=False)
216
217         for arg in args:
218                 if arg in modes:
219                         if not opts.mode:
220                                 opts.mode = arg
221                                 break
222
223         if not opts.mode:
224                 opts.mode = 'full'
225         
226         if opts.mode == 'ci':
227                 opts.mode = 'commit'  # backwards compat shortcut
228
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')
234
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)
239
240         for val in range(opts.quiet):
241                 logger = logging.getLogger()
242                 logger.setLevel(logger.getEffectiveLevel() + 10)
243
244         return (opts, args)
245
246 qahelp={
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"
318 }
319
320 qacats = qahelp.keys()
321 qacats.sort()
322
323 qawarnings = set((
324 "changelog.missing",
325 "changelog.notadded",
326 "digestentry.unused",
327 "ebuild.notadded",
328 "ebuild.nostable",
329 "ebuild.allmasked",
330 "ebuild.nesteddie",
331 "desktop.invalid",
332 "DEPEND.badmasked","RDEPEND.badmasked","PDEPEND.badmasked",
333 "DEPEND.badindev","RDEPEND.badindev","PDEPEND.badindev",
334 "DEPEND.badmaskedindev","RDEPEND.badmaskedindev","PDEPEND.badmaskedindev",
335 "DESCRIPTION.toolong",
336 "KEYWORDS.dropped",
337 "KEYWORDS.stupid",
338 "KEYWORDS.missing",
339 "IUSE.undefined",
340 "RDEPEND.suspect",
341 "RESTRICT.invalid",
342 "SRC_URI.mirror",
343 "ebuild.minorsyn",
344 "ebuild.badheader",
345 "ebuild.patches",
346 "file.size",
347 "inherit.autotools",
348 "java.eclassesnotused",
349 "virtual.versioned",
350 "virtual.exists",
351 "virtual.unavailable",
352 "usage.obsolete",
353 "LIVEVCS.stable"
354 ))
355
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)
361 commitmessage=None
362 for x in missingvars:
363         x += ".missing"
364         if x not in qacats:
365                 logging.warn('* missingvars values need to be added to qahelp ("%s")' % x)
366                 qacats.append(x)
367                 qawarnings.add(x)
368
369 valid_restrict = frozenset(["binchecks", "bindist",
370         "fetch", "installsources", "mirror",
371         "primaryuri", "strip", "test", "userpriv"])
372
373 live_eclasses = frozenset([
374         "bzr",
375         "cvs",
376         "darcs",
377         "git",
378         "mercurial",
379         "subversion"
380 ])
381
382 suspect_rdepend = frozenset([
383         "app-arch/cabextract",
384         "app-arch/rpm2targz",
385         "app-doc/doxygen",
386         "dev-lang/nasm",
387         "dev-lang/swig",
388         "dev-lang/yasm",
389         "dev-perl/extutils-pkgconfig",
390         "dev-python/setuptools",
391         "dev-util/byacc",
392         "dev-util/cmake",
393         "dev-util/ftjam",
394         "dev-util/gtk-doc",
395         "dev-util/gtk-doc-am",
396         "dev-util/intltool",
397         "dev-util/jam",
398         "dev-util/pkgconfig",
399         "dev-util/scons",
400         "dev-util/unifdef",
401         "dev-util/yacc",
402         "media-gfx/ebdftopcf",
403         "sys-apps/help2man",
404         "sys-devel/autoconf",
405         "sys-devel/automake",
406         "sys-devel/bin86",
407         "sys-devel/bison",
408         "sys-devel/dev86",
409         "sys-devel/flex",
410         "sys-devel/libtool",
411         "sys-devel/m4",
412         "sys-devel/pmake",
413         "x11-misc/bdftopcf",
414         "x11-misc/imake",
415 ])
416
417 # file.executable
418 no_exec = frozenset(["Manifest","ChangeLog","metadata.xml"])
419
420 options, arguments = ParseArgs(sys.argv, qahelp)
421
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).
425 can_force = True
426
427 portdir, portdir_overlay, mydir = utilities.FindPortdir(repoman_settings)
428 if portdir is None:
429         sys.exit(1)
430
431 vcs = None
432 if os.path.isdir("CVS"):
433         vcs = "cvs"
434 if os.path.isdir(".svn"):
435         vcs = "svn"
436 elif os.path.isdir(os.path.join(portdir_overlay, ".git")):
437         vcs = "git"
438
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:
442         if vcs != "git":
443                 vcs_global_opts = "-q"
444         else:
445                 vcs_global_opts = ""
446 vcs_global_opts = vcs_global_opts.split()
447
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")
453         if repo_lines and \
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 " \
458                 "more information."
459                 prefix = bad(" * ")
460                 from textwrap import wrap
461                 for line in wrap(msg, 70):
462                         print prefix + line
463                 sys.exit(1)
464         del repo_lines
465
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
469
470 os.environ["PORTDIR"] = portdir
471 if portdir_overlay != portdir:
472         os.environ["PORTDIR_OVERLAY"] = portdir_overlay
473 else:
474         os.environ["PORTDIR_OVERLAY"] = ""
475
476 logging.info('Setting paths:')
477 logging.info('PORTDIR = "' + os.environ['PORTDIR'] + '"')
478 logging.info('PORTDIR_OVERLAY = "' + os.environ['PORTDIR_OVERLAY']+'"')
479
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"]
495
496 myreporoot = os.path.basename(portdir_overlay)
497 myreporoot += mydir[len(portdir_overlay):]
498
499 reposplit = myreporoot.split(os.path.sep)
500 repolevel = len(reposplit)
501
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."
509         print red("***")
510         err("Unable to identify level we're commiting from for %s" % '/'.join(reposplit))
511
512 startdir = normalize_path(mydir)
513 repodir = startdir
514 for x in range(0, repolevel - 1):
515         repodir = os.path.dirname(repodir)
516
517 def caterror(mycat):
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.")
519
520 # setup a uselist from portage
521 uselist=[]
522 try:
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")
532         sys.exit(1)
533
534 # retrieve a list of current licenses in portage
535 liclist = set(portage.listdir(os.path.join(portdir, "licenses")))
536 if not liclist:
537         logging.fatal("Couldn't find licenses?")
538         sys.exit(1)
539 if portdir_overlay != portdir:
540         liclist.update(portage.listdir(os.path.join(portdir_overlay, "licenses")))
541
542 # retrieve list of offical keywords
543 kwlist = set(portage.grabfile(os.path.join(portdir, "profiles", "arch.list")))
544 if not kwlist:
545         logging.fatal("Couldn't read KEYWORDS from arch.list")
546         sys.exit(1)
547
548 if portdir_overlay != portdir:
549         kwlist.update(portage.grabfile(
550                 os.path.join(portdir_overlay, "profiles", "arch.list")))
551
552 scanlist=[]
553 if repolevel==2:
554         #we are inside a category directory
555         catdir=reposplit[-1]
556         if catdir not in repoman_settings.categories:
557                 caterror(catdir)
558         mydirlist=os.listdir(startdir)
559         for x in mydirlist:
560                 if x == "CVS" or x.startswith("."):
561                         continue
562                 if os.path.isdir(startdir+"/"+x):
563                         scanlist.append(catdir+"/"+x)
564         repo_subdir = catdir + os.sep
565 elif repolevel==1:
566         for x in repoman_settings.categories:
567                 if not os.path.isdir(startdir+"/"+x):
568                         continue
569                 for y in os.listdir(startdir+"/"+x):
570                         if y == "CVS" or y.startswith("."):
571                                 continue
572                         if os.path.isdir(startdir+"/"+x+"/"+y):
573                                 scanlist.append(x+"/"+y)
574         repo_subdir = ""
575 elif repolevel==3:
576         catdir = reposplit[-2]
577         if catdir not in repoman_settings.categories:
578                 caterror(catdir)
579         scanlist.append(catdir+"/"+reposplit[-1])
580         repo_subdir = scanlist[-1] + os.sep
581 repo_subdir_len = len(repo_subdir)
582 scanlist.sort()
583
584 logging.debug("Found the following packages to scan:\n%s" % '\n'.join(scanlist))
585
586 profiles={}
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')):
591                 if x[0]=="#":
592                         continue
593                 arch=x.split()
594                 if len(arch) == 0:
595                         continue
596                 if len(arch)!=3:
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]
607                         continue
608                 if arch[0] in profiles:
609                         profiles[arch[0]]+= [[arch[1], arch[2]]]
610                 else:
611                         profiles[arch[0]] = [[arch[1], arch[2]]]
612
613         for x in repoman_settings.archlist():
614                 if x[0] == "~":
615                         continue
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.")
620                         print
621 else:
622         print red("profiles.desc does not exist: "+descfile)
623         print red("You need to do \"cvs update\" in profiles dir.")
624         print
625         sys.exit(1)
626
627 def dev_keywords(profiles):
628         """
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.
633         """
634         type_arch_map = {}
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)
638                         if arch_set is None:
639                                 arch_set = set()
640                                 type_arch_map[profile_type] = arch_set
641                         arch_set.add(arch)
642
643         dev_keywords = type_arch_map.get('dev', set())
644         dev_keywords.update(['~' + arch for arch in dev_keywords])
645         return frozenset(dev_keywords)
646
647 dev_keywords = dev_keywords(profiles)
648
649 stats={}
650 fails={}
651
652 # provided by the desktop-file-utils package
653 desktop_file_validate = find_binary("desktop-file-validate")
654 desktop_pattern = re.compile(r'.*\.desktop$')
655
656 for x in qacats:
657         stats[x]=0
658         fails[x]=[]
659 xmllint_capable = False
660 metadata_dtd = os.path.join(repoman_settings["DISTDIR"], 'metadata.dtd')
661 if options.mode == "manifest":
662         pass
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"
667                 sys.exit(1)
668 else:
669         #hardcoded paths/urls suck. :-/
670         must_fetch=1
671         backup_exists=0
672         try:
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.
678                         backup_exists=1
679                 else:
680                         must_fetch=0
681
682         except (OSError,IOError), e:
683                 if e.errno != 2:
684                         print red("!!!")+" caught exception '%s' for %s/metadata.dtd, bailing" % (str(e), portage.CACHE_PATH)
685                         sys.exit(1)
686
687         if must_fetch:
688                 print 
689                 print green("***")+" the local copy of metadata.dtd needs to be refetched, doing that now"
690                 print
691                 val = 0
692                 try:
693                         try:
694                                 os.unlink(metadata_dtd)
695                         except OSError, e:
696                                 if e.errno != errno.ENOENT:
697                                         raise
698                                 del e
699                         val=portage.fetch(['http://www.gentoo.org/dtd/metadata.dtd'],repoman_settings,fetchonly=0, \
700                                 try_mirrors=0)
701
702                 except SystemExit, e:
703                         raise  # Need to propogate this
704                 except Exception,e:
705                         print
706                         print red("!!!")+" attempting to fetch 'http://www.gentoo.org/dtd/metadata.dtd', caught"
707                         print red("!!!")+" exception '%s' though." % str(e)
708                         val=0
709                 if not val:
710                         print red("!!!")+" fetching new metadata.dtd failed, aborting"
711                         sys.exit(1)
712         #this can be problematic if xmllint changes their output
713         xmllint_capable=True
714
715 if options.mode == 'commit' and vcs:
716         utilities.detect_vcs_conflicts(options, vcs)
717
718 if options.mode == "manifest":
719         pass
720 elif options.pretend:
721         print green("\nRepoMan does a once-over of the neighborhood...")
722 else:
723         print green("\nRepoMan scours the neighborhood...")
724
725 new_ebuilds = set()
726 modified_changelogs = set()
727 mychanged = []
728 mynew = []
729 myremoved = []
730
731 if vcs == "cvs":
732         mycvstree = cvstree.getentries("./", recursive=1)
733         mychanged = cvstree.findchanged(mycvstree, recursive=1, basedir="./")
734         mynew = cvstree.findnew(mycvstree, recursive=1, basedir="./")
735 if vcs == "svn":
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") ]
739 elif vcs == "git":
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
744         if strip_levels:
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]
748 if vcs:
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")
752
753 have_pmasked = False
754 have_dev_keywords = False
755 dofail = 0
756 arch_caches={}
757 arch_xmatch_caches = {}
758 shared_xmatch_caches = {"cp-list":{}}
759
760 # Disable the "ebuild.notadded" check when not in commit mode and
761 # running `svn status` in every package dir will be too expensive.
762
763 check_ebuild_notadded = not \
764         (vcs == "svn" and repolevel < 3 and options.mode != "commit")
765
766 # Build a regex from thirdpartymirrors for the SRC_URI.mirror check.
767 thirdpartymirrors = portage.flatten(repoman_settings.thirdpartymirrors().values())
768
769 for x in scanlist:
770         #ebuilds and digests added to cvs respectively.
771         logging.info("checking package %s" % x)
772         eadded=[]
773         catdir,pkgdir=x.split("/")
774         checkdir=repodir+"/"+x
775         checkdir_relative = ""
776         if repolevel < 3:
777                 checkdir_relative = os.path.join(pkgdir, checkdir_relative)
778         if repolevel < 2:
779                 checkdir_relative = os.path.join(catdir, checkdir_relative)
780         checkdir_relative = os.path.join(".", checkdir_relative)
781
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."
787                         dofail = 1
788                 if options.mode == "manifest":
789                         continue
790                 elif dofail:
791                         sys.exit(1)
792
793         checkdirlist=os.listdir(checkdir)
794         ebuildlist=[]
795         pkgs = {}
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"):
802                         pf = y[:-7]
803                         ebuildlist.append(pf)
804                         cpv = "%s/%s" % (catdir, pf)
805                         try:
806                                 myaux = dict(izip(allvars, portdb.aux_get(cpv, allvars)))
807                         except KeyError:
808                                 stats["ebuild.syntax"] += 1
809                                 fails["ebuild.syntax"].append(os.path.join(x, y))
810                                 continue
811                         except IOError:
812                                 stats["ebuild.output"] += 1
813                                 fails["ebuild.output"].append(os.path.join(x, y))
814                                 continue
815                         if not portage.eapi_is_supported(myaux["EAPI"]):
816                                 stats["EAPI.unsupported"] += 1
817                                 fails["EAPI.unsupported"].append(os.path.join(x, y))
818                                 continue
819                         pkgs[pf] = Package(cpv=cpv, metadata=myaux,
820                                 root_config=root_config)
821
822         # Sort ebuilds in ascending order for the KEYWORDS.dropped check.
823         pkgsplits = {}
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]]
831         del pkgsplits
832
833         slot_keywords = {}
834
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.
841                 can_force = False
842                 continue
843
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))
849                                 break
850
851                 if not (y in ("ChangeLog", "metadata.xml") or y.endswith(".ebuild")):
852                         continue
853                 try:
854                         line = 1
855                         for l in codecs.open(checkdir+"/"+y, "r", "utf8"):
856                                 line +=1
857                 except UnicodeDecodeError, ue:
858                         stats["file.UTF8"] += 1
859                         s = ue.object[:ue.start]
860                         l2 = s.count("\n")
861                         line += l2
862                         if l2 != 0:
863                                 s = s[s.rfind("\n") + 1:]
864                         fails["file.UTF8"].append("%s/%s: line %i, just after: '%s'" % (checkdir, y, line, s))
865
866         if vcs == "git" and check_ebuild_notadded:
867                 myf = os.popen("git ls-files --others %s" % \
868                         (portage._shell_quote(checkdir_relative),))
869                 for l in myf:
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])))
874                 myf.close()
875
876         if vcs in ("cvs", "svn") and check_ebuild_notadded:
877                 try:
878                         if vcs == "cvs":
879                                 myf=open(checkdir+"/CVS/Entries","r")
880                         if vcs == "svn":
881                                 myf = os.popen("svn status --depth=files --verbose " + checkdir)
882                         myl = myf.readlines()
883                         myf.close()
884                         for l in myl:
885                                 if vcs == "cvs":
886                                         if l[0]!="/":
887                                                 continue
888                                         splitl=l[1:].split("/")
889                                         if not len(splitl):
890                                                 continue
891                                         if splitl[0][-7:]==".ebuild":
892                                                 eadded.append(splitl[0][:-7])
893                                 if vcs == "svn":
894                                         if l[:1] == "?":
895                                                 continue
896                                         l = l.split()[-1]
897                                         if l[-7:] == ".ebuild":
898                                                 eadded.append(os.path.basename(l[:-7]))
899                         if vcs == "svn":
900                                 myf = os.popen("svn status " + checkdir)
901                                 myl=myf.readlines()
902                                 myf.close()
903                                 for l in myl:
904                                         if l[0] == "A":
905                                                 l = l.rstrip().split(' ')[-1]
906                                                 if l[-7:] == ".ebuild":
907                                                         eadded.append(os.path.basename(l[:-7]))
908                 except IOError:
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")
915                         continue
916
917         mf = Manifest(checkdir, repoman_settings["DISTDIR"])
918         mydigests=mf.getTypeDigests("DIST")
919
920         fetchlist_dict = portage.FetchlistDict(checkdir, repoman_settings, portdb)
921         myfiles_all = []
922         src_uri_error = False
923         for mykey in fetchlist_dict:
924                 try:
925                         myfiles_all.extend(fetchlist_dict[mykey])
926                 except portage.exception.InvalidDependString, e:
927                         src_uri_error = True
928                         try:
929                                 portdb.aux_get(mykey, ["SRC_URI"])
930                         except KeyError:
931                                 # This will be reported as an "ebuild.syntax" error.
932                                 pass
933                         else:
934                                 stats["SRC_URI.syntax"] = stats["SRC_URI.syntax"] + 1
935                                 fails["SRC_URI.syntax"].append(
936                                         "%s.ebuild SRC_URI: %s" % (mykey, e))
937         del fetchlist_dict
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"
943                 # or "ebuild.sytax".
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)
949         del myfiles_all
950
951         if os.path.exists(checkdir+"/files"):
952                 filesdirlist=os.listdir(checkdir+"/files")
953
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.
956                 while filesdirlist:
957                         y = filesdirlist.pop(0)
958                         relative_path = os.path.join(x, "files", y)
959                         full_path = os.path.join(repodir, relative_path)
960                         try:
961                                 mystat = os.stat(full_path)
962                         except OSError, oe:
963                                 if oe.errno == 2:
964                                         # don't worry about it.  it likely was removed via fix above.
965                                         continue
966                                 else:
967                                         raise oe
968                         if S_ISDIR(mystat.st_mode):
969                                 # !!! VCS "portability" alert!  Need some function isVcsDir() or alike !!!
970                                 if y == "CVS" or y == ".svn":
971                                         continue
972                                 for z in os.listdir(checkdir+"/files/"+y):
973                                         if z == "CVS" or z == ".svn":
974                                                 continue
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)
980
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))
985                                         break
986
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:
999                                                         continue
1000                                                 stats["desktop.invalid"] += 1
1001                                                 fails["desktop.invalid"].append(
1002                                                         relative_path + ': %s' % error_match.group(1))
1003
1004         del mydigests
1005
1006         if "ChangeLog" not in checkdirlist:
1007                 stats["changelog.missing"]+=1
1008                 fails["changelog.missing"].append(x+"/ChangeLog")
1009         
1010         #metadata.xml file check
1011         muselist = []
1012
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
1017         else:
1018                 metadata_bad = False
1019
1020                 # load USE flags from metadata.xml
1021                 try:
1022                         f = open(os.path.join(checkdir, "metadata.xml"))
1023                         utilities.parse_metadata_use(f, muselist)
1024                         f.close()
1025                 except (EnvironmentError, ParseError), e:
1026                         metadata_bad = True
1027                         stats["metadata.bad"] += 1
1028                         fails["metadata.bad"].append("%s/metadata.xml: %s" % (x, e))
1029                         del e
1030
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")))
1038                         if st != os.EX_OK:
1039                                 print red("!!!") + " metadata.xml is invalid:"
1040                                 for z in out.splitlines():
1041                                         print red("!!! ")+z
1042                                 stats["metadata.bad"]+=1
1043                                 fails["metadata.bad"].append(x+"/metadata.xml")
1044
1045                 del metadata_bad
1046
1047         changelog_path = os.path.join(checkdir_relative, "ChangeLog")
1048         changelog_modified = changelog_path in modified_changelogs
1049
1050         allmasked = True
1051
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"
1056                 if repolevel < 3:
1057                         ebuild_path = os.path.join(pkgdir, ebuild_path)
1058                 if repolevel < 2:
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)
1064
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")
1076                         continue
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")
1081                         continue
1082
1083                 pkg = pkgs[y]
1084                 myaux = pkg.metadata
1085                 eapi = myaux["EAPI"]
1086                 inherited = pkg.inherited
1087                 live_ebuild = live_eclasses.intersection(inherited)
1088
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
1097                                                 break
1098                                 if not contains_mirror:
1099                                         continue
1100
1101                                 stats["SRC_URI.mirror"] += 1
1102                                 fails["SRC_URI.mirror"].append(
1103                                         "%s: '%s' found in thirdpartymirrors" % \
1104                                         (relative_path, mirror))
1105
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
1110                 try:
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))
1116                         del e
1117                         continue
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)
1129
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"):
1134                                         continue
1135                                 if live_ebuild and missing_var == "KEYWORDS":
1136                                         continue
1137                                 myqakey=missingvars[pos]+".missing"
1138                                 stats[myqakey]=stats[myqakey]+1
1139                                 fails[myqakey].append(x+"/"+y+".ebuild")
1140
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))
1147
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)
1154                 if stable_keywords:
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))
1161
1162                 ebuild_archs = set(kw.lstrip("~") for kw in keywords \
1163                         if not kw.startswith("-"))
1164
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)))
1175
1176                 slot_keywords[myaux["SLOT"]].update(ebuild_archs)
1177
1178                 # KEYWORDS="-*" is a stupid replacement for package.mask and screws general KEYWORDS semantics
1179                 if "-*" in keywords:
1180                         haskeyword = False
1181                         for kw in keywords:
1182                                 if kw[0] == "~":
1183                                         kw = kw[1:]
1184                                 if kw in kwlist:
1185                                         haskeyword = True
1186                         if not haskeyword:
1187                                 stats["KEYWORDS.stupid"] += 1
1188                                 fails["KEYWORDS.stupid"].append(x+"/"+y+".ebuild")
1189
1190                 """
1191                 Ebuilds that inherit a "Live" eclass (darcs,subversion,git,cvs,etc..) should
1192                 not be allowed to be marked stable
1193                 """
1194                 if live_ebuild:
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)
1200                                 del 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
1207
1208                 if options.ignore_arches:
1209                         arches = [[repoman_settings["ARCH"], repoman_settings["ARCH"],
1210                                 repoman_settings["ACCEPT_KEYWORDS"].split()]]
1211                 else:
1212                         arches=[]
1213                         for keyword in myaux["KEYWORDS"].split():
1214                                 if (keyword[0]=="-"):
1215                                         continue
1216                                 elif (keyword[0]=="~"):
1217                                         arches.append([keyword, keyword[1:], [keyword[1:], keyword]])
1218                                 else:
1219                                         arches.append([keyword, keyword, [keyword]])
1220                                         allmasked = False
1221
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("+"):
1229                                 myflag = myflag[1:]
1230                         myiuse.add(myflag)
1231
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]
1239                         
1240                         if mydepstr.find(" ?") != -1:
1241                                 badsyntax.append("'?' preceded by space")
1242
1243                         try:
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:
1248                                         raise ValueError
1249                         except ValueError:
1250                                 badsyntax.append("parenthesis mismatch")
1251                                 mydeplist = []
1252                         except portage.exception.InvalidDependString, e:
1253                                 badsyntax.append(str(e))
1254                                 del e
1255                                 mydeplist = []
1256
1257                         try:
1258                                 portage.dep.use_reduce(mydeplist, matchall=1)
1259                         except portage.exception.InvalidDependString, e:
1260                                 badsyntax.append(str(e))
1261
1262                         for token in operator_tokens:
1263                                 if mydepstr.startswith(token+" "):
1264                                         myteststr = mydepstr[len(token):]
1265                                 else:
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))
1273
1274                         if mytype in ("DEPEND", "RDEPEND", "PDEPEND"):
1275                                 for token in mydepstr.split():
1276                                         if token in operator_tokens or \
1277                                                 token.endswith("?"):
1278                                                 continue
1279                                         try:
1280                                                 atom = portage.dep.Atom(token)
1281                                         except portage.exception.InvalidAtom:
1282                                                 badsyntax.append("'%s' not a valid atom" % token)
1283                                         else:
1284                                                 is_blocker = atom.blocker
1285
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)
1298                                                 if eapi == "0":
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))
1318
1319                         type_list.extend([mytype] * (len(badsyntax) - len(type_list)))
1320
1321                 for m,b in zip(type_list, badsyntax):
1322                         stats[m+".syntax"] += 1
1323                         fails[m+".syntax"].append(catpkg+".ebuild "+m+": "+b)
1324
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
1330
1331                 # uselist checks - global
1332                 myuse = []
1333                 default_use = []
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)
1340
1341                 # uselist checks - metadata
1342                 for mypos in range(len(myuse)-1,-1,-1):
1343                         if myuse[mypos] and (myuse[mypos] in muselist):
1344                                 del myuse[mypos]
1345
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))
1353
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])      
1357
1358                 # license checks
1359                 if not badlicsyntax:
1360                         myuse = myaux["LICENSE"]
1361                         # Parse the LICENSE variable, remove USE conditions and
1362                         # flatten it.
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])
1373
1374                 #keyword checks
1375                 myuse = myaux["KEYWORDS"].split()
1376                 for mykey in myuse:
1377                         myskey=mykey[:]
1378                         if myskey[0]=="-":
1379                                 myskey=myskey[1:]
1380                         if myskey[0]=="~":
1381                                 myskey=myskey[1:]
1382                         if mykey!="-*":
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)
1389
1390                 #restrict checks
1391                 myrestrict = None
1392                 try:
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))
1399                         del e
1400                 if myrestrict:
1401                         myrestrict = set(portage.flatten(myrestrict))
1402                         mybadrestrict = myrestrict.difference(valid_restrict)
1403                         if mybadrestrict:
1404                                 stats["RESTRICT.invalid"] += len(mybadrestrict)
1405                                 for mybad in mybadrestrict:
1406                                         fails["RESTRICT.invalid"].append(x+"/"+y+".ebuild: %s" % mybad)
1407                 # Syntax Checks
1408                 relative_path = os.path.join(x, y + ".ebuild")
1409                 full_path = os.path.join(repodir, relative_path)
1410                 f = open(full_path, 'rb')
1411                 try:
1412                         for check_name, e in run_checks(f, pkg):
1413                                 stats[check_name] += 1
1414                                 fails[check_name].append(relative_path + ': %s' % e)
1415                 finally:
1416                         f.close()
1417                         del f
1418
1419                 if options.force:
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.
1423                         continue
1424
1425                 for keyword,arch,groups in arches:
1426
1427                         if arch not in profiles:
1428                                 # A missing profile will create an error further down
1429                                 # during the KEYWORDS verification.
1430                                 continue
1431                                 
1432                         for prof in profiles[arch]:
1433
1434                                 if prof[1] not in ("stable", "dev") or \
1435                                         prof[1] == "dev" and not options.include_dev:
1436                                         continue
1437
1438                                 profdir = portdir+"/profiles/"+prof[0]
1439         
1440                                 if prof[0] in arch_caches:
1441                                         dep_settings = arch_caches[prof[0]]
1442                                 else:
1443                                         dep_settings = portage.config(
1444                                                 config_profile_path=profdir,
1445                                                 config_incrementals=portage.const.INCREMENTALS,
1446                                                 local_config=False)
1447                                         if options.without_mask:
1448                                                 dep_settings.pmaskdict.clear()
1449                                         arch_caches[prof[0]] = dep_settings
1450                                         while True:
1451                                                 try:
1452                                                         # Protect ACCEPT_KEYWORDS from config.regenerate()
1453                                                         # (just in case)
1454                                                         dep_settings.incrementals.remove("ACCEPT_KEYWORDS")
1455                                                 except ValueError:
1456                                                         break
1457
1458                                 xmatch_cache_key = (prof[0], tuple(groups))
1459                                 xcache = arch_xmatch_caches.get(xmatch_cache_key)
1460                                 if xcache is None:
1461                                         portdb.melt()
1462                                         portdb.freeze()
1463                                         xcache = portdb.xcache
1464                                         xcache.update(shared_xmatch_caches)
1465                                         arch_xmatch_caches[xmatch_cache_key] = xcache
1466
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")
1475
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)
1481
1482                                 if not baddepsyntax:
1483                                         ismasked = os.path.join(catdir, y) not in \
1484                                                 portdb.xmatch("list-visible", x)
1485                                         if ismasked:
1486                                                 if not have_pmasked:
1487                                                         have_pmasked = bool(dep_settings._getMaskAtom(
1488                                                                 pkg.cpv, pkg.metadata))
1489                                                 if options.ignore_masked:
1490                                                         continue
1491                                                 #we are testing deps for a masked package; give it some lee-way
1492                                                 suffix="masked"
1493                                                 matchmode = "minimum-all"
1494                                         else:
1495                                                 suffix=""
1496                                                 matchmode = "minimum-visible"
1497
1498                                         if not have_dev_keywords:
1499                                                 have_dev_keywords = \
1500                                                         bool(dev_keywords.intersection(keywords))
1501
1502                                         if prof[1] == "dev":
1503                                                 suffix=suffix+"indev"
1504
1505                                         for mytype,mypos in [["DEPEND",len(missingvars)],["RDEPEND",len(missingvars)+1],["PDEPEND",len(missingvars)+2]]:
1506                                                 
1507                                                 mykey=mytype+".bad"+suffix
1508                                                 myvalue = myaux[mytype]
1509                                                 if not myvalue:
1510                                                         continue
1511                                                 try:
1512                                                         mydep = portage.dep_check(myvalue, portdb,
1513                                                                 dep_settings, use="all", mode=matchmode,
1514                                                                 trees=trees)
1515                                                 except KeyError, e:
1516                                                         stats[mykey]=stats[mykey]+1
1517                                                         fails[mykey].append(x+"/"+y+".ebuild: "+keyword+"("+prof[0]+") "+repr(e[0]))
1518                                                         continue
1519         
1520                                                 if mydep[0]==1:
1521                                                         if mydep[1]!=[]:
1522                                                                 #we have some unsolvable deps
1523                                                                 #remove ! deps, which always show up as unsatisfiable
1524                                                                 d=0
1525                                                                 while d<len(mydep[1]):
1526                                                                         if mydep[1][d][0]=="!":
1527                                                                                 del mydep[1][d]
1528                                                                         else:
1529                                                                                 d += 1
1530                                                                 #if we emptied out our list, continue:
1531                                                                 if not mydep[1]:
1532                                                                         continue
1533                                                                 stats[mykey]=stats[mykey]+1
1534                                                                 fails[mykey].append(x+"/"+y+".ebuild: "+keyword+"("+prof[0]+") "+repr(mydep[1]))
1535                                                 else:
1536                                                         stats[mykey]=stats[mykey]+1
1537                                                         fails[mykey].append(x+"/"+y+".ebuild: "+keyword+"("+prof[0]+") "+repr(mydep[1]))
1538
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)
1547
1548 if options.mode == "manifest":
1549         sys.exit(dofail)
1550
1551 #dofail will be set to 1 if we have failed in at least one non-warning category
1552 dofail=0
1553 #dowarn will be set to 1 if we tripped any warnings
1554 dowarn=0
1555 #dofull will be set if we should print a "repoman full" informational message
1556 dofull = options.mode != 'full'
1557
1558 for x in qacats:
1559         if not stats[x]:
1560                 continue
1561         dowarn = 1
1562         if x not in qawarnings:
1563                 dofail = 1
1564
1565 if dofail or \
1566         (dowarn and not (options.quiet or options.mode == "scan")):
1567         dofull = 0
1568
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
1580
1581 f = formatter.AbstractFormatter(console_writer)
1582
1583 utilities.format_qa_output(f, stats, fails, dofull, dofail, options, qawarnings)
1584
1585 style_file.flush()
1586 del console_writer, f, style_file
1587 qa_output = qa_output.getvalue()
1588 qa_output = qa_output.splitlines(True)
1589
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"]}"""
1595         mygroups={}
1596         for x in mylist:
1597                 xs=x.split(seperator)
1598                 if xs[0]==".":
1599                         xs=xs[1:]
1600                 if xs[0] not in mygroups:
1601                         mygroups[xs[0]]=[seperator.join(xs[1:])]
1602                 else:
1603                         mygroups[xs[0]]+=[seperator.join(xs[1:])]
1604         return mygroups
1605
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")
1609
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")
1613
1614 if options.mode != 'commit':
1615         if dofull:
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.\""
1619         elif not dofail:
1620                 print green("RepoMan sez:"),"\"If everyone were like you, I'd be out of business!\""
1621         elif dofail:
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"
1624                 sys.exit(1)
1625 else:
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"
1630         elif dofail:
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"
1635                 sys.exit(1)
1636
1637         if options.pretend:
1638                 print green("RepoMan sez:"), "\"So, you want to play it safe. Good call.\"\n"
1639
1640         myunadded = []
1641         if vcs == "cvs":
1642                 try:
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
1647                 except:
1648                         err("Error retrieving CVS tree; exiting.")
1649         if vcs == "svn":
1650                 try:
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
1655                 except:
1656                         err("Error retrieving SVN info; exiting.")
1657         if vcs == "git":
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 ]
1661                 myf.close()
1662
1663         myautoadd=[]
1664         if myunadded:
1665                 for x in range(len(myunadded)-1,-1,-1):
1666                         xs=myunadded[x].split("/")
1667                         if xs[-1]=="files":
1668                                 print "!!! files dir is not added! Please correct this."
1669                                 sys.exit(-1)
1670                         elif xs[-1]=="Manifest":
1671                                 # It's a manifest... auto add
1672                                 myautoadd+=[myunadded[x]]
1673                                 del myunadded[x]
1674
1675         if myautoadd:
1676                 print ">>> Auto-Adding missing Manifest(s)..."
1677                 if options.pretend:
1678                         if vcs == "cvs":
1679                                 print "(cvs add "+" ".join(myautoadd)+")"
1680                         if vcs == "svn":
1681                                 print "(svn add "+" ".join(myautoadd)+")"
1682                         elif vcs == "git":
1683                                 print "(git add "+" ".join(myautoadd)+")"
1684                         retval=0
1685                 else:
1686                         if vcs == "cvs":
1687                                 retval=os.system("cvs add "+" ".join(myautoadd))
1688                         if vcs == "svn":
1689                                 retval=os.system("svn add "+" ".join(myautoadd))
1690                         elif vcs == "git":
1691                                 retval=os.system("git add "+" ".join(myautoadd))
1692                 if retval:
1693                         writemsg_level("!!! Exiting on %s (shell) error code: %s\n" % \
1694                                 (vcs, retval), level=logging.ERROR, noiselevel=-1)
1695                         sys.exit(retval)
1696
1697         if myunadded:
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.")
1700                 for x in myunadded:
1701                         print "   ",x
1702                 print
1703                 print
1704                 sys.exit(1)
1705
1706         if vcs == "cvs":
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="./"))
1714
1715
1716         if vcs == "svn":
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()
1728
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)
1734
1735         elif vcs == "git":
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
1740                 if strip_levels:
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()
1745                 if strip_levels:
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]
1749
1750         if vcs:
1751                 if not (mychanged or mynew or myremoved):
1752                         print green("RepoMan sez:"), "\"Doing nothing is not always good for QA.\""
1753                         print
1754                         print "(Didn't find any changed files...)"
1755                         print
1756                         sys.exit(0)
1757
1758         # Manifests need to be regenerated after all other commits, so don't commit
1759         # them now even if they have changed.
1760         mymanifests = set()
1761         myupdates = set()
1762         for f in mychanged + mynew:
1763                 if "Manifest" == os.path.basename(f):
1764                         mymanifests.add(f)
1765                 else:
1766                         myupdates.add(f)
1767         if vcs == 'git':
1768                 myupdates.difference_update(myremoved)
1769         myupdates = list(myupdates)
1770         mymanifests = list(mymanifests)
1771         myheaders = []
1772         mydirty = []
1773         headerstring = "'\$(Header|Id)"
1774         headerstring += ".*\$'"
1775         for myfile in myupdates:
1776
1777                 # for CVS, no_expansion contains files that are excluded from expansion
1778                 if vcs == "cvs":
1779                         if myfile in no_expansion:
1780                                 continue
1781
1782                 # for SVN, expansion contains files that are included in expansion
1783                 elif vcs == "svn":
1784                         if myfile not in expansion:
1785                                 continue
1786
1787                 myout = commands.getstatusoutput("egrep -q "+headerstring+" "+myfile)
1788                 if myout[0] == 0:
1789                         myheaders.append(myfile)
1790
1791         print "* %s files being committed..." % green(str(len(myupdates))),
1792         if vcs == 'git':
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.
1796                 print
1797         else:
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))
1803
1804         commitmessage = options.commitmsg
1805         if options.commitmsgfile:
1806                 try:
1807                         f = open(options.commitmsgfile)
1808                         commitmessage = f.read()
1809                         f.close()
1810                         del f
1811                 except (IOError, OSError), e:
1812                         if e.errno == errno.ENOENT:
1813                                 portage.writemsg("!!! File Not Found: --commitmsgfile='%s'\n" % options.commitmsgfile)
1814                         else:
1815                                 raise
1816                 # We've read the content so the file is no longer needed.
1817                 commitmessagefile = None
1818         if not commitmessage or not commitmessage.strip():
1819                 try:
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)
1824                         else:
1825                                 commitmessage = utilities.get_commit_message_with_stdin()
1826                 except KeyboardInterrupt:
1827                         exithandler()
1828                 if not commitmessage or not commitmessage.strip():
1829                         print "* no commit message?  aborting commit."
1830                         sys.exit(1)
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")
1835                 sys.stderr.flush()
1836                 portage_version = "Unknown"
1837         unameout = platform.system() + " " + platform.release() + " "
1838         if platform.system() in ["Darwin", "SunOS"]:
1839                 unameout += platform.processor()
1840         else:
1841                 unameout += platform.machine()
1842         commitmessage += "\n(Portage version: %s/%s/%s" % \
1843                 (portage_version, vcs, unameout)
1844         if options.force:
1845                 commitmessage += ", RepoMan options: --force"
1846         commitmessage += ")"
1847
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)
1855                 mymsg.close()
1856
1857                 print
1858                 print green("Using commit message:")
1859                 print green("------------------------------------------------------------------------------")
1860                 print commitmessage
1861                 print green("------------------------------------------------------------------------------")
1862                 print
1863
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]
1868
1869                 commit_cmd = [vcs]
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)
1875
1876                 try:
1877                         if options.pretend:
1878                                 print "(%s)" % (" ".join(commit_cmd),)
1879                         else:
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)
1885                                         sys.exit(retval)
1886                 finally:
1887                         try:
1888                                 os.unlink(commitmessagefile)
1889                         except OSError:
1890                                 pass
1891
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"])
1900                         else:
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"]
1914                 if options.pretend:
1915                         print "("+gpgcmd+" "+filename+")"
1916                 else:
1917                         rValue = os.system(gpgcmd+" "+filename)
1918                         if rValue == os.EX_OK:
1919                                 os.rename(filename+".asc", filename)
1920                         else:
1921                                 raise portage.exception.PortageException("!!! gpg exited with '" + str(rValue) + "' status")
1922
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.*\$$')
1928         for x in myheaders:
1929                 f = open(x)
1930                 mylines = f.readlines()
1931                 f.close()
1932                 modified = False
1933                 for i, line in enumerate(mylines):
1934                         if cvs_header.match(line) and "/Attic/" in line:
1935                                 mylines[i] = line.replace("/Attic/", "/")
1936                                 modified = True
1937                 if modified:
1938                         write_atomic(x, "".join(mylines))
1939
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:
1945                                 del myfiles[x]
1946                 mydone=[]
1947                 if repolevel==3:   # In a package dir
1948                         repoman_settings["O"] = startdir
1949                         portage.digestgen([], repoman_settings, manifestonly=1,
1950                                 myportdb=portdb)
1951                 elif repolevel==2: # In a category dir
1952                         for x in myfiles:
1953                                 xs=x.split("/")
1954                                 if len(xs) < 4-repolevel:
1955                                         continue
1956                                 if xs[0]==".":
1957                                         xs=xs[1:]
1958                                 if xs[0] in mydone:
1959                                         continue
1960                                 mydone.append(xs[0])
1961                                 repoman_settings["O"] = os.path.join(startdir, xs[0])
1962                                 if not os.path.isdir(repoman_settings["O"]):
1963                                         continue
1964                                 portage.digestgen([], repoman_settings, manifestonly=1,
1965                                         myportdb=portdb)
1966                 elif repolevel==1: # repo-cvsroot
1967                         print green("RepoMan sez:"), "\"You're rather crazy... doing the entire repository.\"\n"
1968                         for x in myfiles:
1969                                 xs=x.split("/")
1970                                 if len(xs) < 4-repolevel:
1971                                         continue
1972                                 if xs[0]==".":
1973                                         xs=xs[1:]
1974                                 if "/".join(xs[:2]) in mydone:
1975                                         continue
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"]):
1979                                         continue
1980                                 portage.digestgen([], repoman_settings, manifestonly=1,
1981                                         myportdb=portdb)
1982                 else:
1983                         print red("I'm confused... I don't know where I am!")
1984                         sys.exit(1)
1985
1986                 # Force an unsigned commit when more than one Manifest needs to be signed.
1987                 if repolevel < 3 and "sign" in repoman_settings.features:
1988
1989                         fd, commitmessagefile = tempfile.mkstemp(".repoman.msg")
1990                         mymsg = os.fdopen(fd, "w")
1991                         mymsg.write(commitmessage)
1992                         mymsg.write("\n (Unsigned Manifest commit)")
1993                         mymsg.close()
1994
1995                         commit_cmd = [vcs]
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)
2001
2002                         try:
2003                                 if options.pretend:
2004                                         print "(%s)" % (" ".join(commit_cmd),)
2005                                 else:
2006                                         retval = spawn(commit_cmd, env=os.environ)
2007                                         if retval:
2008                                                 writemsg_level(("!!! Exiting on %s (shell) " + \
2009                                                         "error code: %s\n") % (vcs, retval),
2010                                                         level=logging.ERROR, noiselevel=-1)
2011                                                 sys.exit(retval)
2012                         finally:
2013                                 try:
2014                                         os.unlink(commitmessagefile)
2015                                 except OSError:
2016                                         pass
2017                         manifest_commit_required = False
2018
2019         signed = False
2020         if "sign" in repoman_settings.features:
2021                 signed = True
2022                 myfiles = myupdates + myremoved + mymanifests
2023                 try:
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
2028                                 mydone=[]
2029                                 for x in myfiles:
2030                                         xs=x.split("/")
2031                                         if len(xs) < 4-repolevel:
2032                                                 continue
2033                                         if xs[0]==".":
2034                                                 xs=xs[1:]
2035                                         if xs[0] in mydone:
2036                                                 continue
2037                                         mydone.append(xs[0])
2038                                         repoman_settings["O"] = os.path.join(".", xs[0])
2039                                         if not os.path.isdir(repoman_settings["O"]):
2040                                                 continue
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"
2044                                 mydone=[]
2045                                 for x in myfiles:
2046                                         xs=x.split("/")
2047                                         if len(xs) < 4-repolevel:
2048                                                 continue
2049                                         if xs[0]==".":
2050                                                 xs=xs[1:]
2051                                         if "/".join(xs[:2]) in mydone:
2052                                                 continue
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"]):
2056                                                 continue
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")
2061                         signed = False
2062
2063         if vcs == 'git':
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
2071                 myfiles.sort()
2072                 update_index_cmd = ["git", "update-index"]
2073                 update_index_cmd.extend(f.lstrip("./") for f in myfiles)
2074                 if options.pretend:
2075                         print "(%s)" % (" ".join(update_index_cmd),)
2076                 else:
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)
2082                                 sys.exit(retval)
2083
2084         if vcs == 'git' or manifest_commit_required or signed:
2085
2086                 myfiles = mymanifests[:]
2087                 if vcs == 'git':
2088                         myfiles += myupdates
2089                         myfiles += myremoved
2090                 myfiles.sort()
2091
2092                 fd, commitmessagefile = tempfile.mkstemp(".repoman.msg")
2093                 mymsg = os.fdopen(fd, "w")
2094                 mymsg.write(commitmessage)
2095                 if signed:
2096                         mymsg.write("\n (Signed Manifest commit)")
2097                 else:
2098                         mymsg.write("\n (Unsigned Manifest commit)")
2099                 mymsg.close()
2100
2101                 commit_cmd = [vcs]
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)
2107
2108                 try:
2109                         if options.pretend:
2110                                 print "(%s)" % (" ".join(commit_cmd),)
2111                         else:
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)
2117                                         sys.exit(retval)
2118                 finally:
2119                         try:
2120                                 os.unlink(commitmessagefile)
2121                         except OSError:
2122                                 pass
2123
2124         print
2125         if vcs:
2126                 print "Commit complete."
2127         else:
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"
2130 sys.exit(0)
2131